From ae51790b954b1f4e54f2cbb1ae9350e352613df8 Mon Sep 17 00:00:00 2001 From: odulcy-mindee <106678676+odulcy-mindee@users.noreply.github.com> Date: Wed, 13 Nov 2024 13:18:58 +0000 Subject: [PATCH] [skip ci] Documentation updates --- .buildinfo | 4 +- .doctrees/environment.pickle | Bin 2731890 -> 2732364 bytes _modules/doctr/datasets/cord.html | 4 +- _modules/doctr/datasets/detection.html | 4 +- _modules/doctr/datasets/doc_artefacts.html | 4 +- _modules/doctr/datasets/funsd.html | 4 +- .../doctr/datasets/generator/tensorflow.html | 4 +- _modules/doctr/datasets/ic03.html | 4 +- _modules/doctr/datasets/ic13.html | 4 +- _modules/doctr/datasets/iiit5k.html | 4 +- _modules/doctr/datasets/iiithws.html | 4 +- _modules/doctr/datasets/imgur5k.html | 4 +- _modules/doctr/datasets/loader.html | 4 +- _modules/doctr/datasets/mjsynth.html | 4 +- _modules/doctr/datasets/ocr.html | 4 +- _modules/doctr/datasets/recognition.html | 4 +- _modules/doctr/datasets/sroie.html | 4 +- _modules/doctr/datasets/svhn.html | 4 +- _modules/doctr/datasets/svt.html | 4 +- _modules/doctr/datasets/synthtext.html | 4 +- _modules/doctr/datasets/utils.html | 4 +- _modules/doctr/datasets/wildreceipt.html | 4 +- _modules/doctr/io/elements.html | 4 +- _modules/doctr/io/html.html | 4 +- _modules/doctr/io/image/base.html | 4 +- _modules/doctr/io/image/tensorflow.html | 4 +- _modules/doctr/io/pdf.html | 4 +- _modules/doctr/io/reader.html | 4 +- .../magc_resnet/tensorflow.html | 4 +- .../classification/mobilenet/tensorflow.html | 4 +- .../classification/resnet/tensorflow.html | 4 +- .../classification/textnet/tensorflow.html | 4 +- .../models/classification/vgg/tensorflow.html | 4 +- .../models/classification/vit/tensorflow.html | 4 +- _modules/doctr/models/classification/zoo.html | 4 +- .../tensorflow.html | 4 +- .../models/detection/fast/tensorflow.html | 4 +- .../models/detection/linknet/tensorflow.html | 4 +- _modules/doctr/models/detection/zoo.html | 4 +- _modules/doctr/models/factory/hub.html | 4 +- .../models/recognition/crnn/tensorflow.html | 4 +- .../models/recognition/master/tensorflow.html | 4 +- .../models/recognition/parseq/tensorflow.html | 4 +- .../models/recognition/sar/tensorflow.html | 4 +- .../models/recognition/vitstr/tensorflow.html | 4 +- _modules/doctr/models/recognition/zoo.html | 4 +- _modules/doctr/models/zoo.html | 4 +- _modules/doctr/transforms/modules/base.html | 4 +- .../doctr/transforms/modules/tensorflow.html | 4 +- _modules/doctr/utils/metrics.html | 4 +- _modules/doctr/utils/visualization.html | 4 +- _modules/index.html | 4 +- _static/basic.css | 15 +- _static/doctools.js | 7 - _static/language_data.js | 7 - _static/searchtools.js | 38 +- changelog.html | 4 +- contributing/code_of_conduct.html | 4 +- contributing/contributing.html | 4 +- genindex.html | 4 +- getting_started/installing.html | 4 +- index.html | 4 +- latest/_modules/doctr/datasets/cord.html | 4 +- latest/_modules/doctr/datasets/detection.html | 4 +- .../doctr/datasets/doc_artefacts.html | 4 +- latest/_modules/doctr/datasets/funsd.html | 4 +- .../doctr/datasets/generator/tensorflow.html | 4 +- latest/_modules/doctr/datasets/ic03.html | 4 +- latest/_modules/doctr/datasets/ic13.html | 4 +- latest/_modules/doctr/datasets/iiit5k.html | 4 +- latest/_modules/doctr/datasets/iiithws.html | 4 +- latest/_modules/doctr/datasets/imgur5k.html | 4 +- latest/_modules/doctr/datasets/loader.html | 4 +- latest/_modules/doctr/datasets/mjsynth.html | 4 +- latest/_modules/doctr/datasets/ocr.html | 4 +- .../_modules/doctr/datasets/recognition.html | 4 +- latest/_modules/doctr/datasets/sroie.html | 4 +- latest/_modules/doctr/datasets/svhn.html | 4 +- latest/_modules/doctr/datasets/svt.html | 4 +- latest/_modules/doctr/datasets/synthtext.html | 4 +- latest/_modules/doctr/datasets/utils.html | 4 +- .../_modules/doctr/datasets/wildreceipt.html | 4 +- latest/_modules/doctr/io/elements.html | 4 +- latest/_modules/doctr/io/html.html | 4 +- latest/_modules/doctr/io/image/base.html | 4 +- .../_modules/doctr/io/image/tensorflow.html | 4 +- latest/_modules/doctr/io/pdf.html | 4 +- latest/_modules/doctr/io/reader.html | 4 +- .../magc_resnet/tensorflow.html | 4 +- .../classification/mobilenet/tensorflow.html | 4 +- .../classification/resnet/tensorflow.html | 4 +- .../classification/textnet/tensorflow.html | 4 +- .../models/classification/vgg/tensorflow.html | 4 +- .../models/classification/vit/tensorflow.html | 4 +- .../doctr/models/classification/zoo.html | 4 +- .../tensorflow.html | 4 +- .../models/detection/fast/tensorflow.html | 4 +- .../models/detection/linknet/tensorflow.html | 4 +- .../_modules/doctr/models/detection/zoo.html | 4 +- latest/_modules/doctr/models/factory/hub.html | 4 +- .../models/recognition/crnn/tensorflow.html | 4 +- .../models/recognition/master/tensorflow.html | 4 +- .../models/recognition/parseq/tensorflow.html | 4 +- .../models/recognition/sar/tensorflow.html | 4 +- .../models/recognition/vitstr/tensorflow.html | 4 +- .../doctr/models/recognition/zoo.html | 4 +- latest/_modules/doctr/models/zoo.html | 4 +- .../doctr/transforms/modules/base.html | 4 +- .../doctr/transforms/modules/tensorflow.html | 4 +- latest/_modules/doctr/utils/metrics.html | 4 +- .../_modules/doctr/utils/visualization.html | 4 +- latest/_modules/index.html | 4 +- .../getting_started/installing.rst.txt | 2 +- latest/_static/basic.css | 15 +- latest/_static/doctools.js | 7 - latest/_static/language_data.js | 7 - latest/_static/searchtools.js | 38 +- latest/changelog.html | 4 +- latest/community/resources.html | 4 +- latest/contributing/code_of_conduct.html | 4 +- latest/contributing/contributing.html | 4 +- latest/genindex.html | 4 +- latest/getting_started/installing.html | 6 +- latest/index.html | 4 +- latest/modules/contrib.html | 4 +- latest/modules/datasets.html | 4 +- latest/modules/io.html | 4 +- latest/modules/models.html | 4 +- latest/modules/transforms.html | 4 +- latest/modules/utils.html | 4 +- latest/notebooks.html | 4 +- latest/search.html | 4 +- latest/searchindex.js | 2 +- .../using_doctr/custom_models_training.html | 4 +- latest/using_doctr/running_on_aws.html | 4 +- latest/using_doctr/sharing_models.html | 4 +- latest/using_doctr/using_contrib_modules.html | 4 +- latest/using_doctr/using_datasets.html | 4 +- latest/using_doctr/using_model_export.html | 4 +- latest/using_doctr/using_models.html | 4 +- modules/contrib.html | 4 +- modules/datasets.html | 4 +- modules/io.html | 4 +- modules/models.html | 4 +- modules/transforms.html | 4 +- modules/utils.html | 4 +- notebooks.html | 4 +- search.html | 4 +- searchindex.js | 2 +- using_doctr/custom_models_training.html | 4 +- using_doctr/running_on_aws.html | 4 +- using_doctr/sharing_models.html | 4 +- using_doctr/using_contrib_modules.html | 4 +- using_doctr/using_datasets.html | 4 +- using_doctr/using_model_export.html | 4 +- using_doctr/using_models.html | 4 +- v0.1.0/_modules/doctr/datasets/cord.html | 4 +- v0.1.0/_modules/doctr/datasets/detection.html | 4 +- .../doctr/datasets/doc_artefacts.html | 4 +- v0.1.0/_modules/doctr/datasets/funsd.html | 4 +- .../doctr/datasets/generator/tensorflow.html | 4 +- v0.1.0/_modules/doctr/datasets/ic03.html | 4 +- v0.1.0/_modules/doctr/datasets/ic13.html | 4 +- v0.1.0/_modules/doctr/datasets/iiit5k.html | 4 +- v0.1.0/_modules/doctr/datasets/iiithws.html | 4 +- v0.1.0/_modules/doctr/datasets/imgur5k.html | 4 +- v0.1.0/_modules/doctr/datasets/loader.html | 4 +- v0.1.0/_modules/doctr/datasets/mjsynth.html | 4 +- v0.1.0/_modules/doctr/datasets/ocr.html | 4 +- .../_modules/doctr/datasets/recognition.html | 4 +- v0.1.0/_modules/doctr/datasets/sroie.html | 4 +- v0.1.0/_modules/doctr/datasets/svhn.html | 4 +- v0.1.0/_modules/doctr/datasets/svt.html | 4 +- v0.1.0/_modules/doctr/datasets/synthtext.html | 4 +- v0.1.0/_modules/doctr/datasets/utils.html | 4 +- .../_modules/doctr/datasets/wildreceipt.html | 4 +- v0.1.0/_modules/doctr/io/elements.html | 4 +- v0.1.0/_modules/doctr/io/html.html | 4 +- v0.1.0/_modules/doctr/io/image/base.html | 4 +- .../_modules/doctr/io/image/tensorflow.html | 4 +- v0.1.0/_modules/doctr/io/pdf.html | 4 +- v0.1.0/_modules/doctr/io/reader.html | 4 +- .../magc_resnet/tensorflow.html | 4 +- .../classification/mobilenet/tensorflow.html | 4 +- .../classification/resnet/tensorflow.html | 4 +- .../classification/textnet/tensorflow.html | 4 +- .../models/classification/vgg/tensorflow.html | 4 +- .../models/classification/vit/tensorflow.html | 4 +- .../doctr/models/classification/zoo.html | 4 +- .../tensorflow.html | 4 +- .../models/detection/fast/tensorflow.html | 4 +- .../models/detection/linknet/tensorflow.html | 4 +- .../_modules/doctr/models/detection/zoo.html | 4 +- v0.1.0/_modules/doctr/models/factory/hub.html | 4 +- .../models/recognition/crnn/tensorflow.html | 4 +- .../models/recognition/master/tensorflow.html | 4 +- .../models/recognition/parseq/tensorflow.html | 4 +- .../models/recognition/sar/tensorflow.html | 4 +- .../models/recognition/vitstr/tensorflow.html | 4 +- .../doctr/models/recognition/zoo.html | 4 +- v0.1.0/_modules/doctr/models/zoo.html | 4 +- .../doctr/transforms/modules/base.html | 4 +- .../doctr/transforms/modules/tensorflow.html | 4 +- v0.1.0/_modules/doctr/utils/metrics.html | 4 +- .../_modules/doctr/utils/visualization.html | 4 +- v0.1.0/_modules/index.html | 4 +- .../getting_started/installing.rst.txt | 2 +- v0.1.0/_static/basic.css | 15 +- v0.1.0/_static/doctools.js | 7 - v0.1.0/_static/language_data.js | 7 - v0.1.0/_static/searchtools.js | 38 +- v0.1.0/changelog.html | 4 +- v0.1.0/community/resources.html | 4 +- v0.1.0/contributing/code_of_conduct.html | 4 +- v0.1.0/contributing/contributing.html | 4 +- v0.1.0/genindex.html | 4 +- v0.1.0/getting_started/installing.html | 6 +- v0.1.0/index.html | 4 +- v0.1.0/modules/contrib.html | 4 +- v0.1.0/modules/datasets.html | 4 +- v0.1.0/modules/io.html | 4 +- v0.1.0/modules/models.html | 4 +- v0.1.0/modules/transforms.html | 4 +- v0.1.0/modules/utils.html | 4 +- v0.1.0/notebooks.html | 4 +- v0.1.0/search.html | 4 +- v0.1.0/searchindex.js | 2 +- .../using_doctr/custom_models_training.html | 4 +- v0.1.0/using_doctr/running_on_aws.html | 4 +- v0.1.0/using_doctr/sharing_models.html | 4 +- v0.1.0/using_doctr/using_contrib_modules.html | 4 +- v0.1.0/using_doctr/using_datasets.html | 4 +- v0.1.0/using_doctr/using_model_export.html | 4 +- v0.1.0/using_doctr/using_models.html | 4 +- v0.1.1/_modules/doctr/datasets/cord.html | 4 +- v0.1.1/_modules/doctr/datasets/detection.html | 4 +- .../doctr/datasets/doc_artefacts.html | 4 +- v0.1.1/_modules/doctr/datasets/funsd.html | 4 +- .../doctr/datasets/generator/tensorflow.html | 4 +- v0.1.1/_modules/doctr/datasets/ic03.html | 4 +- v0.1.1/_modules/doctr/datasets/ic13.html | 4 +- v0.1.1/_modules/doctr/datasets/iiit5k.html | 4 +- v0.1.1/_modules/doctr/datasets/iiithws.html | 4 +- v0.1.1/_modules/doctr/datasets/imgur5k.html | 4 +- v0.1.1/_modules/doctr/datasets/loader.html | 4 +- v0.1.1/_modules/doctr/datasets/mjsynth.html | 4 +- v0.1.1/_modules/doctr/datasets/ocr.html | 4 +- .../_modules/doctr/datasets/recognition.html | 4 +- v0.1.1/_modules/doctr/datasets/sroie.html | 4 +- v0.1.1/_modules/doctr/datasets/svhn.html | 4 +- v0.1.1/_modules/doctr/datasets/svt.html | 4 +- v0.1.1/_modules/doctr/datasets/synthtext.html | 4 +- v0.1.1/_modules/doctr/datasets/utils.html | 4 +- .../_modules/doctr/datasets/wildreceipt.html | 4 +- v0.1.1/_modules/doctr/io/elements.html | 4 +- v0.1.1/_modules/doctr/io/html.html | 4 +- v0.1.1/_modules/doctr/io/image/base.html | 4 +- .../_modules/doctr/io/image/tensorflow.html | 4 +- v0.1.1/_modules/doctr/io/pdf.html | 4 +- v0.1.1/_modules/doctr/io/reader.html | 4 +- .../magc_resnet/tensorflow.html | 4 +- .../classification/mobilenet/tensorflow.html | 4 +- .../classification/resnet/tensorflow.html | 4 +- .../classification/textnet/tensorflow.html | 4 +- .../models/classification/vgg/tensorflow.html | 4 +- .../models/classification/vit/tensorflow.html | 4 +- .../doctr/models/classification/zoo.html | 4 +- .../tensorflow.html | 4 +- .../models/detection/fast/tensorflow.html | 4 +- .../models/detection/linknet/tensorflow.html | 4 +- .../_modules/doctr/models/detection/zoo.html | 4 +- v0.1.1/_modules/doctr/models/factory/hub.html | 4 +- .../models/recognition/crnn/tensorflow.html | 4 +- .../models/recognition/master/tensorflow.html | 4 +- .../models/recognition/parseq/tensorflow.html | 4 +- .../models/recognition/sar/tensorflow.html | 4 +- .../models/recognition/vitstr/tensorflow.html | 4 +- .../doctr/models/recognition/zoo.html | 4 +- v0.1.1/_modules/doctr/models/zoo.html | 4 +- .../doctr/transforms/modules/base.html | 4 +- .../doctr/transforms/modules/tensorflow.html | 4 +- v0.1.1/_modules/doctr/utils/metrics.html | 4 +- .../_modules/doctr/utils/visualization.html | 4 +- v0.1.1/_modules/index.html | 4 +- .../getting_started/installing.rst.txt | 2 +- v0.1.1/_static/basic.css | 15 +- v0.1.1/_static/doctools.js | 7 - v0.1.1/_static/language_data.js | 7 - v0.1.1/_static/searchtools.js | 38 +- v0.1.1/changelog.html | 4 +- v0.1.1/community/resources.html | 4 +- v0.1.1/contributing/code_of_conduct.html | 4 +- v0.1.1/contributing/contributing.html | 4 +- v0.1.1/genindex.html | 4 +- v0.1.1/getting_started/installing.html | 6 +- v0.1.1/index.html | 4 +- v0.1.1/modules/contrib.html | 4 +- v0.1.1/modules/datasets.html | 4 +- v0.1.1/modules/io.html | 4 +- v0.1.1/modules/models.html | 4 +- v0.1.1/modules/transforms.html | 4 +- v0.1.1/modules/utils.html | 4 +- v0.1.1/notebooks.html | 4 +- v0.1.1/search.html | 4 +- v0.1.1/searchindex.js | 2 +- .../using_doctr/custom_models_training.html | 4 +- v0.1.1/using_doctr/running_on_aws.html | 4 +- v0.1.1/using_doctr/sharing_models.html | 4 +- v0.1.1/using_doctr/using_contrib_modules.html | 4 +- v0.1.1/using_doctr/using_datasets.html | 4 +- v0.1.1/using_doctr/using_model_export.html | 4 +- v0.1.1/using_doctr/using_models.html | 4 +- v0.2.0/_modules/doctr/datasets/cord.html | 197 ++-- v0.2.0/_modules/doctr/datasets/core.html | 392 ------- v0.2.0/_modules/doctr/datasets/detection.html | 4 +- .../doctr/datasets/doc_artefacts.html | 4 +- v0.2.0/_modules/doctr/datasets/funsd.html | 183 ++- .../doctr/datasets/generator/tensorflow.html | 4 +- v0.2.0/_modules/doctr/datasets/ic03.html | 4 +- v0.2.0/_modules/doctr/datasets/ic13.html | 4 +- v0.2.0/_modules/doctr/datasets/iiit5k.html | 4 +- v0.2.0/_modules/doctr/datasets/iiithws.html | 4 +- v0.2.0/_modules/doctr/datasets/imgur5k.html | 4 +- v0.2.0/_modules/doctr/datasets/loader.html | 94 +- v0.2.0/_modules/doctr/datasets/mjsynth.html | 4 +- v0.2.0/_modules/doctr/datasets/ocr.html | 4 +- .../_modules/doctr/datasets/recognition.html | 4 +- v0.2.0/_modules/doctr/datasets/sroie.html | 194 ++-- v0.2.0/_modules/doctr/datasets/svhn.html | 4 +- v0.2.0/_modules/doctr/datasets/svt.html | 4 +- v0.2.0/_modules/doctr/datasets/synthtext.html | 4 +- v0.2.0/_modules/doctr/datasets/utils.html | 218 +++- .../_modules/doctr/datasets/wildreceipt.html | 4 +- v0.2.0/_modules/doctr/documents/elements.html | 550 --------- v0.2.0/_modules/doctr/documents/reader.html | 606 ---------- v0.2.0/_modules/doctr/io/elements.html | 4 +- v0.2.0/_modules/doctr/io/html.html | 4 +- v0.2.0/_modules/doctr/io/image/base.html | 4 +- .../_modules/doctr/io/image/tensorflow.html | 4 +- v0.2.0/_modules/doctr/io/pdf.html | 4 +- v0.2.0/_modules/doctr/io/reader.html | 4 +- .../magc_resnet/tensorflow.html | 4 +- .../classification/mobilenet/tensorflow.html | 4 +- .../classification/resnet/tensorflow.html | 4 +- .../classification/textnet/tensorflow.html | 4 +- .../models/classification/vgg/tensorflow.html | 4 +- .../models/classification/vit/tensorflow.html | 4 +- .../doctr/models/classification/zoo.html | 4 +- .../differentiable_binarization.html | 876 -------------- .../tensorflow.html | 4 +- .../models/detection/fast/tensorflow.html | 4 +- .../doctr/models/detection/linknet.html | 637 ----------- .../models/detection/linknet/tensorflow.html | 4 +- .../_modules/doctr/models/detection/zoo.html | 169 ++- v0.2.0/_modules/doctr/models/export.html | 411 ------- v0.2.0/_modules/doctr/models/factory/hub.html | 4 +- .../doctr/models/recognition/crnn.html | 579 ---------- .../models/recognition/crnn/tensorflow.html | 4 +- .../models/recognition/master/tensorflow.html | 4 +- .../models/recognition/parseq/tensorflow.html | 4 +- .../doctr/models/recognition/sar.html | 709 ------------ .../models/recognition/sar/tensorflow.html | 4 +- .../models/recognition/vitstr/tensorflow.html | 4 +- .../doctr/models/recognition/zoo.html | 124 +- v0.2.0/_modules/doctr/models/zoo.html | 288 ++++- v0.2.0/_modules/doctr/transforms/modules.html | 716 ------------ .../doctr/transforms/modules/base.html | 4 +- .../doctr/transforms/modules/tensorflow.html | 4 +- v0.2.0/_modules/doctr/utils/metrics.html | 671 +++++++---- .../_modules/doctr/utils/visualization.html | 403 +++++-- v0.2.0/_modules/index.html | 95 +- v0.2.0/_sources/datasets.rst.txt | 68 -- v0.2.0/_sources/documents.rst.txt | 83 -- .../getting_started/installing.rst.txt | 2 +- v0.2.0/_sources/index.rst.txt | 141 ++- v0.2.0/_sources/installing.rst.txt | 26 - v0.2.0/_sources/models.rst.txt | 209 ---- v0.2.0/_sources/transforms.rst.txt | 32 - v0.2.0/_sources/utils.rst.txt | 30 - v0.2.0/_static/basic.css | 15 +- v0.2.0/_static/doctools.js | 7 - v0.2.0/_static/documentation_options.js | 2 +- v0.2.0/_static/language_data.js | 7 - v0.2.0/_static/searchtools.js | 38 +- v0.2.0/changelog.html | 4 +- v0.2.0/community/resources.html | 4 +- v0.2.0/contributing/code_of_conduct.html | 4 +- v0.2.0/contributing/contributing.html | 4 +- v0.2.0/datasets.html | 564 ---------- v0.2.0/documents.html | 736 ------------ v0.2.0/genindex.html | 343 ++++-- v0.2.0/getting_started/installing.html | 6 +- v0.2.0/index.html | 180 +-- v0.2.0/installing.html | 375 ------ v0.2.0/models.html | 953 ---------------- v0.2.0/modules/contrib.html | 4 +- v0.2.0/modules/datasets.html | 4 +- v0.2.0/modules/io.html | 4 +- v0.2.0/modules/models.html | 4 +- v0.2.0/modules/transforms.html | 4 +- v0.2.0/modules/utils.html | 4 +- v0.2.0/notebooks.html | 4 +- v0.2.0/objects.inv | Bin 1870 -> 4167 bytes v0.2.0/py-modindex.html | 330 ------ v0.2.0/search.html | 47 +- v0.2.0/searchindex.js | 2 +- v0.2.0/transforms.html | 678 ----------- .../using_doctr/custom_models_training.html | 4 +- v0.2.0/using_doctr/running_on_aws.html | 4 +- v0.2.0/using_doctr/sharing_models.html | 4 +- v0.2.0/using_doctr/using_contrib_modules.html | 4 +- v0.2.0/using_doctr/using_datasets.html | 4 +- v0.2.0/using_doctr/using_model_export.html | 4 +- v0.2.0/using_doctr/using_models.html | 4 +- v0.2.0/utils.html | 534 --------- v0.2.1/_modules/doctr/datasets/cord.html | 177 ++- v0.2.1/_modules/doctr/datasets/core.html | 417 ------- v0.2.1/_modules/doctr/datasets/detection.html | 4 +- .../doctr/datasets/doc_artefacts.html | 4 +- v0.2.1/_modules/doctr/datasets/funsd.html | 163 ++- .../doctr/datasets/generator/tensorflow.html | 4 +- v0.2.1/_modules/doctr/datasets/ic03.html | 4 +- v0.2.1/_modules/doctr/datasets/ic13.html | 4 +- v0.2.1/_modules/doctr/datasets/iiit5k.html | 4 +- v0.2.1/_modules/doctr/datasets/iiithws.html | 4 +- v0.2.1/_modules/doctr/datasets/imgur5k.html | 4 +- v0.2.1/_modules/doctr/datasets/loader.html | 93 +- v0.2.1/_modules/doctr/datasets/mjsynth.html | 4 +- v0.2.1/_modules/doctr/datasets/ocr.html | 117 +- .../_modules/doctr/datasets/recognition.html | 4 +- v0.2.1/_modules/doctr/datasets/sroie.html | 175 ++- v0.2.1/_modules/doctr/datasets/svhn.html | 4 +- v0.2.1/_modules/doctr/datasets/svt.html | 4 +- v0.2.1/_modules/doctr/datasets/synthtext.html | 4 +- v0.2.1/_modules/doctr/datasets/utils.html | 218 +++- .../_modules/doctr/datasets/wildreceipt.html | 4 +- v0.2.1/_modules/doctr/documents/elements.html | 571 ---------- v0.2.1/_modules/doctr/documents/reader.html | 611 ---------- v0.2.1/_modules/doctr/io/elements.html | 4 +- v0.2.1/_modules/doctr/io/html.html | 4 +- v0.2.1/_modules/doctr/io/image/base.html | 4 +- .../_modules/doctr/io/image/tensorflow.html | 4 +- v0.2.1/_modules/doctr/io/pdf.html | 4 +- v0.2.1/_modules/doctr/io/reader.html | 4 +- .../magc_resnet/tensorflow.html | 4 +- .../classification/mobilenet/tensorflow.html | 4 +- .../classification/resnet/tensorflow.html | 4 +- .../classification/textnet/tensorflow.html | 4 +- .../models/classification/vgg/tensorflow.html | 4 +- .../models/classification/vit/tensorflow.html | 4 +- .../doctr/models/classification/zoo.html | 4 +- .../differentiable_binarization.html | 879 --------------- .../tensorflow.html | 4 +- .../models/detection/fast/tensorflow.html | 4 +- .../doctr/models/detection/linknet.html | 644 ----------- .../models/detection/linknet/tensorflow.html | 4 +- .../_modules/doctr/models/detection/zoo.html | 169 ++- v0.2.1/_modules/doctr/models/export.html | 411 ------- v0.2.1/_modules/doctr/models/factory/hub.html | 4 +- .../doctr/models/recognition/crnn.html | 565 ---------- .../models/recognition/crnn/tensorflow.html | 4 +- .../models/recognition/master/tensorflow.html | 4 +- .../models/recognition/parseq/tensorflow.html | 4 +- .../doctr/models/recognition/sar.html | 712 ------------ .../models/recognition/sar/tensorflow.html | 4 +- .../models/recognition/vitstr/tensorflow.html | 4 +- .../doctr/models/recognition/zoo.html | 124 +- v0.2.1/_modules/doctr/models/zoo.html | 288 ++++- v0.2.1/_modules/doctr/transforms/modules.html | 734 ------------ .../doctr/transforms/modules/base.html | 4 +- .../doctr/transforms/modules/tensorflow.html | 4 +- v0.2.1/_modules/doctr/utils/metrics.html | 538 ++++++--- .../_modules/doctr/utils/visualization.html | 396 +++++-- v0.2.1/_modules/index.html | 93 +- v0.2.1/_sources/changelog.rst.txt | 52 + v0.2.1/_sources/datasets.rst.txt | 68 -- v0.2.1/_sources/documents.rst.txt | 87 -- .../getting_started/installing.rst.txt | 2 +- v0.2.1/_sources/index.rst.txt | 110 +- v0.2.1/_sources/installing.rst.txt | 41 - v0.2.1/_sources/models.rst.txt | 223 ---- v0.2.1/_sources/transforms.rst.txt | 32 - v0.2.1/_sources/utils.rst.txt | 36 - v0.2.1/_static/basic.css | 15 +- v0.2.1/_static/doctools.js | 7 - v0.2.1/_static/documentation_options.js | 2 +- v0.2.1/_static/language_data.js | 7 - v0.2.1/_static/searchtools.js | 38 +- v0.2.1/changelog.html | 129 ++- v0.2.1/community/resources.html | 4 +- v0.2.1/contributing/code_of_conduct.html | 4 +- v0.2.1/contributing/contributing.html | 4 +- v0.2.1/datasets.html | 585 ---------- v0.2.1/documents.html | 772 ------------- v0.2.1/genindex.html | 336 ++++-- v0.2.1/getting_started/installing.html | 6 +- v0.2.1/index.html | 169 ++- v0.2.1/installing.html | 390 ------- v0.2.1/models.html | 989 ---------------- v0.2.1/modules/contrib.html | 4 +- v0.2.1/modules/datasets.html | 4 +- v0.2.1/modules/io.html | 4 +- v0.2.1/modules/models.html | 4 +- v0.2.1/modules/transforms.html | 4 +- v0.2.1/modules/utils.html | 4 +- v0.2.1/notebooks.html | 4 +- v0.2.1/objects.inv | Bin 1944 -> 4167 bytes v0.2.1/py-modindex.html | 330 ------ v0.2.1/search.html | 46 +- v0.2.1/searchindex.js | 2 +- v0.2.1/transforms.html | 684 ----------- .../using_doctr/custom_models_training.html | 4 +- v0.2.1/using_doctr/running_on_aws.html | 4 +- v0.2.1/using_doctr/sharing_models.html | 4 +- v0.2.1/using_doctr/using_contrib_modules.html | 4 +- v0.2.1/using_doctr/using_datasets.html | 4 +- v0.2.1/using_doctr/using_model_export.html | 4 +- v0.2.1/using_doctr/using_models.html | 4 +- v0.2.1/utils.html | 574 ---------- v0.3.0/_modules/doctr/datasets/cord.html | 178 ++- v0.3.0/_modules/doctr/datasets/core.html | 417 ------- .../doctr/datasets/datasets/tensorflow.html | 356 ------ v0.3.0/_modules/doctr/datasets/detection.html | 4 +- .../doctr/datasets/doc_artefacts.html | 4 +- v0.3.0/_modules/doctr/datasets/funsd.html | 163 ++- .../doctr/datasets/generator/tensorflow.html | 4 +- v0.3.0/_modules/doctr/datasets/ic03.html | 4 +- v0.3.0/_modules/doctr/datasets/ic13.html | 4 +- v0.3.0/_modules/doctr/datasets/iiit5k.html | 4 +- v0.3.0/_modules/doctr/datasets/iiithws.html | 4 +- v0.3.0/_modules/doctr/datasets/imgur5k.html | 4 +- v0.3.0/_modules/doctr/datasets/loader.html | 93 +- v0.3.0/_modules/doctr/datasets/mjsynth.html | 4 +- v0.3.0/_modules/doctr/datasets/ocr.html | 121 +- .../_modules/doctr/datasets/recognition.html | 4 +- v0.3.0/_modules/doctr/datasets/sroie.html | 174 ++- v0.3.0/_modules/doctr/datasets/svhn.html | 4 +- v0.3.0/_modules/doctr/datasets/svt.html | 4 +- v0.3.0/_modules/doctr/datasets/synthtext.html | 4 +- v0.3.0/_modules/doctr/datasets/utils.html | 209 +++- .../_modules/doctr/datasets/wildreceipt.html | 4 +- v0.3.0/_modules/doctr/documents/elements.html | 577 ---------- v0.3.0/_modules/doctr/documents/reader.html | 612 ---------- v0.3.0/_modules/doctr/io/elements.html | 4 +- v0.3.0/_modules/doctr/io/html.html | 4 +- v0.3.0/_modules/doctr/io/image/base.html | 4 +- .../_modules/doctr/io/image/tensorflow.html | 4 +- v0.3.0/_modules/doctr/io/pdf.html | 4 +- v0.3.0/_modules/doctr/io/reader.html | 4 +- .../magc_resnet/tensorflow.html | 4 +- .../classification/mobilenet/tensorflow.html | 4 +- .../classification/resnet/tensorflow.html | 4 +- .../classification/textnet/tensorflow.html | 4 +- .../models/classification/vgg/tensorflow.html | 4 +- .../models/classification/vit/tensorflow.html | 4 +- .../doctr/models/classification/zoo.html | 4 +- .../differentiable_binarization.html | 879 --------------- .../tensorflow.html | 410 +++++-- .../models/detection/fast/tensorflow.html | 4 +- .../doctr/models/detection/linknet.html | 644 ----------- .../models/detection/linknet/tensorflow.html | 451 +++++--- .../_modules/doctr/models/detection/zoo.html | 166 ++- v0.3.0/_modules/doctr/models/export.html | 411 ------- v0.3.0/_modules/doctr/models/factory/hub.html | 4 +- .../doctr/models/recognition/crnn.html | 565 ---------- .../models/recognition/crnn/tensorflow.html | 293 +++-- .../models/recognition/master/tensorflow.html | 411 +++---- .../models/recognition/parseq/tensorflow.html | 4 +- .../doctr/models/recognition/sar.html | 712 ------------ .../models/recognition/sar/tensorflow.html | 406 ++++--- .../models/recognition/vitstr/tensorflow.html | 4 +- .../doctr/models/recognition/zoo.html | 127 ++- v0.3.0/_modules/doctr/models/zoo.html | 288 ++++- v0.3.0/_modules/doctr/transforms/modules.html | 734 ------------ .../doctr/transforms/modules/base.html | 324 +++++- .../doctr/transforms/modules/tensorflow.html | 533 +++++++-- v0.3.0/_modules/doctr/utils/metrics.html | 561 +++++---- .../_modules/doctr/utils/visualization.html | 465 +++++--- v0.3.0/_modules/index.html | 81 +- v0.3.0/_sources/changelog.rst.txt | 48 + v0.3.0/_sources/datasets.rst.txt | 68 -- v0.3.0/_sources/documents.rst.txt | 87 -- .../getting_started/installing.rst.txt | 2 +- v0.3.0/_sources/index.rst.txt | 111 +- v0.3.0/_sources/installing.rst.txt | 46 - v0.3.0/_sources/models.rst.txt | 215 ---- v0.3.0/_sources/transforms.rst.txt | 32 - v0.3.0/_sources/utils.rst.txt | 36 - v0.3.0/_static/basic.css | 15 +- v0.3.0/_static/doctools.js | 7 - v0.3.0/_static/documentation_options.js | 2 +- v0.3.0/_static/language_data.js | 7 - v0.3.0/_static/searchtools.js | 38 +- v0.3.0/changelog.html | 124 +- v0.3.0/community/resources.html | 4 +- v0.3.0/contributing/code_of_conduct.html | 4 +- v0.3.0/contributing/contributing.html | 4 +- v0.3.0/datasets.html | 578 ---------- v0.3.0/documents.html | 772 ------------- v0.3.0/genindex.html | 328 ++++-- v0.3.0/getting_started/installing.html | 6 +- v0.3.0/index.html | 171 ++- v0.3.0/installing.html | 395 ------- v0.3.0/models.html | 1002 ----------------- v0.3.0/modules/contrib.html | 4 +- v0.3.0/modules/datasets.html | 4 +- v0.3.0/modules/io.html | 4 +- v0.3.0/modules/models.html | 4 +- v0.3.0/modules/transforms.html | 4 +- v0.3.0/modules/utils.html | 4 +- v0.3.0/notebooks.html | 4 +- v0.3.0/objects.inv | Bin 1962 -> 4167 bytes v0.3.0/py-modindex.html | 330 ------ v0.3.0/search.html | 46 +- v0.3.0/searchindex.js | 2 +- v0.3.0/transforms.html | 684 ----------- .../using_doctr/custom_models_training.html | 4 +- v0.3.0/using_doctr/running_on_aws.html | 4 +- v0.3.0/using_doctr/sharing_models.html | 4 +- v0.3.0/using_doctr/using_contrib_modules.html | 4 +- v0.3.0/using_doctr/using_datasets.html | 4 +- v0.3.0/using_doctr/using_model_export.html | 4 +- v0.3.0/using_doctr/using_models.html | 4 +- v0.3.0/utils.html | 574 ---------- v0.3.1/_modules/doctr/datasets/cord.html | 178 ++- v0.3.1/_modules/doctr/datasets/core.html | 417 ------- .../doctr/datasets/datasets/tensorflow.html | 356 ------ v0.3.1/_modules/doctr/datasets/detection.html | 4 +- .../doctr/datasets/doc_artefacts.html | 4 +- v0.3.1/_modules/doctr/datasets/funsd.html | 163 ++- .../doctr/datasets/generator/tensorflow.html | 4 +- v0.3.1/_modules/doctr/datasets/ic03.html | 4 +- v0.3.1/_modules/doctr/datasets/ic13.html | 4 +- v0.3.1/_modules/doctr/datasets/iiit5k.html | 4 +- v0.3.1/_modules/doctr/datasets/iiithws.html | 4 +- v0.3.1/_modules/doctr/datasets/imgur5k.html | 4 +- v0.3.1/_modules/doctr/datasets/loader.html | 93 +- v0.3.1/_modules/doctr/datasets/mjsynth.html | 4 +- v0.3.1/_modules/doctr/datasets/ocr.html | 121 +- .../_modules/doctr/datasets/recognition.html | 4 +- v0.3.1/_modules/doctr/datasets/sroie.html | 174 ++- v0.3.1/_modules/doctr/datasets/svhn.html | 4 +- v0.3.1/_modules/doctr/datasets/svt.html | 4 +- v0.3.1/_modules/doctr/datasets/synthtext.html | 4 +- v0.3.1/_modules/doctr/datasets/utils.html | 209 +++- .../_modules/doctr/datasets/wildreceipt.html | 4 +- v0.3.1/_modules/doctr/documents/elements.html | 577 ---------- v0.3.1/_modules/doctr/documents/reader.html | 612 ---------- v0.3.1/_modules/doctr/io/elements.html | 4 +- v0.3.1/_modules/doctr/io/html.html | 4 +- v0.3.1/_modules/doctr/io/image/base.html | 4 +- .../_modules/doctr/io/image/tensorflow.html | 4 +- v0.3.1/_modules/doctr/io/pdf.html | 4 +- v0.3.1/_modules/doctr/io/reader.html | 4 +- .../magc_resnet/tensorflow.html | 4 +- .../classification/mobilenet/tensorflow.html | 4 +- .../classification/resnet/tensorflow.html | 4 +- .../classification/textnet/tensorflow.html | 4 +- .../models/classification/vgg/tensorflow.html | 4 +- .../models/classification/vit/tensorflow.html | 4 +- .../doctr/models/classification/zoo.html | 4 +- .../differentiable_binarization.html | 879 --------------- .../tensorflow.html | 410 +++++-- .../models/detection/fast/tensorflow.html | 4 +- .../doctr/models/detection/linknet.html | 644 ----------- .../models/detection/linknet/tensorflow.html | 451 +++++--- .../_modules/doctr/models/detection/zoo.html | 166 ++- v0.3.1/_modules/doctr/models/export.html | 411 ------- v0.3.1/_modules/doctr/models/factory/hub.html | 4 +- .../doctr/models/recognition/crnn.html | 565 ---------- .../models/recognition/crnn/tensorflow.html | 293 +++-- .../models/recognition/master/tensorflow.html | 411 +++---- .../models/recognition/parseq/tensorflow.html | 4 +- .../doctr/models/recognition/sar.html | 712 ------------ .../models/recognition/sar/tensorflow.html | 406 ++++--- .../models/recognition/vitstr/tensorflow.html | 4 +- .../doctr/models/recognition/zoo.html | 127 ++- v0.3.1/_modules/doctr/models/zoo.html | 288 ++++- v0.3.1/_modules/doctr/transforms/modules.html | 734 ------------ .../doctr/transforms/modules/base.html | 324 +++++- .../doctr/transforms/modules/tensorflow.html | 533 +++++++-- v0.3.1/_modules/doctr/utils/metrics.html | 561 +++++---- .../_modules/doctr/utils/visualization.html | 465 +++++--- v0.3.1/_modules/index.html | 81 +- v0.3.1/_sources/changelog.rst.txt | 48 + v0.3.1/_sources/datasets.rst.txt | 68 -- v0.3.1/_sources/documents.rst.txt | 87 -- .../getting_started/installing.rst.txt | 2 +- v0.3.1/_sources/index.rst.txt | 111 +- v0.3.1/_sources/installing.rst.txt | 46 - v0.3.1/_sources/models.rst.txt | 215 ---- v0.3.1/_sources/transforms.rst.txt | 32 - v0.3.1/_sources/utils.rst.txt | 36 - v0.3.1/_static/basic.css | 15 +- v0.3.1/_static/doctools.js | 7 - v0.3.1/_static/documentation_options.js | 2 +- v0.3.1/_static/language_data.js | 7 - v0.3.1/_static/searchtools.js | 38 +- v0.3.1/changelog.html | 124 +- v0.3.1/community/resources.html | 4 +- v0.3.1/contributing/code_of_conduct.html | 4 +- v0.3.1/contributing/contributing.html | 4 +- v0.3.1/datasets.html | 578 ---------- v0.3.1/documents.html | 772 ------------- v0.3.1/genindex.html | 328 ++++-- v0.3.1/getting_started/installing.html | 6 +- v0.3.1/index.html | 171 ++- v0.3.1/installing.html | 395 ------- v0.3.1/models.html | 1002 ----------------- v0.3.1/modules/contrib.html | 4 +- v0.3.1/modules/datasets.html | 4 +- v0.3.1/modules/io.html | 4 +- v0.3.1/modules/models.html | 4 +- v0.3.1/modules/transforms.html | 4 +- v0.3.1/modules/utils.html | 4 +- v0.3.1/notebooks.html | 4 +- v0.3.1/objects.inv | Bin 1962 -> 4167 bytes v0.3.1/py-modindex.html | 330 ------ v0.3.1/search.html | 46 +- v0.3.1/searchindex.js | 2 +- v0.3.1/transforms.html | 684 ----------- .../using_doctr/custom_models_training.html | 4 +- v0.3.1/using_doctr/running_on_aws.html | 4 +- v0.3.1/using_doctr/sharing_models.html | 4 +- v0.3.1/using_doctr/using_contrib_modules.html | 4 +- v0.3.1/using_doctr/using_datasets.html | 4 +- v0.3.1/using_doctr/using_model_export.html | 4 +- v0.3.1/using_doctr/using_models.html | 4 +- v0.3.1/utils.html | 574 ---------- v0.4.0/_modules/doctr/datasets/cord.html | 178 ++- v0.4.0/_modules/doctr/datasets/core.html | 417 ------- .../doctr/datasets/datasets/tensorflow.html | 356 ------ v0.4.0/_modules/doctr/datasets/detection.html | 4 +- .../doctr/datasets/doc_artefacts.html | 4 +- v0.4.0/_modules/doctr/datasets/funsd.html | 163 ++- .../doctr/datasets/generator/tensorflow.html | 4 +- v0.4.0/_modules/doctr/datasets/ic03.html | 4 +- v0.4.0/_modules/doctr/datasets/ic13.html | 4 +- v0.4.0/_modules/doctr/datasets/iiit5k.html | 4 +- v0.4.0/_modules/doctr/datasets/iiithws.html | 4 +- v0.4.0/_modules/doctr/datasets/imgur5k.html | 4 +- v0.4.0/_modules/doctr/datasets/loader.html | 93 +- v0.4.0/_modules/doctr/datasets/mjsynth.html | 4 +- v0.4.0/_modules/doctr/datasets/ocr.html | 121 +- .../_modules/doctr/datasets/recognition.html | 4 +- v0.4.0/_modules/doctr/datasets/sroie.html | 174 ++- v0.4.0/_modules/doctr/datasets/svhn.html | 4 +- v0.4.0/_modules/doctr/datasets/svt.html | 4 +- v0.4.0/_modules/doctr/datasets/synthtext.html | 4 +- v0.4.0/_modules/doctr/datasets/utils.html | 209 +++- .../_modules/doctr/datasets/wildreceipt.html | 4 +- v0.4.0/_modules/doctr/documents/elements.html | 577 ---------- v0.4.0/_modules/doctr/documents/reader.html | 612 ---------- v0.4.0/_modules/doctr/io/elements.html | 4 +- v0.4.0/_modules/doctr/io/html.html | 4 +- v0.4.0/_modules/doctr/io/image/base.html | 4 +- .../_modules/doctr/io/image/tensorflow.html | 4 +- v0.4.0/_modules/doctr/io/pdf.html | 4 +- v0.4.0/_modules/doctr/io/reader.html | 4 +- .../magc_resnet/tensorflow.html | 4 +- .../classification/mobilenet/tensorflow.html | 4 +- .../classification/resnet/tensorflow.html | 4 +- .../classification/textnet/tensorflow.html | 4 +- .../models/classification/vgg/tensorflow.html | 4 +- .../models/classification/vit/tensorflow.html | 4 +- .../doctr/models/classification/zoo.html | 4 +- .../differentiable_binarization.html | 879 --------------- .../tensorflow.html | 410 +++++-- .../models/detection/fast/tensorflow.html | 4 +- .../doctr/models/detection/linknet.html | 644 ----------- .../models/detection/linknet/tensorflow.html | 451 +++++--- .../_modules/doctr/models/detection/zoo.html | 166 ++- v0.4.0/_modules/doctr/models/export.html | 411 ------- v0.4.0/_modules/doctr/models/factory/hub.html | 4 +- .../doctr/models/recognition/crnn.html | 565 ---------- .../models/recognition/crnn/tensorflow.html | 293 +++-- .../models/recognition/master/tensorflow.html | 411 +++---- .../models/recognition/parseq/tensorflow.html | 4 +- .../doctr/models/recognition/sar.html | 712 ------------ .../models/recognition/sar/tensorflow.html | 406 ++++--- .../models/recognition/vitstr/tensorflow.html | 4 +- .../doctr/models/recognition/zoo.html | 127 ++- v0.4.0/_modules/doctr/models/zoo.html | 288 ++++- v0.4.0/_modules/doctr/transforms/modules.html | 734 ------------ .../doctr/transforms/modules/base.html | 324 +++++- .../doctr/transforms/modules/tensorflow.html | 533 +++++++-- v0.4.0/_modules/doctr/utils/metrics.html | 561 +++++---- .../_modules/doctr/utils/visualization.html | 465 +++++--- v0.4.0/_modules/index.html | 81 +- v0.4.0/_sources/changelog.rst.txt | 48 + v0.4.0/_sources/datasets.rst.txt | 68 -- v0.4.0/_sources/documents.rst.txt | 87 -- .../getting_started/installing.rst.txt | 2 +- v0.4.0/_sources/index.rst.txt | 111 +- v0.4.0/_sources/installing.rst.txt | 46 - v0.4.0/_sources/models.rst.txt | 215 ---- v0.4.0/_sources/transforms.rst.txt | 32 - v0.4.0/_sources/utils.rst.txt | 36 - v0.4.0/_static/basic.css | 15 +- v0.4.0/_static/doctools.js | 7 - v0.4.0/_static/documentation_options.js | 2 +- v0.4.0/_static/language_data.js | 7 - v0.4.0/_static/searchtools.js | 38 +- v0.4.0/changelog.html | 124 +- v0.4.0/community/resources.html | 4 +- v0.4.0/contributing/code_of_conduct.html | 4 +- v0.4.0/contributing/contributing.html | 4 +- v0.4.0/datasets.html | 578 ---------- v0.4.0/documents.html | 772 ------------- v0.4.0/genindex.html | 328 ++++-- v0.4.0/getting_started/installing.html | 6 +- v0.4.0/index.html | 171 ++- v0.4.0/installing.html | 395 ------- v0.4.0/models.html | 1002 ----------------- v0.4.0/modules/contrib.html | 4 +- v0.4.0/modules/datasets.html | 4 +- v0.4.0/modules/io.html | 4 +- v0.4.0/modules/models.html | 4 +- v0.4.0/modules/transforms.html | 4 +- v0.4.0/modules/utils.html | 4 +- v0.4.0/notebooks.html | 4 +- v0.4.0/objects.inv | Bin 1962 -> 4167 bytes v0.4.0/py-modindex.html | 330 ------ v0.4.0/search.html | 46 +- v0.4.0/searchindex.js | 2 +- v0.4.0/transforms.html | 684 ----------- .../using_doctr/custom_models_training.html | 4 +- v0.4.0/using_doctr/running_on_aws.html | 4 +- v0.4.0/using_doctr/sharing_models.html | 4 +- v0.4.0/using_doctr/using_contrib_modules.html | 4 +- v0.4.0/using_doctr/using_datasets.html | 4 +- v0.4.0/using_doctr/using_model_export.html | 4 +- v0.4.0/using_doctr/using_models.html | 4 +- v0.4.0/utils.html | 574 ---------- v0.4.1/_modules/doctr/datasets/cord.html | 178 ++- v0.4.1/_modules/doctr/datasets/core.html | 417 ------- .../doctr/datasets/datasets/tensorflow.html | 356 ------ v0.4.1/_modules/doctr/datasets/detection.html | 4 +- .../doctr/datasets/doc_artefacts.html | 4 +- v0.4.1/_modules/doctr/datasets/funsd.html | 163 ++- .../doctr/datasets/generator/tensorflow.html | 4 +- v0.4.1/_modules/doctr/datasets/ic03.html | 4 +- v0.4.1/_modules/doctr/datasets/ic13.html | 4 +- v0.4.1/_modules/doctr/datasets/iiit5k.html | 4 +- v0.4.1/_modules/doctr/datasets/iiithws.html | 4 +- v0.4.1/_modules/doctr/datasets/imgur5k.html | 4 +- v0.4.1/_modules/doctr/datasets/loader.html | 93 +- v0.4.1/_modules/doctr/datasets/mjsynth.html | 4 +- v0.4.1/_modules/doctr/datasets/ocr.html | 121 +- .../_modules/doctr/datasets/recognition.html | 4 +- v0.4.1/_modules/doctr/datasets/sroie.html | 174 ++- v0.4.1/_modules/doctr/datasets/svhn.html | 4 +- v0.4.1/_modules/doctr/datasets/svt.html | 4 +- v0.4.1/_modules/doctr/datasets/synthtext.html | 4 +- v0.4.1/_modules/doctr/datasets/utils.html | 209 +++- .../_modules/doctr/datasets/wildreceipt.html | 4 +- v0.4.1/_modules/doctr/documents/elements.html | 577 ---------- v0.4.1/_modules/doctr/documents/reader.html | 612 ---------- v0.4.1/_modules/doctr/io/elements.html | 4 +- v0.4.1/_modules/doctr/io/html.html | 4 +- v0.4.1/_modules/doctr/io/image/base.html | 4 +- .../_modules/doctr/io/image/tensorflow.html | 4 +- v0.4.1/_modules/doctr/io/pdf.html | 4 +- v0.4.1/_modules/doctr/io/reader.html | 4 +- .../magc_resnet/tensorflow.html | 4 +- .../classification/mobilenet/tensorflow.html | 4 +- .../classification/resnet/tensorflow.html | 4 +- .../classification/textnet/tensorflow.html | 4 +- .../models/classification/vgg/tensorflow.html | 4 +- .../models/classification/vit/tensorflow.html | 4 +- .../doctr/models/classification/zoo.html | 4 +- .../differentiable_binarization.html | 879 --------------- .../tensorflow.html | 410 +++++-- .../models/detection/fast/tensorflow.html | 4 +- .../doctr/models/detection/linknet.html | 644 ----------- .../models/detection/linknet/tensorflow.html | 451 +++++--- .../_modules/doctr/models/detection/zoo.html | 166 ++- v0.4.1/_modules/doctr/models/export.html | 411 ------- v0.4.1/_modules/doctr/models/factory/hub.html | 4 +- .../doctr/models/recognition/crnn.html | 565 ---------- .../models/recognition/crnn/tensorflow.html | 293 +++-- .../models/recognition/master/tensorflow.html | 411 +++---- .../models/recognition/parseq/tensorflow.html | 4 +- .../doctr/models/recognition/sar.html | 712 ------------ .../models/recognition/sar/tensorflow.html | 406 ++++--- .../models/recognition/vitstr/tensorflow.html | 4 +- .../doctr/models/recognition/zoo.html | 127 ++- v0.4.1/_modules/doctr/models/zoo.html | 288 ++++- v0.4.1/_modules/doctr/transforms/modules.html | 734 ------------ .../doctr/transforms/modules/base.html | 324 +++++- .../doctr/transforms/modules/tensorflow.html | 533 +++++++-- v0.4.1/_modules/doctr/utils/metrics.html | 561 +++++---- .../_modules/doctr/utils/visualization.html | 465 +++++--- v0.4.1/_modules/index.html | 81 +- v0.4.1/_sources/changelog.rst.txt | 48 + v0.4.1/_sources/datasets.rst.txt | 68 -- v0.4.1/_sources/documents.rst.txt | 87 -- .../getting_started/installing.rst.txt | 2 +- v0.4.1/_sources/index.rst.txt | 111 +- v0.4.1/_sources/installing.rst.txt | 46 - v0.4.1/_sources/models.rst.txt | 215 ---- v0.4.1/_sources/transforms.rst.txt | 32 - v0.4.1/_sources/utils.rst.txt | 36 - v0.4.1/_static/basic.css | 15 +- v0.4.1/_static/doctools.js | 7 - v0.4.1/_static/documentation_options.js | 2 +- v0.4.1/_static/language_data.js | 7 - v0.4.1/_static/searchtools.js | 38 +- v0.4.1/changelog.html | 124 +- v0.4.1/community/resources.html | 4 +- v0.4.1/contributing/code_of_conduct.html | 4 +- v0.4.1/contributing/contributing.html | 4 +- v0.4.1/datasets.html | 578 ---------- v0.4.1/documents.html | 772 ------------- v0.4.1/genindex.html | 328 ++++-- v0.4.1/getting_started/installing.html | 6 +- v0.4.1/index.html | 171 ++- v0.4.1/installing.html | 395 ------- v0.4.1/models.html | 1002 ----------------- v0.4.1/modules/contrib.html | 4 +- v0.4.1/modules/datasets.html | 4 +- v0.4.1/modules/io.html | 4 +- v0.4.1/modules/models.html | 4 +- v0.4.1/modules/transforms.html | 4 +- v0.4.1/modules/utils.html | 4 +- v0.4.1/notebooks.html | 4 +- v0.4.1/objects.inv | Bin 1962 -> 4167 bytes v0.4.1/py-modindex.html | 330 ------ v0.4.1/search.html | 46 +- v0.4.1/searchindex.js | 2 +- v0.4.1/transforms.html | 684 ----------- .../using_doctr/custom_models_training.html | 4 +- v0.4.1/using_doctr/running_on_aws.html | 4 +- v0.4.1/using_doctr/sharing_models.html | 4 +- v0.4.1/using_doctr/using_contrib_modules.html | 4 +- v0.4.1/using_doctr/using_datasets.html | 4 +- v0.4.1/using_doctr/using_model_export.html | 4 +- v0.4.1/using_doctr/using_models.html | 4 +- v0.4.1/utils.html | 574 ---------- v0.5.0/_modules/doctr/datasets/cord.html | 178 ++- v0.5.0/_modules/doctr/datasets/core.html | 417 ------- .../doctr/datasets/datasets/tensorflow.html | 356 ------ v0.5.0/_modules/doctr/datasets/detection.html | 4 +- .../doctr/datasets/doc_artefacts.html | 4 +- v0.5.0/_modules/doctr/datasets/funsd.html | 163 ++- .../doctr/datasets/generator/tensorflow.html | 4 +- v0.5.0/_modules/doctr/datasets/ic03.html | 4 +- v0.5.0/_modules/doctr/datasets/ic13.html | 4 +- v0.5.0/_modules/doctr/datasets/iiit5k.html | 4 +- v0.5.0/_modules/doctr/datasets/iiithws.html | 4 +- v0.5.0/_modules/doctr/datasets/imgur5k.html | 4 +- v0.5.0/_modules/doctr/datasets/loader.html | 93 +- v0.5.0/_modules/doctr/datasets/mjsynth.html | 4 +- v0.5.0/_modules/doctr/datasets/ocr.html | 121 +- .../_modules/doctr/datasets/recognition.html | 4 +- v0.5.0/_modules/doctr/datasets/sroie.html | 174 ++- v0.5.0/_modules/doctr/datasets/svhn.html | 4 +- v0.5.0/_modules/doctr/datasets/svt.html | 4 +- v0.5.0/_modules/doctr/datasets/synthtext.html | 4 +- v0.5.0/_modules/doctr/datasets/utils.html | 209 +++- .../_modules/doctr/datasets/wildreceipt.html | 4 +- v0.5.0/_modules/doctr/documents/elements.html | 577 ---------- v0.5.0/_modules/doctr/documents/reader.html | 612 ---------- v0.5.0/_modules/doctr/io/elements.html | 4 +- v0.5.0/_modules/doctr/io/html.html | 4 +- v0.5.0/_modules/doctr/io/image/base.html | 4 +- .../_modules/doctr/io/image/tensorflow.html | 4 +- v0.5.0/_modules/doctr/io/pdf.html | 4 +- v0.5.0/_modules/doctr/io/reader.html | 4 +- .../magc_resnet/tensorflow.html | 4 +- .../classification/mobilenet/tensorflow.html | 4 +- .../classification/resnet/tensorflow.html | 4 +- .../classification/textnet/tensorflow.html | 4 +- .../models/classification/vgg/tensorflow.html | 4 +- .../models/classification/vit/tensorflow.html | 4 +- .../doctr/models/classification/zoo.html | 4 +- .../differentiable_binarization.html | 879 --------------- .../tensorflow.html | 410 +++++-- .../models/detection/fast/tensorflow.html | 4 +- .../doctr/models/detection/linknet.html | 644 ----------- .../models/detection/linknet/tensorflow.html | 451 +++++--- .../_modules/doctr/models/detection/zoo.html | 166 ++- v0.5.0/_modules/doctr/models/export.html | 411 ------- v0.5.0/_modules/doctr/models/factory/hub.html | 4 +- .../doctr/models/recognition/crnn.html | 565 ---------- .../models/recognition/crnn/tensorflow.html | 293 +++-- .../models/recognition/master/tensorflow.html | 411 +++---- .../models/recognition/parseq/tensorflow.html | 4 +- .../doctr/models/recognition/sar.html | 712 ------------ .../models/recognition/sar/tensorflow.html | 406 ++++--- .../models/recognition/vitstr/tensorflow.html | 4 +- .../doctr/models/recognition/zoo.html | 127 ++- v0.5.0/_modules/doctr/models/zoo.html | 288 ++++- v0.5.0/_modules/doctr/transforms/modules.html | 734 ------------ .../doctr/transforms/modules/base.html | 324 +++++- .../doctr/transforms/modules/tensorflow.html | 533 +++++++-- v0.5.0/_modules/doctr/utils/metrics.html | 561 +++++---- .../_modules/doctr/utils/visualization.html | 465 +++++--- v0.5.0/_modules/index.html | 81 +- v0.5.0/_sources/changelog.rst.txt | 48 + v0.5.0/_sources/datasets.rst.txt | 68 -- v0.5.0/_sources/documents.rst.txt | 87 -- .../getting_started/installing.rst.txt | 2 +- v0.5.0/_sources/index.rst.txt | 111 +- v0.5.0/_sources/installing.rst.txt | 46 - v0.5.0/_sources/models.rst.txt | 215 ---- v0.5.0/_sources/transforms.rst.txt | 32 - v0.5.0/_sources/utils.rst.txt | 36 - v0.5.0/_static/basic.css | 15 +- v0.5.0/_static/doctools.js | 7 - v0.5.0/_static/documentation_options.js | 2 +- v0.5.0/_static/language_data.js | 7 - v0.5.0/_static/searchtools.js | 38 +- v0.5.0/changelog.html | 124 +- v0.5.0/community/resources.html | 4 +- v0.5.0/contributing/code_of_conduct.html | 4 +- v0.5.0/contributing/contributing.html | 4 +- v0.5.0/datasets.html | 578 ---------- v0.5.0/documents.html | 772 ------------- v0.5.0/genindex.html | 328 ++++-- v0.5.0/getting_started/installing.html | 6 +- v0.5.0/index.html | 171 ++- v0.5.0/installing.html | 395 ------- v0.5.0/models.html | 1002 ----------------- v0.5.0/modules/contrib.html | 4 +- v0.5.0/modules/datasets.html | 4 +- v0.5.0/modules/io.html | 4 +- v0.5.0/modules/models.html | 4 +- v0.5.0/modules/transforms.html | 4 +- v0.5.0/modules/utils.html | 4 +- v0.5.0/notebooks.html | 4 +- v0.5.0/objects.inv | Bin 1962 -> 4167 bytes v0.5.0/py-modindex.html | 330 ------ v0.5.0/search.html | 46 +- v0.5.0/searchindex.js | 2 +- v0.5.0/transforms.html | 684 ----------- .../using_doctr/custom_models_training.html | 4 +- v0.5.0/using_doctr/running_on_aws.html | 4 +- v0.5.0/using_doctr/sharing_models.html | 4 +- v0.5.0/using_doctr/using_contrib_modules.html | 4 +- v0.5.0/using_doctr/using_datasets.html | 4 +- v0.5.0/using_doctr/using_model_export.html | 4 +- v0.5.0/using_doctr/using_models.html | 4 +- v0.5.0/utils.html | 574 ---------- v0.5.1/_modules/doctr/datasets/cord.html | 178 ++- v0.5.1/_modules/doctr/datasets/core.html | 417 ------- .../doctr/datasets/datasets/tensorflow.html | 356 ------ v0.5.1/_modules/doctr/datasets/detection.html | 4 +- .../doctr/datasets/doc_artefacts.html | 4 +- v0.5.1/_modules/doctr/datasets/funsd.html | 163 ++- .../doctr/datasets/generator/tensorflow.html | 4 +- v0.5.1/_modules/doctr/datasets/ic03.html | 4 +- v0.5.1/_modules/doctr/datasets/ic13.html | 4 +- v0.5.1/_modules/doctr/datasets/iiit5k.html | 4 +- v0.5.1/_modules/doctr/datasets/iiithws.html | 4 +- v0.5.1/_modules/doctr/datasets/imgur5k.html | 4 +- v0.5.1/_modules/doctr/datasets/loader.html | 93 +- v0.5.1/_modules/doctr/datasets/mjsynth.html | 4 +- v0.5.1/_modules/doctr/datasets/ocr.html | 121 +- .../_modules/doctr/datasets/recognition.html | 4 +- v0.5.1/_modules/doctr/datasets/sroie.html | 174 ++- v0.5.1/_modules/doctr/datasets/svhn.html | 4 +- v0.5.1/_modules/doctr/datasets/svt.html | 4 +- v0.5.1/_modules/doctr/datasets/synthtext.html | 4 +- v0.5.1/_modules/doctr/datasets/utils.html | 209 +++- .../_modules/doctr/datasets/wildreceipt.html | 4 +- v0.5.1/_modules/doctr/documents/elements.html | 577 ---------- v0.5.1/_modules/doctr/documents/reader.html | 612 ---------- v0.5.1/_modules/doctr/io/elements.html | 4 +- v0.5.1/_modules/doctr/io/html.html | 4 +- v0.5.1/_modules/doctr/io/image/base.html | 4 +- .../_modules/doctr/io/image/tensorflow.html | 4 +- v0.5.1/_modules/doctr/io/pdf.html | 4 +- v0.5.1/_modules/doctr/io/reader.html | 4 +- .../magc_resnet/tensorflow.html | 4 +- .../classification/mobilenet/tensorflow.html | 4 +- .../classification/resnet/tensorflow.html | 4 +- .../classification/textnet/tensorflow.html | 4 +- .../models/classification/vgg/tensorflow.html | 4 +- .../models/classification/vit/tensorflow.html | 4 +- .../doctr/models/classification/zoo.html | 4 +- .../differentiable_binarization.html | 879 --------------- .../tensorflow.html | 410 +++++-- .../models/detection/fast/tensorflow.html | 4 +- .../doctr/models/detection/linknet.html | 644 ----------- .../models/detection/linknet/tensorflow.html | 451 +++++--- .../_modules/doctr/models/detection/zoo.html | 166 ++- v0.5.1/_modules/doctr/models/export.html | 411 ------- v0.5.1/_modules/doctr/models/factory/hub.html | 4 +- .../doctr/models/recognition/crnn.html | 565 ---------- .../models/recognition/crnn/tensorflow.html | 293 +++-- .../models/recognition/master/tensorflow.html | 411 +++---- .../models/recognition/parseq/tensorflow.html | 4 +- .../doctr/models/recognition/sar.html | 712 ------------ .../models/recognition/sar/tensorflow.html | 406 ++++--- .../models/recognition/vitstr/tensorflow.html | 4 +- .../doctr/models/recognition/zoo.html | 127 ++- v0.5.1/_modules/doctr/models/zoo.html | 288 ++++- v0.5.1/_modules/doctr/transforms/modules.html | 734 ------------ .../doctr/transforms/modules/base.html | 324 +++++- .../doctr/transforms/modules/tensorflow.html | 533 +++++++-- v0.5.1/_modules/doctr/utils/metrics.html | 561 +++++---- .../_modules/doctr/utils/visualization.html | 465 +++++--- v0.5.1/_modules/index.html | 81 +- v0.5.1/_sources/changelog.rst.txt | 48 + v0.5.1/_sources/datasets.rst.txt | 68 -- v0.5.1/_sources/documents.rst.txt | 87 -- .../getting_started/installing.rst.txt | 2 +- v0.5.1/_sources/index.rst.txt | 111 +- v0.5.1/_sources/installing.rst.txt | 46 - v0.5.1/_sources/models.rst.txt | 215 ---- v0.5.1/_sources/transforms.rst.txt | 32 - v0.5.1/_sources/utils.rst.txt | 36 - v0.5.1/_static/basic.css | 15 +- v0.5.1/_static/doctools.js | 7 - v0.5.1/_static/documentation_options.js | 2 +- v0.5.1/_static/language_data.js | 7 - v0.5.1/_static/searchtools.js | 38 +- v0.5.1/changelog.html | 124 +- v0.5.1/community/resources.html | 4 +- v0.5.1/contributing/code_of_conduct.html | 4 +- v0.5.1/contributing/contributing.html | 4 +- v0.5.1/datasets.html | 578 ---------- v0.5.1/documents.html | 772 ------------- v0.5.1/genindex.html | 328 ++++-- v0.5.1/getting_started/installing.html | 6 +- v0.5.1/index.html | 171 ++- v0.5.1/installing.html | 395 ------- v0.5.1/models.html | 1002 ----------------- v0.5.1/modules/contrib.html | 4 +- v0.5.1/modules/datasets.html | 4 +- v0.5.1/modules/io.html | 4 +- v0.5.1/modules/models.html | 4 +- v0.5.1/modules/transforms.html | 4 +- v0.5.1/modules/utils.html | 4 +- v0.5.1/notebooks.html | 4 +- v0.5.1/objects.inv | Bin 1962 -> 4167 bytes v0.5.1/py-modindex.html | 330 ------ v0.5.1/search.html | 46 +- v0.5.1/searchindex.js | 2 +- v0.5.1/transforms.html | 684 ----------- .../using_doctr/custom_models_training.html | 4 +- v0.5.1/using_doctr/running_on_aws.html | 4 +- v0.5.1/using_doctr/sharing_models.html | 4 +- v0.5.1/using_doctr/using_contrib_modules.html | 4 +- v0.5.1/using_doctr/using_datasets.html | 4 +- v0.5.1/using_doctr/using_model_export.html | 4 +- v0.5.1/using_doctr/using_models.html | 4 +- v0.5.1/utils.html | 574 ---------- v0.6.0/_modules/doctr/datasets/cord.html | 178 ++- v0.6.0/_modules/doctr/datasets/core.html | 417 ------- .../doctr/datasets/datasets/tensorflow.html | 356 ------ v0.6.0/_modules/doctr/datasets/detection.html | 4 +- .../doctr/datasets/doc_artefacts.html | 4 +- v0.6.0/_modules/doctr/datasets/funsd.html | 163 ++- .../doctr/datasets/generator/tensorflow.html | 4 +- v0.6.0/_modules/doctr/datasets/ic03.html | 4 +- v0.6.0/_modules/doctr/datasets/ic13.html | 4 +- v0.6.0/_modules/doctr/datasets/iiit5k.html | 4 +- v0.6.0/_modules/doctr/datasets/iiithws.html | 4 +- v0.6.0/_modules/doctr/datasets/imgur5k.html | 4 +- v0.6.0/_modules/doctr/datasets/loader.html | 93 +- v0.6.0/_modules/doctr/datasets/mjsynth.html | 4 +- v0.6.0/_modules/doctr/datasets/ocr.html | 121 +- .../_modules/doctr/datasets/recognition.html | 4 +- v0.6.0/_modules/doctr/datasets/sroie.html | 174 ++- v0.6.0/_modules/doctr/datasets/svhn.html | 4 +- v0.6.0/_modules/doctr/datasets/svt.html | 4 +- v0.6.0/_modules/doctr/datasets/synthtext.html | 4 +- v0.6.0/_modules/doctr/datasets/utils.html | 209 +++- .../_modules/doctr/datasets/wildreceipt.html | 4 +- v0.6.0/_modules/doctr/documents/elements.html | 577 ---------- v0.6.0/_modules/doctr/documents/reader.html | 612 ---------- v0.6.0/_modules/doctr/io/elements.html | 4 +- v0.6.0/_modules/doctr/io/html.html | 4 +- v0.6.0/_modules/doctr/io/image/base.html | 4 +- .../_modules/doctr/io/image/tensorflow.html | 4 +- v0.6.0/_modules/doctr/io/pdf.html | 4 +- v0.6.0/_modules/doctr/io/reader.html | 4 +- .../magc_resnet/tensorflow.html | 4 +- .../classification/mobilenet/tensorflow.html | 4 +- .../classification/resnet/tensorflow.html | 4 +- .../classification/textnet/tensorflow.html | 4 +- .../models/classification/vgg/tensorflow.html | 4 +- .../models/classification/vit/tensorflow.html | 4 +- .../doctr/models/classification/zoo.html | 4 +- .../differentiable_binarization.html | 879 --------------- .../tensorflow.html | 410 +++++-- .../models/detection/fast/tensorflow.html | 4 +- .../doctr/models/detection/linknet.html | 644 ----------- .../models/detection/linknet/tensorflow.html | 451 +++++--- .../_modules/doctr/models/detection/zoo.html | 166 ++- v0.6.0/_modules/doctr/models/export.html | 411 ------- v0.6.0/_modules/doctr/models/factory/hub.html | 4 +- .../doctr/models/recognition/crnn.html | 565 ---------- .../models/recognition/crnn/tensorflow.html | 293 +++-- .../models/recognition/master/tensorflow.html | 411 +++---- .../models/recognition/parseq/tensorflow.html | 4 +- .../doctr/models/recognition/sar.html | 712 ------------ .../models/recognition/sar/tensorflow.html | 406 ++++--- .../models/recognition/vitstr/tensorflow.html | 4 +- .../doctr/models/recognition/zoo.html | 127 ++- v0.6.0/_modules/doctr/models/zoo.html | 288 ++++- v0.6.0/_modules/doctr/transforms/modules.html | 734 ------------ .../doctr/transforms/modules/base.html | 324 +++++- .../doctr/transforms/modules/tensorflow.html | 533 +++++++-- v0.6.0/_modules/doctr/utils/metrics.html | 561 +++++---- .../_modules/doctr/utils/visualization.html | 465 +++++--- v0.6.0/_modules/index.html | 81 +- v0.6.0/_sources/changelog.rst.txt | 48 + v0.6.0/_sources/datasets.rst.txt | 68 -- v0.6.0/_sources/documents.rst.txt | 87 -- .../getting_started/installing.rst.txt | 2 +- v0.6.0/_sources/index.rst.txt | 111 +- v0.6.0/_sources/installing.rst.txt | 46 - v0.6.0/_sources/models.rst.txt | 215 ---- v0.6.0/_sources/transforms.rst.txt | 32 - v0.6.0/_sources/utils.rst.txt | 36 - v0.6.0/_static/basic.css | 15 +- v0.6.0/_static/doctools.js | 7 - v0.6.0/_static/documentation_options.js | 2 +- v0.6.0/_static/language_data.js | 7 - v0.6.0/_static/searchtools.js | 38 +- v0.6.0/changelog.html | 124 +- v0.6.0/community/resources.html | 4 +- v0.6.0/contributing/code_of_conduct.html | 4 +- v0.6.0/contributing/contributing.html | 4 +- v0.6.0/datasets.html | 578 ---------- v0.6.0/documents.html | 772 ------------- v0.6.0/genindex.html | 328 ++++-- v0.6.0/getting_started/installing.html | 6 +- v0.6.0/index.html | 171 ++- v0.6.0/installing.html | 395 ------- v0.6.0/models.html | 1002 ----------------- v0.6.0/modules/contrib.html | 4 +- v0.6.0/modules/datasets.html | 4 +- v0.6.0/modules/io.html | 4 +- v0.6.0/modules/models.html | 4 +- v0.6.0/modules/transforms.html | 4 +- v0.6.0/modules/utils.html | 4 +- v0.6.0/notebooks.html | 4 +- v0.6.0/objects.inv | Bin 1962 -> 4167 bytes v0.6.0/py-modindex.html | 330 ------ v0.6.0/search.html | 46 +- v0.6.0/searchindex.js | 2 +- v0.6.0/transforms.html | 684 ----------- .../using_doctr/custom_models_training.html | 4 +- v0.6.0/using_doctr/running_on_aws.html | 4 +- v0.6.0/using_doctr/sharing_models.html | 4 +- v0.6.0/using_doctr/using_contrib_modules.html | 4 +- v0.6.0/using_doctr/using_datasets.html | 4 +- v0.6.0/using_doctr/using_model_export.html | 4 +- v0.6.0/using_doctr/using_models.html | 4 +- v0.6.0/utils.html | 574 ---------- v0.7.0/_modules/doctr/datasets/cord.html | 4 +- v0.7.0/_modules/doctr/datasets/core.html | 417 ------- .../doctr/datasets/datasets/tensorflow.html | 356 ------ v0.7.0/_modules/doctr/datasets/detection.html | 4 +- .../doctr/datasets/doc_artefacts.html | 4 +- v0.7.0/_modules/doctr/datasets/funsd.html | 4 +- .../doctr/datasets/generator/tensorflow.html | 4 +- v0.7.0/_modules/doctr/datasets/ic03.html | 4 +- v0.7.0/_modules/doctr/datasets/ic13.html | 4 +- v0.7.0/_modules/doctr/datasets/iiit5k.html | 4 +- v0.7.0/_modules/doctr/datasets/iiithws.html | 4 +- v0.7.0/_modules/doctr/datasets/imgur5k.html | 4 +- v0.7.0/_modules/doctr/datasets/loader.html | 4 +- v0.7.0/_modules/doctr/datasets/mjsynth.html | 4 +- v0.7.0/_modules/doctr/datasets/ocr.html | 4 +- .../_modules/doctr/datasets/recognition.html | 4 +- v0.7.0/_modules/doctr/datasets/sroie.html | 4 +- v0.7.0/_modules/doctr/datasets/svhn.html | 4 +- v0.7.0/_modules/doctr/datasets/svt.html | 4 +- v0.7.0/_modules/doctr/datasets/synthtext.html | 4 +- v0.7.0/_modules/doctr/datasets/utils.html | 4 +- .../_modules/doctr/datasets/wildreceipt.html | 4 +- v0.7.0/_modules/doctr/documents/elements.html | 577 ---------- v0.7.0/_modules/doctr/documents/reader.html | 612 ---------- v0.7.0/_modules/doctr/io/elements.html | 4 +- v0.7.0/_modules/doctr/io/html.html | 4 +- v0.7.0/_modules/doctr/io/image/base.html | 4 +- .../_modules/doctr/io/image/tensorflow.html | 4 +- v0.7.0/_modules/doctr/io/pdf.html | 4 +- v0.7.0/_modules/doctr/io/reader.html | 4 +- .../magc_resnet/tensorflow.html | 4 +- .../classification/mobilenet/tensorflow.html | 4 +- .../classification/resnet/tensorflow.html | 4 +- .../classification/textnet/tensorflow.html | 4 +- .../models/classification/vgg/tensorflow.html | 4 +- .../models/classification/vit/tensorflow.html | 4 +- .../doctr/models/classification/zoo.html | 4 +- .../differentiable_binarization.html | 879 --------------- .../tensorflow.html | 4 +- .../models/detection/fast/tensorflow.html | 4 +- .../doctr/models/detection/linknet.html | 644 ----------- .../models/detection/linknet/tensorflow.html | 4 +- .../_modules/doctr/models/detection/zoo.html | 4 +- v0.7.0/_modules/doctr/models/export.html | 411 ------- v0.7.0/_modules/doctr/models/factory/hub.html | 4 +- .../doctr/models/recognition/crnn.html | 565 ---------- .../models/recognition/crnn/tensorflow.html | 4 +- .../models/recognition/master/tensorflow.html | 4 +- .../models/recognition/parseq/tensorflow.html | 4 +- .../doctr/models/recognition/sar.html | 712 ------------ .../models/recognition/sar/tensorflow.html | 4 +- .../models/recognition/vitstr/tensorflow.html | 4 +- .../doctr/models/recognition/zoo.html | 4 +- v0.7.0/_modules/doctr/models/zoo.html | 4 +- v0.7.0/_modules/doctr/transforms/modules.html | 734 ------------ .../doctr/transforms/modules/base.html | 4 +- .../doctr/transforms/modules/tensorflow.html | 4 +- v0.7.0/_modules/doctr/utils/metrics.html | 4 +- .../_modules/doctr/utils/visualization.html | 4 +- v0.7.0/_modules/index.html | 4 +- v0.7.0/_sources/datasets.rst.txt | 68 -- v0.7.0/_sources/documents.rst.txt | 87 -- v0.7.0/_sources/installing.rst.txt | 46 - v0.7.0/_sources/models.rst.txt | 215 ---- v0.7.0/_sources/transforms.rst.txt | 32 - v0.7.0/_sources/utils.rst.txt | 36 - v0.7.0/_static/basic.css | 15 +- v0.7.0/_static/doctools.js | 7 - v0.7.0/_static/language_data.js | 7 - v0.7.0/_static/searchtools.js | 38 +- v0.7.0/changelog.html | 4 +- v0.7.0/community/resources.html | 4 +- v0.7.0/contributing/code_of_conduct.html | 4 +- v0.7.0/contributing/contributing.html | 4 +- v0.7.0/datasets.html | 578 ---------- v0.7.0/documents.html | 772 ------------- v0.7.0/genindex.html | 4 +- v0.7.0/getting_started/installing.html | 4 +- v0.7.0/index.html | 4 +- v0.7.0/installing.html | 395 ------- v0.7.0/models.html | 1002 ----------------- v0.7.0/modules/contrib.html | 4 +- v0.7.0/modules/datasets.html | 4 +- v0.7.0/modules/io.html | 4 +- v0.7.0/modules/models.html | 4 +- v0.7.0/modules/transforms.html | 4 +- v0.7.0/modules/utils.html | 4 +- v0.7.0/notebooks.html | 4 +- v0.7.0/py-modindex.html | 330 ------ v0.7.0/search.html | 4 +- v0.7.0/searchindex.js | 2 +- v0.7.0/transforms.html | 684 ----------- .../using_doctr/custom_models_training.html | 4 +- v0.7.0/using_doctr/running_on_aws.html | 4 +- v0.7.0/using_doctr/sharing_models.html | 4 +- v0.7.0/using_doctr/using_contrib_modules.html | 4 +- v0.7.0/using_doctr/using_datasets.html | 4 +- v0.7.0/using_doctr/using_model_export.html | 4 +- v0.7.0/using_doctr/using_models.html | 4 +- v0.7.0/utils.html | 574 ---------- v0.8.0/_modules/doctr/datasets/cord.html | 4 +- v0.8.0/_modules/doctr/datasets/core.html | 417 ------- .../doctr/datasets/datasets/tensorflow.html | 356 ------ v0.8.0/_modules/doctr/datasets/detection.html | 4 +- .../doctr/datasets/doc_artefacts.html | 4 +- v0.8.0/_modules/doctr/datasets/funsd.html | 4 +- .../doctr/datasets/generator/tensorflow.html | 4 +- v0.8.0/_modules/doctr/datasets/ic03.html | 4 +- v0.8.0/_modules/doctr/datasets/ic13.html | 4 +- v0.8.0/_modules/doctr/datasets/iiit5k.html | 4 +- v0.8.0/_modules/doctr/datasets/iiithws.html | 4 +- v0.8.0/_modules/doctr/datasets/imgur5k.html | 4 +- v0.8.0/_modules/doctr/datasets/loader.html | 4 +- v0.8.0/_modules/doctr/datasets/mjsynth.html | 4 +- v0.8.0/_modules/doctr/datasets/ocr.html | 4 +- .../_modules/doctr/datasets/recognition.html | 4 +- v0.8.0/_modules/doctr/datasets/sroie.html | 4 +- v0.8.0/_modules/doctr/datasets/svhn.html | 4 +- v0.8.0/_modules/doctr/datasets/svt.html | 4 +- v0.8.0/_modules/doctr/datasets/synthtext.html | 4 +- v0.8.0/_modules/doctr/datasets/utils.html | 4 +- .../_modules/doctr/datasets/wildreceipt.html | 4 +- v0.8.0/_modules/doctr/documents/elements.html | 577 ---------- v0.8.0/_modules/doctr/documents/reader.html | 612 ---------- v0.8.0/_modules/doctr/io/elements.html | 4 +- v0.8.0/_modules/doctr/io/html.html | 4 +- v0.8.0/_modules/doctr/io/image/base.html | 4 +- .../_modules/doctr/io/image/tensorflow.html | 4 +- v0.8.0/_modules/doctr/io/pdf.html | 4 +- v0.8.0/_modules/doctr/io/reader.html | 4 +- .../magc_resnet/tensorflow.html | 4 +- .../classification/mobilenet/tensorflow.html | 4 +- .../classification/resnet/tensorflow.html | 4 +- .../classification/textnet/tensorflow.html | 4 +- .../models/classification/vgg/tensorflow.html | 4 +- .../models/classification/vit/tensorflow.html | 4 +- .../doctr/models/classification/zoo.html | 4 +- .../differentiable_binarization.html | 879 --------------- .../tensorflow.html | 4 +- .../models/detection/fast/tensorflow.html | 4 +- .../doctr/models/detection/linknet.html | 644 ----------- .../models/detection/linknet/tensorflow.html | 4 +- .../_modules/doctr/models/detection/zoo.html | 4 +- v0.8.0/_modules/doctr/models/export.html | 411 ------- v0.8.0/_modules/doctr/models/factory/hub.html | 4 +- .../doctr/models/recognition/crnn.html | 565 ---------- .../models/recognition/crnn/tensorflow.html | 4 +- .../models/recognition/master/tensorflow.html | 4 +- .../models/recognition/parseq/tensorflow.html | 4 +- .../doctr/models/recognition/sar.html | 712 ------------ .../models/recognition/sar/tensorflow.html | 4 +- .../models/recognition/vitstr/tensorflow.html | 4 +- .../doctr/models/recognition/zoo.html | 4 +- v0.8.0/_modules/doctr/models/zoo.html | 4 +- v0.8.0/_modules/doctr/transforms/modules.html | 734 ------------ .../doctr/transforms/modules/base.html | 4 +- .../doctr/transforms/modules/tensorflow.html | 4 +- v0.8.0/_modules/doctr/utils/metrics.html | 4 +- .../_modules/doctr/utils/visualization.html | 4 +- v0.8.0/_modules/index.html | 4 +- v0.8.0/_sources/datasets.rst.txt | 68 -- v0.8.0/_sources/documents.rst.txt | 87 -- v0.8.0/_sources/installing.rst.txt | 46 - v0.8.0/_sources/models.rst.txt | 215 ---- v0.8.0/_sources/transforms.rst.txt | 32 - v0.8.0/_sources/utils.rst.txt | 36 - v0.8.0/_static/basic.css | 15 +- v0.8.0/_static/doctools.js | 7 - v0.8.0/_static/language_data.js | 7 - v0.8.0/_static/searchtools.js | 38 +- v0.8.0/changelog.html | 4 +- v0.8.0/community/resources.html | 4 +- v0.8.0/contributing/code_of_conduct.html | 4 +- v0.8.0/contributing/contributing.html | 4 +- v0.8.0/datasets.html | 578 ---------- v0.8.0/documents.html | 772 ------------- v0.8.0/genindex.html | 4 +- v0.8.0/getting_started/installing.html | 4 +- v0.8.0/index.html | 4 +- v0.8.0/installing.html | 395 ------- v0.8.0/models.html | 1002 ----------------- v0.8.0/modules/contrib.html | 4 +- v0.8.0/modules/datasets.html | 4 +- v0.8.0/modules/io.html | 4 +- v0.8.0/modules/models.html | 4 +- v0.8.0/modules/transforms.html | 4 +- v0.8.0/modules/utils.html | 4 +- v0.8.0/notebooks.html | 4 +- v0.8.0/py-modindex.html | 330 ------ v0.8.0/search.html | 4 +- v0.8.0/searchindex.js | 2 +- v0.8.0/transforms.html | 684 ----------- .../using_doctr/custom_models_training.html | 4 +- v0.8.0/using_doctr/running_on_aws.html | 4 +- v0.8.0/using_doctr/sharing_models.html | 4 +- v0.8.0/using_doctr/using_contrib_modules.html | 4 +- v0.8.0/using_doctr/using_datasets.html | 4 +- v0.8.0/using_doctr/using_model_export.html | 4 +- v0.8.0/using_doctr/using_models.html | 4 +- v0.8.0/utils.html | 574 ---------- v0.8.1/_modules/doctr/datasets/cord.html | 4 +- v0.8.1/_modules/doctr/datasets/core.html | 417 ------- .../doctr/datasets/datasets/tensorflow.html | 356 ------ v0.8.1/_modules/doctr/datasets/detection.html | 4 +- .../doctr/datasets/doc_artefacts.html | 4 +- v0.8.1/_modules/doctr/datasets/funsd.html | 4 +- .../doctr/datasets/generator/tensorflow.html | 4 +- v0.8.1/_modules/doctr/datasets/ic03.html | 4 +- v0.8.1/_modules/doctr/datasets/ic13.html | 4 +- v0.8.1/_modules/doctr/datasets/iiit5k.html | 4 +- v0.8.1/_modules/doctr/datasets/iiithws.html | 4 +- v0.8.1/_modules/doctr/datasets/imgur5k.html | 4 +- v0.8.1/_modules/doctr/datasets/loader.html | 4 +- v0.8.1/_modules/doctr/datasets/mjsynth.html | 4 +- v0.8.1/_modules/doctr/datasets/ocr.html | 4 +- .../_modules/doctr/datasets/recognition.html | 4 +- v0.8.1/_modules/doctr/datasets/sroie.html | 4 +- v0.8.1/_modules/doctr/datasets/svhn.html | 4 +- v0.8.1/_modules/doctr/datasets/svt.html | 4 +- v0.8.1/_modules/doctr/datasets/synthtext.html | 4 +- v0.8.1/_modules/doctr/datasets/utils.html | 4 +- .../_modules/doctr/datasets/wildreceipt.html | 4 +- v0.8.1/_modules/doctr/documents/elements.html | 577 ---------- v0.8.1/_modules/doctr/documents/reader.html | 612 ---------- v0.8.1/_modules/doctr/io/elements.html | 4 +- v0.8.1/_modules/doctr/io/html.html | 4 +- v0.8.1/_modules/doctr/io/image/base.html | 4 +- .../_modules/doctr/io/image/tensorflow.html | 4 +- v0.8.1/_modules/doctr/io/pdf.html | 4 +- v0.8.1/_modules/doctr/io/reader.html | 4 +- .../magc_resnet/tensorflow.html | 4 +- .../classification/mobilenet/tensorflow.html | 4 +- .../classification/resnet/tensorflow.html | 4 +- .../classification/textnet/tensorflow.html | 4 +- .../models/classification/vgg/tensorflow.html | 4 +- .../models/classification/vit/tensorflow.html | 4 +- .../doctr/models/classification/zoo.html | 4 +- .../differentiable_binarization.html | 879 --------------- .../tensorflow.html | 4 +- .../models/detection/fast/tensorflow.html | 4 +- .../doctr/models/detection/linknet.html | 644 ----------- .../models/detection/linknet/tensorflow.html | 4 +- .../_modules/doctr/models/detection/zoo.html | 4 +- v0.8.1/_modules/doctr/models/export.html | 411 ------- v0.8.1/_modules/doctr/models/factory/hub.html | 4 +- .../doctr/models/recognition/crnn.html | 565 ---------- .../models/recognition/crnn/tensorflow.html | 4 +- .../models/recognition/master/tensorflow.html | 4 +- .../models/recognition/parseq/tensorflow.html | 4 +- .../doctr/models/recognition/sar.html | 712 ------------ .../models/recognition/sar/tensorflow.html | 4 +- .../models/recognition/vitstr/tensorflow.html | 4 +- .../doctr/models/recognition/zoo.html | 4 +- v0.8.1/_modules/doctr/models/zoo.html | 4 +- v0.8.1/_modules/doctr/transforms/modules.html | 734 ------------ .../doctr/transforms/modules/base.html | 4 +- .../doctr/transforms/modules/tensorflow.html | 4 +- v0.8.1/_modules/doctr/utils/metrics.html | 4 +- .../_modules/doctr/utils/visualization.html | 4 +- v0.8.1/_modules/index.html | 4 +- v0.8.1/_sources/datasets.rst.txt | 68 -- v0.8.1/_sources/documents.rst.txt | 87 -- v0.8.1/_sources/installing.rst.txt | 46 - v0.8.1/_sources/models.rst.txt | 215 ---- v0.8.1/_sources/transforms.rst.txt | 32 - v0.8.1/_sources/utils.rst.txt | 36 - v0.8.1/_static/basic.css | 15 +- v0.8.1/_static/doctools.js | 7 - v0.8.1/_static/language_data.js | 7 - v0.8.1/_static/searchtools.js | 38 +- v0.8.1/changelog.html | 4 +- v0.8.1/community/resources.html | 4 +- v0.8.1/contributing/code_of_conduct.html | 4 +- v0.8.1/contributing/contributing.html | 4 +- v0.8.1/datasets.html | 578 ---------- v0.8.1/documents.html | 772 ------------- v0.8.1/genindex.html | 4 +- v0.8.1/getting_started/installing.html | 4 +- v0.8.1/index.html | 4 +- v0.8.1/installing.html | 395 ------- v0.8.1/models.html | 1002 ----------------- v0.8.1/modules/contrib.html | 4 +- v0.8.1/modules/datasets.html | 4 +- v0.8.1/modules/io.html | 4 +- v0.8.1/modules/models.html | 4 +- v0.8.1/modules/transforms.html | 4 +- v0.8.1/modules/utils.html | 4 +- v0.8.1/notebooks.html | 4 +- v0.8.1/py-modindex.html | 330 ------ v0.8.1/search.html | 4 +- v0.8.1/searchindex.js | 2 +- v0.8.1/transforms.html | 684 ----------- .../using_doctr/custom_models_training.html | 4 +- v0.8.1/using_doctr/running_on_aws.html | 4 +- v0.8.1/using_doctr/sharing_models.html | 4 +- v0.8.1/using_doctr/using_contrib_modules.html | 4 +- v0.8.1/using_doctr/using_datasets.html | 4 +- v0.8.1/using_doctr/using_model_export.html | 4 +- v0.8.1/using_doctr/using_models.html | 4 +- v0.8.1/utils.html | 574 ---------- v0.9.0/_modules/doctr/datasets/cord.html | 4 +- v0.9.0/_modules/doctr/datasets/core.html | 417 ------- .../doctr/datasets/datasets/tensorflow.html | 356 ------ v0.9.0/_modules/doctr/datasets/detection.html | 4 +- .../doctr/datasets/doc_artefacts.html | 4 +- v0.9.0/_modules/doctr/datasets/funsd.html | 4 +- .../doctr/datasets/generator/tensorflow.html | 4 +- v0.9.0/_modules/doctr/datasets/ic03.html | 4 +- v0.9.0/_modules/doctr/datasets/ic13.html | 4 +- v0.9.0/_modules/doctr/datasets/iiit5k.html | 4 +- v0.9.0/_modules/doctr/datasets/iiithws.html | 4 +- v0.9.0/_modules/doctr/datasets/imgur5k.html | 4 +- v0.9.0/_modules/doctr/datasets/loader.html | 4 +- v0.9.0/_modules/doctr/datasets/mjsynth.html | 4 +- v0.9.0/_modules/doctr/datasets/ocr.html | 4 +- .../_modules/doctr/datasets/recognition.html | 4 +- v0.9.0/_modules/doctr/datasets/sroie.html | 4 +- v0.9.0/_modules/doctr/datasets/svhn.html | 4 +- v0.9.0/_modules/doctr/datasets/svt.html | 4 +- v0.9.0/_modules/doctr/datasets/synthtext.html | 4 +- v0.9.0/_modules/doctr/datasets/utils.html | 4 +- .../_modules/doctr/datasets/wildreceipt.html | 4 +- v0.9.0/_modules/doctr/documents/elements.html | 577 ---------- v0.9.0/_modules/doctr/documents/reader.html | 612 ---------- v0.9.0/_modules/doctr/io/elements.html | 4 +- v0.9.0/_modules/doctr/io/html.html | 4 +- v0.9.0/_modules/doctr/io/image/base.html | 4 +- .../_modules/doctr/io/image/tensorflow.html | 4 +- v0.9.0/_modules/doctr/io/pdf.html | 4 +- v0.9.0/_modules/doctr/io/reader.html | 4 +- .../magc_resnet/tensorflow.html | 4 +- .../classification/mobilenet/tensorflow.html | 4 +- .../classification/resnet/tensorflow.html | 4 +- .../classification/textnet/tensorflow.html | 4 +- .../models/classification/vgg/tensorflow.html | 4 +- .../models/classification/vit/tensorflow.html | 4 +- .../doctr/models/classification/zoo.html | 4 +- .../differentiable_binarization.html | 879 --------------- .../tensorflow.html | 4 +- .../models/detection/fast/tensorflow.html | 4 +- .../doctr/models/detection/linknet.html | 644 ----------- .../models/detection/linknet/tensorflow.html | 4 +- .../_modules/doctr/models/detection/zoo.html | 4 +- v0.9.0/_modules/doctr/models/export.html | 411 ------- v0.9.0/_modules/doctr/models/factory/hub.html | 4 +- .../doctr/models/recognition/crnn.html | 565 ---------- .../models/recognition/crnn/tensorflow.html | 4 +- .../models/recognition/master/tensorflow.html | 4 +- .../models/recognition/parseq/tensorflow.html | 4 +- .../doctr/models/recognition/sar.html | 712 ------------ .../models/recognition/sar/tensorflow.html | 4 +- .../models/recognition/vitstr/tensorflow.html | 4 +- .../doctr/models/recognition/zoo.html | 4 +- v0.9.0/_modules/doctr/models/zoo.html | 4 +- v0.9.0/_modules/doctr/transforms/modules.html | 734 ------------ .../doctr/transforms/modules/base.html | 4 +- .../doctr/transforms/modules/tensorflow.html | 4 +- v0.9.0/_modules/doctr/utils/metrics.html | 4 +- .../_modules/doctr/utils/visualization.html | 4 +- v0.9.0/_modules/index.html | 4 +- v0.9.0/_sources/datasets.rst.txt | 68 -- v0.9.0/_sources/documents.rst.txt | 87 -- v0.9.0/_sources/installing.rst.txt | 46 - v0.9.0/_sources/models.rst.txt | 215 ---- v0.9.0/_sources/transforms.rst.txt | 32 - v0.9.0/_sources/utils.rst.txt | 36 - v0.9.0/_static/basic.css | 15 +- v0.9.0/_static/doctools.js | 7 - v0.9.0/_static/language_data.js | 7 - v0.9.0/_static/searchtools.js | 38 +- v0.9.0/changelog.html | 4 +- v0.9.0/community/resources.html | 4 +- v0.9.0/contributing/code_of_conduct.html | 4 +- v0.9.0/contributing/contributing.html | 4 +- v0.9.0/datasets.html | 578 ---------- v0.9.0/documents.html | 772 ------------- v0.9.0/genindex.html | 4 +- v0.9.0/getting_started/installing.html | 4 +- v0.9.0/index.html | 4 +- v0.9.0/installing.html | 395 ------- v0.9.0/models.html | 1002 ----------------- v0.9.0/modules/contrib.html | 4 +- v0.9.0/modules/datasets.html | 4 +- v0.9.0/modules/io.html | 4 +- v0.9.0/modules/models.html | 4 +- v0.9.0/modules/transforms.html | 4 +- v0.9.0/modules/utils.html | 4 +- v0.9.0/notebooks.html | 4 +- v0.9.0/py-modindex.html | 330 ------ v0.9.0/search.html | 4 +- v0.9.0/searchindex.js | 2 +- v0.9.0/transforms.html | 684 ----------- .../using_doctr/custom_models_training.html | 4 +- v0.9.0/using_doctr/running_on_aws.html | 4 +- v0.9.0/using_doctr/sharing_models.html | 4 +- v0.9.0/using_doctr/using_contrib_modules.html | 4 +- v0.9.0/using_doctr/using_datasets.html | 4 +- v0.9.0/using_doctr/using_model_export.html | 4 +- v0.9.0/using_doctr/using_models.html | 4 +- v0.9.0/utils.html | 574 ---------- 1654 files changed, 37587 insertions(+), 156933 deletions(-) delete mode 100644 v0.2.0/_modules/doctr/datasets/core.html delete mode 100644 v0.2.0/_modules/doctr/documents/elements.html delete mode 100644 v0.2.0/_modules/doctr/documents/reader.html delete mode 100644 v0.2.0/_modules/doctr/models/detection/differentiable_binarization.html delete mode 100644 v0.2.0/_modules/doctr/models/detection/linknet.html delete mode 100644 v0.2.0/_modules/doctr/models/export.html delete mode 100644 v0.2.0/_modules/doctr/models/recognition/crnn.html delete mode 100644 v0.2.0/_modules/doctr/models/recognition/sar.html delete mode 100644 v0.2.0/_modules/doctr/transforms/modules.html delete mode 100644 v0.2.0/_sources/datasets.rst.txt delete mode 100644 v0.2.0/_sources/documents.rst.txt delete mode 100644 v0.2.0/_sources/installing.rst.txt delete mode 100644 v0.2.0/_sources/models.rst.txt delete mode 100644 v0.2.0/_sources/transforms.rst.txt delete mode 100644 v0.2.0/_sources/utils.rst.txt delete mode 100644 v0.2.0/datasets.html delete mode 100644 v0.2.0/documents.html delete mode 100644 v0.2.0/installing.html delete mode 100644 v0.2.0/models.html delete mode 100644 v0.2.0/py-modindex.html delete mode 100644 v0.2.0/transforms.html delete mode 100644 v0.2.0/utils.html delete mode 100644 v0.2.1/_modules/doctr/datasets/core.html delete mode 100644 v0.2.1/_modules/doctr/documents/elements.html delete mode 100644 v0.2.1/_modules/doctr/documents/reader.html delete mode 100644 v0.2.1/_modules/doctr/models/detection/differentiable_binarization.html delete mode 100644 v0.2.1/_modules/doctr/models/detection/linknet.html delete mode 100644 v0.2.1/_modules/doctr/models/export.html delete mode 100644 v0.2.1/_modules/doctr/models/recognition/crnn.html delete mode 100644 v0.2.1/_modules/doctr/models/recognition/sar.html delete mode 100644 v0.2.1/_modules/doctr/transforms/modules.html delete mode 100644 v0.2.1/_sources/datasets.rst.txt delete mode 100644 v0.2.1/_sources/documents.rst.txt delete mode 100644 v0.2.1/_sources/installing.rst.txt delete mode 100644 v0.2.1/_sources/models.rst.txt delete mode 100644 v0.2.1/_sources/transforms.rst.txt delete mode 100644 v0.2.1/_sources/utils.rst.txt delete mode 100644 v0.2.1/datasets.html delete mode 100644 v0.2.1/documents.html delete mode 100644 v0.2.1/installing.html delete mode 100644 v0.2.1/models.html delete mode 100644 v0.2.1/py-modindex.html delete mode 100644 v0.2.1/transforms.html delete mode 100644 v0.2.1/utils.html delete mode 100644 v0.3.0/_modules/doctr/datasets/core.html delete mode 100644 v0.3.0/_modules/doctr/datasets/datasets/tensorflow.html delete mode 100644 v0.3.0/_modules/doctr/documents/elements.html delete mode 100644 v0.3.0/_modules/doctr/documents/reader.html delete mode 100644 v0.3.0/_modules/doctr/models/detection/differentiable_binarization.html delete mode 100644 v0.3.0/_modules/doctr/models/detection/linknet.html delete mode 100644 v0.3.0/_modules/doctr/models/export.html delete mode 100644 v0.3.0/_modules/doctr/models/recognition/crnn.html delete mode 100644 v0.3.0/_modules/doctr/models/recognition/sar.html delete mode 100644 v0.3.0/_modules/doctr/transforms/modules.html delete mode 100644 v0.3.0/_sources/datasets.rst.txt delete mode 100644 v0.3.0/_sources/documents.rst.txt delete mode 100644 v0.3.0/_sources/installing.rst.txt delete mode 100644 v0.3.0/_sources/models.rst.txt delete mode 100644 v0.3.0/_sources/transforms.rst.txt delete mode 100644 v0.3.0/_sources/utils.rst.txt delete mode 100644 v0.3.0/datasets.html delete mode 100644 v0.3.0/documents.html delete mode 100644 v0.3.0/installing.html delete mode 100644 v0.3.0/models.html delete mode 100644 v0.3.0/py-modindex.html delete mode 100644 v0.3.0/transforms.html delete mode 100644 v0.3.0/utils.html delete mode 100644 v0.3.1/_modules/doctr/datasets/core.html delete mode 100644 v0.3.1/_modules/doctr/datasets/datasets/tensorflow.html delete mode 100644 v0.3.1/_modules/doctr/documents/elements.html delete mode 100644 v0.3.1/_modules/doctr/documents/reader.html delete mode 100644 v0.3.1/_modules/doctr/models/detection/differentiable_binarization.html delete mode 100644 v0.3.1/_modules/doctr/models/detection/linknet.html delete mode 100644 v0.3.1/_modules/doctr/models/export.html delete mode 100644 v0.3.1/_modules/doctr/models/recognition/crnn.html delete mode 100644 v0.3.1/_modules/doctr/models/recognition/sar.html delete mode 100644 v0.3.1/_modules/doctr/transforms/modules.html delete mode 100644 v0.3.1/_sources/datasets.rst.txt delete mode 100644 v0.3.1/_sources/documents.rst.txt delete mode 100644 v0.3.1/_sources/installing.rst.txt delete mode 100644 v0.3.1/_sources/models.rst.txt delete mode 100644 v0.3.1/_sources/transforms.rst.txt delete mode 100644 v0.3.1/_sources/utils.rst.txt delete mode 100644 v0.3.1/datasets.html delete mode 100644 v0.3.1/documents.html delete mode 100644 v0.3.1/installing.html delete mode 100644 v0.3.1/models.html delete mode 100644 v0.3.1/py-modindex.html delete mode 100644 v0.3.1/transforms.html delete mode 100644 v0.3.1/utils.html delete mode 100644 v0.4.0/_modules/doctr/datasets/core.html delete mode 100644 v0.4.0/_modules/doctr/datasets/datasets/tensorflow.html delete mode 100644 v0.4.0/_modules/doctr/documents/elements.html delete mode 100644 v0.4.0/_modules/doctr/documents/reader.html delete mode 100644 v0.4.0/_modules/doctr/models/detection/differentiable_binarization.html delete mode 100644 v0.4.0/_modules/doctr/models/detection/linknet.html delete mode 100644 v0.4.0/_modules/doctr/models/export.html delete mode 100644 v0.4.0/_modules/doctr/models/recognition/crnn.html delete mode 100644 v0.4.0/_modules/doctr/models/recognition/sar.html delete mode 100644 v0.4.0/_modules/doctr/transforms/modules.html delete mode 100644 v0.4.0/_sources/datasets.rst.txt delete mode 100644 v0.4.0/_sources/documents.rst.txt delete mode 100644 v0.4.0/_sources/installing.rst.txt delete mode 100644 v0.4.0/_sources/models.rst.txt delete mode 100644 v0.4.0/_sources/transforms.rst.txt delete mode 100644 v0.4.0/_sources/utils.rst.txt delete mode 100644 v0.4.0/datasets.html delete mode 100644 v0.4.0/documents.html delete mode 100644 v0.4.0/installing.html delete mode 100644 v0.4.0/models.html delete mode 100644 v0.4.0/py-modindex.html delete mode 100644 v0.4.0/transforms.html delete mode 100644 v0.4.0/utils.html delete mode 100644 v0.4.1/_modules/doctr/datasets/core.html delete mode 100644 v0.4.1/_modules/doctr/datasets/datasets/tensorflow.html delete mode 100644 v0.4.1/_modules/doctr/documents/elements.html delete mode 100644 v0.4.1/_modules/doctr/documents/reader.html delete mode 100644 v0.4.1/_modules/doctr/models/detection/differentiable_binarization.html delete mode 100644 v0.4.1/_modules/doctr/models/detection/linknet.html delete mode 100644 v0.4.1/_modules/doctr/models/export.html delete mode 100644 v0.4.1/_modules/doctr/models/recognition/crnn.html delete mode 100644 v0.4.1/_modules/doctr/models/recognition/sar.html delete mode 100644 v0.4.1/_modules/doctr/transforms/modules.html delete mode 100644 v0.4.1/_sources/datasets.rst.txt delete mode 100644 v0.4.1/_sources/documents.rst.txt delete mode 100644 v0.4.1/_sources/installing.rst.txt delete mode 100644 v0.4.1/_sources/models.rst.txt delete mode 100644 v0.4.1/_sources/transforms.rst.txt delete mode 100644 v0.4.1/_sources/utils.rst.txt delete mode 100644 v0.4.1/datasets.html delete mode 100644 v0.4.1/documents.html delete mode 100644 v0.4.1/installing.html delete mode 100644 v0.4.1/models.html delete mode 100644 v0.4.1/py-modindex.html delete mode 100644 v0.4.1/transforms.html delete mode 100644 v0.4.1/utils.html delete mode 100644 v0.5.0/_modules/doctr/datasets/core.html delete mode 100644 v0.5.0/_modules/doctr/datasets/datasets/tensorflow.html delete mode 100644 v0.5.0/_modules/doctr/documents/elements.html delete mode 100644 v0.5.0/_modules/doctr/documents/reader.html delete mode 100644 v0.5.0/_modules/doctr/models/detection/differentiable_binarization.html delete mode 100644 v0.5.0/_modules/doctr/models/detection/linknet.html delete mode 100644 v0.5.0/_modules/doctr/models/export.html delete mode 100644 v0.5.0/_modules/doctr/models/recognition/crnn.html delete mode 100644 v0.5.0/_modules/doctr/models/recognition/sar.html delete mode 100644 v0.5.0/_modules/doctr/transforms/modules.html delete mode 100644 v0.5.0/_sources/datasets.rst.txt delete mode 100644 v0.5.0/_sources/documents.rst.txt delete mode 100644 v0.5.0/_sources/installing.rst.txt delete mode 100644 v0.5.0/_sources/models.rst.txt delete mode 100644 v0.5.0/_sources/transforms.rst.txt delete mode 100644 v0.5.0/_sources/utils.rst.txt delete mode 100644 v0.5.0/datasets.html delete mode 100644 v0.5.0/documents.html delete mode 100644 v0.5.0/installing.html delete mode 100644 v0.5.0/models.html delete mode 100644 v0.5.0/py-modindex.html delete mode 100644 v0.5.0/transforms.html delete mode 100644 v0.5.0/utils.html delete mode 100644 v0.5.1/_modules/doctr/datasets/core.html delete mode 100644 v0.5.1/_modules/doctr/datasets/datasets/tensorflow.html delete mode 100644 v0.5.1/_modules/doctr/documents/elements.html delete mode 100644 v0.5.1/_modules/doctr/documents/reader.html delete mode 100644 v0.5.1/_modules/doctr/models/detection/differentiable_binarization.html delete mode 100644 v0.5.1/_modules/doctr/models/detection/linknet.html delete mode 100644 v0.5.1/_modules/doctr/models/export.html delete mode 100644 v0.5.1/_modules/doctr/models/recognition/crnn.html delete mode 100644 v0.5.1/_modules/doctr/models/recognition/sar.html delete mode 100644 v0.5.1/_modules/doctr/transforms/modules.html delete mode 100644 v0.5.1/_sources/datasets.rst.txt delete mode 100644 v0.5.1/_sources/documents.rst.txt delete mode 100644 v0.5.1/_sources/installing.rst.txt delete mode 100644 v0.5.1/_sources/models.rst.txt delete mode 100644 v0.5.1/_sources/transforms.rst.txt delete mode 100644 v0.5.1/_sources/utils.rst.txt delete mode 100644 v0.5.1/datasets.html delete mode 100644 v0.5.1/documents.html delete mode 100644 v0.5.1/installing.html delete mode 100644 v0.5.1/models.html delete mode 100644 v0.5.1/py-modindex.html delete mode 100644 v0.5.1/transforms.html delete mode 100644 v0.5.1/utils.html delete mode 100644 v0.6.0/_modules/doctr/datasets/core.html delete mode 100644 v0.6.0/_modules/doctr/datasets/datasets/tensorflow.html delete mode 100644 v0.6.0/_modules/doctr/documents/elements.html delete mode 100644 v0.6.0/_modules/doctr/documents/reader.html delete mode 100644 v0.6.0/_modules/doctr/models/detection/differentiable_binarization.html delete mode 100644 v0.6.0/_modules/doctr/models/detection/linknet.html delete mode 100644 v0.6.0/_modules/doctr/models/export.html delete mode 100644 v0.6.0/_modules/doctr/models/recognition/crnn.html delete mode 100644 v0.6.0/_modules/doctr/models/recognition/sar.html delete mode 100644 v0.6.0/_modules/doctr/transforms/modules.html delete mode 100644 v0.6.0/_sources/datasets.rst.txt delete mode 100644 v0.6.0/_sources/documents.rst.txt delete mode 100644 v0.6.0/_sources/installing.rst.txt delete mode 100644 v0.6.0/_sources/models.rst.txt delete mode 100644 v0.6.0/_sources/transforms.rst.txt delete mode 100644 v0.6.0/_sources/utils.rst.txt delete mode 100644 v0.6.0/datasets.html delete mode 100644 v0.6.0/documents.html delete mode 100644 v0.6.0/installing.html delete mode 100644 v0.6.0/models.html delete mode 100644 v0.6.0/py-modindex.html delete mode 100644 v0.6.0/transforms.html delete mode 100644 v0.6.0/utils.html delete mode 100644 v0.7.0/_modules/doctr/datasets/core.html delete mode 100644 v0.7.0/_modules/doctr/datasets/datasets/tensorflow.html delete mode 100644 v0.7.0/_modules/doctr/documents/elements.html delete mode 100644 v0.7.0/_modules/doctr/documents/reader.html delete mode 100644 v0.7.0/_modules/doctr/models/detection/differentiable_binarization.html delete mode 100644 v0.7.0/_modules/doctr/models/detection/linknet.html delete mode 100644 v0.7.0/_modules/doctr/models/export.html delete mode 100644 v0.7.0/_modules/doctr/models/recognition/crnn.html delete mode 100644 v0.7.0/_modules/doctr/models/recognition/sar.html delete mode 100644 v0.7.0/_modules/doctr/transforms/modules.html delete mode 100644 v0.7.0/_sources/datasets.rst.txt delete mode 100644 v0.7.0/_sources/documents.rst.txt delete mode 100644 v0.7.0/_sources/installing.rst.txt delete mode 100644 v0.7.0/_sources/models.rst.txt delete mode 100644 v0.7.0/_sources/transforms.rst.txt delete mode 100644 v0.7.0/_sources/utils.rst.txt delete mode 100644 v0.7.0/datasets.html delete mode 100644 v0.7.0/documents.html delete mode 100644 v0.7.0/installing.html delete mode 100644 v0.7.0/models.html delete mode 100644 v0.7.0/py-modindex.html delete mode 100644 v0.7.0/transforms.html delete mode 100644 v0.7.0/utils.html delete mode 100644 v0.8.0/_modules/doctr/datasets/core.html delete mode 100644 v0.8.0/_modules/doctr/datasets/datasets/tensorflow.html delete mode 100644 v0.8.0/_modules/doctr/documents/elements.html delete mode 100644 v0.8.0/_modules/doctr/documents/reader.html delete mode 100644 v0.8.0/_modules/doctr/models/detection/differentiable_binarization.html delete mode 100644 v0.8.0/_modules/doctr/models/detection/linknet.html delete mode 100644 v0.8.0/_modules/doctr/models/export.html delete mode 100644 v0.8.0/_modules/doctr/models/recognition/crnn.html delete mode 100644 v0.8.0/_modules/doctr/models/recognition/sar.html delete mode 100644 v0.8.0/_modules/doctr/transforms/modules.html delete mode 100644 v0.8.0/_sources/datasets.rst.txt delete mode 100644 v0.8.0/_sources/documents.rst.txt delete mode 100644 v0.8.0/_sources/installing.rst.txt delete mode 100644 v0.8.0/_sources/models.rst.txt delete mode 100644 v0.8.0/_sources/transforms.rst.txt delete mode 100644 v0.8.0/_sources/utils.rst.txt delete mode 100644 v0.8.0/datasets.html delete mode 100644 v0.8.0/documents.html delete mode 100644 v0.8.0/installing.html delete mode 100644 v0.8.0/models.html delete mode 100644 v0.8.0/py-modindex.html delete mode 100644 v0.8.0/transforms.html delete mode 100644 v0.8.0/utils.html delete mode 100644 v0.8.1/_modules/doctr/datasets/core.html delete mode 100644 v0.8.1/_modules/doctr/datasets/datasets/tensorflow.html delete mode 100644 v0.8.1/_modules/doctr/documents/elements.html delete mode 100644 v0.8.1/_modules/doctr/documents/reader.html delete mode 100644 v0.8.1/_modules/doctr/models/detection/differentiable_binarization.html delete mode 100644 v0.8.1/_modules/doctr/models/detection/linknet.html delete mode 100644 v0.8.1/_modules/doctr/models/export.html delete mode 100644 v0.8.1/_modules/doctr/models/recognition/crnn.html delete mode 100644 v0.8.1/_modules/doctr/models/recognition/sar.html delete mode 100644 v0.8.1/_modules/doctr/transforms/modules.html delete mode 100644 v0.8.1/_sources/datasets.rst.txt delete mode 100644 v0.8.1/_sources/documents.rst.txt delete mode 100644 v0.8.1/_sources/installing.rst.txt delete mode 100644 v0.8.1/_sources/models.rst.txt delete mode 100644 v0.8.1/_sources/transforms.rst.txt delete mode 100644 v0.8.1/_sources/utils.rst.txt delete mode 100644 v0.8.1/datasets.html delete mode 100644 v0.8.1/documents.html delete mode 100644 v0.8.1/installing.html delete mode 100644 v0.8.1/models.html delete mode 100644 v0.8.1/py-modindex.html delete mode 100644 v0.8.1/transforms.html delete mode 100644 v0.8.1/utils.html delete mode 100644 v0.9.0/_modules/doctr/datasets/core.html delete mode 100644 v0.9.0/_modules/doctr/datasets/datasets/tensorflow.html delete mode 100644 v0.9.0/_modules/doctr/documents/elements.html delete mode 100644 v0.9.0/_modules/doctr/documents/reader.html delete mode 100644 v0.9.0/_modules/doctr/models/detection/differentiable_binarization.html delete mode 100644 v0.9.0/_modules/doctr/models/detection/linknet.html delete mode 100644 v0.9.0/_modules/doctr/models/export.html delete mode 100644 v0.9.0/_modules/doctr/models/recognition/crnn.html delete mode 100644 v0.9.0/_modules/doctr/models/recognition/sar.html delete mode 100644 v0.9.0/_modules/doctr/transforms/modules.html delete mode 100644 v0.9.0/_sources/datasets.rst.txt delete mode 100644 v0.9.0/_sources/documents.rst.txt delete mode 100644 v0.9.0/_sources/installing.rst.txt delete mode 100644 v0.9.0/_sources/models.rst.txt delete mode 100644 v0.9.0/_sources/transforms.rst.txt delete mode 100644 v0.9.0/_sources/utils.rst.txt delete mode 100644 v0.9.0/datasets.html delete mode 100644 v0.9.0/documents.html delete mode 100644 v0.9.0/installing.html delete mode 100644 v0.9.0/models.html delete mode 100644 v0.9.0/py-modindex.html delete mode 100644 v0.9.0/transforms.html delete mode 100644 v0.9.0/utils.html diff --git a/.buildinfo b/.buildinfo index bd8b9a11ae..2e9e43e824 100644 --- a/.buildinfo +++ b/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 -# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: be0cf43efa4357c625c34906153a2192 +# This file records the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 7d20f70c4164b534d00ead21f34aefbc tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.doctrees/environment.pickle b/.doctrees/environment.pickle index eb8cdbd0b8bcee042f6d69ab67e4629a165e0742..84938764916f26b3582a8360549186bfc42161e0 100644 GIT binary patch literal 2732364 zcmd3P37i~9b-rvJtNXq$kGwusvS(JO4{T%0D_NFxBFP8XW|*Dn-I>ws%y^E~VVnDq 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 dT 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
  • 9mpU{UF`69Uo7>P}zRf{9GdzX)=~UdW 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Ƚ$rm7^RNI5u^3CR49l?+ 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Ƚ$rm7^RNI5u^3CR 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

    IeLQxb$Nt8laltV>SLRC~lP1Hgi)I|d{L=!Yc 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

    !y8BXG=g>^vVEw#5|b8%jtphdYQC1UN!yBctu$ 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 literal 2731890 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 SWnqpL=Lwr3UGX$8pGun-BKipnD$Y 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 DQ3rry92Dl=yh_swMc+nQ03;Fv4)x#l9h zR(dOo#0;U&?Tm4nT~=DtKG#7p2iBuqU6~_UaeAVzM^{vj0b=~1Lm^N%)gg({n16IS zBmp$mMqQJs&?bRoy+Zz5>AX;iVhx(c|wcKJCLUNqwG0CpBSQh>|{;*#DK=SgmzQtB$~FHq<0AQ z;zM|>OjF?lYZskxVv$V zX^EZ5RL%>ijdAue+9@hvSa=M9;&N<5!zHV$wZ}>l2gX#$>jc$dZ<;QVs12r?%b_tD z8Vtzp56&mkL1s6#UQ(I}M-rG(F{VNOxC(+kOAa(}IoK~xg!5p`ttf!U3 zkifGRfE@+{BPD`Dv+*^IGnZTR)R0(V9S_DF2qDA)`}eP|g;~^P`YH;C#SFHtZ4Fah z+X@m?P3t-XWlm?g`4pEVBx*Ib9D^~3^(TlhpU6WwYDZFSID%q)Uo-Q`LK3V|GX{$p zkfxySpLw5%dnq*@RwxnBVwNXQ@hL7`N4DANh>BN}p^14j%7N9tp##{V#oIEz`xoFgmr% zfX~1})

    tj&UTo7biT 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≫)5$aZNV{oEl9@)L455_3P09i?zXp)k{7G=o? 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
    ~) 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 - + doctr.datasets.cord - docTR documentation @@ -458,7 +458,7 @@

    Source code for doctr.datasets.cord

         
       
    - + diff --git a/_modules/doctr/datasets/detection.html b/_modules/doctr/datasets/detection.html index 482649bc23..32525bc6a0 100644 --- a/_modules/doctr/datasets/detection.html +++ b/_modules/doctr/datasets/detection.html @@ -13,7 +13,7 @@ - + doctr.datasets.detection - docTR documentation @@ -426,7 +426,7 @@

    Source code for doctr.datasets.detection

         
       
    - + diff --git a/_modules/doctr/datasets/doc_artefacts.html b/_modules/doctr/datasets/doc_artefacts.html index d937490511..444fee3979 100644 --- a/_modules/doctr/datasets/doc_artefacts.html +++ b/_modules/doctr/datasets/doc_artefacts.html @@ -13,7 +13,7 @@ - + doctr.datasets.doc_artefacts - docTR documentation @@ -410,7 +410,7 @@

    Source code for doctr.datasets.doc_artefacts

       
    - + diff --git a/_modules/doctr/datasets/funsd.html b/_modules/doctr/datasets/funsd.html index 74188240c4..d8864cc816 100644 --- a/_modules/doctr/datasets/funsd.html +++ b/_modules/doctr/datasets/funsd.html @@ -13,7 +13,7 @@ - + doctr.datasets.funsd - docTR documentation @@ -450,7 +450,7 @@

    Source code for doctr.datasets.funsd

         
       
    - + diff --git a/_modules/doctr/datasets/generator/tensorflow.html b/_modules/doctr/datasets/generator/tensorflow.html index 830ef4fb90..bc94cc9843 100644 --- a/_modules/doctr/datasets/generator/tensorflow.html +++ b/_modules/doctr/datasets/generator/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.datasets.generator.tensorflow - docTR documentation @@ -391,7 +391,7 @@

    Source code for doctr.datasets.generator.tensorflow

    - + diff --git a/_modules/doctr/datasets/ic03.html b/_modules/doctr/datasets/ic03.html index a3739563fc..c917a4da26 100644 --- a/_modules/doctr/datasets/ic03.html +++ b/_modules/doctr/datasets/ic03.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic03 - docTR documentation @@ -464,7 +464,7 @@

    Source code for doctr.datasets.ic03

         
       
    - + diff --git a/_modules/doctr/datasets/ic13.html b/_modules/doctr/datasets/ic13.html index 3ea8f023d6..47a68faf11 100644 --- a/_modules/doctr/datasets/ic13.html +++ b/_modules/doctr/datasets/ic13.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic13 - docTR documentation @@ -436,7 +436,7 @@

    Source code for doctr.datasets.ic13

         
       
    - + diff --git a/_modules/doctr/datasets/iiit5k.html b/_modules/doctr/datasets/iiit5k.html index 8a239870ee..57a1362e76 100644 --- a/_modules/doctr/datasets/iiit5k.html +++ b/_modules/doctr/datasets/iiit5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiit5k - docTR documentation @@ -441,7 +441,7 @@

    Source code for doctr.datasets.iiit5k

         
       
    - + diff --git a/_modules/doctr/datasets/iiithws.html b/_modules/doctr/datasets/iiithws.html index 71bebe49c6..222cba3357 100644 --- a/_modules/doctr/datasets/iiithws.html +++ b/_modules/doctr/datasets/iiithws.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiithws - docTR documentation @@ -403,7 +403,7 @@

    Source code for doctr.datasets.iiithws

         
       
    - + diff --git a/_modules/doctr/datasets/imgur5k.html b/_modules/doctr/datasets/imgur5k.html index 0ff0c0b892..3eb805b32f 100644 --- a/_modules/doctr/datasets/imgur5k.html +++ b/_modules/doctr/datasets/imgur5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.imgur5k - docTR documentation @@ -484,7 +484,7 @@

    Source code for doctr.datasets.imgur5k

         
       
    - + diff --git a/_modules/doctr/datasets/loader.html b/_modules/doctr/datasets/loader.html index 4507d39ae8..25f275995d 100644 --- a/_modules/doctr/datasets/loader.html +++ b/_modules/doctr/datasets/loader.html @@ -13,7 +13,7 @@ - + doctr.datasets.loader - docTR documentation @@ -425,7 +425,7 @@

    Source code for doctr.datasets.loader

         
       
    - + diff --git a/_modules/doctr/datasets/mjsynth.html b/_modules/doctr/datasets/mjsynth.html index 7bd13241af..eda383dc50 100644 --- a/_modules/doctr/datasets/mjsynth.html +++ b/_modules/doctr/datasets/mjsynth.html @@ -13,7 +13,7 @@ - + doctr.datasets.mjsynth - docTR documentation @@ -434,7 +434,7 @@

    Source code for doctr.datasets.mjsynth

         
       
    - + diff --git a/_modules/doctr/datasets/ocr.html b/_modules/doctr/datasets/ocr.html index c64f5f3481..78e2491940 100644 --- a/_modules/doctr/datasets/ocr.html +++ b/_modules/doctr/datasets/ocr.html @@ -13,7 +13,7 @@ - + doctr.datasets.ocr - docTR documentation @@ -399,7 +399,7 @@

    Source code for doctr.datasets.ocr

         
       
    - + diff --git a/_modules/doctr/datasets/recognition.html b/_modules/doctr/datasets/recognition.html index 0b4e6f2a77..b8230bab7f 100644 --- a/_modules/doctr/datasets/recognition.html +++ b/_modules/doctr/datasets/recognition.html @@ -13,7 +13,7 @@ - + doctr.datasets.recognition - docTR documentation @@ -384,7 +384,7 @@

    Source code for doctr.datasets.recognition

         
       
    - + diff --git a/_modules/doctr/datasets/sroie.html b/_modules/doctr/datasets/sroie.html index 320b8f3a62..940d9a430f 100644 --- a/_modules/doctr/datasets/sroie.html +++ b/_modules/doctr/datasets/sroie.html @@ -13,7 +13,7 @@ - + doctr.datasets.sroie - docTR documentation @@ -441,7 +441,7 @@

    Source code for doctr.datasets.sroie

         
       
    - + diff --git a/_modules/doctr/datasets/svhn.html b/_modules/doctr/datasets/svhn.html index e67fd6e700..d950950d06 100644 --- a/_modules/doctr/datasets/svhn.html +++ b/_modules/doctr/datasets/svhn.html @@ -13,7 +13,7 @@ - + doctr.datasets.svhn - docTR documentation @@ -469,7 +469,7 @@

    Source code for doctr.datasets.svhn

         
       
    - + diff --git a/_modules/doctr/datasets/svt.html b/_modules/doctr/datasets/svt.html index 213c6ac946..5b0c12077d 100644 --- a/_modules/doctr/datasets/svt.html +++ b/_modules/doctr/datasets/svt.html @@ -13,7 +13,7 @@ - + doctr.datasets.svt - docTR documentation @@ -455,7 +455,7 @@

    Source code for doctr.datasets.svt

         
       
    - + diff --git a/_modules/doctr/datasets/synthtext.html b/_modules/doctr/datasets/synthtext.html index 215d391089..e97d0dc347 100644 --- a/_modules/doctr/datasets/synthtext.html +++ b/_modules/doctr/datasets/synthtext.html @@ -13,7 +13,7 @@ - + doctr.datasets.synthtext - docTR documentation @@ -466,7 +466,7 @@

    Source code for doctr.datasets.synthtext

         
       
    - + diff --git a/_modules/doctr/datasets/utils.html b/_modules/doctr/datasets/utils.html index e288bf6da2..09405434c6 100644 --- a/_modules/doctr/datasets/utils.html +++ b/_modules/doctr/datasets/utils.html @@ -13,7 +13,7 @@ - + doctr.datasets.utils - docTR documentation @@ -550,7 +550,7 @@

    Source code for doctr.datasets.utils

         
       
    - + diff --git a/_modules/doctr/datasets/wildreceipt.html b/_modules/doctr/datasets/wildreceipt.html index e2ea3e24c1..6d840307cf 100644 --- a/_modules/doctr/datasets/wildreceipt.html +++ b/_modules/doctr/datasets/wildreceipt.html @@ -13,7 +13,7 @@ - + doctr.datasets.wildreceipt - docTR documentation @@ -450,7 +450,7 @@

    Source code for doctr.datasets.wildreceipt

         
       
    - + diff --git a/_modules/doctr/io/elements.html b/_modules/doctr/io/elements.html index 68b2cdc9e5..452e16d666 100644 --- a/_modules/doctr/io/elements.html +++ b/_modules/doctr/io/elements.html @@ -13,7 +13,7 @@ - + doctr.io.elements - docTR documentation @@ -1004,7 +1004,7 @@

    Source code for doctr.io.elements

         
       
    - + diff --git a/_modules/doctr/io/html.html b/_modules/doctr/io/html.html index e890485c15..70e9b2eaa2 100644 --- a/_modules/doctr/io/html.html +++ b/_modules/doctr/io/html.html @@ -13,7 +13,7 @@ - + doctr.io.html - docTR documentation @@ -356,7 +356,7 @@

    Source code for doctr.io.html

         
       
    - + diff --git a/_modules/doctr/io/image/base.html b/_modules/doctr/io/image/base.html index 9549f39b01..fb939f7fb8 100644 --- a/_modules/doctr/io/image/base.html +++ b/_modules/doctr/io/image/base.html @@ -13,7 +13,7 @@ - + doctr.io.image.base - docTR documentation @@ -384,7 +384,7 @@

    Source code for doctr.io.image.base

         
       
    - + diff --git a/_modules/doctr/io/image/tensorflow.html b/_modules/doctr/io/image/tensorflow.html index e0687d9a4c..ba0232a43f 100644 --- a/_modules/doctr/io/image/tensorflow.html +++ b/_modules/doctr/io/image/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.io.image.tensorflow - docTR documentation @@ -441,7 +441,7 @@

    Source code for doctr.io.image.tensorflow

         
       
    - + diff --git a/_modules/doctr/io/pdf.html b/_modules/doctr/io/pdf.html index b199e13bc5..be511b6179 100644 --- a/_modules/doctr/io/pdf.html +++ b/_modules/doctr/io/pdf.html @@ -13,7 +13,7 @@ - + doctr.io.pdf - docTR documentation @@ -373,7 +373,7 @@

    Source code for doctr.io.pdf

         
       
    - + diff --git a/_modules/doctr/io/reader.html b/_modules/doctr/io/reader.html index 6c99d69e55..9902fce224 100644 --- a/_modules/doctr/io/reader.html +++ b/_modules/doctr/io/reader.html @@ -13,7 +13,7 @@ - + doctr.io.reader - docTR documentation @@ -422,7 +422,7 @@

    Source code for doctr.io.reader

         
       
    - + diff --git a/_modules/doctr/models/classification/magc_resnet/tensorflow.html b/_modules/doctr/models/classification/magc_resnet/tensorflow.html index 21de6b375c..9f0a1b3f07 100644 --- a/_modules/doctr/models/classification/magc_resnet/tensorflow.html +++ b/_modules/doctr/models/classification/magc_resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.magc_resnet.tensorflow - docTR documentation @@ -527,7 +527,7 @@

    Source code for doctr.models.classification.magc_resnet.tensorflow

    - + diff --git a/_modules/doctr/models/classification/mobilenet/tensorflow.html b/_modules/doctr/models/classification/mobilenet/tensorflow.html index c6e0a49148..af866ac2c4 100644 --- a/_modules/doctr/models/classification/mobilenet/tensorflow.html +++ b/_modules/doctr/models/classification/mobilenet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.mobilenet.tensorflow - docTR documentation @@ -789,7 +789,7 @@

    Source code for doctr.models.classification.mobilenet.tensorflow

    - + diff --git a/_modules/doctr/models/classification/resnet/tensorflow.html b/_modules/doctr/models/classification/resnet/tensorflow.html index aa9cdb2b68..2ea4320137 100644 --- a/_modules/doctr/models/classification/resnet/tensorflow.html +++ b/_modules/doctr/models/classification/resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.resnet.tensorflow - docTR documentation @@ -745,7 +745,7 @@

    Source code for doctr.models.classification.resnet.tensorflow

    - + diff --git a/_modules/doctr/models/classification/textnet/tensorflow.html b/_modules/doctr/models/classification/textnet/tensorflow.html index 43a2f8ae7a..33c5e39f99 100644 --- a/_modules/doctr/models/classification/textnet/tensorflow.html +++ b/_modules/doctr/models/classification/textnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.textnet.tensorflow - docTR documentation @@ -607,7 +607,7 @@

    Source code for doctr.models.classification.textnet.tensorflow

    - + diff --git a/_modules/doctr/models/classification/vgg/tensorflow.html b/_modules/doctr/models/classification/vgg/tensorflow.html index 7044065cc4..e013c3e6bc 100644 --- a/_modules/doctr/models/classification/vgg/tensorflow.html +++ b/_modules/doctr/models/classification/vgg/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vgg.tensorflow - docTR documentation @@ -447,7 +447,7 @@

    Source code for doctr.models.classification.vgg.tensorflow

    - + diff --git a/_modules/doctr/models/classification/vit/tensorflow.html b/_modules/doctr/models/classification/vit/tensorflow.html index 257d6633e7..f7f12eebe5 100644 --- a/_modules/doctr/models/classification/vit/tensorflow.html +++ b/_modules/doctr/models/classification/vit/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vit.tensorflow - docTR documentation @@ -529,7 +529,7 @@

    Source code for doctr.models.classification.vit.tensorflow

    - + diff --git a/_modules/doctr/models/classification/zoo.html b/_modules/doctr/models/classification/zoo.html index b70043b00c..3c0b2359b9 100644 --- a/_modules/doctr/models/classification/zoo.html +++ b/_modules/doctr/models/classification/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.classification.zoo - docTR documentation @@ -441,7 +441,7 @@

    Source code for doctr.models.classification.zoo

    <
    - + diff --git a/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html b/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html index 9fd3c311ed..c1ff543e80 100644 --- a/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html +++ b/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.differentiable_binarization.tensorflow - docTR documentation @@ -755,7 +755,7 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo

    - + diff --git a/_modules/doctr/models/detection/fast/tensorflow.html b/_modules/doctr/models/detection/fast/tensorflow.html index c4e8d515c9..e8b174a3e4 100644 --- a/_modules/doctr/models/detection/fast/tensorflow.html +++ b/_modules/doctr/models/detection/fast/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.fast.tensorflow - docTR documentation @@ -765,7 +765,7 @@

    Source code for doctr.models.detection.fast.tensorflow

    - + diff --git a/_modules/doctr/models/detection/linknet/tensorflow.html b/_modules/doctr/models/detection/linknet/tensorflow.html index cb782d7942..fa1cb26e38 100644 --- a/_modules/doctr/models/detection/linknet/tensorflow.html +++ b/_modules/doctr/models/detection/linknet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.linknet.tensorflow - docTR documentation @@ -712,7 +712,7 @@

    Source code for doctr.models.detection.linknet.tensorflow

    - + diff --git a/_modules/doctr/models/detection/zoo.html b/_modules/doctr/models/detection/zoo.html index bfddc1ff93..de23a0b125 100644 --- a/_modules/doctr/models/detection/zoo.html +++ b/_modules/doctr/models/detection/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.detection.zoo - docTR documentation @@ -431,7 +431,7 @@

    Source code for doctr.models.detection.zoo

         
       
    - + diff --git a/_modules/doctr/models/factory/hub.html b/_modules/doctr/models/factory/hub.html index 9cc2ecf081..c1fc6eb0a3 100644 --- a/_modules/doctr/models/factory/hub.html +++ b/_modules/doctr/models/factory/hub.html @@ -13,7 +13,7 @@ - + doctr.models.factory.hub - docTR documentation @@ -564,7 +564,7 @@

    Source code for doctr.models.factory.hub

         
       
    - + diff --git a/_modules/doctr/models/recognition/crnn/tensorflow.html b/_modules/doctr/models/recognition/crnn/tensorflow.html index c862bf9f98..e42d517f47 100644 --- a/_modules/doctr/models/recognition/crnn/tensorflow.html +++ b/_modules/doctr/models/recognition/crnn/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.crnn.tensorflow - docTR documentation @@ -654,7 +654,7 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    - + diff --git a/_modules/doctr/models/recognition/master/tensorflow.html b/_modules/doctr/models/recognition/master/tensorflow.html index 7ff32ab5a2..45783dfec0 100644 --- a/_modules/doctr/models/recognition/master/tensorflow.html +++ b/_modules/doctr/models/recognition/master/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.master.tensorflow - docTR documentation @@ -651,7 +651,7 @@

    Source code for doctr.models.recognition.master.tensorflow

    - + diff --git a/_modules/doctr/models/recognition/parseq/tensorflow.html b/_modules/doctr/models/recognition/parseq/tensorflow.html index bb550ce424..e7d86137e9 100644 --- a/_modules/doctr/models/recognition/parseq/tensorflow.html +++ b/_modules/doctr/models/recognition/parseq/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.parseq.tensorflow - docTR documentation @@ -842,7 +842,7 @@

    Source code for doctr.models.recognition.parseq.tensorflow

    - + diff --git a/_modules/doctr/models/recognition/sar/tensorflow.html b/_modules/doctr/models/recognition/sar/tensorflow.html index 4feab4f1f1..ac20ea0ead 100644 --- a/_modules/doctr/models/recognition/sar/tensorflow.html +++ b/_modules/doctr/models/recognition/sar/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.sar.tensorflow - docTR documentation @@ -753,7 +753,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    - + diff --git a/_modules/doctr/models/recognition/vitstr/tensorflow.html b/_modules/doctr/models/recognition/vitstr/tensorflow.html index 95585a34e7..1f3b49610e 100644 --- a/_modules/doctr/models/recognition/vitstr/tensorflow.html +++ b/_modules/doctr/models/recognition/vitstr/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.vitstr.tensorflow - docTR documentation @@ -617,7 +617,7 @@

    Source code for doctr.models.recognition.vitstr.tensorflow

    - + diff --git a/_modules/doctr/models/recognition/zoo.html b/_modules/doctr/models/recognition/zoo.html index 41c2ce99fd..a6eb7d8bba 100644 --- a/_modules/doctr/models/recognition/zoo.html +++ b/_modules/doctr/models/recognition/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.zoo - docTR documentation @@ -403,7 +403,7 @@

    Source code for doctr.models.recognition.zoo

       
    - + diff --git a/_modules/doctr/models/zoo.html b/_modules/doctr/models/zoo.html index 73f90b65b6..30e26c4830 100644 --- a/_modules/doctr/models/zoo.html +++ b/_modules/doctr/models/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.zoo - docTR documentation @@ -572,7 +572,7 @@

    Source code for doctr.models.zoo

         
       
    - + diff --git a/_modules/doctr/transforms/modules/base.html b/_modules/doctr/transforms/modules/base.html index 70345f5f87..af786578c8 100644 --- a/_modules/doctr/transforms/modules/base.html +++ b/_modules/doctr/transforms/modules/base.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.base - docTR documentation @@ -639,7 +639,7 @@

    Source code for doctr.transforms.modules.base

    - + diff --git a/_modules/doctr/transforms/modules/tensorflow.html b/_modules/doctr/transforms/modules/tensorflow.html index 61dda14a90..0c02c09da3 100644 --- a/_modules/doctr/transforms/modules/tensorflow.html +++ b/_modules/doctr/transforms/modules/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.tensorflow - docTR documentation @@ -952,7 +952,7 @@

    Source code for doctr.transforms.modules.tensorflow

    - + diff --git a/_modules/doctr/utils/metrics.html b/_modules/doctr/utils/metrics.html index f1a8b695f4..754ae373c4 100644 --- a/_modules/doctr/utils/metrics.html +++ b/_modules/doctr/utils/metrics.html @@ -13,7 +13,7 @@ - + doctr.utils.metrics - docTR documentation @@ -932,7 +932,7 @@

    Source code for doctr.utils.metrics

         
       
    - + diff --git a/_modules/doctr/utils/visualization.html b/_modules/doctr/utils/visualization.html index 0f00db325f..6791861bc8 100644 --- a/_modules/doctr/utils/visualization.html +++ b/_modules/doctr/utils/visualization.html @@ -13,7 +13,7 @@ - + doctr.utils.visualization - docTR documentation @@ -716,7 +716,7 @@

    Source code for doctr.utils.visualization

         
       
    - + diff --git a/_modules/index.html b/_modules/index.html index 58e3fd832a..e1567f3e40 100644 --- a/_modules/index.html +++ b/_modules/index.html @@ -13,7 +13,7 @@ - + Overview: module code - docTR documentation @@ -374,7 +374,7 @@

    All modules for which code is available

    - + diff --git a/_static/basic.css b/_static/basic.css index f316efcb47..7ebbd6d07b 100644 --- a/_static/basic.css +++ b/_static/basic.css @@ -1,12 +1,5 @@ /* - * basic.css - * ~~~~~~~~~ - * * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ /* -- main layout ----------------------------------------------------------- */ @@ -115,15 +108,11 @@ img { /* -- search page ----------------------------------------------------------- */ ul.search { - margin: 10px 0 0 20px; - padding: 0; + margin-top: 10px; } ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; + padding: 5px 0; } ul.search li a { diff --git a/_static/doctools.js b/_static/doctools.js index 4d67807d17..0398ebb9f0 100644 --- a/_static/doctools.js +++ b/_static/doctools.js @@ -1,12 +1,5 @@ /* - * doctools.js - * ~~~~~~~~~~~ - * * Base JavaScript utilities for all Sphinx HTML documentation. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; diff --git a/_static/language_data.js b/_static/language_data.js index 367b8ed81b..c7fe6c6faf 100644 --- a/_static/language_data.js +++ b/_static/language_data.js @@ -1,13 +1,6 @@ /* - * language_data.js - * ~~~~~~~~~~~~~~~~ - * * This script contains the language-specific data used by searchtools.js, * namely the list of stopwords, stemmer, scorer and splitter. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; diff --git a/_static/searchtools.js b/_static/searchtools.js index b08d58c9b9..2c774d17af 100644 --- a/_static/searchtools.js +++ b/_static/searchtools.js @@ -1,12 +1,5 @@ /* - * searchtools.js - * ~~~~~~~~~~~~~~~~ - * * Sphinx JavaScript utilities for the full-text search. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; @@ -20,7 +13,7 @@ if (typeof Scorer === "undefined") { // and returns the new score. /* score: result => { - const [docname, title, anchor, descr, score, filename] = result + const [docname, title, anchor, descr, score, filename, kind] = result return score }, */ @@ -47,6 +40,14 @@ if (typeof Scorer === "undefined") { }; } +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + const _removeChildren = (element) => { while (element && element.lastChild) element.removeChild(element.lastChild); }; @@ -64,9 +65,13 @@ const _displayItem = (item, searchTerms, highlightTerms) => { const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; const contentRoot = document.documentElement.dataset.content_root; - const [docName, title, anchor, descr, score, _filename] = item; + const [docName, title, anchor, descr, score, _filename, kind] = item; let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); let requestUrl; let linkUrl; if (docBuilder === "dirhtml") { @@ -115,8 +120,10 @@ const _finishSearch = (resultCount) => { "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." ); else - Search.status.innerText = _( - "Search finished, found ${resultCount} page(s) matching the search query." + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, ).replace('${resultCount}', resultCount); }; const _displayNextItem = ( @@ -138,7 +145,7 @@ const _displayNextItem = ( else _finishSearch(resultCount); }; // Helper function used by query() to order search results. -// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. // Order the results by score (in opposite order of appearance, since the // `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. const _orderResultsByScoreThenName = (a, b) => { @@ -248,6 +255,7 @@ const Search = { searchSummary.classList.add("search-summary"); searchSummary.innerText = ""; const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); searchList.classList.add("search"); const out = document.getElementById("search-results"); @@ -318,7 +326,7 @@ const Search = { const indexEntries = Search._index.indexentries; // Collect multiple result groups to be sorted separately and then ordered. - // Each is an array of [docname, title, anchor, descr, score, filename]. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. const normalResults = []; const nonMainIndexResults = []; @@ -337,6 +345,7 @@ const Search = { null, score + boost, filenames[file], + SearchResultKind.title, ]); } } @@ -354,6 +363,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.index, ]; if (isMain) { normalResults.push(result); @@ -475,6 +485,7 @@ const Search = { descr, score, filenames[match[0]], + SearchResultKind.object, ]); }; Object.keys(objects).forEach((prefix) => @@ -585,6 +596,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.text, ]); } return results; diff --git a/changelog.html b/changelog.html index 1d18911bb0..a716261488 100644 --- a/changelog.html +++ b/changelog.html @@ -14,7 +14,7 @@ - + Changelog - docTR documentation @@ -437,7 +437,7 @@

    v0.1.0 (2021-03-05) - + diff --git a/contributing/code_of_conduct.html b/contributing/code_of_conduct.html index 7a6adcef5c..3254df539e 100644 --- a/contributing/code_of_conduct.html +++ b/contributing/code_of_conduct.html @@ -14,7 +14,7 @@ - + Contributor Covenant Code of Conduct - docTR documentation @@ -500,7 +500,7 @@

    Attribution - + diff --git a/contributing/contributing.html b/contributing/contributing.html index b3bbd6c13e..9c5c924996 100644 --- a/contributing/contributing.html +++ b/contributing/contributing.html @@ -14,7 +14,7 @@ - + Contributing to docTR - docTR documentation @@ -477,7 +477,7 @@

    Let’s connect - + diff --git a/genindex.html b/genindex.html index f2e6342646..d73a14f26f 100644 --- a/genindex.html +++ b/genindex.html @@ -13,7 +13,7 @@ - Index - docTR documentation + Index - docTR documentation @@ -752,7 +752,7 @@

    W

    - + diff --git a/getting_started/installing.html b/getting_started/installing.html index b453cf5583..e9d26117d9 100644 --- a/getting_started/installing.html +++ b/getting_started/installing.html @@ -14,7 +14,7 @@ - + Installation - docTR documentation @@ -431,7 +431,7 @@

    Via Git - + diff --git a/index.html b/index.html index b6cad4564d..0b2e522a63 100644 --- a/index.html +++ b/index.html @@ -14,7 +14,7 @@ - + docTR documentation @@ -439,7 +439,7 @@

    Supported datasets - + diff --git a/latest/_modules/doctr/datasets/cord.html b/latest/_modules/doctr/datasets/cord.html index 78e70014e3..55b0584830 100644 --- a/latest/_modules/doctr/datasets/cord.html +++ b/latest/_modules/doctr/datasets/cord.html @@ -13,7 +13,7 @@ - + doctr.datasets.cord - docTR documentation @@ -462,7 +462,7 @@

    Source code for doctr.datasets.cord

         
       
    - + diff --git a/latest/_modules/doctr/datasets/detection.html b/latest/_modules/doctr/datasets/detection.html index 739563e466..718001e4cf 100644 --- a/latest/_modules/doctr/datasets/detection.html +++ b/latest/_modules/doctr/datasets/detection.html @@ -13,7 +13,7 @@ - + doctr.datasets.detection - docTR documentation @@ -430,7 +430,7 @@

    Source code for doctr.datasets.detection

         
       
    - + diff --git a/latest/_modules/doctr/datasets/doc_artefacts.html b/latest/_modules/doctr/datasets/doc_artefacts.html index 3313ae4660..94c32aaa0f 100644 --- a/latest/_modules/doctr/datasets/doc_artefacts.html +++ b/latest/_modules/doctr/datasets/doc_artefacts.html @@ -13,7 +13,7 @@ - + doctr.datasets.doc_artefacts - docTR documentation @@ -414,7 +414,7 @@

    Source code for doctr.datasets.doc_artefacts

       
    - + diff --git a/latest/_modules/doctr/datasets/funsd.html b/latest/_modules/doctr/datasets/funsd.html index e52abc5428..f08612f9fa 100644 --- a/latest/_modules/doctr/datasets/funsd.html +++ b/latest/_modules/doctr/datasets/funsd.html @@ -13,7 +13,7 @@ - + doctr.datasets.funsd - docTR documentation @@ -454,7 +454,7 @@

    Source code for doctr.datasets.funsd

         
       
    - + diff --git a/latest/_modules/doctr/datasets/generator/tensorflow.html b/latest/_modules/doctr/datasets/generator/tensorflow.html index 9f562582d9..a3e619f720 100644 --- a/latest/_modules/doctr/datasets/generator/tensorflow.html +++ b/latest/_modules/doctr/datasets/generator/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.datasets.generator.tensorflow - docTR documentation @@ -395,7 +395,7 @@

    Source code for doctr.datasets.generator.tensorflow

    - + diff --git a/latest/_modules/doctr/datasets/ic03.html b/latest/_modules/doctr/datasets/ic03.html index 3d221d07de..60e54a8a4b 100644 --- a/latest/_modules/doctr/datasets/ic03.html +++ b/latest/_modules/doctr/datasets/ic03.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic03 - docTR documentation @@ -468,7 +468,7 @@

    Source code for doctr.datasets.ic03

         
       
    - + diff --git a/latest/_modules/doctr/datasets/ic13.html b/latest/_modules/doctr/datasets/ic13.html index 8137e08e9f..219c98dcd1 100644 --- a/latest/_modules/doctr/datasets/ic13.html +++ b/latest/_modules/doctr/datasets/ic13.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic13 - docTR documentation @@ -440,7 +440,7 @@

    Source code for doctr.datasets.ic13

         
       
    - + diff --git a/latest/_modules/doctr/datasets/iiit5k.html b/latest/_modules/doctr/datasets/iiit5k.html index 1fc8ecfb27..b49c80fe18 100644 --- a/latest/_modules/doctr/datasets/iiit5k.html +++ b/latest/_modules/doctr/datasets/iiit5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiit5k - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.datasets.iiit5k

         
       
    - + diff --git a/latest/_modules/doctr/datasets/iiithws.html b/latest/_modules/doctr/datasets/iiithws.html index 07f5b13685..f7220afbc7 100644 --- a/latest/_modules/doctr/datasets/iiithws.html +++ b/latest/_modules/doctr/datasets/iiithws.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiithws - docTR documentation @@ -407,7 +407,7 @@

    Source code for doctr.datasets.iiithws

         
       
    - + diff --git a/latest/_modules/doctr/datasets/imgur5k.html b/latest/_modules/doctr/datasets/imgur5k.html index 68d433ca62..51c6545db4 100644 --- a/latest/_modules/doctr/datasets/imgur5k.html +++ b/latest/_modules/doctr/datasets/imgur5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.imgur5k - docTR documentation @@ -488,7 +488,7 @@

    Source code for doctr.datasets.imgur5k

         
       
    - + diff --git a/latest/_modules/doctr/datasets/loader.html b/latest/_modules/doctr/datasets/loader.html index d1785caa1c..ed80350ef0 100644 --- a/latest/_modules/doctr/datasets/loader.html +++ b/latest/_modules/doctr/datasets/loader.html @@ -13,7 +13,7 @@ - + doctr.datasets.loader - docTR documentation @@ -429,7 +429,7 @@

    Source code for doctr.datasets.loader

         
       
    - + diff --git a/latest/_modules/doctr/datasets/mjsynth.html b/latest/_modules/doctr/datasets/mjsynth.html index 77bb01d523..df34e49cf9 100644 --- a/latest/_modules/doctr/datasets/mjsynth.html +++ b/latest/_modules/doctr/datasets/mjsynth.html @@ -13,7 +13,7 @@ - + doctr.datasets.mjsynth - docTR documentation @@ -438,7 +438,7 @@

    Source code for doctr.datasets.mjsynth

         
       
    - + diff --git a/latest/_modules/doctr/datasets/ocr.html b/latest/_modules/doctr/datasets/ocr.html index 5832933ea5..ce1ed8b0d4 100644 --- a/latest/_modules/doctr/datasets/ocr.html +++ b/latest/_modules/doctr/datasets/ocr.html @@ -13,7 +13,7 @@ - + doctr.datasets.ocr - docTR documentation @@ -403,7 +403,7 @@

    Source code for doctr.datasets.ocr

         
       
    - + diff --git a/latest/_modules/doctr/datasets/recognition.html b/latest/_modules/doctr/datasets/recognition.html index 512c70c308..1754789364 100644 --- a/latest/_modules/doctr/datasets/recognition.html +++ b/latest/_modules/doctr/datasets/recognition.html @@ -13,7 +13,7 @@ - + doctr.datasets.recognition - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.datasets.recognition

         
       
    - + diff --git a/latest/_modules/doctr/datasets/sroie.html b/latest/_modules/doctr/datasets/sroie.html index 94c963390e..04cf10bda2 100644 --- a/latest/_modules/doctr/datasets/sroie.html +++ b/latest/_modules/doctr/datasets/sroie.html @@ -13,7 +13,7 @@ - + doctr.datasets.sroie - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.datasets.sroie

         
       
    - + diff --git a/latest/_modules/doctr/datasets/svhn.html b/latest/_modules/doctr/datasets/svhn.html index 48e4e4d210..60e02b1b3b 100644 --- a/latest/_modules/doctr/datasets/svhn.html +++ b/latest/_modules/doctr/datasets/svhn.html @@ -13,7 +13,7 @@ - + doctr.datasets.svhn - docTR documentation @@ -473,7 +473,7 @@

    Source code for doctr.datasets.svhn

         
       
    - + diff --git a/latest/_modules/doctr/datasets/svt.html b/latest/_modules/doctr/datasets/svt.html index 4144dc6b9b..a997fcbb50 100644 --- a/latest/_modules/doctr/datasets/svt.html +++ b/latest/_modules/doctr/datasets/svt.html @@ -13,7 +13,7 @@ - + doctr.datasets.svt - docTR documentation @@ -459,7 +459,7 @@

    Source code for doctr.datasets.svt

         
       
    - + diff --git a/latest/_modules/doctr/datasets/synthtext.html b/latest/_modules/doctr/datasets/synthtext.html index 3b9de506a7..c776e1d673 100644 --- a/latest/_modules/doctr/datasets/synthtext.html +++ b/latest/_modules/doctr/datasets/synthtext.html @@ -13,7 +13,7 @@ - + doctr.datasets.synthtext - docTR documentation @@ -470,7 +470,7 @@

    Source code for doctr.datasets.synthtext

         
       
    - + diff --git a/latest/_modules/doctr/datasets/utils.html b/latest/_modules/doctr/datasets/utils.html index 9defb17ba5..bde9304597 100644 --- a/latest/_modules/doctr/datasets/utils.html +++ b/latest/_modules/doctr/datasets/utils.html @@ -13,7 +13,7 @@ - + doctr.datasets.utils - docTR documentation @@ -554,7 +554,7 @@

    Source code for doctr.datasets.utils

         
       
    - + diff --git a/latest/_modules/doctr/datasets/wildreceipt.html b/latest/_modules/doctr/datasets/wildreceipt.html index c543ee7cac..12c6aebd14 100644 --- a/latest/_modules/doctr/datasets/wildreceipt.html +++ b/latest/_modules/doctr/datasets/wildreceipt.html @@ -13,7 +13,7 @@ - + doctr.datasets.wildreceipt - docTR documentation @@ -454,7 +454,7 @@

    Source code for doctr.datasets.wildreceipt

         
       
    - + diff --git a/latest/_modules/doctr/io/elements.html b/latest/_modules/doctr/io/elements.html index 753a47455c..e049d6ce30 100644 --- a/latest/_modules/doctr/io/elements.html +++ b/latest/_modules/doctr/io/elements.html @@ -13,7 +13,7 @@ - + doctr.io.elements - docTR documentation @@ -1008,7 +1008,7 @@

    Source code for doctr.io.elements

         
       
    - + diff --git a/latest/_modules/doctr/io/html.html b/latest/_modules/doctr/io/html.html index 7ad5b97031..be73631500 100644 --- a/latest/_modules/doctr/io/html.html +++ b/latest/_modules/doctr/io/html.html @@ -13,7 +13,7 @@ - + doctr.io.html - docTR documentation @@ -360,7 +360,7 @@

    Source code for doctr.io.html

         
       
    - + diff --git a/latest/_modules/doctr/io/image/base.html b/latest/_modules/doctr/io/image/base.html index 336b4bff0e..a50c95d595 100644 --- a/latest/_modules/doctr/io/image/base.html +++ b/latest/_modules/doctr/io/image/base.html @@ -13,7 +13,7 @@ - + doctr.io.image.base - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.io.image.base

         
       
    - + diff --git a/latest/_modules/doctr/io/image/tensorflow.html b/latest/_modules/doctr/io/image/tensorflow.html index f1846820a3..3b9e731756 100644 --- a/latest/_modules/doctr/io/image/tensorflow.html +++ b/latest/_modules/doctr/io/image/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.io.image.tensorflow - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.io.image.tensorflow

         
       
    - + diff --git a/latest/_modules/doctr/io/pdf.html b/latest/_modules/doctr/io/pdf.html index e3abf6960b..e5b94811c3 100644 --- a/latest/_modules/doctr/io/pdf.html +++ b/latest/_modules/doctr/io/pdf.html @@ -13,7 +13,7 @@ - + doctr.io.pdf - docTR documentation @@ -377,7 +377,7 @@

    Source code for doctr.io.pdf

         
       
    - + diff --git a/latest/_modules/doctr/io/reader.html b/latest/_modules/doctr/io/reader.html index c1ddc26edd..d36e5bb553 100644 --- a/latest/_modules/doctr/io/reader.html +++ b/latest/_modules/doctr/io/reader.html @@ -13,7 +13,7 @@ - + doctr.io.reader - docTR documentation @@ -426,7 +426,7 @@

    Source code for doctr.io.reader

         
       
    - + diff --git a/latest/_modules/doctr/models/classification/magc_resnet/tensorflow.html b/latest/_modules/doctr/models/classification/magc_resnet/tensorflow.html index 9f074805c1..61a010d548 100644 --- a/latest/_modules/doctr/models/classification/magc_resnet/tensorflow.html +++ b/latest/_modules/doctr/models/classification/magc_resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.magc_resnet.tensorflow - docTR documentation @@ -531,7 +531,7 @@

    Source code for doctr.models.classification.magc_resnet.tensorflow

    - + diff --git a/latest/_modules/doctr/models/classification/mobilenet/tensorflow.html b/latest/_modules/doctr/models/classification/mobilenet/tensorflow.html index 6a63851276..7c448394ad 100644 --- a/latest/_modules/doctr/models/classification/mobilenet/tensorflow.html +++ b/latest/_modules/doctr/models/classification/mobilenet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.mobilenet.tensorflow - docTR documentation @@ -793,7 +793,7 @@

    Source code for doctr.models.classification.mobilenet.tensorflow

    - + diff --git a/latest/_modules/doctr/models/classification/resnet/tensorflow.html b/latest/_modules/doctr/models/classification/resnet/tensorflow.html index 095d377f31..aed4343741 100644 --- a/latest/_modules/doctr/models/classification/resnet/tensorflow.html +++ b/latest/_modules/doctr/models/classification/resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.resnet.tensorflow - docTR documentation @@ -749,7 +749,7 @@

    Source code for doctr.models.classification.resnet.tensorflow

    - + diff --git a/latest/_modules/doctr/models/classification/textnet/tensorflow.html b/latest/_modules/doctr/models/classification/textnet/tensorflow.html index ad254ebbfb..c5567d7d67 100644 --- a/latest/_modules/doctr/models/classification/textnet/tensorflow.html +++ b/latest/_modules/doctr/models/classification/textnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.textnet.tensorflow - docTR documentation @@ -611,7 +611,7 @@

    Source code for doctr.models.classification.textnet.tensorflow

    - + diff --git a/latest/_modules/doctr/models/classification/vgg/tensorflow.html b/latest/_modules/doctr/models/classification/vgg/tensorflow.html index 01ae452624..788111ae87 100644 --- a/latest/_modules/doctr/models/classification/vgg/tensorflow.html +++ b/latest/_modules/doctr/models/classification/vgg/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vgg.tensorflow - docTR documentation @@ -451,7 +451,7 @@

    Source code for doctr.models.classification.vgg.tensorflow

    - + diff --git a/latest/_modules/doctr/models/classification/vit/tensorflow.html b/latest/_modules/doctr/models/classification/vit/tensorflow.html index 1333cf6045..971ba5abe9 100644 --- a/latest/_modules/doctr/models/classification/vit/tensorflow.html +++ b/latest/_modules/doctr/models/classification/vit/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vit.tensorflow - docTR documentation @@ -533,7 +533,7 @@

    Source code for doctr.models.classification.vit.tensorflow

    - + diff --git a/latest/_modules/doctr/models/classification/zoo.html b/latest/_modules/doctr/models/classification/zoo.html index f7796a7522..3eb2a3ec4e 100644 --- a/latest/_modules/doctr/models/classification/zoo.html +++ b/latest/_modules/doctr/models/classification/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.classification.zoo - docTR documentation @@ -447,7 +447,7 @@

    Source code for doctr.models.classification.zoo

    <
    - + diff --git a/latest/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html b/latest/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html index 4325d0b74a..66cef8663d 100644 --- a/latest/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html +++ b/latest/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.differentiable_binarization.tensorflow - docTR documentation @@ -759,7 +759,7 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo

    - + diff --git a/latest/_modules/doctr/models/detection/fast/tensorflow.html b/latest/_modules/doctr/models/detection/fast/tensorflow.html index 5b84d2dea1..65e1a77af8 100644 --- a/latest/_modules/doctr/models/detection/fast/tensorflow.html +++ b/latest/_modules/doctr/models/detection/fast/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.fast.tensorflow - docTR documentation @@ -769,7 +769,7 @@

    Source code for doctr.models.detection.fast.tensorflow

    - + diff --git a/latest/_modules/doctr/models/detection/linknet/tensorflow.html b/latest/_modules/doctr/models/detection/linknet/tensorflow.html index dbb58e37cf..ce995f99d4 100644 --- a/latest/_modules/doctr/models/detection/linknet/tensorflow.html +++ b/latest/_modules/doctr/models/detection/linknet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.linknet.tensorflow - docTR documentation @@ -716,7 +716,7 @@

    Source code for doctr.models.detection.linknet.tensorflow

    - + diff --git a/latest/_modules/doctr/models/detection/zoo.html b/latest/_modules/doctr/models/detection/zoo.html index 312f4584ab..3651c4e2d3 100644 --- a/latest/_modules/doctr/models/detection/zoo.html +++ b/latest/_modules/doctr/models/detection/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.detection.zoo - docTR documentation @@ -450,7 +450,7 @@

    Source code for doctr.models.detection.zoo

         
       
    - + diff --git a/latest/_modules/doctr/models/factory/hub.html b/latest/_modules/doctr/models/factory/hub.html index 8274a809f5..756b2c7a17 100644 --- a/latest/_modules/doctr/models/factory/hub.html +++ b/latest/_modules/doctr/models/factory/hub.html @@ -13,7 +13,7 @@ - + doctr.models.factory.hub - docTR documentation @@ -568,7 +568,7 @@

    Source code for doctr.models.factory.hub

         
       
    - + diff --git a/latest/_modules/doctr/models/recognition/crnn/tensorflow.html b/latest/_modules/doctr/models/recognition/crnn/tensorflow.html index e50c245923..bc64da9a1b 100644 --- a/latest/_modules/doctr/models/recognition/crnn/tensorflow.html +++ b/latest/_modules/doctr/models/recognition/crnn/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.crnn.tensorflow - docTR documentation @@ -658,7 +658,7 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    - + diff --git a/latest/_modules/doctr/models/recognition/master/tensorflow.html b/latest/_modules/doctr/models/recognition/master/tensorflow.html index 152ebb7e59..aa6aa69325 100644 --- a/latest/_modules/doctr/models/recognition/master/tensorflow.html +++ b/latest/_modules/doctr/models/recognition/master/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.master.tensorflow - docTR documentation @@ -655,7 +655,7 @@

    Source code for doctr.models.recognition.master.tensorflow

    - + diff --git a/latest/_modules/doctr/models/recognition/parseq/tensorflow.html b/latest/_modules/doctr/models/recognition/parseq/tensorflow.html index 0819737dfc..b181acef53 100644 --- a/latest/_modules/doctr/models/recognition/parseq/tensorflow.html +++ b/latest/_modules/doctr/models/recognition/parseq/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.parseq.tensorflow - docTR documentation @@ -845,7 +845,7 @@

    Source code for doctr.models.recognition.parseq.tensorflow

    - + diff --git a/latest/_modules/doctr/models/recognition/sar/tensorflow.html b/latest/_modules/doctr/models/recognition/sar/tensorflow.html index 010bc2bc54..4a591e6451 100644 --- a/latest/_modules/doctr/models/recognition/sar/tensorflow.html +++ b/latest/_modules/doctr/models/recognition/sar/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.sar.tensorflow - docTR documentation @@ -757,7 +757,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    - + diff --git a/latest/_modules/doctr/models/recognition/vitstr/tensorflow.html b/latest/_modules/doctr/models/recognition/vitstr/tensorflow.html index 6e101893bf..c594d40a56 100644 --- a/latest/_modules/doctr/models/recognition/vitstr/tensorflow.html +++ b/latest/_modules/doctr/models/recognition/vitstr/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.vitstr.tensorflow - docTR documentation @@ -621,7 +621,7 @@

    Source code for doctr.models.recognition.vitstr.tensorflow

    - + diff --git a/latest/_modules/doctr/models/recognition/zoo.html b/latest/_modules/doctr/models/recognition/zoo.html index 2c47f88de4..f664304019 100644 --- a/latest/_modules/doctr/models/recognition/zoo.html +++ b/latest/_modules/doctr/models/recognition/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.zoo - docTR documentation @@ -415,7 +415,7 @@

    Source code for doctr.models.recognition.zoo

       
    - + diff --git a/latest/_modules/doctr/models/zoo.html b/latest/_modules/doctr/models/zoo.html index 5b22f2c79f..d459671648 100644 --- a/latest/_modules/doctr/models/zoo.html +++ b/latest/_modules/doctr/models/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.zoo - docTR documentation @@ -576,7 +576,7 @@

    Source code for doctr.models.zoo

         
       
    - + diff --git a/latest/_modules/doctr/transforms/modules/base.html b/latest/_modules/doctr/transforms/modules/base.html index 96ebd680b7..4596df3848 100644 --- a/latest/_modules/doctr/transforms/modules/base.html +++ b/latest/_modules/doctr/transforms/modules/base.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.base - docTR documentation @@ -643,7 +643,7 @@

    Source code for doctr.transforms.modules.base

    - + diff --git a/latest/_modules/doctr/transforms/modules/tensorflow.html b/latest/_modules/doctr/transforms/modules/tensorflow.html index 0e18bcc922..acbbe96225 100644 --- a/latest/_modules/doctr/transforms/modules/tensorflow.html +++ b/latest/_modules/doctr/transforms/modules/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.tensorflow - docTR documentation @@ -956,7 +956,7 @@

    Source code for doctr.transforms.modules.tensorflow

    - + diff --git a/latest/_modules/doctr/utils/metrics.html b/latest/_modules/doctr/utils/metrics.html index d35d7e9672..8a37d5949a 100644 --- a/latest/_modules/doctr/utils/metrics.html +++ b/latest/_modules/doctr/utils/metrics.html @@ -13,7 +13,7 @@ - + doctr.utils.metrics - docTR documentation @@ -936,7 +936,7 @@

    Source code for doctr.utils.metrics

         
       
    - + diff --git a/latest/_modules/doctr/utils/visualization.html b/latest/_modules/doctr/utils/visualization.html index e608d492a4..c818be6d7b 100644 --- a/latest/_modules/doctr/utils/visualization.html +++ b/latest/_modules/doctr/utils/visualization.html @@ -13,7 +13,7 @@ - + doctr.utils.visualization - docTR documentation @@ -720,7 +720,7 @@

    Source code for doctr.utils.visualization

         
       
    - + diff --git a/latest/_modules/index.html b/latest/_modules/index.html index 758ef41bd0..5793c44f20 100644 --- a/latest/_modules/index.html +++ b/latest/_modules/index.html @@ -13,7 +13,7 @@ - + Overview: module code - docTR documentation @@ -378,7 +378,7 @@

    All modules for which code is available

    - + diff --git a/latest/_sources/getting_started/installing.rst.txt b/latest/_sources/getting_started/installing.rst.txt index e764e734a7..39e79aa3dd 100644 --- a/latest/_sources/getting_started/installing.rst.txt +++ b/latest/_sources/getting_started/installing.rst.txt @@ -3,7 +3,7 @@ Installation ************ -This library requires `Python `_ 3.9 or higher. +This library requires `Python `_ 3.10 or higher. Prerequisites diff --git a/latest/_static/basic.css b/latest/_static/basic.css index f316efcb47..7ebbd6d07b 100644 --- a/latest/_static/basic.css +++ b/latest/_static/basic.css @@ -1,12 +1,5 @@ /* - * basic.css - * ~~~~~~~~~ - * * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ /* -- main layout ----------------------------------------------------------- */ @@ -115,15 +108,11 @@ img { /* -- search page ----------------------------------------------------------- */ ul.search { - margin: 10px 0 0 20px; - padding: 0; + margin-top: 10px; } ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; + padding: 5px 0; } ul.search li a { diff --git a/latest/_static/doctools.js b/latest/_static/doctools.js index 4d67807d17..0398ebb9f0 100644 --- a/latest/_static/doctools.js +++ b/latest/_static/doctools.js @@ -1,12 +1,5 @@ /* - * doctools.js - * ~~~~~~~~~~~ - * * Base JavaScript utilities for all Sphinx HTML documentation. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; diff --git a/latest/_static/language_data.js b/latest/_static/language_data.js index 367b8ed81b..c7fe6c6faf 100644 --- a/latest/_static/language_data.js +++ b/latest/_static/language_data.js @@ -1,13 +1,6 @@ /* - * language_data.js - * ~~~~~~~~~~~~~~~~ - * * This script contains the language-specific data used by searchtools.js, * namely the list of stopwords, stemmer, scorer and splitter. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; diff --git a/latest/_static/searchtools.js b/latest/_static/searchtools.js index b08d58c9b9..2c774d17af 100644 --- a/latest/_static/searchtools.js +++ b/latest/_static/searchtools.js @@ -1,12 +1,5 @@ /* - * searchtools.js - * ~~~~~~~~~~~~~~~~ - * * Sphinx JavaScript utilities for the full-text search. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; @@ -20,7 +13,7 @@ if (typeof Scorer === "undefined") { // and returns the new score. /* score: result => { - const [docname, title, anchor, descr, score, filename] = result + const [docname, title, anchor, descr, score, filename, kind] = result return score }, */ @@ -47,6 +40,14 @@ if (typeof Scorer === "undefined") { }; } +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + const _removeChildren = (element) => { while (element && element.lastChild) element.removeChild(element.lastChild); }; @@ -64,9 +65,13 @@ const _displayItem = (item, searchTerms, highlightTerms) => { const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; const contentRoot = document.documentElement.dataset.content_root; - const [docName, title, anchor, descr, score, _filename] = item; + const [docName, title, anchor, descr, score, _filename, kind] = item; let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); let requestUrl; let linkUrl; if (docBuilder === "dirhtml") { @@ -115,8 +120,10 @@ const _finishSearch = (resultCount) => { "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." ); else - Search.status.innerText = _( - "Search finished, found ${resultCount} page(s) matching the search query." + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, ).replace('${resultCount}', resultCount); }; const _displayNextItem = ( @@ -138,7 +145,7 @@ const _displayNextItem = ( else _finishSearch(resultCount); }; // Helper function used by query() to order search results. -// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. // Order the results by score (in opposite order of appearance, since the // `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. const _orderResultsByScoreThenName = (a, b) => { @@ -248,6 +255,7 @@ const Search = { searchSummary.classList.add("search-summary"); searchSummary.innerText = ""; const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); searchList.classList.add("search"); const out = document.getElementById("search-results"); @@ -318,7 +326,7 @@ const Search = { const indexEntries = Search._index.indexentries; // Collect multiple result groups to be sorted separately and then ordered. - // Each is an array of [docname, title, anchor, descr, score, filename]. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. const normalResults = []; const nonMainIndexResults = []; @@ -337,6 +345,7 @@ const Search = { null, score + boost, filenames[file], + SearchResultKind.title, ]); } } @@ -354,6 +363,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.index, ]; if (isMain) { normalResults.push(result); @@ -475,6 +485,7 @@ const Search = { descr, score, filenames[match[0]], + SearchResultKind.object, ]); }; Object.keys(objects).forEach((prefix) => @@ -585,6 +596,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.text, ]); } return results; diff --git a/latest/changelog.html b/latest/changelog.html index ac81a6f231..fc45a50384 100644 --- a/latest/changelog.html +++ b/latest/changelog.html @@ -14,7 +14,7 @@ - + Changelog - docTR documentation @@ -446,7 +446,7 @@

    v0.1.0 (2021-03-05) - + diff --git a/latest/community/resources.html b/latest/community/resources.html index 2564037893..9a1988258c 100644 --- a/latest/community/resources.html +++ b/latest/community/resources.html @@ -14,7 +14,7 @@ - + Community resources - docTR documentation @@ -389,7 +389,7 @@

    Community resources - + diff --git a/latest/contributing/code_of_conduct.html b/latest/contributing/code_of_conduct.html index 5ea4a1f99d..03422dbb4d 100644 --- a/latest/contributing/code_of_conduct.html +++ b/latest/contributing/code_of_conduct.html @@ -14,7 +14,7 @@ - + Contributor Covenant Code of Conduct - docTR documentation @@ -504,7 +504,7 @@

    Attribution - + diff --git a/latest/contributing/contributing.html b/latest/contributing/contributing.html index e5a85682c6..05e2b3641b 100644 --- a/latest/contributing/contributing.html +++ b/latest/contributing/contributing.html @@ -14,7 +14,7 @@ - + Contributing to docTR - docTR documentation @@ -481,7 +481,7 @@

    Let’s connect - + diff --git a/latest/genindex.html b/latest/genindex.html index cbb43f08d8..21520455b4 100644 --- a/latest/genindex.html +++ b/latest/genindex.html @@ -13,7 +13,7 @@ - Index - docTR documentation + Index - docTR documentation @@ -756,7 +756,7 @@

    W

    - + diff --git a/latest/getting_started/installing.html b/latest/getting_started/installing.html index a488e9a030..af3b58193e 100644 --- a/latest/getting_started/installing.html +++ b/latest/getting_started/installing.html @@ -14,7 +14,7 @@ - + Installation - docTR documentation @@ -305,7 +305,7 @@

    Installation

    -

    This library requires Python 3.9 or higher.

    +

    This library requires Python 3.10 or higher.

    Prerequisites

    Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so:

    @@ -435,7 +435,7 @@

    Via Git - + diff --git a/latest/index.html b/latest/index.html index 76509686f5..3a06afc6d9 100644 --- a/latest/index.html +++ b/latest/index.html @@ -14,7 +14,7 @@ - + docTR documentation @@ -445,7 +445,7 @@

    Supported datasets - + diff --git a/latest/modules/contrib.html b/latest/modules/contrib.html index e99f6b3f74..7fb86b8b38 100644 --- a/latest/modules/contrib.html +++ b/latest/modules/contrib.html @@ -14,7 +14,7 @@ - + doctr.contrib - docTR documentation @@ -380,7 +380,7 @@

    Supported contribution modules - + diff --git a/latest/modules/datasets.html b/latest/modules/datasets.html index 456e10b172..380a986793 100644 --- a/latest/modules/datasets.html +++ b/latest/modules/datasets.html @@ -14,7 +14,7 @@ - + doctr.datasets - docTR documentation @@ -1081,7 +1081,7 @@

    Returns: - + diff --git a/latest/modules/io.html b/latest/modules/io.html index 01eadaa4b8..24c41954be 100644 --- a/latest/modules/io.html +++ b/latest/modules/io.html @@ -14,7 +14,7 @@ - + doctr.io - docTR documentation @@ -760,7 +760,7 @@

    Returns: - + diff --git a/latest/modules/models.html b/latest/modules/models.html index c465cc0586..91b8810a6a 100644 --- a/latest/modules/models.html +++ b/latest/modules/models.html @@ -14,7 +14,7 @@ - + doctr.models - docTR documentation @@ -1612,7 +1612,7 @@

    Args: - + diff --git a/latest/modules/transforms.html b/latest/modules/transforms.html index 30f7a2631a..c5ead3f3ce 100644 --- a/latest/modules/transforms.html +++ b/latest/modules/transforms.html @@ -14,7 +14,7 @@ - + doctr.transforms - docTR documentation @@ -835,7 +835,7 @@

    Args:< - + diff --git a/latest/modules/utils.html b/latest/modules/utils.html index 888a32c321..b7f6fc570b 100644 --- a/latest/modules/utils.html +++ b/latest/modules/utils.html @@ -14,7 +14,7 @@ - + doctr.utils - docTR documentation @@ -715,7 +715,7 @@

    Args: - + diff --git a/latest/notebooks.html b/latest/notebooks.html index f97771aebb..d36539f59e 100644 --- a/latest/notebooks.html +++ b/latest/notebooks.html @@ -14,7 +14,7 @@ - + docTR Notebooks - docTR documentation @@ -391,7 +391,7 @@

    docTR Notebooks - + diff --git a/latest/search.html b/latest/search.html index 82b8bd6950..d050f5eac7 100644 --- a/latest/search.html +++ b/latest/search.html @@ -14,7 +14,7 @@ - + Search - docTR documentation @@ -340,7 +340,7 @@ - + diff --git a/latest/searchindex.js b/latest/searchindex.js index bfa546d0e9..6f154115ab 100644 --- a/latest/searchindex.js +++ b/latest/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles": {"1. Correction": [[2, "correction"]], "2. Warning": [[2, "warning"]], "3. Temporary Ban": [[2, "temporary-ban"]], "4. Permanent Ban": [[2, "permanent-ban"]], "AWS Lambda": [[14, null]], "Advanced options": [[19, "advanced-options"]], "Args:": [[7, "args"], [7, "id4"], [7, "id7"], [7, "id10"], [7, "id13"], [7, "id16"], [7, "id19"], [7, "id22"], [7, "id25"], [7, "id29"], [7, "id32"], [7, "id37"], [7, "id40"], [7, "id46"], [7, "id49"], [7, "id50"], [7, "id51"], [7, "id54"], [7, "id57"], [7, "id60"], [7, "id61"], [8, "args"], [8, "id2"], [8, "id3"], [8, "id4"], [8, "id5"], [8, "id6"], [8, "id7"], [8, "id10"], [8, "id12"], [8, "id14"], [8, "id16"], [8, "id20"], [8, "id24"], [8, "id28"], [9, "args"], [9, "id3"], [9, "id8"], [9, "id13"], [9, "id17"], [9, "id21"], [9, "id26"], [9, "id31"], [9, "id36"], [9, "id41"], [9, "id46"], [9, "id50"], [9, "id54"], [9, "id59"], [9, "id63"], [9, "id68"], [9, "id73"], [9, "id77"], [9, "id81"], [9, "id85"], [9, "id90"], [9, "id95"], [9, "id99"], [9, "id104"], [9, "id109"], [9, "id114"], [9, "id119"], [9, "id123"], [9, "id127"], [9, "id132"], [9, "id137"], [9, "id142"], [9, "id146"], [9, "id150"], [9, "id155"], [9, "id159"], [9, "id163"], [9, "id167"], [9, "id169"], [9, "id171"], [9, "id173"], [10, "args"], [10, "id1"], [10, "id2"], [10, "id3"], [10, "id4"], [10, "id5"], [10, "id6"], [10, "id7"], [10, "id8"], [10, "id9"], [10, "id10"], [10, "id11"], [10, "id12"], [10, "id13"], [10, "id14"], [10, "id15"], [10, "id16"], [10, "id17"], [10, "id18"], [10, "id19"], [11, "args"], [11, "id3"], [11, "id4"], [11, "id5"], [11, "id6"], [11, "id7"], [11, "id8"], [11, "id9"]], "Artefact": [[8, "artefact"]], "ArtefactDetection": [[16, "artefactdetection"]], "Attribution": [[2, "attribution"]], "Available Datasets": [[17, "available-datasets"]], "Available architectures": [[19, "available-architectures"], [19, "id1"], [19, "id2"]], "Available contribution modules": [[16, "available-contribution-modules"]], "Block": [[8, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[17, null]], "Choosing the right model": [[19, null]], "Classification": [[15, "classification"]], "Code quality": [[3, "code-quality"]], "Code style verification": [[3, "code-style-verification"]], "Codebase structure": [[3, "codebase-structure"]], "Commits": [[3, "commits"]], "Community resources": [[1, null]], "Composing transformations": [[10, "composing-transformations"]], "Continuous Integration": [[3, "continuous-integration"]], "Contributing to docTR": [[3, null]], "Contributor Covenant Code of Conduct": [[2, null]], "Custom dataset loader": [[7, "custom-dataset-loader"]], "Custom orientation classification models": [[13, "custom-orientation-classification-models"]], "Data Loading": [[17, "data-loading"]], "Dataloader": [[7, "dataloader"]], "Detection": [[15, "detection"], [17, "detection"]], "Detection predictors": [[19, "detection-predictors"]], "Developer mode installation": [[3, "developer-mode-installation"]], "Developing docTR": [[3, "developing-doctr"]], "Document": [[8, "document"]], "Document structure": [[8, "document-structure"]], "End-to-End OCR": [[19, "end-to-end-ocr"]], "Enforcement": [[2, "enforcement"]], "Enforcement Guidelines": [[2, "enforcement-guidelines"]], "Enforcement Responsibilities": [[2, "enforcement-responsibilities"]], "Export to ONNX": [[18, "export-to-onnx"]], "Feature requests & bug report": [[3, "feature-requests-bug-report"]], "Feedback": [[3, "feedback"]], "File reading": [[8, "file-reading"]], "Half-precision": [[18, "half-precision"]], "Installation": [[4, null]], "Integrate contributions into your pipeline": [[16, null]], "Let\u2019s connect": [[3, "let-s-connect"]], "Line": [[8, "line"]], "Loading from Huggingface Hub": [[15, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[13, "loading-your-custom-trained-model"]], "Loading your custom trained orientation classification model": [[13, "loading-your-custom-trained-orientation-classification-model"]], "Main Features": [[5, "main-features"]], "Model optimization": [[18, "model-optimization"]], "Model zoo": [[5, "model-zoo"]], "Modifying the documentation": [[3, "modifying-the-documentation"]], "Naming conventions": [[15, "naming-conventions"]], "OCR": [[17, "ocr"]], "Object Detection": [[17, "object-detection"]], "Our Pledge": [[2, "our-pledge"]], "Our Standards": [[2, "our-standards"]], "Page": [[8, "page"]], "Preparing your model for inference": [[18, null]], "Prerequisites": [[4, "prerequisites"]], "Pretrained community models": [[15, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[15, "pushing-to-the-huggingface-hub"]], "Questions": [[3, "questions"]], "Recognition": [[15, "recognition"], [17, "recognition"]], "Recognition predictors": [[19, "recognition-predictors"]], "Returns:": [[7, "returns"], [8, "returns"], [8, "id11"], [8, "id13"], [8, "id15"], [8, "id19"], [8, "id23"], [8, "id27"], [8, "id31"], [9, "returns"], [9, "id6"], [9, "id11"], [9, "id16"], [9, "id20"], [9, "id24"], [9, "id29"], [9, "id34"], [9, "id39"], [9, "id44"], [9, "id49"], [9, "id53"], [9, "id57"], [9, "id62"], [9, "id66"], [9, "id71"], [9, "id76"], [9, "id80"], [9, "id84"], [9, "id88"], [9, "id93"], [9, "id98"], [9, "id102"], [9, "id107"], [9, "id112"], [9, "id117"], [9, "id122"], [9, "id126"], [9, "id130"], [9, "id135"], [9, "id140"], [9, "id145"], [9, "id149"], [9, "id153"], [9, "id158"], [9, "id162"], [9, "id166"], [9, "id168"], [9, "id170"], [9, "id172"], [11, "returns"]], "Scope": [[2, "scope"]], "Share your model with the community": [[15, null]], "Supported Vocabs": [[7, "supported-vocabs"]], "Supported contribution modules": [[6, "supported-contribution-modules"]], "Supported datasets": [[5, "supported-datasets"]], "Supported transformations": [[10, "supported-transformations"]], "Synthetic dataset generator": [[7, "synthetic-dataset-generator"], [17, "synthetic-dataset-generator"]], "Task evaluation": [[11, "task-evaluation"]], "Text Detection": [[19, "text-detection"]], "Text Recognition": [[19, "text-recognition"]], "Text detection models": [[5, "text-detection-models"]], "Text recognition models": [[5, "text-recognition-models"]], "Train your own model": [[13, null]], "Two-stage approaches": [[19, "two-stage-approaches"]], "Unit tests": [[3, "unit-tests"]], "Use your own datasets": [[17, "use-your-own-datasets"]], "Using your ONNX exported model": [[18, "using-your-onnx-exported-model"]], "Via Conda (Only for Linux)": [[4, "via-conda-only-for-linux"]], "Via Git": [[4, "via-git"]], "Via Python Package": [[4, "via-python-package"]], "Visualization": [[11, "visualization"]], "What should I do with the output?": [[19, "what-should-i-do-with-the-output"]], "Word": [[8, "word"]], "docTR Notebooks": [[12, null]], "docTR Vocabs": [[7, "id62"]], "docTR: Document Text Recognition": [[5, null]], "doctr.contrib": [[6, null]], "doctr.datasets": [[7, null], [7, "datasets"]], "doctr.io": [[8, null]], "doctr.models": [[9, null]], "doctr.models.classification": [[9, "doctr-models-classification"]], "doctr.models.detection": [[9, "doctr-models-detection"]], "doctr.models.factory": [[9, "doctr-models-factory"]], "doctr.models.recognition": [[9, "doctr-models-recognition"]], "doctr.models.zoo": [[9, "doctr-models-zoo"]], "doctr.transforms": [[10, null]], "doctr.utils": [[11, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.10.0 (2024-10-21)": [[0, "v0-10-0-2024-10-21"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]], "v0.7.0 (2023-09-09)": [[0, "v0-7-0-2023-09-09"]], "v0.8.0 (2024-02-28)": [[0, "v0-8-0-2024-02-28"]], "v0.8.1 (2024-03-04)": [[0, "v0-8-1-2024-03-04"]], "v0.9.0 (2024-08-08)": [[0, "v0-9-0-2024-08-08"]]}, "docnames": ["changelog", "community/resources", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/contrib", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_contrib_modules", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "community/resources.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/contrib.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_contrib_modules.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[8, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[8, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[10, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[7, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[10, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[10, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[7, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[7, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[9, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[7, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[7, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[8, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[8, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[7, "doctr.datasets.encode_sequences", false]], "fast_base() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_base", false]], "fast_small() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_small", false]], "fast_tiny() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_tiny", false]], "from_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[7, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[10, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[10, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[7, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[7, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[7, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[7, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[7, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[9, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[10, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[8, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet34() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[7, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_crop_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_crop_orientation", false]], "mobilenet_v3_small_page_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_page_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[10, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[9, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[7, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[10, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[8, "doctr.io.Page", false]], "page_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.page_orientation_predictor", false]], "parseq() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[10, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[10, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[10, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[10, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[10, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[10, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[10, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[10, "doctr.transforms.RandomJpegQuality", false]], "randomresize (class in doctr.transforms)": [[10, "doctr.transforms.RandomResize", false]], "randomrotate (class in doctr.transforms)": [[10, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[10, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[10, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[8, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[8, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[8, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[7, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[10, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[8, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[8, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[7, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[7, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[7, "doctr.datasets.SVT", false]], "synthtext (class in doctr.datasets)": [[7, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.TextMatch", false]], "textnet_base() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_base", false]], "textnet_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_small", false]], "textnet_tiny() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_tiny", false]], "togray (class in doctr.transforms)": [[10, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[11, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_small", false]], "wildreceipt (class in doctr.datasets)": [[7, "doctr.datasets.WILDRECEIPT", false]], "word (class in doctr.io)": [[8, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[7, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[7, 0, 1, "", "CORD"], [7, 0, 1, "", "CharacterGenerator"], [7, 0, 1, "", "DetectionDataset"], [7, 0, 1, "", "DocArtefacts"], [7, 0, 1, "", "FUNSD"], [7, 0, 1, "", "IC03"], [7, 0, 1, "", "IC13"], [7, 0, 1, "", "IIIT5K"], [7, 0, 1, "", "IIITHWS"], [7, 0, 1, "", "IMGUR5K"], [7, 0, 1, "", "MJSynth"], [7, 0, 1, "", "OCRDataset"], [7, 0, 1, "", "RecognitionDataset"], [7, 0, 1, "", "SROIE"], [7, 0, 1, "", "SVHN"], [7, 0, 1, "", "SVT"], [7, 0, 1, "", "SynthText"], [7, 0, 1, "", "WILDRECEIPT"], [7, 0, 1, "", "WordGenerator"], [7, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[7, 0, 1, "", "DataLoader"]], "doctr.io": [[8, 0, 1, "", "Artefact"], [8, 0, 1, "", "Block"], [8, 0, 1, "", "Document"], [8, 0, 1, "", "DocumentFile"], [8, 0, 1, "", "Line"], [8, 0, 1, "", "Page"], [8, 0, 1, "", "Word"], [8, 1, 1, "", "decode_img_as_tensor"], [8, 1, 1, "", "read_html"], [8, 1, 1, "", "read_img_as_numpy"], [8, 1, 1, "", "read_img_as_tensor"], [8, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[8, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[8, 2, 1, "", "from_images"], [8, 2, 1, "", "from_pdf"], [8, 2, 1, "", "from_url"]], "doctr.io.Page": [[8, 2, 1, "", "show"]], "doctr.models": [[9, 1, 1, "", "kie_predictor"], [9, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[9, 1, 1, "", "crop_orientation_predictor"], [9, 1, 1, "", "magc_resnet31"], [9, 1, 1, "", "mobilenet_v3_large"], [9, 1, 1, "", "mobilenet_v3_large_r"], [9, 1, 1, "", "mobilenet_v3_small"], [9, 1, 1, "", "mobilenet_v3_small_crop_orientation"], [9, 1, 1, "", "mobilenet_v3_small_page_orientation"], [9, 1, 1, "", "mobilenet_v3_small_r"], [9, 1, 1, "", "page_orientation_predictor"], [9, 1, 1, "", "resnet18"], [9, 1, 1, "", "resnet31"], [9, 1, 1, "", "resnet34"], [9, 1, 1, "", "resnet50"], [9, 1, 1, "", "textnet_base"], [9, 1, 1, "", "textnet_small"], [9, 1, 1, "", "textnet_tiny"], [9, 1, 1, "", "vgg16_bn_r"], [9, 1, 1, "", "vit_b"], [9, 1, 1, "", "vit_s"]], "doctr.models.detection": [[9, 1, 1, "", "db_mobilenet_v3_large"], [9, 1, 1, "", "db_resnet50"], [9, 1, 1, "", "detection_predictor"], [9, 1, 1, "", "fast_base"], [9, 1, 1, "", "fast_small"], [9, 1, 1, "", "fast_tiny"], [9, 1, 1, "", "linknet_resnet18"], [9, 1, 1, "", "linknet_resnet34"], [9, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[9, 1, 1, "", "from_hub"], [9, 1, 1, "", "login_to_hub"], [9, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[9, 1, 1, "", "crnn_mobilenet_v3_large"], [9, 1, 1, "", "crnn_mobilenet_v3_small"], [9, 1, 1, "", "crnn_vgg16_bn"], [9, 1, 1, "", "master"], [9, 1, 1, "", "parseq"], [9, 1, 1, "", "recognition_predictor"], [9, 1, 1, "", "sar_resnet31"], [9, 1, 1, "", "vitstr_base"], [9, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[10, 0, 1, "", "ChannelShuffle"], [10, 0, 1, "", "ColorInversion"], [10, 0, 1, "", "Compose"], [10, 0, 1, "", "GaussianBlur"], [10, 0, 1, "", "GaussianNoise"], [10, 0, 1, "", "LambdaTransformation"], [10, 0, 1, "", "Normalize"], [10, 0, 1, "", "OneOf"], [10, 0, 1, "", "RandomApply"], [10, 0, 1, "", "RandomBrightness"], [10, 0, 1, "", "RandomContrast"], [10, 0, 1, "", "RandomCrop"], [10, 0, 1, "", "RandomGamma"], [10, 0, 1, "", "RandomHorizontalFlip"], [10, 0, 1, "", "RandomHue"], [10, 0, 1, "", "RandomJpegQuality"], [10, 0, 1, "", "RandomResize"], [10, 0, 1, "", "RandomRotate"], [10, 0, 1, "", "RandomSaturation"], [10, 0, 1, "", "RandomShadow"], [10, 0, 1, "", "Resize"], [10, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[11, 0, 1, "", "DetectionMetric"], [11, 0, 1, "", "LocalizationConfusion"], [11, 0, 1, "", "OCRMetric"], [11, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.visualization": [[11, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [2, 8, 9, 11, 15, 18], "0": [2, 4, 7, 10, 11, 13, 16, 17, 19], "00": 19, "01": 19, "0123456789": 7, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "02562": 9, "03": 19, "035": 19, "0361328125": 19, "04": 19, "05": 19, "06": 19, "06640625": 19, "07": 19, "08": [10, 19], "09": 19, "0966796875": 19, "1": [7, 8, 9, 10, 11, 13, 17, 19], "10": [7, 11, 19], "100": [7, 10, 11, 17, 19], "1000": 19, "101": 7, "1024": [9, 13, 19], "104": 7, "106": 7, "108": 7, "1095": 17, "11": 19, "110": 11, "1107": 17, "114": 7, "115": 7, "1156": 17, "116": 7, "118": 7, "11800h": 19, "11th": 19, "12": 19, "120": 7, "123": 7, "126": 7, "1268": 17, "128": [9, 13, 18, 19], "13": 19, "130": 7, "13068": 17, "131": 7, "1337891": 17, "1357421875": 19, "1396484375": 19, "14": 19, "1420": 19, "14470v1": 7, "149": 17, "15": 19, "150": [11, 19], "1552": 19, "16": [9, 18, 19], "1630859375": 19, "1684": 19, "16x16": 9, "17": 19, "1778": 19, "1782": 19, "18": [9, 19], "185546875": 19, "1900": 19, "1910": 9, "19342": 17, "19370": 17, "195": 7, "19598": 17, "199": 19, "1999": 19, "2": [4, 5, 7, 8, 9, 10, 16, 19], "20": 19, "200": 11, "2000": 17, "2003": [5, 7], "2012": 7, "2013": [5, 7], "2015": 7, "2019": 5, "2023": 1, "207901": 17, "21": 19, "2103": 7, "2186": 17, "21888": 17, "22": 19, "224": [9, 10], "225": 10, "22672": 17, "229": [10, 17], "23": 19, "233": 17, "236": 7, "24": 19, "246": 17, "249": 17, "25": 19, "2504": 19, "255": [8, 9, 10, 11, 19], "256": 9, "257": 17, "26": 19, "26032": 17, "264": 13, "27": 19, "2700": 17, "2710": 19, "2749": 13, "28": 19, "287": 13, "29": 19, "296": 13, "299": 13, "2d": 19, "3": [4, 5, 8, 9, 10, 11, 18, 19], "30": 19, "300": 17, "3000": 17, "301": 13, "30595": 19, "30ghz": 19, "31": 9, "32": [7, 9, 10, 13, 17, 18, 19], "3232421875": 19, "33": [10, 19], "33402": 17, "33608": 17, "34": [9, 19], "340": 19, "3456": 19, "3515625": 19, "36": 19, "360": 17, "37": [7, 19], "38": 19, "39": 19, "4": [9, 10, 11, 19], "40": 19, "406": 10, "41": 19, "42": 19, "43": 19, "44": 19, "45": 19, "456": 10, "46": 19, "47": 19, "472": 17, "48": [7, 19], "485": 10, "49": 19, "49377": 17, "5": [7, 10, 11, 16, 19], "50": [9, 17, 19], "51": 19, "51171875": 19, "512": 9, "52": [7, 19], "529": 19, "53": 19, "54": 19, "540": 19, "5478515625": 19, "55": 19, "56": 19, "57": 19, "58": [7, 19], "580": 19, "5810546875": 19, "583": 19, "59": 19, "597": 19, "5k": [5, 7], "5m": 19, "6": [10, 19], "60": 10, "600": [9, 11, 19], "61": 19, "62": 19, "626": 17, "63": 19, "64": [9, 10, 19], "641": 19, "647": 17, "65": 19, "66": 19, "67": 19, "68": 19, "69": 19, "693": 13, "694": 13, "695": 13, "6m": 19, "7": 19, "70": [7, 11, 19], "707470": 17, "71": [7, 19], "7100000": 17, "7141797": 17, "7149": 17, "72": 19, "72dpi": 8, "73": 19, "73257": 17, "74": 19, "75": [10, 19], "7581382": 17, "76": 19, "77": 19, "772": 13, "772875": 17, "78": 19, "785": 13, "79": 19, "793533": 17, "796": 17, "798": 13, "7m": 19, "8": [9, 10, 19], "80": 19, "800": [9, 11, 17, 19], "81": 19, "82": 19, "83": 19, "84": 19, "849": 17, "85": 19, "8564453125": 19, "857": 19, "85875": 17, "86": 19, "8603515625": 19, "87": 19, "8707": 17, "88": 19, "89": 19, "9": [4, 10, 19], "90": 19, "90k": 7, "90kdict32px": 7, "91": 19, "914085328578949": 19, "92": 19, "93": 19, "94": [7, 19], "95": [11, 19], "9578408598899841": 19, "96": 19, "97": 19, "98": 19, "99": 19, "9949972033500671": 19, "A": [2, 3, 5, 7, 8, 9, 12, 18], "As": 3, "Be": 19, "Being": 2, "By": 14, "For": [2, 3, 4, 13, 19], "If": [3, 8, 9, 13, 19], "In": [3, 7, 17], "It": [10, 15, 16, 18], "Its": [5, 9], "No": [2, 19], "Of": 7, "Or": [16, 18], "The": [2, 3, 7, 8, 11, 14, 16, 17, 18, 19], "Then": 9, "To": [3, 4, 14, 15, 16, 18, 19], "_": [2, 7, 9], "__call__": 19, "_build": 3, "_i": 11, "ab": 7, "abc": 18, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "abdef": [7, 17], "abl": [17, 19], "about": [2, 17, 19], "abov": 19, "abstract": 1, "abstractdataset": 7, "abus": 2, "accept": 2, "access": [5, 8, 17, 19], "account": [2, 15], "accur": 19, "accuraci": 11, "achiev": 18, "act": 2, "action": 2, "activ": 5, "ad": [3, 9, 10], "adapt": 2, "add": [10, 11, 15, 19], "add_hook": 19, "add_label": 11, "addit": [3, 4, 8, 16, 19], "addition": [3, 19], "address": [2, 8], "adjust": 10, "advanc": 2, "advantag": 18, "advis": 3, "aesthet": [5, 7], "affect": 2, "after": [15, 19], "ag": 2, "again": 9, "aggreg": [11, 17], "aggress": 2, "align": [2, 8, 10], "all": [2, 3, 6, 7, 8, 10, 11, 16, 17, 19], "allow": [2, 18], "along": 19, "alreadi": [3, 18], "also": [2, 9, 15, 16, 17, 19], "alwai": 17, "an": [2, 3, 5, 7, 8, 9, 11, 16, 18, 19], "analysi": [8, 16], "ancient_greek": 7, "andrej": 1, "angl": [8, 10], "ani": [2, 7, 8, 9, 10, 11, 18, 19], "annot": 7, "anot": 17, "anoth": [9, 13, 17], "answer": 2, "anyascii": 11, "anyon": 5, "anyth": 16, "api": [3, 5], "apolog": 2, "apologi": 2, "app": 3, "appear": 2, "appli": [2, 7, 10], "applic": [5, 9], "appoint": 2, "appreci": 15, "appropri": [2, 3, 19], "ar": [2, 3, 4, 6, 7, 8, 10, 11, 12, 16, 17, 19], "arab": 7, "arabic_diacrit": 7, "arabic_lett": 7, "arabic_punctu": 7, "arbitrarili": [5, 9], "arch": [9, 15], "architectur": [5, 9, 15, 16], "area": 19, "argument": [7, 8, 9, 11, 13, 19], "around": 2, "arrai": [8, 10, 11], "art": [5, 16], "artefact": [11, 16, 19], "artefact_typ": 8, "articl": 1, "artifici": [5, 7], "arxiv": [7, 9], "asarrai": 11, "ascii_lett": 7, "aspect": [5, 9, 10, 19], "assess": 11, "assign": 11, "associ": 8, "assum": 9, "assume_straight_pag": [9, 13, 19], "astyp": [9, 11, 19], "attack": 2, "attend": [5, 9], "attent": [2, 9], "autom": 5, "automat": 19, "autoregress": [5, 9], "avail": [2, 5, 6, 10], "averag": [10, 19], "avoid": [2, 4], "aw": [5, 19], "awar": 19, "azur": 19, "b": [9, 11, 19], "b_j": 11, "back": 3, "backbon": 9, "backend": 19, "background": 17, "bangla": 7, "bar": 16, "bar_cod": 17, "baranovskij": 1, "base": [5, 9, 16], "baselin": [5, 9, 19], "batch": [7, 9, 10, 16, 17, 19], "batch_siz": [7, 9, 13, 16, 17, 18], "bblanchon": 4, "bbox": 19, "becaus": 14, "been": [3, 11, 17, 19], "befor": [7, 9, 10, 19], "begin": 11, "behavior": [2, 19], "being": [11, 19], "belong": 19, "benchmark": 19, "best": [1, 2], "better": [12, 19], "between": [10, 11, 19], "bgr": 8, "bilinear": 10, "bin_thresh": 19, "binar": [5, 9, 19], "binari": [8, 18, 19], "bit": 18, "block": [11, 19], "block_1_1": 19, "blur": 10, "bmvc": 7, "bn": 15, "bodi": [2, 19], "bool": [7, 8, 9, 10, 11], "boolean": [9, 19], "both": [5, 7, 10, 17, 19], "bottom": [9, 19], "bound": [7, 8, 9, 10, 11, 16, 17, 19], "box": [7, 8, 9, 10, 11, 16, 17, 19], "box_thresh": 19, "bright": 10, "browser": [3, 5], "build": [3, 4, 18], "built": 3, "byte": [8, 19], "c": [4, 8, 11], "c_j": 11, "cach": [3, 7, 14], "cache_sampl": 7, "call": 18, "callabl": [7, 10], "can": [3, 4, 13, 14, 15, 16, 17, 19], "capabl": [3, 12, 19], "case": [7, 11], "cf": 19, "cfg": 19, "challeng": 7, "challenge2_test_task12_imag": 7, "challenge2_test_task1_gt": 7, "challenge2_training_task12_imag": 7, "challenge2_training_task1_gt": 7, "chang": [14, 19], "channel": [2, 3, 8, 10], "channel_prior": 4, "channelshuffl": 10, "charact": [5, 7, 8, 11, 17, 19], "charactergener": [7, 17], "characterist": 2, "charg": 19, "charset": 19, "chart": 8, "check": [3, 15, 19], "checkpoint": 9, "chip": 4, "christian": 1, "ci": 3, "clarifi": 2, "clariti": 2, "class": [2, 7, 8, 10, 11, 19], "class_nam": 13, "classif": [17, 19], "classmethod": 8, "clear": 3, "clone": 4, "close": 3, "co": 15, "code": [5, 8, 16], "codecov": 3, "colab": 12, "collate_fn": 7, "collect": [8, 16], "color": 10, "colorinvers": 10, "column": 8, "com": [2, 4, 8, 9, 15], "combin": 19, "command": [3, 16], "comment": 2, "commit": 2, "common": [2, 10, 11, 18], "commun": 2, "compar": 5, "comparison": [11, 19], "competit": 7, "compil": [12, 19], "complaint": 2, "complementari": 11, "complet": 3, "compon": 19, "compos": [7, 19], "comprehens": 19, "comput": [7, 11, 18, 19], "conf_threshold": 16, "confid": [8, 19], "config": [4, 9], "configur": 9, "confus": 11, "consecut": [10, 19], "consequ": 2, "consid": [2, 3, 7, 8, 11, 19], "consist": 19, "consolid": [5, 7], "constant": 10, "construct": 2, "contact": 2, "contain": [1, 6, 7, 12, 17, 19], "content": [7, 8, 19], "context": 9, "contib": 4, "continu": 2, "contrast": 10, "contrast_factor": 10, "contrib": [4, 16], "contribut": 2, "contributor": 3, "convers": 8, "convert": [8, 10], "convolut": 9, "cool": 1, "coordin": [8, 19], "cord": [5, 7, 17, 19], "core": [11, 19], "corner": 19, "correct": 10, "correspond": [4, 8, 10, 19], "could": [2, 16], "counterpart": 11, "cover": 3, "coverag": 3, "cpu": [5, 13, 18], "creat": [1, 15], "crnn": [5, 9, 15], "crnn_mobilenet_v3_larg": [9, 15, 19], "crnn_mobilenet_v3_smal": [9, 18, 19], "crnn_vgg16_bn": [9, 13, 15, 19], "crop": [8, 9, 10, 13, 17, 19], "crop_orient": [8, 19], "crop_orientation_predictor": [9, 13], "crop_param": 13, "cuda": 18, "currenc": 7, "current": [3, 13, 19], "custom": [15, 16, 18, 19], "custom_crop_orientation_model": 13, "custom_page_orientation_model": 13, "customhook": 19, "cvit": 5, "czczup": 9, "czech": 7, "d": [7, 17], "danish": 7, "data": [5, 7, 8, 10, 11, 13, 15], "dataload": 17, "dataset": [9, 13, 19], "dataset_info": 7, "date": [13, 19], "db": 15, "db_mobilenet_v3_larg": [9, 15, 19], "db_resnet34": 19, "db_resnet50": [9, 13, 15, 19], "dbnet": [5, 9], "deal": [12, 19], "decis": 2, "decod": 8, "decode_img_as_tensor": 8, "dedic": 18, "deem": 2, "deep": [9, 19], "def": 19, "default": [4, 8, 13, 14, 19], "defer": 17, "defin": [11, 18], "degre": [8, 10, 19], "degress": 8, "delet": 3, "delimit": 19, "delta": 10, "demo": [3, 5], "demonstr": 2, "depend": [3, 4, 5, 19], "deploi": 3, "deploy": 5, "derogatori": 2, "describ": 9, "descript": 12, "design": 10, "desir": 8, "det_arch": [9, 13, 15, 18], "det_b": 19, "det_model": [13, 15, 18], "det_param": 13, "det_predictor": [13, 19], "detail": [13, 19], "detect": [1, 7, 8, 11, 12, 13, 16], "detect_languag": 9, "detect_orient": [9, 13, 19], "detection_predictor": [9, 19], "detection_task": [7, 17], "detectiondataset": [7, 17], "detectionmetr": 11, "detectionpredictor": [9, 13], "detector": [5, 9, 16], "deterior": 9, "determin": 2, "dev": [3, 14], "develop": 4, "deviat": 10, "devic": 18, "dict": [8, 11, 19], "dictionari": [8, 11], "differ": 2, "differenti": [5, 9], "digit": [5, 7, 17], "dimens": [8, 11, 19], "dimension": 10, "direct": 7, "directli": [15, 19], "directori": [3, 14], "disabl": [2, 14, 19], "disable_crop_orient": 19, "disable_page_orient": 19, "disclaim": 19, "discuss": 3, "disparag": 2, "displai": [8, 11], "display_artefact": 11, "distribut": 10, "div": 19, "divers": 2, "divid": 8, "do": [3, 4, 9], "doc": [3, 8, 16, 18, 19], "docartefact": [7, 17], "docstr": 3, "doctr": [1, 4, 13, 14, 15, 16, 17, 18, 19], "doctr_cache_dir": 14, "doctr_multiprocessing_dis": 14, "document": [1, 7, 9, 11, 12, 13, 16, 17, 18, 19], "documentbuild": 19, "documentfil": [8, 13, 15, 16, 18], "doesn": 18, "don": [13, 19], "done": 10, "download": [7, 17], "downsiz": 9, "draw": 10, "drop": 7, "drop_last": 7, "dtype": [8, 9, 10, 11, 18], "dual": [5, 7], "dummi": 15, "dummy_img": 19, "dummy_input": 18, "dure": 2, "dutch": 7, "dynam": [7, 16], "dynamic_seq_length": 7, "e": [2, 3, 4, 8, 9], "each": [5, 7, 8, 9, 10, 11, 17, 19], "eas": 3, "easi": [5, 11, 15, 18], "easili": [8, 11, 13, 15, 17, 19], "econom": 2, "edit": 2, "educ": 2, "effect": 19, "effici": [3, 5, 7, 9], "either": [11, 19], "element": [7, 8, 9, 19], "els": [3, 16], "email": 2, "empathi": 2, "en": 19, "enabl": [7, 8], "enclos": 8, "encod": [5, 7, 8, 9, 19], "encode_sequ": 7, "encount": 3, "encrypt": 8, "end": [5, 7, 9, 11], "english": [7, 17], "enough": [3, 19], "ensur": 3, "entri": 7, "environ": [2, 14], "eo": 7, "equiv": 19, "estim": 9, "etc": [8, 16], "ethnic": 2, "evalu": [17, 19], "event": 2, "everyon": 2, "everyth": [3, 19], "exact": [11, 19], "exampl": [2, 3, 5, 7, 9, 15, 19], "exchang": 18, "execut": 19, "exist": 15, "expand": 10, "expect": [8, 10, 11], "experi": 2, "explan": [2, 19], "explicit": 2, "exploit": [5, 9], "export": [8, 9, 11, 12, 16, 19], "export_as_straight_box": [9, 19], "export_as_xml": 19, "export_model_to_onnx": 18, "express": [2, 10], "extens": 8, "extern": [2, 17], "extract": [1, 5, 7], "extractor": 9, "f_": 11, "f_a": 11, "factor": 10, "fair": 2, "fairli": 2, "fals": [7, 8, 9, 10, 11, 13, 19], "faq": 2, "fascan": 15, "fast": [5, 7, 9], "fast_bas": [9, 19], "fast_smal": [9, 19], "fast_tini": [9, 19], "faster": [5, 9, 18], "fasterrcnn_mobilenet_v3_large_fpn": 9, "favorit": 19, "featur": [4, 9, 11, 12, 13, 16], "feedback": 2, "feel": [3, 15], "felix92": 15, "few": [18, 19], "figsiz": 11, "figur": [11, 16], "file": [3, 7], "final": 9, "find": [3, 17], "fine": 1, "finnish": 7, "first": [3, 7], "firsthand": 7, "fit": [9, 19], "flag": 19, "flip": 10, "float": [8, 10, 11, 18], "float32": [8, 9, 10, 18], "fn": 10, "focu": 15, "focus": [2, 7], "folder": 7, "follow": [2, 3, 4, 7, 10, 11, 13, 14, 15, 16, 19], "font": 7, "font_famili": 7, "foral": 11, "forc": 3, "forg": 4, "form": [5, 7, 19], "format": [8, 11, 13, 17, 18, 19], "forpost": [5, 7], "forum": 3, "found": 1, "fp16": 18, "frac": 11, "framework": [4, 15, 17, 19], "free": [2, 3, 15], "french": [7, 13, 15, 19], "friendli": 5, "from": [1, 2, 5, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19], "from_hub": [9, 15], "from_imag": [8, 15, 16, 18], "from_pdf": 8, "from_url": 8, "full": [7, 11, 19], "function": [7, 10, 11, 16], "funsd": [5, 7, 17, 19], "further": 17, "futur": 7, "g": [8, 9], "g_": 11, "g_x": 11, "gallagh": 1, "gamma": 10, "gaussian": 10, "gaussianblur": 10, "gaussiannois": 10, "gen": 19, "gender": 2, "gener": [3, 5, 8, 9], "generic_cyrillic_lett": 7, "geometri": [5, 8, 19], "geq": 11, "german": [7, 13, 15], "get": [18, 19], "git": 15, "github": [3, 4, 9, 15], "give": [2, 16], "given": [7, 8, 10, 11, 19], "global": 9, "go": 19, "good": 18, "googl": 3, "googlevis": 5, "gpu": [5, 16, 18], "gracefulli": 2, "graph": [5, 7, 8], "grayscal": 10, "ground": 11, "groung": 11, "group": [5, 19], "gt": 11, "gt_box": 11, "gt_label": 11, "guid": 3, "guidanc": 17, "gvision": 19, "h": [8, 9, 10], "h_": 11, "ha": [3, 7, 11, 17], "handl": [12, 17, 19], "handwrit": 7, "handwritten": 17, "harass": 2, "hardwar": 19, "harm": 2, "hat": 11, "have": [2, 3, 11, 13, 15, 17, 18, 19], "head": [9, 19], "healthi": 2, "hebrew": 7, "height": [8, 10], "hello": [11, 19], "help": 18, "here": [6, 10, 12, 16, 17, 19], "hf": 9, "hf_hub_download": 9, "high": 8, "higher": [4, 7, 19], "hindi": 7, "hindi_digit": 7, "hocr": 19, "hook": 19, "horizont": [8, 10, 19], "hous": 7, "how": [1, 3, 12, 13, 15, 17], "howev": 17, "hsv": 10, "html": [2, 3, 4, 8, 19], "http": [2, 4, 7, 8, 9, 15, 19], "hub": 9, "hue": 10, "huggingfac": 9, "hw": 7, "i": [2, 3, 7, 8, 9, 10, 11, 14, 15, 16, 17, 18], "i7": 19, "ibrahimov": 1, "ic03": [5, 7, 17], "ic13": [5, 7, 17], "icdar": [5, 7], "icdar2019": 7, "id": 19, "ident": 2, "identifi": 5, "iiit": [5, 7], "iiit5k": [7, 17], "iiithw": [5, 7, 17], "imag": [1, 5, 7, 8, 9, 10, 11, 15, 16, 17, 19], "imagenet": 9, "imageri": 2, "images_90k_norm": 7, "img": [7, 10, 17, 18], "img_cont": 8, "img_fold": [7, 17], "img_path": 8, "img_transform": 7, "imgur5k": [5, 7, 17], "imgur5k_annot": 7, "imlist": 7, "impact": 2, "implement": [7, 8, 9, 10, 11, 19], "import": [7, 8, 9, 10, 11, 13, 15, 16, 17, 18, 19], "improv": 9, "inappropri": 2, "incid": 2, "includ": [2, 7, 17, 18], "inclus": 2, "increas": 10, "independ": 10, "index": [3, 8], "indic": 11, "individu": 2, "infer": [5, 9, 10, 16, 19], "inform": [1, 2, 3, 5, 7, 17], "input": [3, 8, 9, 10, 18, 19], "input_crop": 9, "input_pag": [9, 11, 19], "input_shap": 18, "input_tensor": 9, "inspir": [2, 10], "instal": [15, 16, 18], "instanc": [2, 19], "instanti": [9, 19], "instead": [7, 8, 9], "insult": 2, "int": [7, 8, 9, 10], "int64": 11, "integ": 11, "integr": [1, 5, 15, 17], "intel": 19, "interact": [2, 8, 11], "interfac": [15, 18], "interoper": 18, "interpol": 10, "interpret": [7, 8], "intersect": 11, "invert": 10, "investig": 2, "invis": 2, "involv": [2, 19], "io": [13, 15, 16, 18], "iou": 11, "iou_thresh": 11, "iou_threshold": 16, "irregular": [5, 9, 17], "isn": 7, "issu": [2, 3, 15], "italian": 7, "iter": [7, 10, 17, 19], "its": [8, 9, 10, 11, 17, 19], "itself": [9, 15], "j": 11, "jame": 1, "job": 3, "join": 3, "jpeg": 10, "jpegqual": 10, "jpg": [7, 8, 15, 18], "json": [7, 17, 19], "json_output": 19, "jump": 3, "just": 2, "kei": [5, 7], "kera": [9, 18], "kernel": [5, 9, 10], "kernel_shap": 10, "keywoard": 9, "keyword": [7, 8, 9, 11], "kie": [9, 13], "kie_predictor": [9, 13], "kiepredictor": 9, "kind": 2, "know": [3, 18], "kwarg": [7, 8, 9, 11], "l": 11, "l_j": 11, "label": [7, 11, 16, 17], "label_fil": [7, 17], "label_fold": 7, "label_path": [7, 17], "labels_path": [7, 17], "ladder": 2, "lambda": 10, "lambdatransform": 10, "lang": 19, "languag": [2, 5, 7, 8, 9, 15, 19], "larg": [9, 15], "largest": 11, "last": [4, 7], "latenc": 9, "later": 3, "latest": 19, "latin": 7, "layer": 18, "layout": 19, "lead": 2, "leader": 2, "learn": [2, 5, 9, 18, 19], "least": 4, "left": [11, 19], "legacy_french": 7, "length": [7, 19], "less": [18, 19], "level": [2, 7, 11, 19], "leverag": 12, "lf": 15, "librari": [3, 4, 12, 13], "light": 5, "lightweight": 18, "like": 2, "limits_": 11, "line": [5, 9, 11, 19], "line_1_1": 19, "link": 13, "linknet": [5, 9], "linknet_resnet18": [9, 13, 18, 19], "linknet_resnet34": [9, 18, 19], "linknet_resnet50": [9, 19], "list": [7, 8, 10, 11, 15], "ll": 11, "load": [5, 7, 9, 16, 18], "load_state_dict": 13, "load_weight": 13, "loc_pr": 19, "local": [3, 5, 7, 9, 11, 17, 19], "localis": 7, "localizationconfus": 11, "locat": [3, 8, 19], "login": 9, "login_to_hub": [9, 15], "logo": [8, 16, 17], "love": 15, "lower": [10, 11, 19], "m": [3, 11, 19], "m1": 4, "macbook": 4, "machin": 18, "made": 5, "magc_resnet31": 9, "mai": [2, 3], "mail": 2, "main": 12, "maintain": 5, "mainten": 3, "make": [2, 3, 11, 13, 14, 15, 18, 19], "mani": [17, 19], "manipul": 19, "map": [7, 9], "map_loc": 13, "master": [5, 9, 19], "match": [11, 19], "mathcal": 11, "matplotlib": [8, 11], "max": [7, 10, 11], "max_angl": 10, "max_area": 10, "max_char": [7, 17], "max_delta": 10, "max_gain": 10, "max_gamma": 10, "max_qual": 10, "max_ratio": 10, "maximum": [7, 10], "maxval": [9, 10], "mbox": 11, "mean": [10, 11, 13], "meaniou": 11, "meant": [8, 18], "measur": 19, "media": 2, "median": 9, "meet": 13, "member": 2, "memori": [14, 18], "mention": 19, "merg": 7, "messag": 3, "meta": 19, "metadata": 18, "metal": 4, "method": [8, 10, 19], "metric": [11, 19], "middl": 19, "might": [18, 19], "min": 10, "min_area": 10, "min_char": [7, 17], "min_gain": 10, "min_gamma": 10, "min_qual": 10, "min_ratio": 10, "min_val": 10, "minde": [1, 2, 4, 5, 9], "minim": [3, 5], "minimalist": [5, 9], "minimum": [4, 7, 10, 11, 19], "minval": 10, "miss": 4, "mistak": 2, "mixed_float16": 18, "mixed_precis": 18, "mjsynth": [5, 7, 17], "mnt": 7, "mobilenet": [9, 15], "mobilenet_v3_larg": 9, "mobilenet_v3_large_r": 9, "mobilenet_v3_smal": [9, 13], "mobilenet_v3_small_crop_orient": [9, 13], "mobilenet_v3_small_page_orient": [9, 13], "mobilenet_v3_small_r": 9, "mobilenetv3": 9, "modal": [5, 7], "mode": 4, "model": [7, 11, 14, 16, 17], "model_nam": [9, 15, 18], "model_path": [16, 18], "moder": 2, "modif": 3, "modifi": [9, 14, 19], "modul": [4, 8, 9, 10, 11, 19], "more": [3, 17, 19], "moscardi": 1, "most": 19, "mozilla": 2, "multi": [5, 9], "multilingu": [7, 15], "multipl": [7, 8, 10, 19], "multipli": 10, "multiprocess": 14, "my": 9, "my_awesome_model": 15, "my_hook": 19, "n": [7, 11], "name": [7, 9, 18, 19], "nation": 2, "natur": [2, 5, 7], "ndarrai": [7, 8, 10, 11], "necessari": [4, 13, 14], "need": [3, 4, 7, 11, 13, 14, 15, 16, 19], "neg": 10, "nest": 19, "netraj": 1, "network": [5, 7, 9, 18], "neural": [5, 7, 9, 18], "new": [3, 11], "next": [7, 17], "nois": 10, "noisi": [5, 7], "non": [5, 7, 8, 9, 10, 11], "none": [7, 8, 9, 10, 11, 19], "normal": [9, 10], "norwegian": 7, "note": [0, 3, 7, 9, 13, 15, 16, 18], "now": 3, "np": [9, 10, 11, 19], "num_output_channel": 10, "num_sampl": [7, 17], "number": [7, 9, 10, 11, 19], "numpi": [8, 9, 11, 19], "o": 4, "obb": 16, "obj_detect": 15, "object": [7, 8, 11, 16, 19], "objectness_scor": [8, 19], "oblig": 2, "obtain": 19, "occupi": 18, "ocr": [1, 5, 7, 9, 11, 15], "ocr_carea": 19, "ocr_db_crnn": 11, "ocr_lin": 19, "ocr_pag": 19, "ocr_par": 19, "ocr_predictor": [9, 13, 15, 18, 19], "ocrdataset": [7, 17], "ocrmetr": 11, "ocrpredictor": [9, 13], "ocrx_word": 19, "offens": 2, "offici": [2, 9], "offlin": 2, "offset": 10, "onc": 19, "one": [3, 7, 9, 10, 13, 15, 19], "oneof": 10, "ones": [7, 11], "onli": [3, 9, 10, 11, 13, 15, 17, 18, 19], "onlin": 2, "onnx": 16, "onnxruntim": [16, 18], "onnxtr": 18, "opac": 10, "opacity_rang": 10, "open": [1, 2, 3, 15, 18], "opinion": 2, "optic": [5, 19], "optim": [5, 19], "option": [7, 9, 13], "order": [3, 7, 8, 10], "org": [2, 7, 9, 19], "organ": 8, "orient": [2, 8, 9, 12, 16, 19], "orientationpredictor": 9, "other": [2, 3], "otherwis": [2, 8, 11], "our": [1, 3, 9, 19], "out": [3, 9, 10, 11, 19], "outpout": 19, "output": [8, 10, 18], "output_s": [8, 10], "outsid": 14, "over": [7, 11, 19], "overal": [2, 9], "overlai": 8, "overview": 16, "overwrit": 13, "overwritten": 15, "own": 5, "p": [10, 19], "packag": [3, 5, 11, 14, 16, 17, 18], "pad": [7, 9, 10, 19], "page": [4, 7, 9, 11, 13, 19], "page1": 8, "page2": 8, "page_1": 19, "page_idx": [8, 19], "page_orientation_predictor": [9, 13], "page_param": 13, "pair": 11, "paper": 9, "par_1_1": 19, "paragraph": 19, "paragraph_break": 19, "parallel": 9, "param": [10, 19], "paramet": [5, 8, 9, 18], "pars": [5, 7], "parseq": [5, 9, 15, 18, 19], "part": [7, 10, 19], "parti": 4, "partial": 19, "particip": 2, "pass": [7, 8, 9, 13, 19], "password": 8, "patch": [9, 11], "path": [7, 8, 16, 17, 18], "path_to_checkpoint": 13, "path_to_custom_model": 18, "path_to_pt": 13, "patil": 1, "pattern": 2, "pdf": [8, 9, 12], "pdfpage": 8, "peopl": 2, "per": [10, 19], "perform": [5, 8, 9, 10, 11, 14, 18, 19], "period": 2, "permiss": 2, "permut": [5, 9], "persian_lett": 7, "person": [2, 17], "phase": 19, "photo": 17, "physic": [2, 8], "pick": 10, "pictur": 8, "pip": [3, 4, 16, 18], "pipelin": 19, "pixel": [8, 10, 19], "pleas": 3, "plot": 11, "plt": 11, "plug": 15, "plugin": 4, "png": 8, "point": 18, "polici": 14, "polish": 7, "polit": 2, "polygon": [7, 11, 19], "pool": 9, "portugues": 7, "posit": [2, 11], "possibl": [3, 11, 15, 19], "post": [2, 19], "postprocessor": 19, "potenti": 9, "power": 5, "ppageno": 19, "pre": [3, 9, 18], "precis": [11, 19], "pred": 11, "pred_box": 11, "pred_label": 11, "predefin": 17, "predict": [8, 9, 11, 19], "predictor": [5, 8, 9, 12, 13, 15, 18], "prefer": 17, "preinstal": 4, "preprocessor": [13, 19], "prerequisit": 15, "present": 12, "preserv": [9, 10, 19], "preserve_aspect_ratio": [8, 9, 10, 13, 19], "pretrain": [5, 9, 11, 13, 18, 19], "pretrained_backbon": [9, 13], "print": 19, "prior": 7, "privaci": 2, "privat": 2, "probabl": [1, 10], "problem": 3, "procedur": 10, "process": [3, 5, 8, 9, 13, 19], "processor": 19, "produc": [12, 19], "product": 18, "profession": 2, "project": [3, 17], "promptli": 2, "proper": 3, "properli": 7, "provid": [2, 3, 5, 15, 16, 17, 19], "public": [2, 5], "publicli": 19, "publish": 2, "pull": 15, "punctuat": 7, "pure": 7, "purpos": 3, "push_to_hf_hub": [9, 15], "py": 15, "pypdfium2": [4, 8], "pyplot": [8, 11], "python": [1, 3, 16], "python3": 15, "pytorch": [4, 5, 9, 10, 13, 15, 18, 19], "q": 3, "qr": [8, 16], "qr_code": 17, "qualiti": 10, "question": 2, "quickli": 5, "quicktour": 12, "r": 19, "race": 2, "ramdisk": 7, "rand": [9, 10, 11, 18, 19], "random": [9, 10, 11, 19], "randomappli": 10, "randombright": 10, "randomcontrast": 10, "randomcrop": 10, "randomgamma": 10, "randomhorizontalflip": 10, "randomhu": 10, "randomjpegqu": 10, "randomli": 10, "randomres": 10, "randomrot": 10, "randomsatur": 10, "randomshadow": 10, "rang": 10, "rassi": 15, "ratio": [9, 10, 19], "raw": [8, 11], "re": 18, "read": [5, 7, 9], "read_html": 8, "read_img_as_numpi": 8, "read_img_as_tensor": 8, "read_pdf": 8, "readi": 18, "real": [1, 5, 9, 10], "realli": 1, "reason": [2, 5, 7], "rebuild": 3, "rebuilt": 3, "recal": [11, 19], "receipt": [5, 7, 19], "reco_arch": [9, 13, 15, 18], "reco_b": 19, "reco_model": [13, 15, 18], "reco_param": 13, "reco_predictor": 13, "recogn": 19, "recognit": [7, 11, 12, 13], "recognition_predictor": [9, 19], "recognition_task": [7, 17], "recognitiondataset": [7, 17], "recognitionpredictor": [9, 13], "rectangular": 9, "reduc": [4, 10], "refer": [3, 4, 13, 15, 16, 17, 19], "regardless": 2, "region": 19, "regroup": 11, "regular": 17, "reject": 2, "rel": [8, 10, 11, 19], "relat": 8, "releas": [0, 4], "relev": 16, "religion": 2, "remov": 2, "render": [8, 19], "repo": 9, "repo_id": [9, 15], "report": 2, "repositori": [7, 9, 15], "repres": [2, 18, 19], "represent": [5, 9], "request": [2, 15], "requir": [4, 10, 18], "research": 5, "residu": 9, "resiz": [10, 19], "resnet": 9, "resnet18": [9, 15], "resnet31": 9, "resnet34": 9, "resnet50": [9, 15], "resolv": 8, "resolve_block": 19, "resolve_lin": 19, "resourc": 17, "respect": 2, "rest": [3, 10, 11], "restrict": 14, "result": [3, 7, 8, 12, 15, 18, 19], "return": 19, "reusabl": 19, "review": 2, "rgb": [8, 10], "rgb_mode": 8, "rgb_output": 8, "right": [2, 9, 11], "roboflow": 1, "robust": [5, 7], "root": 7, "rotat": [7, 8, 9, 10, 11, 12, 13, 17, 19], "run": [3, 4, 9], "same": [3, 8, 11, 17, 18, 19], "sampl": [7, 9, 17, 19], "sample_transform": 7, "sanjin": 1, "sar": [5, 9], "sar_resnet31": [9, 19], "satur": 10, "save": [9, 17], "scale": [8, 9, 10, 11], "scale_rang": 10, "scan": [5, 7], "scene": [5, 7, 9], "score": [8, 11], "script": [3, 17], "seamless": 5, "seamlessli": [5, 19], "search": [1, 9], "searchabl": 12, "sec": 19, "second": 19, "section": [1, 13, 15, 16, 18, 19], "secur": [2, 14], "see": [2, 3], "seen": 19, "segment": [5, 9, 19], "self": 19, "semant": [5, 9], "send": 19, "sens": 11, "sensit": 17, "separ": 19, "sequenc": [5, 7, 8, 9, 11, 19], "sequenti": [10, 19], "seri": 2, "seriou": 2, "set": [2, 4, 7, 9, 11, 14, 16, 19], "set_global_polici": 18, "sever": [8, 10, 19], "sex": 2, "sexual": 2, "shade": 10, "shape": [5, 8, 9, 10, 11, 19], "share": [14, 17], "shift": 10, "shm": 14, "should": [3, 7, 8, 10, 11], "show": [5, 8, 9, 11, 13, 15, 16], "showcas": [3, 12], "shuffl": [7, 10], "side": 11, "signatur": 8, "signific": 17, "simpl": [5, 9, 18], "simpler": 9, "sinc": [7, 17], "singl": [2, 3, 5, 7], "single_img_doc": 18, "size": [2, 7, 8, 10, 16, 19], "skew": 19, "slack": 3, "slightli": 9, "small": [3, 9, 19], "smallest": 8, "snapshot_download": 9, "snippet": 19, "so": [3, 4, 7, 9, 15, 17], "social": 2, "socio": 2, "some": [1, 4, 12, 15, 17], "someth": 3, "somewher": 3, "sort": 2, "sourc": [1, 7, 8, 9, 10, 11, 15], "space": [2, 19], "span": 19, "spanish": 7, "spatial": [5, 7, 8], "specif": [3, 4, 11, 13, 17, 19], "specifi": [2, 7, 8], "speed": [5, 9, 19], "sphinx": 3, "sroie": [5, 7, 17], "stabl": 4, "stackoverflow": 3, "stage": 5, "standalon": 12, "standard": 10, "start": 7, "state": [1, 5, 11, 16], "static": 11, "statist": 1, "statu": 2, "std": [10, 13], "step": 14, "still": 19, "str": [7, 8, 9, 10, 11], "straight": [7, 9, 17, 19], "straighten": 19, "straighten_pag": [9, 13, 19], "straigten_pag": 13, "stream": 8, "street": [5, 7], "strict": 4, "strictli": 11, "string": [7, 8, 11, 19], "strive": 4, "strong": [5, 9], "structur": [18, 19], "subset": [7, 19], "suggest": [3, 15], "sum": 11, "summari": 11, "support": [4, 13, 16, 18, 19], "sustain": 2, "svhn": [5, 7, 17], "svt": [7, 17], "swedish": 7, "symmetr": [9, 10, 19], "symmetric_pad": [9, 10, 19], "synthet": 5, "synthtext": [5, 7, 17], "system": 19, "t": [3, 7, 13, 18, 19], "tabl": [15, 16, 17], "take": [2, 7, 19], "target": [7, 8, 10, 11, 17], "target_s": 7, "task": [5, 7, 9, 15, 17, 19], "task2": 7, "team": 4, "techminde": 4, "templat": [3, 5], "tensor": [7, 8, 10, 19], "tensorflow": [4, 5, 8, 9, 10, 13, 15, 18, 19], "tensorspec": 18, "term": 2, "test": [7, 17], "test_set": 7, "text": [1, 7, 8, 9, 11, 17], "text_output": 19, "textmatch": 11, "textnet": 9, "textnet_bas": 9, "textnet_smal": 9, "textnet_tini": 9, "textract": [5, 19], "textstylebrush": [5, 7], "textual": [5, 7, 8, 9, 19], "tf": [4, 8, 9, 10, 15, 18], "than": [3, 11, 15], "thank": 3, "thei": [2, 11], "them": [7, 19], "thi": [1, 2, 3, 4, 6, 7, 10, 11, 13, 14, 15, 17, 18, 19], "thing": [18, 19], "third": 4, "those": [2, 8, 19], "threaten": 2, "threshold": 19, "through": [2, 10, 16, 17], "tilman": 15, "time": [1, 2, 5, 9, 11, 17], "tini": 9, "titl": [8, 19], "tm": 19, "tmp": 14, "togeth": [3, 8], "tograi": 10, "tool": [1, 17], "top": [11, 18, 19], "topic": 3, "torch": [4, 10, 13, 15, 18], "torchvis": 10, "total": 13, "toward": [2, 4], "train": [3, 7, 9, 10, 15, 16, 17, 18, 19], "train_it": [7, 17], "train_load": [7, 17], "train_pytorch": 15, "train_set": [7, 17], "train_tensorflow": 15, "trainabl": [5, 9], "tranform": 10, "transcrib": 19, "transfer": [5, 7], "transfo": 10, "transform": [5, 7, 9], "translat": 2, "troll": 2, "true": [7, 8, 9, 10, 11, 13, 14, 15, 17, 18, 19], "truth": 11, "tune": [1, 18], "tupl": [7, 8, 10, 11], "two": [8, 14], "txt": 7, "type": [8, 11, 15, 18, 19], "typic": 19, "u": [2, 3], "ucsd": 7, "udac": 3, "uint8": [8, 9, 11, 19], "ukrainian": 7, "unaccept": 2, "underli": [17, 19], "underneath": 8, "understand": [5, 7, 19], "uniform": [9, 10], "uniformli": 10, "uninterrupt": [8, 19], "union": 11, "unit": 1, "unittest": 3, "unlock": 8, "unoffici": 9, "unprofession": 2, "unsolicit": 2, "unsupervis": 5, "unwelcom": 2, "up": [9, 19], "updat": 11, "upgrad": 3, "upper": [7, 10], "uppercas": 17, "url": 8, "us": [2, 3, 4, 7, 9, 11, 12, 13, 14, 15, 16, 19], "usabl": 19, "usag": [14, 18], "use_polygon": [7, 11, 17], "useabl": 19, "user": [5, 8, 12], "utf": 19, "util": 18, "v1": 15, "v3": [9, 15, 19], "valid": 17, "valu": [3, 8, 10, 19], "valuabl": 5, "variabl": 14, "varieti": 7, "veri": 9, "verma": 1, "version": [2, 3, 4, 18, 19], "vgg": 9, "vgg16": 15, "vgg16_bn_r": 9, "via": 2, "video": 1, "vietnames": 7, "view": [5, 7], "viewpoint": 2, "violat": 2, "visibl": 2, "vision": [5, 7, 9], "visiondataset": 7, "visiontransform": 9, "visual": [4, 5, 16], "visualize_pag": 11, "vit_": 9, "vit_b": 9, "vitstr": [5, 9, 18], "vitstr_bas": [9, 19], "vitstr_smal": [9, 13, 18, 19], "viz": 4, "vocab": [13, 15, 17, 18, 19], "vocabulari": [7, 13, 15], "w": [8, 9, 10, 11], "w3": 19, "wa": 2, "wai": [2, 5, 17], "want": [3, 18, 19], "warmup": 19, "wasn": 3, "we": [1, 2, 3, 4, 5, 8, 10, 13, 15, 17, 18, 19], "weasyprint": 8, "web": [3, 8], "websit": 7, "welcom": 2, "well": [1, 2, 18], "were": [2, 8, 19], "what": [1, 2], "when": [2, 3, 9], "whenev": 3, "where": [3, 8, 10, 11], "whether": [3, 7, 8, 10, 11, 17, 19], "which": [2, 9, 14, 16, 17, 19], "whichev": 4, "while": [10, 19], "why": 2, "width": [8, 10], "wiki": 2, "wildreceipt": [5, 7, 17], "window": [9, 11], "wish": 3, "within": 2, "without": [2, 7, 9], "wonder": 3, "word": [5, 7, 9, 11, 19], "word_1_1": 19, "word_1_2": 19, "word_1_3": 19, "wordgener": [7, 17], "words_onli": 11, "work": [1, 13, 14, 19], "workflow": 3, "worklow": 3, "world": [11, 19], "worth": 9, "wrap": 19, "wrapper": [7, 10], "write": 14, "written": [2, 8], "www": [2, 8, 19], "x": [8, 10, 11], "x_ascend": 19, "x_descend": 19, "x_i": 11, "x_size": 19, "x_wconf": 19, "xhtml": 19, "xmax": 8, "xmin": 8, "xml": 19, "xml_bytes_str": 19, "xml_element": 19, "xml_output": 19, "xmln": 19, "y": 11, "y_i": 11, "y_j": 11, "yet": 16, "ymax": 8, "ymin": 8, "yolov8": 16, "you": [3, 4, 7, 8, 9, 13, 14, 15, 16, 17, 18, 19], "your": [3, 5, 8, 11, 19], "yoursit": 8, "yugesh": 1, "zero": [10, 11], "zoo": 13, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 7, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 7, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": 7, "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 7, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 7, "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 7, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 7, "\u00e4\u00f6\u00e4\u00f6": 7, "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 7, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": 7, "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": 7, "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": 7, "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": 7, "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": 7, "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0905": 7, "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": 7, "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": 7, "\u0950": 7, "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": 7, "\u09bd": 7, "\u09ce": 7, "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": 7}, "titles": ["Changelog", "Community resources", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.contrib", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Integrate contributions into your pipeline", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 3, "0": 0, "01": 0, "02": 0, "03": 0, "04": 0, "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 2], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 2], "2021": 0, "2022": 0, "2023": 0, "2024": 0, "21": 0, "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 2], "31": 0, "4": [0, 2], "5": 0, "6": 0, "7": 0, "8": 0, "9": 0, "advanc": 19, "approach": 19, "architectur": 19, "arg": [7, 8, 9, 10, 11], "artefact": 8, "artefactdetect": 16, "attribut": 2, "avail": [16, 17, 19], "aw": 14, "ban": 2, "block": 8, "bug": 3, "changelog": 0, "choos": [17, 19], "classif": [9, 13, 15], "code": [2, 3], "codebas": 3, "commit": 3, "commun": [1, 15], "compos": 10, "conda": 4, "conduct": 2, "connect": 3, "continu": 3, "contrib": 6, "contribut": [3, 6, 16], "contributor": 2, "convent": 15, "correct": 2, "coven": 2, "custom": [7, 13], "data": 17, "dataload": 7, "dataset": [5, 7, 17], "detect": [5, 9, 15, 17, 19], "develop": 3, "do": 19, "doctr": [3, 5, 6, 7, 8, 9, 10, 11, 12], "document": [3, 5, 8], "end": 19, "enforc": 2, "evalu": 11, "export": 18, "factori": 9, "featur": [3, 5], "feedback": 3, "file": 8, "from": 15, "gener": [7, 17], "git": 4, "guidelin": 2, "half": 18, "hub": 15, "huggingfac": 15, "i": 19, "infer": 18, "instal": [3, 4], "integr": [3, 16], "io": 8, "lambda": 14, "let": 3, "line": 8, "linux": 4, "load": [13, 15, 17], "loader": 7, "main": 5, "mode": 3, "model": [5, 9, 13, 15, 18, 19], "modifi": 3, "modul": [6, 16], "name": 15, "notebook": 12, "object": 17, "ocr": [17, 19], "onli": 4, "onnx": 18, "optim": 18, "option": 19, "orient": 13, "our": 2, "output": 19, "own": [13, 17], "packag": 4, "page": 8, "perman": 2, "pipelin": 16, "pledg": 2, "precis": 18, "predictor": 19, "prepar": 18, "prerequisit": 4, "pretrain": 15, "push": 15, "python": 4, "qualiti": 3, "question": 3, "read": 8, "readi": 17, "recognit": [5, 9, 15, 17, 19], "report": 3, "request": 3, "resourc": 1, "respons": 2, "return": [7, 8, 9, 11], "right": 19, "scope": 2, "share": 15, "should": 19, "stage": 19, "standard": 2, "structur": [3, 8], "style": 3, "support": [5, 6, 7, 10], "synthet": [7, 17], "task": 11, "temporari": 2, "test": 3, "text": [5, 19], "train": 13, "transform": 10, "two": 19, "unit": 3, "us": [17, 18], "util": 11, "v0": 0, "verif": 3, "via": 4, "visual": 11, "vocab": 7, "warn": 2, "what": 19, "word": 8, "your": [13, 15, 16, 17, 18], "zoo": [5, 9]}}) \ No newline at end of file +Search.setIndex({"alltitles": {"1. Correction": [[2, "correction"]], "2. Warning": [[2, "warning"]], "3. Temporary Ban": [[2, "temporary-ban"]], "4. Permanent Ban": [[2, "permanent-ban"]], "AWS Lambda": [[14, null]], "Advanced options": [[19, "advanced-options"]], "Args:": [[7, "args"], [7, "id4"], [7, "id7"], [7, "id10"], [7, "id13"], [7, "id16"], [7, "id19"], [7, "id22"], [7, "id25"], [7, "id29"], [7, "id32"], [7, "id37"], [7, "id40"], [7, "id46"], [7, "id49"], [7, "id50"], [7, "id51"], [7, "id54"], [7, "id57"], [7, "id60"], [7, "id61"], [8, "args"], [8, "id2"], [8, "id3"], [8, "id4"], [8, "id5"], [8, "id6"], [8, "id7"], [8, "id10"], [8, "id12"], [8, "id14"], [8, "id16"], [8, "id20"], [8, "id24"], [8, "id28"], [9, "args"], [9, "id3"], [9, "id8"], [9, "id13"], [9, "id17"], [9, "id21"], [9, "id26"], [9, "id31"], [9, "id36"], [9, "id41"], [9, "id46"], [9, "id50"], [9, "id54"], [9, "id59"], [9, "id63"], [9, "id68"], [9, "id73"], [9, "id77"], [9, "id81"], [9, "id85"], [9, "id90"], [9, "id95"], [9, "id99"], [9, "id104"], [9, "id109"], [9, "id114"], [9, "id119"], [9, "id123"], [9, "id127"], [9, "id132"], [9, "id137"], [9, "id142"], [9, "id146"], [9, "id150"], [9, "id155"], [9, "id159"], [9, "id163"], [9, "id167"], [9, "id169"], [9, "id171"], [9, "id173"], [10, "args"], [10, "id1"], [10, "id2"], [10, "id3"], [10, "id4"], [10, "id5"], [10, "id6"], [10, "id7"], [10, "id8"], [10, "id9"], [10, "id10"], [10, "id11"], [10, "id12"], [10, "id13"], [10, "id14"], [10, "id15"], [10, "id16"], [10, "id17"], [10, "id18"], [10, "id19"], [11, "args"], [11, "id3"], [11, "id4"], [11, "id5"], [11, "id6"], [11, "id7"], [11, "id8"], [11, "id9"]], "Artefact": [[8, "artefact"]], "ArtefactDetection": [[16, "artefactdetection"]], "Attribution": [[2, "attribution"]], "Available Datasets": [[17, "available-datasets"]], "Available architectures": [[19, "available-architectures"], [19, "id1"], [19, "id2"]], "Available contribution modules": [[16, "available-contribution-modules"]], "Block": [[8, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[17, null]], "Choosing the right model": [[19, null]], "Classification": [[15, "classification"]], "Code quality": [[3, "code-quality"]], "Code style verification": [[3, "code-style-verification"]], "Codebase structure": [[3, "codebase-structure"]], "Commits": [[3, "commits"]], "Community resources": [[1, null]], "Composing transformations": [[10, "composing-transformations"]], "Continuous Integration": [[3, "continuous-integration"]], "Contributing to docTR": [[3, null]], "Contributor Covenant Code of Conduct": [[2, null]], "Custom dataset loader": [[7, "custom-dataset-loader"]], "Custom orientation classification models": [[13, "custom-orientation-classification-models"]], "Data Loading": [[17, "data-loading"]], "Dataloader": [[7, "dataloader"]], "Detection": [[15, "detection"], [17, "detection"]], "Detection predictors": [[19, "detection-predictors"]], "Developer mode installation": [[3, "developer-mode-installation"]], "Developing docTR": [[3, "developing-doctr"]], "Document": [[8, "document"]], "Document structure": [[8, "document-structure"]], "End-to-End OCR": [[19, "end-to-end-ocr"]], "Enforcement": [[2, "enforcement"]], "Enforcement Guidelines": [[2, "enforcement-guidelines"]], "Enforcement Responsibilities": [[2, "enforcement-responsibilities"]], "Export to ONNX": [[18, "export-to-onnx"]], "Feature requests & bug report": [[3, "feature-requests-bug-report"]], "Feedback": [[3, "feedback"]], "File reading": [[8, "file-reading"]], "Half-precision": [[18, "half-precision"]], "Installation": [[4, null]], "Integrate contributions into your pipeline": [[16, null]], "Let\u2019s connect": [[3, "let-s-connect"]], "Line": [[8, "line"]], "Loading from Huggingface Hub": [[15, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[13, "loading-your-custom-trained-model"]], "Loading your custom trained orientation classification model": [[13, "loading-your-custom-trained-orientation-classification-model"]], "Main Features": [[5, "main-features"]], "Model optimization": [[18, "model-optimization"]], "Model zoo": [[5, "model-zoo"]], "Modifying the documentation": [[3, "modifying-the-documentation"]], "Naming conventions": [[15, "naming-conventions"]], "OCR": [[17, "ocr"]], "Object Detection": [[17, "object-detection"]], "Our Pledge": [[2, "our-pledge"]], "Our Standards": [[2, "our-standards"]], "Page": [[8, "page"]], "Preparing your model for inference": [[18, null]], "Prerequisites": [[4, "prerequisites"]], "Pretrained community models": [[15, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[15, "pushing-to-the-huggingface-hub"]], "Questions": [[3, "questions"]], "Recognition": [[15, "recognition"], [17, "recognition"]], "Recognition predictors": [[19, "recognition-predictors"]], "Returns:": [[7, "returns"], [8, "returns"], [8, "id11"], [8, "id13"], [8, "id15"], [8, "id19"], [8, "id23"], [8, "id27"], [8, "id31"], [9, "returns"], [9, "id6"], [9, "id11"], [9, "id16"], [9, "id20"], [9, "id24"], [9, "id29"], [9, "id34"], [9, "id39"], [9, "id44"], [9, "id49"], [9, "id53"], [9, "id57"], [9, "id62"], [9, "id66"], [9, "id71"], [9, "id76"], [9, "id80"], [9, "id84"], [9, "id88"], [9, "id93"], [9, "id98"], [9, "id102"], [9, "id107"], [9, "id112"], [9, "id117"], [9, "id122"], [9, "id126"], [9, "id130"], [9, "id135"], [9, "id140"], [9, "id145"], [9, "id149"], [9, "id153"], [9, "id158"], [9, "id162"], [9, "id166"], [9, "id168"], [9, "id170"], [9, "id172"], [11, "returns"]], "Scope": [[2, "scope"]], "Share your model with the community": [[15, null]], "Supported Vocabs": [[7, "supported-vocabs"]], "Supported contribution modules": [[6, "supported-contribution-modules"]], "Supported datasets": [[5, "supported-datasets"]], "Supported transformations": [[10, "supported-transformations"]], "Synthetic dataset generator": [[7, "synthetic-dataset-generator"], [17, "synthetic-dataset-generator"]], "Task evaluation": [[11, "task-evaluation"]], "Text Detection": [[19, "text-detection"]], "Text Recognition": [[19, "text-recognition"]], "Text detection models": [[5, "text-detection-models"]], "Text recognition models": [[5, "text-recognition-models"]], "Train your own model": [[13, null]], "Two-stage approaches": [[19, "two-stage-approaches"]], "Unit tests": [[3, "unit-tests"]], "Use your own datasets": [[17, "use-your-own-datasets"]], "Using your ONNX exported model": [[18, "using-your-onnx-exported-model"]], "Via Conda (Only for Linux)": [[4, "via-conda-only-for-linux"]], "Via Git": [[4, "via-git"]], "Via Python Package": [[4, "via-python-package"]], "Visualization": [[11, "visualization"]], "What should I do with the output?": [[19, "what-should-i-do-with-the-output"]], "Word": [[8, "word"]], "docTR Notebooks": [[12, null]], "docTR Vocabs": [[7, "id62"]], "docTR: Document Text Recognition": [[5, null]], "doctr.contrib": [[6, null]], "doctr.datasets": [[7, null], [7, "datasets"]], "doctr.io": [[8, null]], "doctr.models": [[9, null]], "doctr.models.classification": [[9, "doctr-models-classification"]], "doctr.models.detection": [[9, "doctr-models-detection"]], "doctr.models.factory": [[9, "doctr-models-factory"]], "doctr.models.recognition": [[9, "doctr-models-recognition"]], "doctr.models.zoo": [[9, "doctr-models-zoo"]], "doctr.transforms": [[10, null]], "doctr.utils": [[11, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.10.0 (2024-10-21)": [[0, "v0-10-0-2024-10-21"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]], "v0.7.0 (2023-09-09)": [[0, "v0-7-0-2023-09-09"]], "v0.8.0 (2024-02-28)": [[0, "v0-8-0-2024-02-28"]], "v0.8.1 (2024-03-04)": [[0, "v0-8-1-2024-03-04"]], "v0.9.0 (2024-08-08)": [[0, "v0-9-0-2024-08-08"]]}, "docnames": ["changelog", "community/resources", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/contrib", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_contrib_modules", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "community/resources.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/contrib.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_contrib_modules.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[8, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[8, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[10, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[7, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[10, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[10, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[7, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[7, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[9, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[7, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[7, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[8, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[8, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[7, "doctr.datasets.encode_sequences", false]], "fast_base() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_base", false]], "fast_small() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_small", false]], "fast_tiny() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_tiny", false]], "from_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[7, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[10, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[10, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[7, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[7, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[7, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[7, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[7, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[9, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[10, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[8, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet34() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[7, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_crop_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_crop_orientation", false]], "mobilenet_v3_small_page_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_page_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[10, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[9, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[7, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[10, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[8, "doctr.io.Page", false]], "page_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.page_orientation_predictor", false]], "parseq() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[10, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[10, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[10, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[10, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[10, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[10, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[10, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[10, "doctr.transforms.RandomJpegQuality", false]], "randomresize (class in doctr.transforms)": [[10, "doctr.transforms.RandomResize", false]], "randomrotate (class in doctr.transforms)": [[10, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[10, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[10, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[8, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[8, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[8, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[7, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[10, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[8, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[8, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[7, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[7, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[7, "doctr.datasets.SVT", false]], "synthtext (class in doctr.datasets)": [[7, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.TextMatch", false]], "textnet_base() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_base", false]], "textnet_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_small", false]], "textnet_tiny() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_tiny", false]], "togray (class in doctr.transforms)": [[10, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[11, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_small", false]], "wildreceipt (class in doctr.datasets)": [[7, "doctr.datasets.WILDRECEIPT", false]], "word (class in doctr.io)": [[8, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[7, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[7, 0, 1, "", "CORD"], [7, 0, 1, "", "CharacterGenerator"], [7, 0, 1, "", "DetectionDataset"], [7, 0, 1, "", "DocArtefacts"], [7, 0, 1, "", "FUNSD"], [7, 0, 1, "", "IC03"], [7, 0, 1, "", "IC13"], [7, 0, 1, "", "IIIT5K"], [7, 0, 1, "", "IIITHWS"], [7, 0, 1, "", "IMGUR5K"], [7, 0, 1, "", "MJSynth"], [7, 0, 1, "", "OCRDataset"], [7, 0, 1, "", "RecognitionDataset"], [7, 0, 1, "", "SROIE"], [7, 0, 1, "", "SVHN"], [7, 0, 1, "", "SVT"], [7, 0, 1, "", "SynthText"], [7, 0, 1, "", "WILDRECEIPT"], [7, 0, 1, "", "WordGenerator"], [7, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[7, 0, 1, "", "DataLoader"]], "doctr.io": [[8, 0, 1, "", "Artefact"], [8, 0, 1, "", "Block"], [8, 0, 1, "", "Document"], [8, 0, 1, "", "DocumentFile"], [8, 0, 1, "", "Line"], [8, 0, 1, "", "Page"], [8, 0, 1, "", "Word"], [8, 1, 1, "", "decode_img_as_tensor"], [8, 1, 1, "", "read_html"], [8, 1, 1, "", "read_img_as_numpy"], [8, 1, 1, "", "read_img_as_tensor"], [8, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[8, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[8, 2, 1, "", "from_images"], [8, 2, 1, "", "from_pdf"], [8, 2, 1, "", "from_url"]], "doctr.io.Page": [[8, 2, 1, "", "show"]], "doctr.models": [[9, 1, 1, "", "kie_predictor"], [9, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[9, 1, 1, "", "crop_orientation_predictor"], [9, 1, 1, "", "magc_resnet31"], [9, 1, 1, "", "mobilenet_v3_large"], [9, 1, 1, "", "mobilenet_v3_large_r"], [9, 1, 1, "", "mobilenet_v3_small"], [9, 1, 1, "", "mobilenet_v3_small_crop_orientation"], [9, 1, 1, "", "mobilenet_v3_small_page_orientation"], [9, 1, 1, "", "mobilenet_v3_small_r"], [9, 1, 1, "", "page_orientation_predictor"], [9, 1, 1, "", "resnet18"], [9, 1, 1, "", "resnet31"], [9, 1, 1, "", "resnet34"], [9, 1, 1, "", "resnet50"], [9, 1, 1, "", "textnet_base"], [9, 1, 1, "", "textnet_small"], [9, 1, 1, "", "textnet_tiny"], [9, 1, 1, "", "vgg16_bn_r"], [9, 1, 1, "", "vit_b"], [9, 1, 1, "", "vit_s"]], "doctr.models.detection": [[9, 1, 1, "", "db_mobilenet_v3_large"], [9, 1, 1, "", "db_resnet50"], [9, 1, 1, "", "detection_predictor"], [9, 1, 1, "", "fast_base"], [9, 1, 1, "", "fast_small"], [9, 1, 1, "", "fast_tiny"], [9, 1, 1, "", "linknet_resnet18"], [9, 1, 1, "", "linknet_resnet34"], [9, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[9, 1, 1, "", "from_hub"], [9, 1, 1, "", "login_to_hub"], [9, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[9, 1, 1, "", "crnn_mobilenet_v3_large"], [9, 1, 1, "", "crnn_mobilenet_v3_small"], [9, 1, 1, "", "crnn_vgg16_bn"], [9, 1, 1, "", "master"], [9, 1, 1, "", "parseq"], [9, 1, 1, "", "recognition_predictor"], [9, 1, 1, "", "sar_resnet31"], [9, 1, 1, "", "vitstr_base"], [9, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[10, 0, 1, "", "ChannelShuffle"], [10, 0, 1, "", "ColorInversion"], [10, 0, 1, "", "Compose"], [10, 0, 1, "", "GaussianBlur"], [10, 0, 1, "", "GaussianNoise"], [10, 0, 1, "", "LambdaTransformation"], [10, 0, 1, "", "Normalize"], [10, 0, 1, "", "OneOf"], [10, 0, 1, "", "RandomApply"], [10, 0, 1, "", "RandomBrightness"], [10, 0, 1, "", "RandomContrast"], [10, 0, 1, "", "RandomCrop"], [10, 0, 1, "", "RandomGamma"], [10, 0, 1, "", "RandomHorizontalFlip"], [10, 0, 1, "", "RandomHue"], [10, 0, 1, "", "RandomJpegQuality"], [10, 0, 1, "", "RandomResize"], [10, 0, 1, "", "RandomRotate"], [10, 0, 1, "", "RandomSaturation"], [10, 0, 1, "", "RandomShadow"], [10, 0, 1, "", "Resize"], [10, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[11, 0, 1, "", "DetectionMetric"], [11, 0, 1, "", "LocalizationConfusion"], [11, 0, 1, "", "OCRMetric"], [11, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.visualization": [[11, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [2, 8, 9, 11, 15, 18], "0": [2, 4, 7, 10, 11, 13, 16, 17, 19], "00": 19, "01": 19, "0123456789": 7, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "02562": 9, "03": 19, "035": 19, "0361328125": 19, "04": 19, "05": 19, "06": 19, "06640625": 19, "07": 19, "08": [10, 19], "09": 19, "0966796875": 19, "1": [7, 8, 9, 10, 11, 13, 17, 19], "10": [4, 7, 11, 19], "100": [7, 10, 11, 17, 19], "1000": 19, "101": 7, "1024": [9, 13, 19], "104": 7, "106": 7, "108": 7, "1095": 17, "11": 19, "110": 11, "1107": 17, "114": 7, "115": 7, "1156": 17, "116": 7, "118": 7, "11800h": 19, "11th": 19, "12": 19, "120": 7, "123": 7, "126": 7, "1268": 17, "128": [9, 13, 18, 19], "13": 19, "130": 7, "13068": 17, "131": 7, "1337891": 17, "1357421875": 19, "1396484375": 19, "14": 19, "1420": 19, "14470v1": 7, "149": 17, "15": 19, "150": [11, 19], "1552": 19, "16": [9, 18, 19], "1630859375": 19, "1684": 19, "16x16": 9, "17": 19, "1778": 19, "1782": 19, "18": [9, 19], "185546875": 19, "1900": 19, "1910": 9, "19342": 17, "19370": 17, "195": 7, "19598": 17, "199": 19, "1999": 19, "2": [4, 5, 7, 8, 9, 10, 16, 19], "20": 19, "200": 11, "2000": 17, "2003": [5, 7], "2012": 7, "2013": [5, 7], "2015": 7, "2019": 5, "2023": 1, "207901": 17, "21": 19, "2103": 7, "2186": 17, "21888": 17, "22": 19, "224": [9, 10], "225": 10, "22672": 17, "229": [10, 17], "23": 19, "233": 17, "236": 7, "24": 19, "246": 17, "249": 17, "25": 19, "2504": 19, "255": [8, 9, 10, 11, 19], "256": 9, "257": 17, "26": 19, "26032": 17, "264": 13, "27": 19, "2700": 17, "2710": 19, "2749": 13, "28": 19, "287": 13, "29": 19, "296": 13, "299": 13, "2d": 19, "3": [4, 5, 8, 9, 10, 11, 18, 19], "30": 19, "300": 17, "3000": 17, "301": 13, "30595": 19, "30ghz": 19, "31": 9, "32": [7, 9, 10, 13, 17, 18, 19], "3232421875": 19, "33": [10, 19], "33402": 17, "33608": 17, "34": [9, 19], "340": 19, "3456": 19, "3515625": 19, "36": 19, "360": 17, "37": [7, 19], "38": 19, "39": 19, "4": [9, 10, 11, 19], "40": 19, "406": 10, "41": 19, "42": 19, "43": 19, "44": 19, "45": 19, "456": 10, "46": 19, "47": 19, "472": 17, "48": [7, 19], "485": 10, "49": 19, "49377": 17, "5": [7, 10, 11, 16, 19], "50": [9, 17, 19], "51": 19, "51171875": 19, "512": 9, "52": [7, 19], "529": 19, "53": 19, "54": 19, "540": 19, "5478515625": 19, "55": 19, "56": 19, "57": 19, "58": [7, 19], "580": 19, "5810546875": 19, "583": 19, "59": 19, "597": 19, "5k": [5, 7], "5m": 19, "6": [10, 19], "60": 10, "600": [9, 11, 19], "61": 19, "62": 19, "626": 17, "63": 19, "64": [9, 10, 19], "641": 19, "647": 17, "65": 19, "66": 19, "67": 19, "68": 19, "69": 19, "693": 13, "694": 13, "695": 13, "6m": 19, "7": 19, "70": [7, 11, 19], "707470": 17, "71": [7, 19], "7100000": 17, "7141797": 17, "7149": 17, "72": 19, "72dpi": 8, "73": 19, "73257": 17, "74": 19, "75": [10, 19], "7581382": 17, "76": 19, "77": 19, "772": 13, "772875": 17, "78": 19, "785": 13, "79": 19, "793533": 17, "796": 17, "798": 13, "7m": 19, "8": [9, 10, 19], "80": 19, "800": [9, 11, 17, 19], "81": 19, "82": 19, "83": 19, "84": 19, "849": 17, "85": 19, "8564453125": 19, "857": 19, "85875": 17, "86": 19, "8603515625": 19, "87": 19, "8707": 17, "88": 19, "89": 19, "9": [10, 19], "90": 19, "90k": 7, "90kdict32px": 7, "91": 19, "914085328578949": 19, "92": 19, "93": 19, "94": [7, 19], "95": [11, 19], "9578408598899841": 19, "96": 19, "97": 19, "98": 19, "99": 19, "9949972033500671": 19, "A": [2, 3, 5, 7, 8, 9, 12, 18], "As": 3, "Be": 19, "Being": 2, "By": 14, "For": [2, 3, 4, 13, 19], "If": [3, 8, 9, 13, 19], "In": [3, 7, 17], "It": [10, 15, 16, 18], "Its": [5, 9], "No": [2, 19], "Of": 7, "Or": [16, 18], "The": [2, 3, 7, 8, 11, 14, 16, 17, 18, 19], "Then": 9, "To": [3, 4, 14, 15, 16, 18, 19], "_": [2, 7, 9], "__call__": 19, "_build": 3, "_i": 11, "ab": 7, "abc": 18, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "abdef": [7, 17], "abl": [17, 19], "about": [2, 17, 19], "abov": 19, "abstract": 1, "abstractdataset": 7, "abus": 2, "accept": 2, "access": [5, 8, 17, 19], "account": [2, 15], "accur": 19, "accuraci": 11, "achiev": 18, "act": 2, "action": 2, "activ": 5, "ad": [3, 9, 10], "adapt": 2, "add": [10, 11, 15, 19], "add_hook": 19, "add_label": 11, "addit": [3, 4, 8, 16, 19], "addition": [3, 19], "address": [2, 8], "adjust": 10, "advanc": 2, "advantag": 18, "advis": 3, "aesthet": [5, 7], "affect": 2, "after": [15, 19], "ag": 2, "again": 9, "aggreg": [11, 17], "aggress": 2, "align": [2, 8, 10], "all": [2, 3, 6, 7, 8, 10, 11, 16, 17, 19], "allow": [2, 18], "along": 19, "alreadi": [3, 18], "also": [2, 9, 15, 16, 17, 19], "alwai": 17, "an": [2, 3, 5, 7, 8, 9, 11, 16, 18, 19], "analysi": [8, 16], "ancient_greek": 7, "andrej": 1, "angl": [8, 10], "ani": [2, 7, 8, 9, 10, 11, 18, 19], "annot": 7, "anot": 17, "anoth": [9, 13, 17], "answer": 2, "anyascii": 11, "anyon": 5, "anyth": 16, "api": [3, 5], "apolog": 2, "apologi": 2, "app": 3, "appear": 2, "appli": [2, 7, 10], "applic": [5, 9], "appoint": 2, "appreci": 15, "appropri": [2, 3, 19], "ar": [2, 3, 4, 6, 7, 8, 10, 11, 12, 16, 17, 19], "arab": 7, "arabic_diacrit": 7, "arabic_lett": 7, "arabic_punctu": 7, "arbitrarili": [5, 9], "arch": [9, 15], "architectur": [5, 9, 15, 16], "area": 19, "argument": [7, 8, 9, 11, 13, 19], "around": 2, "arrai": [8, 10, 11], "art": [5, 16], "artefact": [11, 16, 19], "artefact_typ": 8, "articl": 1, "artifici": [5, 7], "arxiv": [7, 9], "asarrai": 11, "ascii_lett": 7, "aspect": [5, 9, 10, 19], "assess": 11, "assign": 11, "associ": 8, "assum": 9, "assume_straight_pag": [9, 13, 19], "astyp": [9, 11, 19], "attack": 2, "attend": [5, 9], "attent": [2, 9], "autom": 5, "automat": 19, "autoregress": [5, 9], "avail": [2, 5, 6, 10], "averag": [10, 19], "avoid": [2, 4], "aw": [5, 19], "awar": 19, "azur": 19, "b": [9, 11, 19], "b_j": 11, "back": 3, "backbon": 9, "backend": 19, "background": 17, "bangla": 7, "bar": 16, "bar_cod": 17, "baranovskij": 1, "base": [5, 9, 16], "baselin": [5, 9, 19], "batch": [7, 9, 10, 16, 17, 19], "batch_siz": [7, 9, 13, 16, 17, 18], "bblanchon": 4, "bbox": 19, "becaus": 14, "been": [3, 11, 17, 19], "befor": [7, 9, 10, 19], "begin": 11, "behavior": [2, 19], "being": [11, 19], "belong": 19, "benchmark": 19, "best": [1, 2], "better": [12, 19], "between": [10, 11, 19], "bgr": 8, "bilinear": 10, "bin_thresh": 19, "binar": [5, 9, 19], "binari": [8, 18, 19], "bit": 18, "block": [11, 19], "block_1_1": 19, "blur": 10, "bmvc": 7, "bn": 15, "bodi": [2, 19], "bool": [7, 8, 9, 10, 11], "boolean": [9, 19], "both": [5, 7, 10, 17, 19], "bottom": [9, 19], "bound": [7, 8, 9, 10, 11, 16, 17, 19], "box": [7, 8, 9, 10, 11, 16, 17, 19], "box_thresh": 19, "bright": 10, "browser": [3, 5], "build": [3, 4, 18], "built": 3, "byte": [8, 19], "c": [4, 8, 11], "c_j": 11, "cach": [3, 7, 14], "cache_sampl": 7, "call": 18, "callabl": [7, 10], "can": [3, 4, 13, 14, 15, 16, 17, 19], "capabl": [3, 12, 19], "case": [7, 11], "cf": 19, "cfg": 19, "challeng": 7, "challenge2_test_task12_imag": 7, "challenge2_test_task1_gt": 7, "challenge2_training_task12_imag": 7, "challenge2_training_task1_gt": 7, "chang": [14, 19], "channel": [2, 3, 8, 10], "channel_prior": 4, "channelshuffl": 10, "charact": [5, 7, 8, 11, 17, 19], "charactergener": [7, 17], "characterist": 2, "charg": 19, "charset": 19, "chart": 8, "check": [3, 15, 19], "checkpoint": 9, "chip": 4, "christian": 1, "ci": 3, "clarifi": 2, "clariti": 2, "class": [2, 7, 8, 10, 11, 19], "class_nam": 13, "classif": [17, 19], "classmethod": 8, "clear": 3, "clone": 4, "close": 3, "co": 15, "code": [5, 8, 16], "codecov": 3, "colab": 12, "collate_fn": 7, "collect": [8, 16], "color": 10, "colorinvers": 10, "column": 8, "com": [2, 4, 8, 9, 15], "combin": 19, "command": [3, 16], "comment": 2, "commit": 2, "common": [2, 10, 11, 18], "commun": 2, "compar": 5, "comparison": [11, 19], "competit": 7, "compil": [12, 19], "complaint": 2, "complementari": 11, "complet": 3, "compon": 19, "compos": [7, 19], "comprehens": 19, "comput": [7, 11, 18, 19], "conf_threshold": 16, "confid": [8, 19], "config": [4, 9], "configur": 9, "confus": 11, "consecut": [10, 19], "consequ": 2, "consid": [2, 3, 7, 8, 11, 19], "consist": 19, "consolid": [5, 7], "constant": 10, "construct": 2, "contact": 2, "contain": [1, 6, 7, 12, 17, 19], "content": [7, 8, 19], "context": 9, "contib": 4, "continu": 2, "contrast": 10, "contrast_factor": 10, "contrib": [4, 16], "contribut": 2, "contributor": 3, "convers": 8, "convert": [8, 10], "convolut": 9, "cool": 1, "coordin": [8, 19], "cord": [5, 7, 17, 19], "core": [11, 19], "corner": 19, "correct": 10, "correspond": [4, 8, 10, 19], "could": [2, 16], "counterpart": 11, "cover": 3, "coverag": 3, "cpu": [5, 13, 18], "creat": [1, 15], "crnn": [5, 9, 15], "crnn_mobilenet_v3_larg": [9, 15, 19], "crnn_mobilenet_v3_smal": [9, 18, 19], "crnn_vgg16_bn": [9, 13, 15, 19], "crop": [8, 9, 10, 13, 17, 19], "crop_orient": [8, 19], "crop_orientation_predictor": [9, 13], "crop_param": 13, "cuda": 18, "currenc": 7, "current": [3, 13, 19], "custom": [15, 16, 18, 19], "custom_crop_orientation_model": 13, "custom_page_orientation_model": 13, "customhook": 19, "cvit": 5, "czczup": 9, "czech": 7, "d": [7, 17], "danish": 7, "data": [5, 7, 8, 10, 11, 13, 15], "dataload": 17, "dataset": [9, 13, 19], "dataset_info": 7, "date": [13, 19], "db": 15, "db_mobilenet_v3_larg": [9, 15, 19], "db_resnet34": 19, "db_resnet50": [9, 13, 15, 19], "dbnet": [5, 9], "deal": [12, 19], "decis": 2, "decod": 8, "decode_img_as_tensor": 8, "dedic": 18, "deem": 2, "deep": [9, 19], "def": 19, "default": [4, 8, 13, 14, 19], "defer": 17, "defin": [11, 18], "degre": [8, 10, 19], "degress": 8, "delet": 3, "delimit": 19, "delta": 10, "demo": [3, 5], "demonstr": 2, "depend": [3, 4, 5, 19], "deploi": 3, "deploy": 5, "derogatori": 2, "describ": 9, "descript": 12, "design": 10, "desir": 8, "det_arch": [9, 13, 15, 18], "det_b": 19, "det_model": [13, 15, 18], "det_param": 13, "det_predictor": [13, 19], "detail": [13, 19], "detect": [1, 7, 8, 11, 12, 13, 16], "detect_languag": 9, "detect_orient": [9, 13, 19], "detection_predictor": [9, 19], "detection_task": [7, 17], "detectiondataset": [7, 17], "detectionmetr": 11, "detectionpredictor": [9, 13], "detector": [5, 9, 16], "deterior": 9, "determin": 2, "dev": [3, 14], "develop": 4, "deviat": 10, "devic": 18, "dict": [8, 11, 19], "dictionari": [8, 11], "differ": 2, "differenti": [5, 9], "digit": [5, 7, 17], "dimens": [8, 11, 19], "dimension": 10, "direct": 7, "directli": [15, 19], "directori": [3, 14], "disabl": [2, 14, 19], "disable_crop_orient": 19, "disable_page_orient": 19, "disclaim": 19, "discuss": 3, "disparag": 2, "displai": [8, 11], "display_artefact": 11, "distribut": 10, "div": 19, "divers": 2, "divid": 8, "do": [3, 4, 9], "doc": [3, 8, 16, 18, 19], "docartefact": [7, 17], "docstr": 3, "doctr": [1, 4, 13, 14, 15, 16, 17, 18, 19], "doctr_cache_dir": 14, "doctr_multiprocessing_dis": 14, "document": [1, 7, 9, 11, 12, 13, 16, 17, 18, 19], "documentbuild": 19, "documentfil": [8, 13, 15, 16, 18], "doesn": 18, "don": [13, 19], "done": 10, "download": [7, 17], "downsiz": 9, "draw": 10, "drop": 7, "drop_last": 7, "dtype": [8, 9, 10, 11, 18], "dual": [5, 7], "dummi": 15, "dummy_img": 19, "dummy_input": 18, "dure": 2, "dutch": 7, "dynam": [7, 16], "dynamic_seq_length": 7, "e": [2, 3, 4, 8, 9], "each": [5, 7, 8, 9, 10, 11, 17, 19], "eas": 3, "easi": [5, 11, 15, 18], "easili": [8, 11, 13, 15, 17, 19], "econom": 2, "edit": 2, "educ": 2, "effect": 19, "effici": [3, 5, 7, 9], "either": [11, 19], "element": [7, 8, 9, 19], "els": [3, 16], "email": 2, "empathi": 2, "en": 19, "enabl": [7, 8], "enclos": 8, "encod": [5, 7, 8, 9, 19], "encode_sequ": 7, "encount": 3, "encrypt": 8, "end": [5, 7, 9, 11], "english": [7, 17], "enough": [3, 19], "ensur": 3, "entri": 7, "environ": [2, 14], "eo": 7, "equiv": 19, "estim": 9, "etc": [8, 16], "ethnic": 2, "evalu": [17, 19], "event": 2, "everyon": 2, "everyth": [3, 19], "exact": [11, 19], "exampl": [2, 3, 5, 7, 9, 15, 19], "exchang": 18, "execut": 19, "exist": 15, "expand": 10, "expect": [8, 10, 11], "experi": 2, "explan": [2, 19], "explicit": 2, "exploit": [5, 9], "export": [8, 9, 11, 12, 16, 19], "export_as_straight_box": [9, 19], "export_as_xml": 19, "export_model_to_onnx": 18, "express": [2, 10], "extens": 8, "extern": [2, 17], "extract": [1, 5, 7], "extractor": 9, "f_": 11, "f_a": 11, "factor": 10, "fair": 2, "fairli": 2, "fals": [7, 8, 9, 10, 11, 13, 19], "faq": 2, "fascan": 15, "fast": [5, 7, 9], "fast_bas": [9, 19], "fast_smal": [9, 19], "fast_tini": [9, 19], "faster": [5, 9, 18], "fasterrcnn_mobilenet_v3_large_fpn": 9, "favorit": 19, "featur": [4, 9, 11, 12, 13, 16], "feedback": 2, "feel": [3, 15], "felix92": 15, "few": [18, 19], "figsiz": 11, "figur": [11, 16], "file": [3, 7], "final": 9, "find": [3, 17], "fine": 1, "finnish": 7, "first": [3, 7], "firsthand": 7, "fit": [9, 19], "flag": 19, "flip": 10, "float": [8, 10, 11, 18], "float32": [8, 9, 10, 18], "fn": 10, "focu": 15, "focus": [2, 7], "folder": 7, "follow": [2, 3, 4, 7, 10, 11, 13, 14, 15, 16, 19], "font": 7, "font_famili": 7, "foral": 11, "forc": 3, "forg": 4, "form": [5, 7, 19], "format": [8, 11, 13, 17, 18, 19], "forpost": [5, 7], "forum": 3, "found": 1, "fp16": 18, "frac": 11, "framework": [4, 15, 17, 19], "free": [2, 3, 15], "french": [7, 13, 15, 19], "friendli": 5, "from": [1, 2, 5, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19], "from_hub": [9, 15], "from_imag": [8, 15, 16, 18], "from_pdf": 8, "from_url": 8, "full": [7, 11, 19], "function": [7, 10, 11, 16], "funsd": [5, 7, 17, 19], "further": 17, "futur": 7, "g": [8, 9], "g_": 11, "g_x": 11, "gallagh": 1, "gamma": 10, "gaussian": 10, "gaussianblur": 10, "gaussiannois": 10, "gen": 19, "gender": 2, "gener": [3, 5, 8, 9], "generic_cyrillic_lett": 7, "geometri": [5, 8, 19], "geq": 11, "german": [7, 13, 15], "get": [18, 19], "git": 15, "github": [3, 4, 9, 15], "give": [2, 16], "given": [7, 8, 10, 11, 19], "global": 9, "go": 19, "good": 18, "googl": 3, "googlevis": 5, "gpu": [5, 16, 18], "gracefulli": 2, "graph": [5, 7, 8], "grayscal": 10, "ground": 11, "groung": 11, "group": [5, 19], "gt": 11, "gt_box": 11, "gt_label": 11, "guid": 3, "guidanc": 17, "gvision": 19, "h": [8, 9, 10], "h_": 11, "ha": [3, 7, 11, 17], "handl": [12, 17, 19], "handwrit": 7, "handwritten": 17, "harass": 2, "hardwar": 19, "harm": 2, "hat": 11, "have": [2, 3, 11, 13, 15, 17, 18, 19], "head": [9, 19], "healthi": 2, "hebrew": 7, "height": [8, 10], "hello": [11, 19], "help": 18, "here": [6, 10, 12, 16, 17, 19], "hf": 9, "hf_hub_download": 9, "high": 8, "higher": [4, 7, 19], "hindi": 7, "hindi_digit": 7, "hocr": 19, "hook": 19, "horizont": [8, 10, 19], "hous": 7, "how": [1, 3, 12, 13, 15, 17], "howev": 17, "hsv": 10, "html": [2, 3, 4, 8, 19], "http": [2, 4, 7, 8, 9, 15, 19], "hub": 9, "hue": 10, "huggingfac": 9, "hw": 7, "i": [2, 3, 7, 8, 9, 10, 11, 14, 15, 16, 17, 18], "i7": 19, "ibrahimov": 1, "ic03": [5, 7, 17], "ic13": [5, 7, 17], "icdar": [5, 7], "icdar2019": 7, "id": 19, "ident": 2, "identifi": 5, "iiit": [5, 7], "iiit5k": [7, 17], "iiithw": [5, 7, 17], "imag": [1, 5, 7, 8, 9, 10, 11, 15, 16, 17, 19], "imagenet": 9, "imageri": 2, "images_90k_norm": 7, "img": [7, 10, 17, 18], "img_cont": 8, "img_fold": [7, 17], "img_path": 8, "img_transform": 7, "imgur5k": [5, 7, 17], "imgur5k_annot": 7, "imlist": 7, "impact": 2, "implement": [7, 8, 9, 10, 11, 19], "import": [7, 8, 9, 10, 11, 13, 15, 16, 17, 18, 19], "improv": 9, "inappropri": 2, "incid": 2, "includ": [2, 7, 17, 18], "inclus": 2, "increas": 10, "independ": 10, "index": [3, 8], "indic": 11, "individu": 2, "infer": [5, 9, 10, 16, 19], "inform": [1, 2, 3, 5, 7, 17], "input": [3, 8, 9, 10, 18, 19], "input_crop": 9, "input_pag": [9, 11, 19], "input_shap": 18, "input_tensor": 9, "inspir": [2, 10], "instal": [15, 16, 18], "instanc": [2, 19], "instanti": [9, 19], "instead": [7, 8, 9], "insult": 2, "int": [7, 8, 9, 10], "int64": 11, "integ": 11, "integr": [1, 5, 15, 17], "intel": 19, "interact": [2, 8, 11], "interfac": [15, 18], "interoper": 18, "interpol": 10, "interpret": [7, 8], "intersect": 11, "invert": 10, "investig": 2, "invis": 2, "involv": [2, 19], "io": [13, 15, 16, 18], "iou": 11, "iou_thresh": 11, "iou_threshold": 16, "irregular": [5, 9, 17], "isn": 7, "issu": [2, 3, 15], "italian": 7, "iter": [7, 10, 17, 19], "its": [8, 9, 10, 11, 17, 19], "itself": [9, 15], "j": 11, "jame": 1, "job": 3, "join": 3, "jpeg": 10, "jpegqual": 10, "jpg": [7, 8, 15, 18], "json": [7, 17, 19], "json_output": 19, "jump": 3, "just": 2, "kei": [5, 7], "kera": [9, 18], "kernel": [5, 9, 10], "kernel_shap": 10, "keywoard": 9, "keyword": [7, 8, 9, 11], "kie": [9, 13], "kie_predictor": [9, 13], "kiepredictor": 9, "kind": 2, "know": [3, 18], "kwarg": [7, 8, 9, 11], "l": 11, "l_j": 11, "label": [7, 11, 16, 17], "label_fil": [7, 17], "label_fold": 7, "label_path": [7, 17], "labels_path": [7, 17], "ladder": 2, "lambda": 10, "lambdatransform": 10, "lang": 19, "languag": [2, 5, 7, 8, 9, 15, 19], "larg": [9, 15], "largest": 11, "last": [4, 7], "latenc": 9, "later": 3, "latest": 19, "latin": 7, "layer": 18, "layout": 19, "lead": 2, "leader": 2, "learn": [2, 5, 9, 18, 19], "least": 4, "left": [11, 19], "legacy_french": 7, "length": [7, 19], "less": [18, 19], "level": [2, 7, 11, 19], "leverag": 12, "lf": 15, "librari": [3, 4, 12, 13], "light": 5, "lightweight": 18, "like": 2, "limits_": 11, "line": [5, 9, 11, 19], "line_1_1": 19, "link": 13, "linknet": [5, 9], "linknet_resnet18": [9, 13, 18, 19], "linknet_resnet34": [9, 18, 19], "linknet_resnet50": [9, 19], "list": [7, 8, 10, 11, 15], "ll": 11, "load": [5, 7, 9, 16, 18], "load_state_dict": 13, "load_weight": 13, "loc_pr": 19, "local": [3, 5, 7, 9, 11, 17, 19], "localis": 7, "localizationconfus": 11, "locat": [3, 8, 19], "login": 9, "login_to_hub": [9, 15], "logo": [8, 16, 17], "love": 15, "lower": [10, 11, 19], "m": [3, 11, 19], "m1": 4, "macbook": 4, "machin": 18, "made": 5, "magc_resnet31": 9, "mai": [2, 3], "mail": 2, "main": 12, "maintain": 5, "mainten": 3, "make": [2, 3, 11, 13, 14, 15, 18, 19], "mani": [17, 19], "manipul": 19, "map": [7, 9], "map_loc": 13, "master": [5, 9, 19], "match": [11, 19], "mathcal": 11, "matplotlib": [8, 11], "max": [7, 10, 11], "max_angl": 10, "max_area": 10, "max_char": [7, 17], "max_delta": 10, "max_gain": 10, "max_gamma": 10, "max_qual": 10, "max_ratio": 10, "maximum": [7, 10], "maxval": [9, 10], "mbox": 11, "mean": [10, 11, 13], "meaniou": 11, "meant": [8, 18], "measur": 19, "media": 2, "median": 9, "meet": 13, "member": 2, "memori": [14, 18], "mention": 19, "merg": 7, "messag": 3, "meta": 19, "metadata": 18, "metal": 4, "method": [8, 10, 19], "metric": [11, 19], "middl": 19, "might": [18, 19], "min": 10, "min_area": 10, "min_char": [7, 17], "min_gain": 10, "min_gamma": 10, "min_qual": 10, "min_ratio": 10, "min_val": 10, "minde": [1, 2, 4, 5, 9], "minim": [3, 5], "minimalist": [5, 9], "minimum": [4, 7, 10, 11, 19], "minval": 10, "miss": 4, "mistak": 2, "mixed_float16": 18, "mixed_precis": 18, "mjsynth": [5, 7, 17], "mnt": 7, "mobilenet": [9, 15], "mobilenet_v3_larg": 9, "mobilenet_v3_large_r": 9, "mobilenet_v3_smal": [9, 13], "mobilenet_v3_small_crop_orient": [9, 13], "mobilenet_v3_small_page_orient": [9, 13], "mobilenet_v3_small_r": 9, "mobilenetv3": 9, "modal": [5, 7], "mode": 4, "model": [7, 11, 14, 16, 17], "model_nam": [9, 15, 18], "model_path": [16, 18], "moder": 2, "modif": 3, "modifi": [9, 14, 19], "modul": [4, 8, 9, 10, 11, 19], "more": [3, 17, 19], "moscardi": 1, "most": 19, "mozilla": 2, "multi": [5, 9], "multilingu": [7, 15], "multipl": [7, 8, 10, 19], "multipli": 10, "multiprocess": 14, "my": 9, "my_awesome_model": 15, "my_hook": 19, "n": [7, 11], "name": [7, 9, 18, 19], "nation": 2, "natur": [2, 5, 7], "ndarrai": [7, 8, 10, 11], "necessari": [4, 13, 14], "need": [3, 4, 7, 11, 13, 14, 15, 16, 19], "neg": 10, "nest": 19, "netraj": 1, "network": [5, 7, 9, 18], "neural": [5, 7, 9, 18], "new": [3, 11], "next": [7, 17], "nois": 10, "noisi": [5, 7], "non": [5, 7, 8, 9, 10, 11], "none": [7, 8, 9, 10, 11, 19], "normal": [9, 10], "norwegian": 7, "note": [0, 3, 7, 9, 13, 15, 16, 18], "now": 3, "np": [9, 10, 11, 19], "num_output_channel": 10, "num_sampl": [7, 17], "number": [7, 9, 10, 11, 19], "numpi": [8, 9, 11, 19], "o": 4, "obb": 16, "obj_detect": 15, "object": [7, 8, 11, 16, 19], "objectness_scor": [8, 19], "oblig": 2, "obtain": 19, "occupi": 18, "ocr": [1, 5, 7, 9, 11, 15], "ocr_carea": 19, "ocr_db_crnn": 11, "ocr_lin": 19, "ocr_pag": 19, "ocr_par": 19, "ocr_predictor": [9, 13, 15, 18, 19], "ocrdataset": [7, 17], "ocrmetr": 11, "ocrpredictor": [9, 13], "ocrx_word": 19, "offens": 2, "offici": [2, 9], "offlin": 2, "offset": 10, "onc": 19, "one": [3, 7, 9, 10, 13, 15, 19], "oneof": 10, "ones": [7, 11], "onli": [3, 9, 10, 11, 13, 15, 17, 18, 19], "onlin": 2, "onnx": 16, "onnxruntim": [16, 18], "onnxtr": 18, "opac": 10, "opacity_rang": 10, "open": [1, 2, 3, 15, 18], "opinion": 2, "optic": [5, 19], "optim": [5, 19], "option": [7, 9, 13], "order": [3, 7, 8, 10], "org": [2, 7, 9, 19], "organ": 8, "orient": [2, 8, 9, 12, 16, 19], "orientationpredictor": 9, "other": [2, 3], "otherwis": [2, 8, 11], "our": [1, 3, 9, 19], "out": [3, 9, 10, 11, 19], "outpout": 19, "output": [8, 10, 18], "output_s": [8, 10], "outsid": 14, "over": [7, 11, 19], "overal": [2, 9], "overlai": 8, "overview": 16, "overwrit": 13, "overwritten": 15, "own": 5, "p": [10, 19], "packag": [3, 5, 11, 14, 16, 17, 18], "pad": [7, 9, 10, 19], "page": [4, 7, 9, 11, 13, 19], "page1": 8, "page2": 8, "page_1": 19, "page_idx": [8, 19], "page_orientation_predictor": [9, 13], "page_param": 13, "pair": 11, "paper": 9, "par_1_1": 19, "paragraph": 19, "paragraph_break": 19, "parallel": 9, "param": [10, 19], "paramet": [5, 8, 9, 18], "pars": [5, 7], "parseq": [5, 9, 15, 18, 19], "part": [7, 10, 19], "parti": 4, "partial": 19, "particip": 2, "pass": [7, 8, 9, 13, 19], "password": 8, "patch": [9, 11], "path": [7, 8, 16, 17, 18], "path_to_checkpoint": 13, "path_to_custom_model": 18, "path_to_pt": 13, "patil": 1, "pattern": 2, "pdf": [8, 9, 12], "pdfpage": 8, "peopl": 2, "per": [10, 19], "perform": [5, 8, 9, 10, 11, 14, 18, 19], "period": 2, "permiss": 2, "permut": [5, 9], "persian_lett": 7, "person": [2, 17], "phase": 19, "photo": 17, "physic": [2, 8], "pick": 10, "pictur": 8, "pip": [3, 4, 16, 18], "pipelin": 19, "pixel": [8, 10, 19], "pleas": 3, "plot": 11, "plt": 11, "plug": 15, "plugin": 4, "png": 8, "point": 18, "polici": 14, "polish": 7, "polit": 2, "polygon": [7, 11, 19], "pool": 9, "portugues": 7, "posit": [2, 11], "possibl": [3, 11, 15, 19], "post": [2, 19], "postprocessor": 19, "potenti": 9, "power": 5, "ppageno": 19, "pre": [3, 9, 18], "precis": [11, 19], "pred": 11, "pred_box": 11, "pred_label": 11, "predefin": 17, "predict": [8, 9, 11, 19], "predictor": [5, 8, 9, 12, 13, 15, 18], "prefer": 17, "preinstal": 4, "preprocessor": [13, 19], "prerequisit": 15, "present": 12, "preserv": [9, 10, 19], "preserve_aspect_ratio": [8, 9, 10, 13, 19], "pretrain": [5, 9, 11, 13, 18, 19], "pretrained_backbon": [9, 13], "print": 19, "prior": 7, "privaci": 2, "privat": 2, "probabl": [1, 10], "problem": 3, "procedur": 10, "process": [3, 5, 8, 9, 13, 19], "processor": 19, "produc": [12, 19], "product": 18, "profession": 2, "project": [3, 17], "promptli": 2, "proper": 3, "properli": 7, "provid": [2, 3, 5, 15, 16, 17, 19], "public": [2, 5], "publicli": 19, "publish": 2, "pull": 15, "punctuat": 7, "pure": 7, "purpos": 3, "push_to_hf_hub": [9, 15], "py": 15, "pypdfium2": [4, 8], "pyplot": [8, 11], "python": [1, 3, 16], "python3": 15, "pytorch": [4, 5, 9, 10, 13, 15, 18, 19], "q": 3, "qr": [8, 16], "qr_code": 17, "qualiti": 10, "question": 2, "quickli": 5, "quicktour": 12, "r": 19, "race": 2, "ramdisk": 7, "rand": [9, 10, 11, 18, 19], "random": [9, 10, 11, 19], "randomappli": 10, "randombright": 10, "randomcontrast": 10, "randomcrop": 10, "randomgamma": 10, "randomhorizontalflip": 10, "randomhu": 10, "randomjpegqu": 10, "randomli": 10, "randomres": 10, "randomrot": 10, "randomsatur": 10, "randomshadow": 10, "rang": 10, "rassi": 15, "ratio": [9, 10, 19], "raw": [8, 11], "re": 18, "read": [5, 7, 9], "read_html": 8, "read_img_as_numpi": 8, "read_img_as_tensor": 8, "read_pdf": 8, "readi": 18, "real": [1, 5, 9, 10], "realli": 1, "reason": [2, 5, 7], "rebuild": 3, "rebuilt": 3, "recal": [11, 19], "receipt": [5, 7, 19], "reco_arch": [9, 13, 15, 18], "reco_b": 19, "reco_model": [13, 15, 18], "reco_param": 13, "reco_predictor": 13, "recogn": 19, "recognit": [7, 11, 12, 13], "recognition_predictor": [9, 19], "recognition_task": [7, 17], "recognitiondataset": [7, 17], "recognitionpredictor": [9, 13], "rectangular": 9, "reduc": [4, 10], "refer": [3, 4, 13, 15, 16, 17, 19], "regardless": 2, "region": 19, "regroup": 11, "regular": 17, "reject": 2, "rel": [8, 10, 11, 19], "relat": 8, "releas": [0, 4], "relev": 16, "religion": 2, "remov": 2, "render": [8, 19], "repo": 9, "repo_id": [9, 15], "report": 2, "repositori": [7, 9, 15], "repres": [2, 18, 19], "represent": [5, 9], "request": [2, 15], "requir": [4, 10, 18], "research": 5, "residu": 9, "resiz": [10, 19], "resnet": 9, "resnet18": [9, 15], "resnet31": 9, "resnet34": 9, "resnet50": [9, 15], "resolv": 8, "resolve_block": 19, "resolve_lin": 19, "resourc": 17, "respect": 2, "rest": [3, 10, 11], "restrict": 14, "result": [3, 7, 8, 12, 15, 18, 19], "return": 19, "reusabl": 19, "review": 2, "rgb": [8, 10], "rgb_mode": 8, "rgb_output": 8, "right": [2, 9, 11], "roboflow": 1, "robust": [5, 7], "root": 7, "rotat": [7, 8, 9, 10, 11, 12, 13, 17, 19], "run": [3, 4, 9], "same": [3, 8, 11, 17, 18, 19], "sampl": [7, 9, 17, 19], "sample_transform": 7, "sanjin": 1, "sar": [5, 9], "sar_resnet31": [9, 19], "satur": 10, "save": [9, 17], "scale": [8, 9, 10, 11], "scale_rang": 10, "scan": [5, 7], "scene": [5, 7, 9], "score": [8, 11], "script": [3, 17], "seamless": 5, "seamlessli": [5, 19], "search": [1, 9], "searchabl": 12, "sec": 19, "second": 19, "section": [1, 13, 15, 16, 18, 19], "secur": [2, 14], "see": [2, 3], "seen": 19, "segment": [5, 9, 19], "self": 19, "semant": [5, 9], "send": 19, "sens": 11, "sensit": 17, "separ": 19, "sequenc": [5, 7, 8, 9, 11, 19], "sequenti": [10, 19], "seri": 2, "seriou": 2, "set": [2, 4, 7, 9, 11, 14, 16, 19], "set_global_polici": 18, "sever": [8, 10, 19], "sex": 2, "sexual": 2, "shade": 10, "shape": [5, 8, 9, 10, 11, 19], "share": [14, 17], "shift": 10, "shm": 14, "should": [3, 7, 8, 10, 11], "show": [5, 8, 9, 11, 13, 15, 16], "showcas": [3, 12], "shuffl": [7, 10], "side": 11, "signatur": 8, "signific": 17, "simpl": [5, 9, 18], "simpler": 9, "sinc": [7, 17], "singl": [2, 3, 5, 7], "single_img_doc": 18, "size": [2, 7, 8, 10, 16, 19], "skew": 19, "slack": 3, "slightli": 9, "small": [3, 9, 19], "smallest": 8, "snapshot_download": 9, "snippet": 19, "so": [3, 4, 7, 9, 15, 17], "social": 2, "socio": 2, "some": [1, 4, 12, 15, 17], "someth": 3, "somewher": 3, "sort": 2, "sourc": [1, 7, 8, 9, 10, 11, 15], "space": [2, 19], "span": 19, "spanish": 7, "spatial": [5, 7, 8], "specif": [3, 4, 11, 13, 17, 19], "specifi": [2, 7, 8], "speed": [5, 9, 19], "sphinx": 3, "sroie": [5, 7, 17], "stabl": 4, "stackoverflow": 3, "stage": 5, "standalon": 12, "standard": 10, "start": 7, "state": [1, 5, 11, 16], "static": 11, "statist": 1, "statu": 2, "std": [10, 13], "step": 14, "still": 19, "str": [7, 8, 9, 10, 11], "straight": [7, 9, 17, 19], "straighten": 19, "straighten_pag": [9, 13, 19], "straigten_pag": 13, "stream": 8, "street": [5, 7], "strict": 4, "strictli": 11, "string": [7, 8, 11, 19], "strive": 4, "strong": [5, 9], "structur": [18, 19], "subset": [7, 19], "suggest": [3, 15], "sum": 11, "summari": 11, "support": [4, 13, 16, 18, 19], "sustain": 2, "svhn": [5, 7, 17], "svt": [7, 17], "swedish": 7, "symmetr": [9, 10, 19], "symmetric_pad": [9, 10, 19], "synthet": 5, "synthtext": [5, 7, 17], "system": 19, "t": [3, 7, 13, 18, 19], "tabl": [15, 16, 17], "take": [2, 7, 19], "target": [7, 8, 10, 11, 17], "target_s": 7, "task": [5, 7, 9, 15, 17, 19], "task2": 7, "team": 4, "techminde": 4, "templat": [3, 5], "tensor": [7, 8, 10, 19], "tensorflow": [4, 5, 8, 9, 10, 13, 15, 18, 19], "tensorspec": 18, "term": 2, "test": [7, 17], "test_set": 7, "text": [1, 7, 8, 9, 11, 17], "text_output": 19, "textmatch": 11, "textnet": 9, "textnet_bas": 9, "textnet_smal": 9, "textnet_tini": 9, "textract": [5, 19], "textstylebrush": [5, 7], "textual": [5, 7, 8, 9, 19], "tf": [4, 8, 9, 10, 15, 18], "than": [3, 11, 15], "thank": 3, "thei": [2, 11], "them": [7, 19], "thi": [1, 2, 3, 4, 6, 7, 10, 11, 13, 14, 15, 17, 18, 19], "thing": [18, 19], "third": 4, "those": [2, 8, 19], "threaten": 2, "threshold": 19, "through": [2, 10, 16, 17], "tilman": 15, "time": [1, 2, 5, 9, 11, 17], "tini": 9, "titl": [8, 19], "tm": 19, "tmp": 14, "togeth": [3, 8], "tograi": 10, "tool": [1, 17], "top": [11, 18, 19], "topic": 3, "torch": [4, 10, 13, 15, 18], "torchvis": 10, "total": 13, "toward": [2, 4], "train": [3, 7, 9, 10, 15, 16, 17, 18, 19], "train_it": [7, 17], "train_load": [7, 17], "train_pytorch": 15, "train_set": [7, 17], "train_tensorflow": 15, "trainabl": [5, 9], "tranform": 10, "transcrib": 19, "transfer": [5, 7], "transfo": 10, "transform": [5, 7, 9], "translat": 2, "troll": 2, "true": [7, 8, 9, 10, 11, 13, 14, 15, 17, 18, 19], "truth": 11, "tune": [1, 18], "tupl": [7, 8, 10, 11], "two": [8, 14], "txt": 7, "type": [8, 11, 15, 18, 19], "typic": 19, "u": [2, 3], "ucsd": 7, "udac": 3, "uint8": [8, 9, 11, 19], "ukrainian": 7, "unaccept": 2, "underli": [17, 19], "underneath": 8, "understand": [5, 7, 19], "uniform": [9, 10], "uniformli": 10, "uninterrupt": [8, 19], "union": 11, "unit": 1, "unittest": 3, "unlock": 8, "unoffici": 9, "unprofession": 2, "unsolicit": 2, "unsupervis": 5, "unwelcom": 2, "up": [9, 19], "updat": 11, "upgrad": 3, "upper": [7, 10], "uppercas": 17, "url": 8, "us": [2, 3, 4, 7, 9, 11, 12, 13, 14, 15, 16, 19], "usabl": 19, "usag": [14, 18], "use_polygon": [7, 11, 17], "useabl": 19, "user": [5, 8, 12], "utf": 19, "util": 18, "v1": 15, "v3": [9, 15, 19], "valid": 17, "valu": [3, 8, 10, 19], "valuabl": 5, "variabl": 14, "varieti": 7, "veri": 9, "verma": 1, "version": [2, 3, 4, 18, 19], "vgg": 9, "vgg16": 15, "vgg16_bn_r": 9, "via": 2, "video": 1, "vietnames": 7, "view": [5, 7], "viewpoint": 2, "violat": 2, "visibl": 2, "vision": [5, 7, 9], "visiondataset": 7, "visiontransform": 9, "visual": [4, 5, 16], "visualize_pag": 11, "vit_": 9, "vit_b": 9, "vitstr": [5, 9, 18], "vitstr_bas": [9, 19], "vitstr_smal": [9, 13, 18, 19], "viz": 4, "vocab": [13, 15, 17, 18, 19], "vocabulari": [7, 13, 15], "w": [8, 9, 10, 11], "w3": 19, "wa": 2, "wai": [2, 5, 17], "want": [3, 18, 19], "warmup": 19, "wasn": 3, "we": [1, 2, 3, 4, 5, 8, 10, 13, 15, 17, 18, 19], "weasyprint": 8, "web": [3, 8], "websit": 7, "welcom": 2, "well": [1, 2, 18], "were": [2, 8, 19], "what": [1, 2], "when": [2, 3, 9], "whenev": 3, "where": [3, 8, 10, 11], "whether": [3, 7, 8, 10, 11, 17, 19], "which": [2, 9, 14, 16, 17, 19], "whichev": 4, "while": [10, 19], "why": 2, "width": [8, 10], "wiki": 2, "wildreceipt": [5, 7, 17], "window": [9, 11], "wish": 3, "within": 2, "without": [2, 7, 9], "wonder": 3, "word": [5, 7, 9, 11, 19], "word_1_1": 19, "word_1_2": 19, "word_1_3": 19, "wordgener": [7, 17], "words_onli": 11, "work": [1, 13, 14, 19], "workflow": 3, "worklow": 3, "world": [11, 19], "worth": 9, "wrap": 19, "wrapper": [7, 10], "write": 14, "written": [2, 8], "www": [2, 8, 19], "x": [8, 10, 11], "x_ascend": 19, "x_descend": 19, "x_i": 11, "x_size": 19, "x_wconf": 19, "xhtml": 19, "xmax": 8, "xmin": 8, "xml": 19, "xml_bytes_str": 19, "xml_element": 19, "xml_output": 19, "xmln": 19, "y": 11, "y_i": 11, "y_j": 11, "yet": 16, "ymax": 8, "ymin": 8, "yolov8": 16, "you": [3, 4, 7, 8, 9, 13, 14, 15, 16, 17, 18, 19], "your": [3, 5, 8, 11, 19], "yoursit": 8, "yugesh": 1, "zero": [10, 11], "zoo": 13, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 7, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 7, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": 7, "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 7, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 7, "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 7, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 7, "\u00e4\u00f6\u00e4\u00f6": 7, "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 7, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": 7, "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": 7, "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": 7, "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": 7, "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": 7, "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0905": 7, "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": 7, "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": 7, "\u0950": 7, "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": 7, "\u09bd": 7, "\u09ce": 7, "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": 7}, "titles": ["Changelog", "Community resources", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.contrib", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Integrate contributions into your pipeline", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 3, "0": 0, "01": 0, "02": 0, "03": 0, "04": 0, "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 2], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 2], "2021": 0, "2022": 0, "2023": 0, "2024": 0, "21": 0, "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 2], "31": 0, "4": [0, 2], "5": 0, "6": 0, "7": 0, "8": 0, "9": 0, "advanc": 19, "approach": 19, "architectur": 19, "arg": [7, 8, 9, 10, 11], "artefact": 8, "artefactdetect": 16, "attribut": 2, "avail": [16, 17, 19], "aw": 14, "ban": 2, "block": 8, "bug": 3, "changelog": 0, "choos": [17, 19], "classif": [9, 13, 15], "code": [2, 3], "codebas": 3, "commit": 3, "commun": [1, 15], "compos": 10, "conda": 4, "conduct": 2, "connect": 3, "continu": 3, "contrib": 6, "contribut": [3, 6, 16], "contributor": 2, "convent": 15, "correct": 2, "coven": 2, "custom": [7, 13], "data": 17, "dataload": 7, "dataset": [5, 7, 17], "detect": [5, 9, 15, 17, 19], "develop": 3, "do": 19, "doctr": [3, 5, 6, 7, 8, 9, 10, 11, 12], "document": [3, 5, 8], "end": 19, "enforc": 2, "evalu": 11, "export": 18, "factori": 9, "featur": [3, 5], "feedback": 3, "file": 8, "from": 15, "gener": [7, 17], "git": 4, "guidelin": 2, "half": 18, "hub": 15, "huggingfac": 15, "i": 19, "infer": 18, "instal": [3, 4], "integr": [3, 16], "io": 8, "lambda": 14, "let": 3, "line": 8, "linux": 4, "load": [13, 15, 17], "loader": 7, "main": 5, "mode": 3, "model": [5, 9, 13, 15, 18, 19], "modifi": 3, "modul": [6, 16], "name": 15, "notebook": 12, "object": 17, "ocr": [17, 19], "onli": 4, "onnx": 18, "optim": 18, "option": 19, "orient": 13, "our": 2, "output": 19, "own": [13, 17], "packag": 4, "page": 8, "perman": 2, "pipelin": 16, "pledg": 2, "precis": 18, "predictor": 19, "prepar": 18, "prerequisit": 4, "pretrain": 15, "push": 15, "python": 4, "qualiti": 3, "question": 3, "read": 8, "readi": 17, "recognit": [5, 9, 15, 17, 19], "report": 3, "request": 3, "resourc": 1, "respons": 2, "return": [7, 8, 9, 11], "right": 19, "scope": 2, "share": 15, "should": 19, "stage": 19, "standard": 2, "structur": [3, 8], "style": 3, "support": [5, 6, 7, 10], "synthet": [7, 17], "task": 11, "temporari": 2, "test": 3, "text": [5, 19], "train": 13, "transform": 10, "two": 19, "unit": 3, "us": [17, 18], "util": 11, "v0": 0, "verif": 3, "via": 4, "visual": 11, "vocab": 7, "warn": 2, "what": 19, "word": 8, "your": [13, 15, 16, 17, 18], "zoo": [5, 9]}}) \ No newline at end of file diff --git a/latest/using_doctr/custom_models_training.html b/latest/using_doctr/custom_models_training.html index df39d8d568..b714c1f971 100644 --- a/latest/using_doctr/custom_models_training.html +++ b/latest/using_doctr/custom_models_training.html @@ -14,7 +14,7 @@ - + Train your own model - docTR documentation @@ -619,7 +619,7 @@

    Loading your custom trained orientation classification model - + diff --git a/latest/using_doctr/running_on_aws.html b/latest/using_doctr/running_on_aws.html index 16ceaca7a1..808ea541cd 100644 --- a/latest/using_doctr/running_on_aws.html +++ b/latest/using_doctr/running_on_aws.html @@ -14,7 +14,7 @@ - + AWS Lambda - docTR documentation @@ -362,7 +362,7 @@

    AWS Lambda - + diff --git a/latest/using_doctr/sharing_models.html b/latest/using_doctr/sharing_models.html index d76b4017f4..c9e978400a 100644 --- a/latest/using_doctr/sharing_models.html +++ b/latest/using_doctr/sharing_models.html @@ -14,7 +14,7 @@ - + Share your model with the community - docTR documentation @@ -544,7 +544,7 @@

    Recognition - + diff --git a/latest/using_doctr/using_contrib_modules.html b/latest/using_doctr/using_contrib_modules.html index 50598dae5d..0c5fffdf9f 100644 --- a/latest/using_doctr/using_contrib_modules.html +++ b/latest/using_doctr/using_contrib_modules.html @@ -14,7 +14,7 @@ - + Integrate contributions into your pipeline - docTR documentation @@ -415,7 +415,7 @@

    ArtefactDetection - + diff --git a/latest/using_doctr/using_datasets.html b/latest/using_doctr/using_datasets.html index 460476dbbf..8a7d4f0a64 100644 --- a/latest/using_doctr/using_datasets.html +++ b/latest/using_doctr/using_datasets.html @@ -14,7 +14,7 @@ - + Choose a ready to use dataset - docTR documentation @@ -642,7 +642,7 @@

    Data Loading - + diff --git a/latest/using_doctr/using_model_export.html b/latest/using_doctr/using_model_export.html index 6124c00ebe..6790dd0642 100644 --- a/latest/using_doctr/using_model_export.html +++ b/latest/using_doctr/using_model_export.html @@ -14,7 +14,7 @@ - + Preparing your model for inference - docTR documentation @@ -467,7 +467,7 @@

    Using your ONNX exported model - + diff --git a/latest/using_doctr/using_models.html b/latest/using_doctr/using_models.html index 61f1f5ab7a..9ead8498e1 100644 --- a/latest/using_doctr/using_models.html +++ b/latest/using_doctr/using_models.html @@ -14,7 +14,7 @@ - + Choosing the right model - docTR documentation @@ -1253,7 +1253,7 @@

    Advanced options - + diff --git a/modules/contrib.html b/modules/contrib.html index 22b0c508a6..b8878635b6 100644 --- a/modules/contrib.html +++ b/modules/contrib.html @@ -14,7 +14,7 @@ - + doctr.contrib - docTR documentation @@ -376,7 +376,7 @@

    Supported contribution modules - + diff --git a/modules/datasets.html b/modules/datasets.html index 0fe4b78d48..dfcacbc96e 100644 --- a/modules/datasets.html +++ b/modules/datasets.html @@ -14,7 +14,7 @@ - + doctr.datasets - docTR documentation @@ -1077,7 +1077,7 @@

    Returns: - + diff --git a/modules/io.html b/modules/io.html index 924d292c59..77e9e017bf 100644 --- a/modules/io.html +++ b/modules/io.html @@ -14,7 +14,7 @@ - + doctr.io - docTR documentation @@ -756,7 +756,7 @@

    Returns: - + diff --git a/modules/models.html b/modules/models.html index bf45d11a71..f4a9833365 100644 --- a/modules/models.html +++ b/modules/models.html @@ -14,7 +14,7 @@ - + doctr.models - docTR documentation @@ -1598,7 +1598,7 @@

    Args: - + diff --git a/modules/transforms.html b/modules/transforms.html index 6d77d16e7b..bc254c867b 100644 --- a/modules/transforms.html +++ b/modules/transforms.html @@ -14,7 +14,7 @@ - + doctr.transforms - docTR documentation @@ -831,7 +831,7 @@

    Args:< - + diff --git a/modules/utils.html b/modules/utils.html index 3dd3ecbd96..6784d81f6f 100644 --- a/modules/utils.html +++ b/modules/utils.html @@ -14,7 +14,7 @@ - + doctr.utils - docTR documentation @@ -711,7 +711,7 @@

    Args: - + diff --git a/notebooks.html b/notebooks.html index f3ea994e49..647f73d4eb 100644 --- a/notebooks.html +++ b/notebooks.html @@ -14,7 +14,7 @@ - + docTR Notebooks - docTR documentation @@ -387,7 +387,7 @@

    docTR Notebooks - + diff --git a/search.html b/search.html index f0693e2c97..0e0da5efb3 100644 --- a/search.html +++ b/search.html @@ -14,7 +14,7 @@ - + Search - docTR documentation @@ -336,7 +336,7 @@ - + diff --git a/searchindex.js b/searchindex.js index 8598997441..df18967072 100644 --- a/searchindex.js +++ b/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles": {"1. Correction": [[1, "correction"]], "2. Warning": [[1, "warning"]], "3. Temporary Ban": [[1, "temporary-ban"]], "4. Permanent Ban": [[1, "permanent-ban"]], "AWS Lambda": [[13, null]], "Advanced options": [[18, "advanced-options"]], "Args:": [[6, "args"], [6, "id4"], [6, "id7"], [6, "id10"], [6, "id13"], [6, "id16"], [6, "id19"], [6, "id22"], [6, "id25"], [6, "id29"], [6, "id32"], [6, "id37"], [6, "id40"], [6, "id46"], [6, "id49"], [6, "id50"], [6, "id51"], [6, "id54"], [6, "id57"], [6, "id60"], [6, "id61"], [7, "args"], [7, "id2"], [7, "id3"], [7, "id4"], [7, "id5"], [7, "id6"], [7, "id7"], [7, "id10"], [7, "id12"], [7, "id14"], [7, "id16"], [7, "id20"], [7, "id24"], [7, "id28"], [8, "args"], [8, "id3"], [8, "id8"], [8, "id13"], [8, "id17"], [8, "id21"], [8, "id26"], [8, "id31"], [8, "id36"], [8, "id41"], [8, "id46"], [8, "id50"], [8, "id54"], [8, "id59"], [8, "id63"], [8, "id68"], [8, "id73"], [8, "id77"], [8, "id81"], [8, "id85"], [8, "id90"], [8, "id95"], [8, "id99"], [8, "id104"], [8, "id109"], [8, "id114"], [8, "id119"], [8, "id123"], [8, "id127"], [8, "id132"], [8, "id137"], [8, "id142"], [8, "id146"], [8, "id150"], [8, "id155"], [8, "id159"], [8, "id163"], [8, "id167"], [8, "id169"], [8, "id171"], [8, "id173"], [9, "args"], [9, "id1"], [9, "id2"], [9, "id3"], [9, "id4"], [9, "id5"], [9, "id6"], [9, "id7"], [9, "id8"], [9, "id9"], [9, "id10"], [9, "id11"], [9, "id12"], [9, "id13"], [9, "id14"], [9, "id15"], [9, "id16"], [9, "id17"], [9, "id18"], [9, "id19"], [10, "args"], [10, "id3"], [10, "id4"], [10, "id5"], [10, "id6"], [10, "id7"], [10, "id8"], [10, "id9"]], "Artefact": [[7, "artefact"]], "ArtefactDetection": [[15, "artefactdetection"]], "Attribution": [[1, "attribution"]], "Available Datasets": [[16, "available-datasets"]], "Available architectures": [[18, "available-architectures"], [18, "id1"], [18, "id2"]], "Available contribution modules": [[15, "available-contribution-modules"]], "Block": [[7, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[16, null]], "Choosing the right model": [[18, null]], "Classification": [[14, "classification"]], "Code quality": [[2, "code-quality"]], "Code style verification": [[2, "code-style-verification"]], "Codebase structure": [[2, "codebase-structure"]], "Commits": [[2, "commits"]], "Composing transformations": [[9, "composing-transformations"]], "Continuous Integration": [[2, "continuous-integration"]], "Contributing to docTR": [[2, null]], "Contributor Covenant Code of Conduct": [[1, null]], "Custom dataset loader": [[6, "custom-dataset-loader"]], "Custom orientation classification models": [[12, "custom-orientation-classification-models"]], "Data Loading": [[16, "data-loading"]], "Dataloader": [[6, "dataloader"]], "Detection": [[14, "detection"], [16, "detection"]], "Detection predictors": [[18, "detection-predictors"]], "Developer mode installation": [[2, "developer-mode-installation"]], "Developing docTR": [[2, "developing-doctr"]], "Document": [[7, "document"]], "Document structure": [[7, "document-structure"]], "End-to-End OCR": [[18, "end-to-end-ocr"]], "Enforcement": [[1, "enforcement"]], "Enforcement Guidelines": [[1, "enforcement-guidelines"]], "Enforcement Responsibilities": [[1, "enforcement-responsibilities"]], "Export to ONNX": [[17, "export-to-onnx"]], "Feature requests & bug report": [[2, "feature-requests-bug-report"]], "Feedback": [[2, "feedback"]], "File reading": [[7, "file-reading"]], "Half-precision": [[17, "half-precision"]], "Installation": [[3, null]], "Integrate contributions into your pipeline": [[15, null]], "Let\u2019s connect": [[2, "let-s-connect"]], "Line": [[7, "line"]], "Loading from Huggingface Hub": [[14, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[12, "loading-your-custom-trained-model"]], "Loading your custom trained orientation classification model": [[12, "loading-your-custom-trained-orientation-classification-model"]], "Main Features": [[4, "main-features"]], "Model optimization": [[17, "model-optimization"]], "Model zoo": [[4, "model-zoo"]], "Modifying the documentation": [[2, "modifying-the-documentation"]], "Naming conventions": [[14, "naming-conventions"]], "OCR": [[16, "ocr"]], "Object Detection": [[16, "object-detection"]], "Our Pledge": [[1, "our-pledge"]], "Our Standards": [[1, "our-standards"]], "Page": [[7, "page"]], "Preparing your model for inference": [[17, null]], "Prerequisites": [[3, "prerequisites"]], "Pretrained community models": [[14, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[14, "pushing-to-the-huggingface-hub"]], "Questions": [[2, "questions"]], "Recognition": [[14, "recognition"], [16, "recognition"]], "Recognition predictors": [[18, "recognition-predictors"]], "Returns:": [[6, "returns"], [7, "returns"], [7, "id11"], [7, "id13"], [7, "id15"], [7, "id19"], [7, "id23"], [7, "id27"], [7, "id31"], [8, "returns"], [8, "id6"], [8, "id11"], [8, "id16"], [8, "id20"], [8, "id24"], [8, "id29"], [8, "id34"], [8, "id39"], [8, "id44"], [8, "id49"], [8, "id53"], [8, "id57"], [8, "id62"], [8, "id66"], [8, "id71"], [8, "id76"], [8, "id80"], [8, "id84"], [8, "id88"], [8, "id93"], [8, "id98"], [8, "id102"], [8, "id107"], [8, "id112"], [8, "id117"], [8, "id122"], [8, "id126"], [8, "id130"], [8, "id135"], [8, "id140"], [8, "id145"], [8, "id149"], [8, "id153"], [8, "id158"], [8, "id162"], [8, "id166"], [8, "id168"], [8, "id170"], [8, "id172"], [10, "returns"]], "Scope": [[1, "scope"]], "Share your model with the community": [[14, null]], "Supported Vocabs": [[6, "supported-vocabs"]], "Supported contribution modules": [[5, "supported-contribution-modules"]], "Supported datasets": [[4, "supported-datasets"]], "Supported transformations": [[9, "supported-transformations"]], "Synthetic dataset generator": [[6, "synthetic-dataset-generator"], [16, "synthetic-dataset-generator"]], "Task evaluation": [[10, "task-evaluation"]], "Text Detection": [[18, "text-detection"]], "Text Recognition": [[18, "text-recognition"]], "Text detection models": [[4, "text-detection-models"]], "Text recognition models": [[4, "text-recognition-models"]], "Train your own model": [[12, null]], "Two-stage approaches": [[18, "two-stage-approaches"]], "Unit tests": [[2, "unit-tests"]], "Use your own datasets": [[16, "use-your-own-datasets"]], "Using your ONNX exported model": [[17, "using-your-onnx-exported-model"]], "Via Conda (Only for Linux)": [[3, "via-conda-only-for-linux"]], "Via Git": [[3, "via-git"]], "Via Python Package": [[3, "via-python-package"]], "Visualization": [[10, "visualization"]], "What should I do with the output?": [[18, "what-should-i-do-with-the-output"]], "Word": [[7, "word"]], "docTR Notebooks": [[11, null]], "docTR Vocabs": [[6, "id62"]], "docTR: Document Text Recognition": [[4, null]], "doctr.contrib": [[5, null]], "doctr.datasets": [[6, null], [6, "datasets"]], "doctr.io": [[7, null]], "doctr.models": [[8, null]], "doctr.models.classification": [[8, "doctr-models-classification"]], "doctr.models.detection": [[8, "doctr-models-detection"]], "doctr.models.factory": [[8, "doctr-models-factory"]], "doctr.models.recognition": [[8, "doctr-models-recognition"]], "doctr.models.zoo": [[8, "doctr-models-zoo"]], "doctr.transforms": [[9, null]], "doctr.utils": [[10, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]], "v0.7.0 (2023-09-09)": [[0, "v0-7-0-2023-09-09"]], "v0.8.0 (2024-02-28)": [[0, "v0-8-0-2024-02-28"]], "v0.8.1 (2024-03-04)": [[0, "v0-8-1-2024-03-04"]], "v0.9.0 (2024-08-08)": [[0, "v0-9-0-2024-08-08"]]}, "docnames": ["changelog", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/contrib", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_contrib_modules", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/contrib.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_contrib_modules.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[7, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[7, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[9, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[6, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[9, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[9, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[6, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[8, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[6, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[8, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[8, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[7, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[8, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[6, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[10, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[6, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[7, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[7, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[6, "doctr.datasets.encode_sequences", false]], "fast_base() (in module doctr.models.detection)": [[8, "doctr.models.detection.fast_base", false]], "fast_small() (in module doctr.models.detection)": [[8, "doctr.models.detection.fast_small", false]], "fast_tiny() (in module doctr.models.detection)": [[8, "doctr.models.detection.fast_tiny", false]], "from_hub() (in module doctr.models.factory)": [[8, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[7, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[7, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[7, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[6, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[9, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[9, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[6, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[6, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[6, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[6, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[6, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[8, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[9, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[7, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[8, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet34() (in module doctr.models.detection)": [[8, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[8, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[10, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[8, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[8, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[6, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_crop_orientation() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_small_crop_orientation", false]], "mobilenet_v3_small_page_orientation() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_small_page_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[9, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[8, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[6, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[10, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[9, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[7, "doctr.io.Page", false]], "page_orientation_predictor() (in module doctr.models.classification)": [[8, "doctr.models.classification.page_orientation_predictor", false]], "parseq() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[8, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[9, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[9, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[9, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[9, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[9, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[9, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[9, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[9, "doctr.transforms.RandomJpegQuality", false]], "randomresize (class in doctr.transforms)": [[9, "doctr.transforms.RandomResize", false]], "randomrotate (class in doctr.transforms)": [[9, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[9, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[9, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[7, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[7, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[7, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[7, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[6, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[9, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[8, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[8, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[8, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[8, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[7, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[7, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[6, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[10, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[10, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[10, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[10, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[6, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[6, "doctr.datasets.SVT", false]], "synthtext (class in doctr.datasets)": [[6, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[10, "doctr.utils.metrics.TextMatch", false]], "textnet_base() (in module doctr.models.classification)": [[8, "doctr.models.classification.textnet_base", false]], "textnet_small() (in module doctr.models.classification)": [[8, "doctr.models.classification.textnet_small", false]], "textnet_tiny() (in module doctr.models.classification)": [[8, "doctr.models.classification.textnet_tiny", false]], "togray (class in doctr.transforms)": [[9, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[10, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[10, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[10, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[10, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[8, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[10, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[8, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[8, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.vitstr_small", false]], "wildreceipt (class in doctr.datasets)": [[6, "doctr.datasets.WILDRECEIPT", false]], "word (class in doctr.io)": [[7, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[6, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[6, 0, 1, "", "CORD"], [6, 0, 1, "", "CharacterGenerator"], [6, 0, 1, "", "DetectionDataset"], [6, 0, 1, "", "DocArtefacts"], [6, 0, 1, "", "FUNSD"], [6, 0, 1, "", "IC03"], [6, 0, 1, "", "IC13"], [6, 0, 1, "", "IIIT5K"], [6, 0, 1, "", "IIITHWS"], [6, 0, 1, "", "IMGUR5K"], [6, 0, 1, "", "MJSynth"], [6, 0, 1, "", "OCRDataset"], [6, 0, 1, "", "RecognitionDataset"], [6, 0, 1, "", "SROIE"], [6, 0, 1, "", "SVHN"], [6, 0, 1, "", "SVT"], [6, 0, 1, "", "SynthText"], [6, 0, 1, "", "WILDRECEIPT"], [6, 0, 1, "", "WordGenerator"], [6, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[6, 0, 1, "", "DataLoader"]], "doctr.io": [[7, 0, 1, "", "Artefact"], [7, 0, 1, "", "Block"], [7, 0, 1, "", "Document"], [7, 0, 1, "", "DocumentFile"], [7, 0, 1, "", "Line"], [7, 0, 1, "", "Page"], [7, 0, 1, "", "Word"], [7, 1, 1, "", "decode_img_as_tensor"], [7, 1, 1, "", "read_html"], [7, 1, 1, "", "read_img_as_numpy"], [7, 1, 1, "", "read_img_as_tensor"], [7, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[7, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[7, 2, 1, "", "from_images"], [7, 2, 1, "", "from_pdf"], [7, 2, 1, "", "from_url"]], "doctr.io.Page": [[7, 2, 1, "", "show"]], "doctr.models": [[8, 1, 1, "", "kie_predictor"], [8, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[8, 1, 1, "", "crop_orientation_predictor"], [8, 1, 1, "", "magc_resnet31"], [8, 1, 1, "", "mobilenet_v3_large"], [8, 1, 1, "", "mobilenet_v3_large_r"], [8, 1, 1, "", "mobilenet_v3_small"], [8, 1, 1, "", "mobilenet_v3_small_crop_orientation"], [8, 1, 1, "", "mobilenet_v3_small_page_orientation"], [8, 1, 1, "", "mobilenet_v3_small_r"], [8, 1, 1, "", "page_orientation_predictor"], [8, 1, 1, "", "resnet18"], [8, 1, 1, "", "resnet31"], [8, 1, 1, "", "resnet34"], [8, 1, 1, "", "resnet50"], [8, 1, 1, "", "textnet_base"], [8, 1, 1, "", "textnet_small"], [8, 1, 1, "", "textnet_tiny"], [8, 1, 1, "", "vgg16_bn_r"], [8, 1, 1, "", "vit_b"], [8, 1, 1, "", "vit_s"]], "doctr.models.detection": [[8, 1, 1, "", "db_mobilenet_v3_large"], [8, 1, 1, "", "db_resnet50"], [8, 1, 1, "", "detection_predictor"], [8, 1, 1, "", "fast_base"], [8, 1, 1, "", "fast_small"], [8, 1, 1, "", "fast_tiny"], [8, 1, 1, "", "linknet_resnet18"], [8, 1, 1, "", "linknet_resnet34"], [8, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[8, 1, 1, "", "from_hub"], [8, 1, 1, "", "login_to_hub"], [8, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[8, 1, 1, "", "crnn_mobilenet_v3_large"], [8, 1, 1, "", "crnn_mobilenet_v3_small"], [8, 1, 1, "", "crnn_vgg16_bn"], [8, 1, 1, "", "master"], [8, 1, 1, "", "parseq"], [8, 1, 1, "", "recognition_predictor"], [8, 1, 1, "", "sar_resnet31"], [8, 1, 1, "", "vitstr_base"], [8, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[9, 0, 1, "", "ChannelShuffle"], [9, 0, 1, "", "ColorInversion"], [9, 0, 1, "", "Compose"], [9, 0, 1, "", "GaussianBlur"], [9, 0, 1, "", "GaussianNoise"], [9, 0, 1, "", "LambdaTransformation"], [9, 0, 1, "", "Normalize"], [9, 0, 1, "", "OneOf"], [9, 0, 1, "", "RandomApply"], [9, 0, 1, "", "RandomBrightness"], [9, 0, 1, "", "RandomContrast"], [9, 0, 1, "", "RandomCrop"], [9, 0, 1, "", "RandomGamma"], [9, 0, 1, "", "RandomHorizontalFlip"], [9, 0, 1, "", "RandomHue"], [9, 0, 1, "", "RandomJpegQuality"], [9, 0, 1, "", "RandomResize"], [9, 0, 1, "", "RandomRotate"], [9, 0, 1, "", "RandomSaturation"], [9, 0, 1, "", "RandomShadow"], [9, 0, 1, "", "Resize"], [9, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[10, 0, 1, "", "DetectionMetric"], [10, 0, 1, "", "LocalizationConfusion"], [10, 0, 1, "", "OCRMetric"], [10, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[10, 2, 1, "", "summary"], [10, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[10, 2, 1, "", "summary"], [10, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[10, 2, 1, "", "summary"], [10, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[10, 2, 1, "", "summary"], [10, 2, 1, "", "update"]], "doctr.utils.visualization": [[10, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [1, 7, 8, 10, 14, 17], "0": [1, 3, 6, 9, 10, 12, 15, 16, 18], "00": 18, "01": 18, "0123456789": 6, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 6, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 6, "02562": 8, "03": 18, "035": 18, "0361328125": 18, "04": 18, "05": 18, "06": 18, "06640625": 18, "07": 18, "08": [9, 18], "09": 18, "0966796875": 18, "1": [6, 7, 8, 9, 10, 12, 16, 18], "10": [6, 10, 18], "100": [6, 9, 10, 16, 18], "1000": 18, "101": 6, "1024": [8, 12, 18], "104": 6, "106": 6, "108": 6, "1095": 16, "11": 18, "110": 10, "1107": 16, "114": 6, "115": 6, "1156": 16, "116": 6, "118": 6, "11800h": 18, "11th": 18, "12": 18, "120": 6, "123": 6, "126": 6, "1268": 16, "128": [8, 12, 17, 18], "13": 18, "130": 6, "13068": 16, "131": 6, "1337891": 16, "1357421875": 18, "1396484375": 18, "14": 18, "1420": 18, "14470v1": 6, "149": 16, "15": 18, "150": [10, 18], "1552": 18, "16": [8, 17, 18], "1630859375": 18, "1684": 18, "16x16": 8, "17": 18, "1778": 18, "1782": 18, "18": [8, 18], "185546875": 18, "1900": 18, "1910": 8, "19342": 16, "19370": 16, "195": 6, "19598": 16, "199": 18, "1999": 18, "2": [3, 4, 6, 7, 9, 15, 18], "20": 18, "200": 10, "2000": 16, "2003": [4, 6], "2012": 6, "2013": [4, 6], "2015": 6, "2019": 4, "207901": 16, "21": 18, "2103": 6, "2186": 16, "21888": 16, "22": 18, "224": [8, 9], "225": 9, "22672": 16, "229": [9, 16], "23": 18, "233": 16, "236": 6, "24": 18, "246": 16, "249": 16, "25": 18, "2504": 18, "255": [7, 8, 9, 10, 18], "256": 8, "257": 16, "26": 18, "26032": 16, "264": 12, "27": 18, "2700": 16, "2710": 18, "2749": 12, "28": 18, "287": 12, "29": 18, "296": 12, "299": 12, "2d": 18, "3": [3, 4, 7, 8, 9, 10, 17, 18], "30": 18, "300": 16, "3000": 16, "301": 12, "30595": 18, "30ghz": 18, "31": 8, "32": [6, 8, 9, 12, 16, 17, 18], "3232421875": 18, "33": [9, 18], "33402": 16, "33608": 16, "34": [8, 18], "340": 18, "3456": 18, "3515625": 18, "36": 18, "360": 16, "37": [6, 18], "38": 18, "39": 18, "4": [8, 9, 10, 18], "40": 18, "406": 9, "41": 18, "42": 18, "43": 18, "44": 18, "45": 18, "456": 9, "46": 18, "47": 18, "472": 16, "48": [6, 18], "485": 9, "49": 18, "49377": 16, "5": [6, 9, 10, 15, 18], "50": [8, 16, 18], "51": 18, "51171875": 18, "512": 8, "52": [6, 18], "529": 18, "53": 18, "54": 18, "540": 18, "5478515625": 18, "55": 18, "56": 18, "57": 18, "58": [6, 18], "580": 18, "5810546875": 18, "583": 18, "59": 18, "597": 18, "5k": [4, 6], "5m": 18, "6": [9, 18], "60": 9, "600": [8, 10, 18], "61": 18, "62": 18, "626": 16, "63": 18, "64": [8, 9, 18], "641": 18, "647": 16, "65": 18, "66": 18, "67": 18, "68": 18, "69": 18, "693": 12, "694": 12, "695": 12, "6m": 18, "7": 18, "70": [6, 10, 18], "707470": 16, "71": [6, 18], "7100000": 16, "7141797": 16, "7149": 16, "72": 18, "72dpi": 7, "73": 18, "73257": 16, "74": 18, "75": [9, 18], "7581382": 16, "76": 18, "77": 18, "772": 12, "772875": 16, "78": 18, "785": 12, "79": 18, "793533": 16, "796": 16, "798": 12, "7m": 18, "8": [8, 9, 18], "80": 18, "800": [8, 10, 16, 18], "81": 18, "82": 18, "83": 18, "84": 18, "849": 16, "85": 18, "8564453125": 18, "857": 18, "85875": 16, "86": 18, "8603515625": 18, "87": 18, "8707": 16, "88": 18, "89": 18, "9": [3, 9, 18], "90": 18, "90k": 6, "90kdict32px": 6, "91": 18, "914085328578949": 18, "92": 18, "93": 18, "94": [6, 18], "95": [10, 18], "9578408598899841": 18, "96": 18, "97": 18, "98": 18, "99": 18, "9949972033500671": 18, "A": [1, 2, 4, 6, 7, 8, 11, 17], "As": 2, "Be": 18, "Being": 1, "By": 13, "For": [1, 2, 3, 12, 18], "If": [2, 7, 8, 12, 18], "In": [2, 6, 16], "It": [9, 14, 15, 17], "Its": [4, 8], "No": [1, 18], "Of": 6, "Or": [15, 17], "The": [1, 2, 6, 7, 10, 13, 15, 16, 17, 18], "Then": 8, "To": [2, 3, 13, 14, 15, 17, 18], "_": [1, 6, 8], "__call__": 18, "_build": 2, "_i": 10, "ab": 6, "abc": 17, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 6, "abdef": [6, 16], "abl": [16, 18], "about": [1, 16, 18], "abov": 18, "abstractdataset": 6, "abus": 1, "accept": 1, "access": [4, 7, 16, 18], "account": [1, 14], "accur": 18, "accuraci": 10, "achiev": 17, "act": 1, "action": 1, "activ": 4, "ad": [2, 8, 9], "adapt": 1, "add": [9, 10, 14, 18], "add_hook": 18, "add_label": 10, "addit": [2, 3, 7, 15, 18], "addition": [2, 18], "address": [1, 7], "adjust": 9, "advanc": 1, "advantag": 17, "advis": 2, "aesthet": [4, 6], "affect": 1, "after": [14, 18], "ag": 1, "again": 8, "aggreg": [10, 16], "aggress": 1, "align": [1, 7, 9], "all": [1, 2, 5, 6, 7, 9, 10, 15, 16, 18], "allow": [1, 17], "along": 18, "alreadi": [2, 17], "also": [1, 8, 14, 15, 16, 18], "alwai": 16, "an": [1, 2, 4, 6, 7, 8, 10, 15, 17, 18], "analysi": [7, 15], "ancient_greek": 6, "angl": [7, 9], "ani": [1, 6, 7, 8, 9, 10, 17, 18], "annot": 6, "anot": 16, "anoth": [8, 12, 16], "answer": 1, "anyascii": 10, "anyon": 4, "anyth": 15, "api": [2, 4], "apolog": 1, "apologi": 1, "app": 2, "appear": 1, "appli": [1, 6, 9], "applic": [4, 8], "appoint": 1, "appreci": 14, "appropri": [1, 2, 18], "ar": [1, 2, 3, 5, 6, 7, 9, 10, 11, 15, 16, 18], "arab": 6, "arabic_diacrit": 6, "arabic_lett": 6, "arabic_punctu": 6, "arbitrarili": [4, 8], "arch": [8, 14], "architectur": [4, 8, 14, 15], "area": 18, "argument": [6, 7, 8, 10, 12, 18], "around": 1, "arrai": [7, 9, 10], "art": [4, 15], "artefact": [10, 15, 18], "artefact_typ": 7, "artifici": [4, 6], "arxiv": [6, 8], "asarrai": 10, "ascii_lett": 6, "aspect": [4, 8, 9, 18], "assess": 10, "assign": 10, "associ": 7, "assum": 8, "assume_straight_pag": [8, 12, 18], "astyp": [8, 10, 18], "attack": 1, "attend": [4, 8], "attent": [1, 8], "autom": 4, "automat": 18, "autoregress": [4, 8], "avail": [1, 4, 5, 9], "averag": [9, 18], "avoid": [1, 3], "aw": [4, 18], "awar": 18, "azur": 18, "b": [8, 10, 18], "b_j": 10, "back": 2, "backbon": 8, "backend": 18, "background": 16, "bangla": 6, "bar": 15, "bar_cod": 16, "base": [4, 8, 15], "baselin": [4, 8, 18], "batch": [6, 8, 9, 15, 16, 18], "batch_siz": [6, 12, 15, 16, 17], "bblanchon": 3, "bbox": 18, "becaus": 13, "been": [2, 10, 16, 18], "befor": [6, 8, 9, 18], "begin": 10, "behavior": [1, 18], "being": [10, 18], "belong": 18, "benchmark": 18, "best": 1, "better": [11, 18], "between": [9, 10, 18], "bgr": 7, "bilinear": 9, "bin_thresh": 18, "binar": [4, 8, 18], "binari": [7, 17, 18], "bit": 17, "block": [10, 18], "block_1_1": 18, "blur": 9, "bmvc": 6, "bn": 14, "bodi": [1, 18], "bool": [6, 7, 8, 9, 10], "boolean": [8, 18], "both": [4, 6, 9, 16, 18], "bottom": [8, 18], "bound": [6, 7, 8, 9, 10, 15, 16, 18], "box": [6, 7, 8, 9, 10, 15, 16, 18], "box_thresh": 18, "bright": 9, "browser": [2, 4], "build": [2, 3, 17], "built": 2, "byte": [7, 18], "c": [3, 7, 10], "c_j": 10, "cach": [2, 6, 13], "cache_sampl": 6, "call": 17, "callabl": [6, 9], "can": [2, 3, 12, 13, 14, 15, 16, 18], "capabl": [2, 11, 18], "case": [6, 10], "cf": 18, "cfg": 18, "challeng": 6, "challenge2_test_task12_imag": 6, "challenge2_test_task1_gt": 6, "challenge2_training_task12_imag": 6, "challenge2_training_task1_gt": 6, "chang": [13, 18], "channel": [1, 2, 7, 9], "channel_prior": 3, "channelshuffl": 9, "charact": [4, 6, 7, 10, 16, 18], "charactergener": [6, 16], "characterist": 1, "charg": 18, "charset": 18, "chart": 7, "check": [2, 14, 18], "checkpoint": 8, "chip": 3, "ci": 2, "clarifi": 1, "clariti": 1, "class": [1, 6, 7, 9, 10, 18], "class_nam": 12, "classif": [16, 18], "classmethod": 7, "clear": 2, "clone": 3, "close": 2, "co": 14, "code": [4, 7, 15], "codecov": 2, "colab": 11, "collate_fn": 6, "collect": [7, 15], "color": 9, "colorinvers": 9, "column": 7, "com": [1, 3, 7, 8, 14], "combin": 18, "command": [2, 15], "comment": 1, "commit": 1, "common": [1, 9, 10, 17], "commun": 1, "compar": 4, "comparison": [10, 18], "competit": 6, "compil": [11, 18], "complaint": 1, "complementari": 10, "complet": 2, "compon": 18, "compos": [6, 18], "comprehens": 18, "comput": [6, 10, 17, 18], "conf_threshold": 15, "confid": [7, 18], "config": [3, 8], "configur": 8, "confus": 10, "consecut": [9, 18], "consequ": 1, "consid": [1, 2, 6, 7, 10, 18], "consist": 18, "consolid": [4, 6], "constant": 9, "construct": 1, "contact": 1, "contain": [5, 6, 11, 16, 18], "content": [6, 7, 18], "context": 8, "contib": 3, "continu": 1, "contrast": 9, "contrast_factor": 9, "contrib": [3, 15], "contribut": 1, "contributor": 2, "convers": 7, "convert": [7, 9], "convolut": 8, "coordin": [7, 18], "cord": [4, 6, 16, 18], "core": [10, 18], "corner": 18, "correct": 9, "correspond": [3, 7, 9, 18], "could": [1, 15], "counterpart": 10, "cover": 2, "coverag": 2, "cpu": [4, 12, 17], "creat": 14, "crnn": [4, 8, 14], "crnn_mobilenet_v3_larg": [8, 14, 18], "crnn_mobilenet_v3_smal": [8, 17, 18], "crnn_vgg16_bn": [8, 12, 14, 18], "crop": [7, 8, 9, 12, 16, 18], "crop_orient": [7, 18], "crop_orientation_predictor": [8, 12], "crop_param": 12, "cuda": 17, "currenc": 6, "current": [2, 12, 18], "custom": [14, 15, 17, 18], "custom_crop_orientation_model": 12, "custom_page_orientation_model": 12, "customhook": 18, "cvit": 4, "czczup": 8, "czech": 6, "d": [6, 16], "danish": 6, "data": [4, 6, 7, 9, 10, 12, 14], "dataload": 16, "dataset": [8, 12, 18], "dataset_info": 6, "date": [12, 18], "db": 14, "db_mobilenet_v3_larg": [8, 14, 18], "db_resnet34": 18, "db_resnet50": [8, 12, 14, 18], "dbnet": [4, 8], "deal": [11, 18], "decis": 1, "decod": 7, "decode_img_as_tensor": 7, "dedic": 17, "deem": 1, "deep": [8, 18], "def": 18, "default": [3, 7, 12, 13, 18], "defer": 16, "defin": [10, 17], "degre": [7, 9, 18], "degress": 7, "delet": 2, "delimit": 18, "delta": 9, "demo": [2, 4], "demonstr": 1, "depend": [2, 3, 4, 18], "deploi": 2, "deploy": 4, "derogatori": 1, "describ": 8, "descript": 11, "design": 9, "desir": 7, "det_arch": [8, 12, 14, 17], "det_b": 18, "det_model": [12, 14, 17], "det_param": 12, "det_predictor": [12, 18], "detail": [12, 18], "detect": [6, 7, 10, 11, 12, 15], "detect_languag": 8, "detect_orient": [8, 12, 18], "detection_predictor": [8, 18], "detection_task": [6, 16], "detectiondataset": [6, 16], "detectionmetr": 10, "detectionpredictor": [8, 12], "detector": [4, 8, 15], "deterior": 8, "determin": 1, "dev": [2, 13], "develop": 3, "deviat": 9, "devic": 17, "dict": [7, 10, 18], "dictionari": [7, 10], "differ": 1, "differenti": [4, 8], "digit": [4, 6, 16], "dimens": [7, 10, 18], "dimension": 9, "direct": 6, "directli": [14, 18], "directori": [2, 13], "disabl": [1, 13, 18], "disable_crop_orient": 18, "disable_page_orient": 18, "disclaim": 18, "discuss": 2, "disparag": 1, "displai": [7, 10], "display_artefact": 10, "distribut": 9, "div": 18, "divers": 1, "divid": 7, "do": [2, 3, 8], "doc": [2, 7, 15, 17, 18], "docartefact": [6, 16], "docstr": 2, "doctr": [3, 12, 13, 14, 15, 16, 17, 18], "doctr_cache_dir": 13, "doctr_multiprocessing_dis": 13, "document": [6, 8, 10, 11, 12, 15, 16, 17, 18], "documentbuild": 18, "documentfil": [7, 12, 14, 15, 17], "doesn": 17, "don": [12, 18], "done": 9, "download": [6, 16], "downsiz": 8, "draw": 9, "drop": 6, "drop_last": 6, "dtype": [7, 8, 9, 10, 17], "dual": [4, 6], "dummi": 14, "dummy_img": 18, "dummy_input": 17, "dure": 1, "dutch": 6, "dynam": [6, 15], "dynamic_seq_length": 6, "e": [1, 2, 3, 7, 8], "each": [4, 6, 7, 8, 9, 10, 16, 18], "eas": 2, "easi": [4, 10, 14, 17], "easili": [7, 10, 12, 14, 16, 18], "econom": 1, "edit": 1, "educ": 1, "effect": 18, "effici": [2, 4, 6, 8], "either": [10, 18], "element": [6, 7, 8, 18], "els": [2, 15], "email": 1, "empathi": 1, "en": 18, "enabl": [6, 7], "enclos": 7, "encod": [4, 6, 7, 8, 18], "encode_sequ": 6, "encount": 2, "encrypt": 7, "end": [4, 6, 8, 10], "english": [6, 16], "enough": [2, 18], "ensur": 2, "entri": 6, "environ": [1, 13], "eo": 6, "equiv": 18, "estim": 8, "etc": [7, 15], "ethnic": 1, "evalu": [16, 18], "event": 1, "everyon": 1, "everyth": [2, 18], "exact": [10, 18], "exampl": [1, 2, 4, 6, 8, 14, 18], "exchang": 17, "execut": 18, "exist": 14, "expand": 9, "expect": [7, 9, 10], "experi": 1, "explan": [1, 18], "explicit": 1, "exploit": [4, 8], "export": [7, 8, 10, 11, 15, 18], "export_as_straight_box": [8, 18], "export_as_xml": 18, "export_model_to_onnx": 17, "express": [1, 9], "extens": 7, "extern": [1, 16], "extract": [4, 6], "extractor": 8, "f_": 10, "f_a": 10, "factor": 9, "fair": 1, "fairli": 1, "fals": [6, 7, 8, 9, 10, 12, 18], "faq": 1, "fascan": 14, "fast": [4, 6, 8], "fast_bas": [8, 18], "fast_smal": [8, 18], "fast_tini": [8, 18], "faster": [4, 8, 17], "fasterrcnn_mobilenet_v3_large_fpn": 8, "favorit": 18, "featur": [3, 8, 10, 11, 12, 15], "feedback": 1, "feel": [2, 14], "felix92": 14, "few": [17, 18], "figsiz": 10, "figur": [10, 15], "file": [2, 6], "final": 8, "find": [2, 16], "finnish": 6, "first": [2, 6], "firsthand": 6, "fit": [8, 18], "flag": 18, "flip": 9, "float": [7, 9, 10, 17], "float32": [7, 8, 9, 17], "fn": 9, "focu": 14, "focus": [1, 6], "folder": 6, "follow": [1, 2, 3, 6, 9, 10, 12, 13, 14, 15, 18], "font": 6, "font_famili": 6, "foral": 10, "forc": 2, "forg": 3, "form": [4, 6, 18], "format": [7, 10, 12, 16, 17, 18], "forpost": [4, 6], "forum": 2, "fp16": 17, "frac": 10, "framework": [3, 14, 16, 18], "free": [1, 2, 14], "french": [6, 12, 14, 18], "friendli": 4, "from": [1, 4, 6, 7, 8, 9, 10, 11, 12, 15, 16, 17, 18], "from_hub": [8, 14], "from_imag": [7, 14, 15, 17], "from_pdf": 7, "from_url": 7, "full": [6, 10, 18], "function": [6, 9, 10, 15], "funsd": [4, 6, 16, 18], "further": 16, "futur": 6, "g": [7, 8], "g_": 10, "g_x": 10, "gamma": 9, "gaussian": 9, "gaussianblur": 9, "gaussiannois": 9, "gen": 18, "gender": 1, "gener": [2, 4, 7, 8], "generic_cyrillic_lett": 6, "geometri": [4, 7, 18], "geq": 10, "german": [6, 12, 14], "get": [17, 18], "git": 14, "github": [2, 3, 8, 14], "give": [1, 15], "given": [6, 7, 9, 10, 18], "global": 8, "go": 18, "good": 17, "googl": 2, "googlevis": 4, "gpu": [4, 15, 17], "gracefulli": 1, "graph": [4, 6, 7], "grayscal": 9, "ground": 10, "groung": 10, "group": [4, 18], "gt": 10, "gt_box": 10, "gt_label": 10, "guid": 2, "guidanc": 16, "gvision": 18, "h": [7, 8, 9], "h_": 10, "ha": [2, 6, 10, 16], "handl": [11, 16, 18], "handwrit": 6, "handwritten": 16, "harass": 1, "hardwar": 18, "harm": 1, "hat": 10, "have": [1, 2, 10, 12, 14, 16, 17, 18], "head": [8, 18], "healthi": 1, "hebrew": 6, "height": [7, 9], "hello": [10, 18], "help": 17, "here": [5, 9, 11, 15, 16, 18], "hf": 8, "hf_hub_download": 8, "high": 7, "higher": [3, 6, 18], "hindi": 6, "hindi_digit": 6, "hocr": 18, "hook": 18, "horizont": [7, 9, 18], "hous": 6, "how": [2, 11, 12, 14, 16], "howev": 16, "hsv": 9, "html": [1, 2, 3, 7, 18], "http": [1, 3, 6, 7, 8, 14, 18], "hub": 8, "hue": 9, "huggingfac": 8, "hw": 6, "i": [1, 2, 6, 7, 8, 9, 10, 13, 14, 15, 16, 17], "i7": 18, "ic03": [4, 6, 16], "ic13": [4, 6, 16], "icdar": [4, 6], "icdar2019": 6, "id": 18, "ident": 1, "identifi": 4, "iiit": [4, 6], "iiit5k": [6, 16], "iiithw": [4, 6, 16], "imag": [4, 6, 7, 8, 9, 10, 14, 15, 16, 18], "imagenet": 8, "imageri": 1, "images_90k_norm": 6, "img": [6, 9, 16, 17], "img_cont": 7, "img_fold": [6, 16], "img_path": 7, "img_transform": 6, "imgur5k": [4, 6, 16], "imgur5k_annot": 6, "imlist": 6, "impact": 1, "implement": [6, 7, 8, 9, 10, 18], "import": [6, 7, 8, 9, 10, 12, 14, 15, 16, 17, 18], "improv": 8, "inappropri": 1, "incid": 1, "includ": [1, 6, 16, 17], "inclus": 1, "increas": 9, "independ": 9, "index": [2, 7], "indic": 10, "individu": 1, "infer": [4, 8, 9, 15, 18], "inform": [1, 2, 4, 6, 16], "input": [2, 7, 8, 9, 17, 18], "input_crop": 8, "input_pag": [8, 10, 18], "input_shap": 17, "input_tensor": 8, "inspir": [1, 9], "instal": [14, 15, 17], "instanc": [1, 18], "instanti": [8, 18], "instead": [6, 7, 8], "insult": 1, "int": [6, 7, 9], "int64": 10, "integ": 10, "integr": [4, 14, 16], "intel": 18, "interact": [1, 7, 10], "interfac": [14, 17], "interoper": 17, "interpol": 9, "interpret": [6, 7], "intersect": 10, "invert": 9, "investig": 1, "invis": 1, "involv": [1, 18], "io": [12, 14, 15, 17], "iou": 10, "iou_thresh": 10, "iou_threshold": 15, "irregular": [4, 8, 16], "isn": 6, "issu": [1, 2, 14], "italian": 6, "iter": [6, 9, 16, 18], "its": [7, 8, 9, 10, 16, 18], "itself": [8, 14], "j": 10, "job": 2, "join": 2, "jpeg": 9, "jpegqual": 9, "jpg": [6, 7, 14, 17], "json": [6, 16, 18], "json_output": 18, "jump": 2, "just": 1, "kei": [4, 6], "kera": [8, 17], "kernel": [4, 8, 9], "kernel_shap": 9, "keywoard": 8, "keyword": [6, 7, 8, 10], "kie": [8, 12], "kie_predictor": [8, 12], "kiepredictor": 8, "kind": 1, "know": [2, 17], "kwarg": [6, 7, 8, 10], "l": 10, "l_j": 10, "label": [6, 10, 15, 16], "label_fil": [6, 16], "label_fold": 6, "label_path": [6, 16], "labels_path": [6, 16], "ladder": 1, "lambda": 9, "lambdatransform": 9, "lang": 18, "languag": [1, 4, 6, 7, 8, 14, 18], "larg": [8, 14], "largest": 10, "last": [3, 6], "latenc": 8, "later": 2, "latest": 18, "latin": 6, "layer": 17, "layout": 18, "lead": 1, "leader": 1, "learn": [1, 4, 8, 17, 18], "least": 3, "left": [10, 18], "legacy_french": 6, "length": [6, 18], "less": [17, 18], "level": [1, 6, 10, 18], "leverag": 11, "lf": 14, "librari": [2, 3, 11, 12], "light": 4, "lightweight": 17, "like": 1, "limits_": 10, "line": [4, 8, 10, 18], "line_1_1": 18, "link": 12, "linknet": [4, 8], "linknet_resnet18": [8, 12, 17, 18], "linknet_resnet34": [8, 17, 18], "linknet_resnet50": [8, 18], "list": [6, 7, 9, 10, 14], "ll": 10, "load": [4, 6, 8, 15, 17], "load_state_dict": 12, "load_weight": 12, "loc_pr": 18, "local": [2, 4, 6, 8, 10, 16, 18], "localis": 6, "localizationconfus": 10, "locat": [2, 7, 18], "login": 8, "login_to_hub": [8, 14], "logo": [7, 15, 16], "love": 14, "lower": [9, 10, 18], "m": [2, 10, 18], "m1": 3, "macbook": 3, "machin": 17, "made": 4, "magc_resnet31": 8, "mai": [1, 2], "mail": 1, "main": 11, "maintain": 4, "mainten": 2, "make": [1, 2, 10, 12, 13, 14, 17, 18], "mani": [16, 18], "manipul": 18, "map": [6, 8], "map_loc": 12, "master": [4, 8, 18], "match": [10, 18], "mathcal": 10, "matplotlib": [7, 10], "max": [6, 9, 10], "max_angl": 9, "max_area": 9, "max_char": [6, 16], "max_delta": 9, "max_gain": 9, "max_gamma": 9, "max_qual": 9, "max_ratio": 9, "maximum": [6, 9], "maxval": [8, 9], "mbox": 10, "mean": [9, 10, 12], "meaniou": 10, "meant": [7, 17], "measur": 18, "media": 1, "median": 8, "meet": 12, "member": 1, "memori": [13, 17], "mention": 18, "merg": 6, "messag": 2, "meta": 18, "metadata": 17, "metal": 3, "method": [7, 9, 18], "metric": [10, 18], "middl": 18, "might": [17, 18], "min": 9, "min_area": 9, "min_char": [6, 16], "min_gain": 9, "min_gamma": 9, "min_qual": 9, "min_ratio": 9, "min_val": 9, "minde": [1, 3, 4, 8], "minim": [2, 4], "minimalist": [4, 8], "minimum": [3, 6, 9, 10, 18], "minval": 9, "miss": 3, "mistak": 1, "mixed_float16": 17, "mixed_precis": 17, "mjsynth": [4, 6, 16], "mnt": 6, "mobilenet": [8, 14], "mobilenet_v3_larg": 8, "mobilenet_v3_large_r": 8, "mobilenet_v3_smal": [8, 12], "mobilenet_v3_small_crop_orient": [8, 12], "mobilenet_v3_small_page_orient": [8, 12], "mobilenet_v3_small_r": 8, "mobilenetv3": 8, "modal": [4, 6], "mode": 3, "model": [6, 10, 13, 15, 16], "model_nam": [8, 14, 17], "model_path": [15, 17], "moder": 1, "modif": 2, "modifi": [8, 13, 18], "modul": [3, 7, 8, 9, 10, 18], "more": [2, 16, 18], "most": 18, "mozilla": 1, "multi": [4, 8], "multilingu": [6, 14], "multipl": [6, 7, 9, 18], "multipli": 9, "multiprocess": 13, "my": 8, "my_awesome_model": 14, "my_hook": 18, "n": [6, 10], "name": [6, 8, 17, 18], "nation": 1, "natur": [1, 4, 6], "ndarrai": [6, 7, 9, 10], "necessari": [3, 12, 13], "need": [2, 3, 6, 10, 12, 13, 14, 15, 18], "neg": 9, "nest": 18, "network": [4, 6, 8, 17], "neural": [4, 6, 8, 17], "new": [2, 10], "next": [6, 16], "nois": 9, "noisi": [4, 6], "non": [4, 6, 7, 8, 9, 10], "none": [6, 7, 8, 9, 10, 18], "normal": [8, 9], "norwegian": 6, "note": [0, 2, 6, 8, 12, 14, 15, 17], "now": 2, "np": [8, 9, 10, 18], "num_output_channel": 9, "num_sampl": [6, 16], "number": [6, 9, 10, 18], "numpi": [7, 8, 10, 18], "o": 3, "obb": 15, "obj_detect": 14, "object": [6, 7, 10, 15, 18], "objectness_scor": [7, 18], "oblig": 1, "obtain": 18, "occupi": 17, "ocr": [4, 6, 8, 10, 14], "ocr_carea": 18, "ocr_db_crnn": 10, "ocr_lin": 18, "ocr_pag": 18, "ocr_par": 18, "ocr_predictor": [8, 12, 14, 17, 18], "ocrdataset": [6, 16], "ocrmetr": 10, "ocrpredictor": [8, 12], "ocrx_word": 18, "offens": 1, "offici": [1, 8], "offlin": 1, "offset": 9, "onc": 18, "one": [2, 6, 8, 9, 12, 14, 18], "oneof": 9, "ones": [6, 10], "onli": [2, 8, 9, 10, 12, 14, 16, 17, 18], "onlin": 1, "onnx": 15, "onnxruntim": [15, 17], "onnxtr": 17, "opac": 9, "opacity_rang": 9, "open": [1, 2, 14, 17], "opinion": 1, "optic": [4, 18], "optim": [4, 18], "option": [6, 8, 12], "order": [2, 6, 7, 9], "org": [1, 6, 8, 18], "organ": 7, "orient": [1, 7, 8, 11, 15, 18], "orientationpredictor": 8, "other": [1, 2], "otherwis": [1, 7, 10], "our": [2, 8, 18], "out": [2, 8, 9, 10, 18], "outpout": 18, "output": [7, 9, 17], "output_s": [7, 9], "outsid": 13, "over": [6, 10, 18], "overal": [1, 8], "overlai": 7, "overview": 15, "overwrit": 12, "overwritten": 14, "own": 4, "p": [9, 18], "packag": [2, 4, 10, 13, 15, 16, 17], "pad": [6, 8, 9, 18], "page": [3, 6, 8, 10, 12, 18], "page1": 7, "page2": 7, "page_1": 18, "page_idx": [7, 18], "page_orientation_predictor": [8, 12], "page_param": 12, "pair": 10, "paper": 8, "par_1_1": 18, "paragraph": 18, "paragraph_break": 18, "param": [9, 18], "paramet": [4, 7, 8, 17], "pars": [4, 6], "parseq": [4, 8, 14, 17, 18], "part": [6, 9, 18], "parti": 3, "partial": 18, "particip": 1, "pass": [6, 7, 8, 12, 18], "password": 7, "patch": [8, 10], "path": [6, 7, 15, 16, 17], "path_to_checkpoint": 12, "path_to_custom_model": 17, "path_to_pt": 12, "pattern": 1, "pdf": [7, 8, 11], "pdfpage": 7, "peopl": 1, "per": [9, 18], "perform": [4, 7, 8, 9, 10, 13, 17, 18], "period": 1, "permiss": 1, "permut": [4, 8], "persian_lett": 6, "person": [1, 16], "phase": 18, "photo": 16, "physic": [1, 7], "pick": 9, "pictur": 7, "pip": [2, 3, 15, 17], "pipelin": 18, "pixel": [7, 9, 18], "pleas": 2, "plot": 10, "plt": 10, "plug": 14, "plugin": 3, "png": 7, "point": 17, "polici": 13, "polish": 6, "polit": 1, "polygon": [6, 10, 18], "pool": 8, "portugues": 6, "posit": [1, 10], "possibl": [2, 10, 14, 18], "post": [1, 18], "postprocessor": 18, "potenti": 8, "power": 4, "ppageno": 18, "pre": [2, 8, 17], "precis": [10, 18], "pred": 10, "pred_box": 10, "pred_label": 10, "predefin": 16, "predict": [7, 8, 10, 18], "predictor": [4, 7, 8, 11, 12, 14, 17], "prefer": 16, "preinstal": 3, "preprocessor": [12, 18], "prerequisit": 14, "present": 11, "preserv": [8, 9, 18], "preserve_aspect_ratio": [7, 8, 9, 12, 18], "pretrain": [4, 8, 10, 12, 17, 18], "pretrained_backbon": [8, 12], "print": 18, "prior": 6, "privaci": 1, "privat": 1, "probabl": 9, "problem": 2, "procedur": 9, "process": [2, 4, 7, 12, 18], "processor": 18, "produc": [11, 18], "product": 17, "profession": 1, "project": [2, 16], "promptli": 1, "proper": 2, "properli": 6, "provid": [1, 2, 4, 14, 15, 16, 18], "public": [1, 4], "publicli": 18, "publish": 1, "pull": 14, "punctuat": 6, "pure": 6, "purpos": 2, "push_to_hf_hub": [8, 14], "py": 14, "pypdfium2": [3, 7], "pyplot": [7, 10], "python": [2, 15], "python3": 14, "pytorch": [3, 4, 8, 9, 12, 14, 17, 18], "q": 2, "qr": [7, 15], "qr_code": 16, "qualiti": 9, "question": 1, "quickli": 4, "quicktour": 11, "r": 18, "race": 1, "ramdisk": 6, "rand": [8, 9, 10, 17, 18], "random": [8, 9, 10, 18], "randomappli": 9, "randombright": 9, "randomcontrast": 9, "randomcrop": 9, "randomgamma": 9, "randomhorizontalflip": 9, "randomhu": 9, "randomjpegqu": 9, "randomli": 9, "randomres": 9, "randomrot": 9, "randomsatur": 9, "randomshadow": 9, "rang": 9, "rassi": 14, "ratio": [8, 9, 18], "raw": [7, 10], "re": 17, "read": [4, 6, 8], "read_html": 7, "read_img_as_numpi": 7, "read_img_as_tensor": 7, "read_pdf": 7, "readi": 17, "real": [4, 8, 9], "reason": [1, 4, 6], "rebuild": 2, "rebuilt": 2, "recal": [10, 18], "receipt": [4, 6, 18], "reco_arch": [8, 12, 14, 17], "reco_b": 18, "reco_model": [12, 14, 17], "reco_param": 12, "reco_predictor": 12, "recogn": 18, "recognit": [6, 10, 11, 12], "recognition_predictor": [8, 18], "recognition_task": [6, 16], "recognitiondataset": [6, 16], "recognitionpredictor": [8, 12], "rectangular": 8, "reduc": [3, 9], "refer": [2, 3, 12, 14, 15, 16, 18], "regardless": 1, "region": 18, "regroup": 10, "regular": 16, "reject": 1, "rel": [7, 9, 10, 18], "relat": 7, "releas": [0, 3], "relev": 15, "religion": 1, "remov": 1, "render": [7, 18], "repo": 8, "repo_id": [8, 14], "report": 1, "repositori": [6, 8, 14], "repres": [1, 17, 18], "represent": [4, 8], "request": [1, 14], "requir": [3, 9, 17], "research": 4, "residu": 8, "resiz": [9, 18], "resnet": 8, "resnet18": [8, 14], "resnet31": 8, "resnet34": 8, "resnet50": [8, 14], "resolv": 7, "resolve_block": 18, "resolve_lin": 18, "resourc": 16, "respect": 1, "rest": [2, 9, 10], "restrict": 13, "result": [2, 6, 7, 11, 14, 17, 18], "return": 18, "reusabl": 18, "review": 1, "rgb": [7, 9], "rgb_mode": 7, "rgb_output": 7, "right": [1, 8, 10], "robust": [4, 6], "root": 6, "rotat": [6, 7, 8, 9, 10, 11, 12, 16, 18], "run": [2, 3, 8], "same": [2, 7, 10, 16, 17, 18], "sampl": [6, 16, 18], "sample_transform": 6, "sar": [4, 8], "sar_resnet31": [8, 18], "satur": 9, "save": [8, 16], "scale": [7, 8, 9, 10], "scale_rang": 9, "scan": [4, 6], "scene": [4, 6, 8], "score": [7, 10], "script": [2, 16], "seamless": 4, "seamlessli": [4, 18], "search": 8, "searchabl": 11, "sec": 18, "second": 18, "section": [12, 14, 15, 17, 18], "secur": [1, 13], "see": [1, 2], "seen": 18, "segment": [4, 8, 18], "self": 18, "semant": [4, 8], "send": 18, "sens": 10, "sensit": 16, "separ": 18, "sequenc": [4, 6, 7, 8, 10, 18], "sequenti": [9, 18], "seri": 1, "seriou": 1, "set": [1, 3, 6, 8, 10, 13, 15, 18], "set_global_polici": 17, "sever": [7, 9, 18], "sex": 1, "sexual": 1, "shade": 9, "shape": [4, 7, 8, 9, 10, 18], "share": [13, 16], "shift": 9, "shm": 13, "should": [2, 6, 7, 9, 10], "show": [4, 7, 8, 10, 12, 14, 15], "showcas": [2, 11], "shuffl": [6, 9], "side": 10, "signatur": 7, "signific": 16, "simpl": [4, 8, 17], "simpler": 8, "sinc": [6, 16], "singl": [1, 2, 4, 6], "single_img_doc": 17, "size": [1, 6, 7, 9, 15, 18], "skew": 18, "slack": 2, "slightli": 8, "small": [2, 8, 18], "smallest": 7, "snapshot_download": 8, "snippet": 18, "so": [2, 3, 6, 8, 14, 16], "social": 1, "socio": 1, "some": [3, 11, 14, 16], "someth": 2, "somewher": 2, "sort": 1, "sourc": [6, 7, 8, 9, 10, 14], "space": [1, 18], "span": 18, "spanish": 6, "spatial": [4, 6, 7], "specif": [2, 3, 10, 12, 16, 18], "specifi": [1, 6, 7], "speed": [4, 8, 18], "sphinx": 2, "sroie": [4, 6, 16], "stabl": 3, "stackoverflow": 2, "stage": 4, "standalon": 11, "standard": 9, "start": 6, "state": [4, 10, 15], "static": 10, "statu": 1, "std": [9, 12], "step": 13, "still": 18, "str": [6, 7, 8, 9, 10], "straight": [6, 8, 16, 18], "straighten": 18, "straighten_pag": [8, 12, 18], "straigten_pag": 12, "stream": 7, "street": [4, 6], "strict": 3, "strictli": 10, "string": [6, 7, 10, 18], "strive": 3, "strong": [4, 8], "structur": [17, 18], "subset": [6, 18], "suggest": [2, 14], "sum": 10, "summari": 10, "support": [3, 12, 15, 17, 18], "sustain": 1, "svhn": [4, 6, 16], "svt": [6, 16], "swedish": 6, "symmetr": [8, 9, 18], "symmetric_pad": [8, 9, 18], "synthet": 4, "synthtext": [4, 6, 16], "system": 18, "t": [2, 6, 12, 17, 18], "tabl": [14, 15, 16], "take": [1, 6, 18], "target": [6, 7, 9, 10, 16], "target_s": 6, "task": [4, 6, 8, 14, 16, 18], "task2": 6, "team": 3, "techminde": 3, "templat": [2, 4], "tensor": [6, 7, 9, 18], "tensorflow": [3, 4, 7, 8, 9, 12, 14, 17, 18], "tensorspec": 17, "term": 1, "test": [6, 16], "test_set": 6, "text": [6, 7, 8, 10, 16], "text_output": 18, "textmatch": 10, "textnet": 8, "textnet_bas": 8, "textnet_smal": 8, "textnet_tini": 8, "textract": [4, 18], "textstylebrush": [4, 6], "textual": [4, 6, 7, 8, 18], "tf": [3, 7, 8, 9, 14, 17], "than": [2, 10, 14], "thank": 2, "thei": [1, 10], "them": [6, 18], "thi": [1, 2, 3, 5, 6, 9, 10, 12, 13, 14, 16, 17, 18], "thing": [17, 18], "third": 3, "those": [1, 7, 18], "threaten": 1, "threshold": 18, "through": [1, 9, 15, 16], "tilman": 14, "time": [1, 4, 8, 10, 16], "tini": 8, "titl": [7, 18], "tm": 18, "tmp": 13, "togeth": [2, 7], "tograi": 9, "tool": 16, "top": [10, 17, 18], "topic": 2, "torch": [3, 9, 12, 14, 17], "torchvis": 9, "total": 12, "toward": [1, 3], "train": [2, 6, 8, 9, 14, 15, 16, 17, 18], "train_it": [6, 16], "train_load": [6, 16], "train_pytorch": 14, "train_set": [6, 16], "train_tensorflow": 14, "trainabl": [4, 8], "tranform": 9, "transcrib": 18, "transfer": [4, 6], "transfo": 9, "transform": [4, 6, 8], "translat": 1, "troll": 1, "true": [6, 7, 8, 9, 10, 12, 13, 14, 16, 17, 18], "truth": 10, "tune": 17, "tupl": [6, 7, 9, 10], "two": [7, 13], "txt": 6, "type": [7, 10, 14, 17, 18], "typic": 18, "u": [1, 2], "ucsd": 6, "udac": 2, "uint8": [7, 8, 10, 18], "ukrainian": 6, "unaccept": 1, "underli": [16, 18], "underneath": 7, "understand": [4, 6, 18], "uniform": [8, 9], "uniformli": 9, "uninterrupt": [7, 18], "union": 10, "unittest": 2, "unlock": 7, "unoffici": 8, "unprofession": 1, "unsolicit": 1, "unsupervis": 4, "unwelcom": 1, "up": [8, 18], "updat": 10, "upgrad": 2, "upper": [6, 9], "uppercas": 16, "url": 7, "us": [1, 2, 3, 6, 8, 10, 11, 12, 13, 14, 15, 18], "usabl": 18, "usag": [13, 17], "use_polygon": [6, 10, 16], "useabl": 18, "user": [4, 7, 11], "utf": 18, "util": 17, "v1": 14, "v3": [8, 14, 18], "valid": 16, "valu": [2, 7, 9, 18], "valuabl": 4, "variabl": 13, "varieti": 6, "veri": 8, "version": [1, 2, 3, 17, 18], "vgg": 8, "vgg16": 14, "vgg16_bn_r": 8, "via": 1, "vietnames": 6, "view": [4, 6], "viewpoint": 1, "violat": 1, "visibl": 1, "vision": [4, 6, 8], "visiondataset": 6, "visiontransform": 8, "visual": [3, 4, 15], "visualize_pag": 10, "vit_": 8, "vit_b": 8, "vitstr": [4, 8, 17], "vitstr_bas": [8, 18], "vitstr_smal": [8, 12, 17, 18], "viz": 3, "vocab": [12, 14, 16, 17, 18], "vocabulari": [6, 12, 14], "w": [7, 8, 9, 10], "w3": 18, "wa": 1, "wai": [1, 4, 16], "want": [2, 17, 18], "warmup": 18, "wasn": 2, "we": [1, 2, 3, 4, 7, 9, 12, 14, 16, 17, 18], "weasyprint": 7, "web": [2, 7], "websit": 6, "welcom": 1, "well": [1, 17], "were": [1, 7, 18], "what": 1, "when": [1, 2, 8], "whenev": 2, "where": [2, 7, 9, 10], "whether": [2, 6, 7, 9, 10, 16, 18], "which": [1, 8, 13, 15, 16, 18], "whichev": 3, "while": [9, 18], "why": 1, "width": [7, 9], "wiki": 1, "wildreceipt": [4, 6, 16], "window": [8, 10], "wish": 2, "within": 1, "without": [1, 6, 8], "wonder": 2, "word": [4, 6, 8, 10, 18], "word_1_1": 18, "word_1_2": 18, "word_1_3": 18, "wordgener": [6, 16], "words_onli": 10, "work": [12, 13, 18], "workflow": 2, "worklow": 2, "world": [10, 18], "worth": 8, "wrap": 18, "wrapper": [6, 9], "write": 13, "written": [1, 7], "www": [1, 7, 18], "x": [7, 9, 10], "x_ascend": 18, "x_descend": 18, "x_i": 10, "x_size": 18, "x_wconf": 18, "xhtml": 18, "xmax": 7, "xmin": 7, "xml": 18, "xml_bytes_str": 18, "xml_element": 18, "xml_output": 18, "xmln": 18, "y": 10, "y_i": 10, "y_j": 10, "yet": 15, "ymax": 7, "ymin": 7, "yolov8": 15, "you": [2, 3, 6, 7, 8, 12, 13, 14, 15, 16, 17, 18], "your": [2, 4, 7, 10, 18], "yoursit": 7, "zero": [9, 10], "zoo": 12, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 6, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 6, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": 6, "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 6, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 6, "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 6, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 6, "\u00e4\u00f6\u00e4\u00f6": 6, "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 6, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": 6, "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": 6, "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": 6, "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 6, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": 6, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": 6, "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": 6, "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": 6, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 6, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 6, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 6, "\u067e\u0686\u06a2\u06a4\u06af": 6, "\u0905": 6, "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": 6, "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": 6, "\u0950": 6, "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": 6, "\u09bd": 6, "\u09ce": 6, "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": 6}, "titles": ["Changelog", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.contrib", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Integrate contributions into your pipeline", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 2, "0": 0, "01": 0, "02": 0, "03": 0, "04": 0, "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 1], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 1], "2021": 0, "2022": 0, "2023": 0, "2024": 0, "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 1], "31": 0, "4": [0, 1], "5": 0, "6": 0, "7": 0, "8": 0, "9": 0, "advanc": 18, "approach": 18, "architectur": 18, "arg": [6, 7, 8, 9, 10], "artefact": 7, "artefactdetect": 15, "attribut": 1, "avail": [15, 16, 18], "aw": 13, "ban": 1, "block": 7, "bug": 2, "changelog": 0, "choos": [16, 18], "classif": [8, 12, 14], "code": [1, 2], "codebas": 2, "commit": 2, "commun": 14, "compos": 9, "conda": 3, "conduct": 1, "connect": 2, "continu": 2, "contrib": 5, "contribut": [2, 5, 15], "contributor": 1, "convent": 14, "correct": 1, "coven": 1, "custom": [6, 12], "data": 16, "dataload": 6, "dataset": [4, 6, 16], "detect": [4, 8, 14, 16, 18], "develop": 2, "do": 18, "doctr": [2, 4, 5, 6, 7, 8, 9, 10, 11], "document": [2, 4, 7], "end": 18, "enforc": 1, "evalu": 10, "export": 17, "factori": 8, "featur": [2, 4], "feedback": 2, "file": 7, "from": 14, "gener": [6, 16], "git": 3, "guidelin": 1, "half": 17, "hub": 14, "huggingfac": 14, "i": 18, "infer": 17, "instal": [2, 3], "integr": [2, 15], "io": 7, "lambda": 13, "let": 2, "line": 7, "linux": 3, "load": [12, 14, 16], "loader": 6, "main": 4, "mode": 2, "model": [4, 8, 12, 14, 17, 18], "modifi": 2, "modul": [5, 15], "name": 14, "notebook": 11, "object": 16, "ocr": [16, 18], "onli": 3, "onnx": 17, "optim": 17, "option": 18, "orient": 12, "our": 1, "output": 18, "own": [12, 16], "packag": 3, "page": 7, "perman": 1, "pipelin": 15, "pledg": 1, "precis": 17, "predictor": 18, "prepar": 17, "prerequisit": 3, "pretrain": 14, "push": 14, "python": 3, "qualiti": 2, "question": 2, "read": 7, "readi": 16, "recognit": [4, 8, 14, 16, 18], "report": 2, "request": 2, "respons": 1, "return": [6, 7, 8, 10], "right": 18, "scope": 1, "share": 14, "should": 18, "stage": 18, "standard": 1, "structur": [2, 7], "style": 2, "support": [4, 5, 6, 9], "synthet": [6, 16], "task": 10, "temporari": 1, "test": 2, "text": [4, 18], "train": 12, "transform": 9, "two": 18, "unit": 2, "us": [16, 17], "util": 10, "v0": 0, "verif": 2, "via": 3, "visual": 10, "vocab": 6, "warn": 1, "what": 18, "word": 7, "your": [12, 14, 15, 16, 17], "zoo": [4, 8]}}) \ No newline at end of file +Search.setIndex({"alltitles": {"1. Correction": [[1, "correction"]], "2. Warning": [[1, "warning"]], "3. Temporary Ban": [[1, "temporary-ban"]], "4. Permanent Ban": [[1, "permanent-ban"]], "AWS Lambda": [[13, null]], "Advanced options": [[18, "advanced-options"]], "Args:": [[6, "args"], [6, "id4"], [6, "id7"], [6, "id10"], [6, "id13"], [6, "id16"], [6, "id19"], [6, "id22"], [6, "id25"], [6, "id29"], [6, "id32"], [6, "id37"], [6, "id40"], [6, "id46"], [6, "id49"], [6, "id50"], [6, "id51"], [6, "id54"], [6, "id57"], [6, "id60"], [6, "id61"], [7, "args"], [7, "id2"], [7, "id3"], [7, "id4"], [7, "id5"], [7, "id6"], [7, "id7"], [7, "id10"], [7, "id12"], [7, "id14"], [7, "id16"], [7, "id20"], [7, "id24"], [7, "id28"], [8, "args"], [8, "id3"], [8, "id8"], [8, "id13"], [8, "id17"], [8, "id21"], [8, "id26"], [8, "id31"], [8, "id36"], [8, "id41"], [8, "id46"], [8, "id50"], [8, "id54"], [8, "id59"], [8, "id63"], [8, "id68"], [8, "id73"], [8, "id77"], [8, "id81"], [8, "id85"], [8, "id90"], [8, "id95"], [8, "id99"], [8, "id104"], [8, "id109"], [8, "id114"], [8, "id119"], [8, "id123"], [8, "id127"], [8, "id132"], [8, "id137"], [8, "id142"], [8, "id146"], [8, "id150"], [8, "id155"], [8, "id159"], [8, "id163"], [8, "id167"], [8, "id169"], [8, "id171"], [8, "id173"], [9, "args"], [9, "id1"], [9, "id2"], [9, "id3"], [9, "id4"], [9, "id5"], [9, "id6"], [9, "id7"], [9, "id8"], [9, "id9"], [9, "id10"], [9, "id11"], [9, "id12"], [9, "id13"], [9, "id14"], [9, "id15"], [9, "id16"], [9, "id17"], [9, "id18"], [9, "id19"], [10, "args"], [10, "id3"], [10, "id4"], [10, "id5"], [10, "id6"], [10, "id7"], [10, "id8"], [10, "id9"]], "Artefact": [[7, "artefact"]], "ArtefactDetection": [[15, "artefactdetection"]], "Attribution": [[1, "attribution"]], "Available Datasets": [[16, "available-datasets"]], "Available architectures": [[18, "available-architectures"], [18, "id1"], [18, "id2"]], "Available contribution modules": [[15, "available-contribution-modules"]], "Block": [[7, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[16, null]], "Choosing the right model": [[18, null]], "Classification": [[14, "classification"]], "Code quality": [[2, "code-quality"]], "Code style verification": [[2, "code-style-verification"]], "Codebase structure": [[2, "codebase-structure"]], "Commits": [[2, "commits"]], "Composing transformations": [[9, "composing-transformations"]], "Continuous Integration": [[2, "continuous-integration"]], "Contributing to docTR": [[2, null]], "Contributor Covenant Code of Conduct": [[1, null]], "Custom dataset loader": [[6, "custom-dataset-loader"]], "Custom orientation classification models": [[12, "custom-orientation-classification-models"]], "Data Loading": [[16, "data-loading"]], "Dataloader": [[6, "dataloader"]], "Detection": [[14, "detection"], [16, "detection"]], "Detection predictors": [[18, "detection-predictors"]], "Developer mode installation": [[2, "developer-mode-installation"]], "Developing docTR": [[2, "developing-doctr"]], "Document": [[7, "document"]], "Document structure": [[7, "document-structure"]], "End-to-End OCR": [[18, "end-to-end-ocr"]], "Enforcement": [[1, "enforcement"]], "Enforcement Guidelines": [[1, "enforcement-guidelines"]], "Enforcement Responsibilities": [[1, "enforcement-responsibilities"]], "Export to ONNX": [[17, "export-to-onnx"]], "Feature requests & bug report": [[2, "feature-requests-bug-report"]], "Feedback": [[2, "feedback"]], "File reading": [[7, "file-reading"]], "Half-precision": [[17, "half-precision"]], "Installation": [[3, null]], "Integrate contributions into your pipeline": [[15, null]], "Let\u2019s connect": [[2, "let-s-connect"]], "Line": [[7, "line"]], "Loading from Huggingface Hub": [[14, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[12, "loading-your-custom-trained-model"]], "Loading your custom trained orientation classification model": [[12, "loading-your-custom-trained-orientation-classification-model"]], "Main Features": [[4, "main-features"]], "Model optimization": [[17, "model-optimization"]], "Model zoo": [[4, "model-zoo"]], "Modifying the documentation": [[2, "modifying-the-documentation"]], "Naming conventions": [[14, "naming-conventions"]], "OCR": [[16, "ocr"]], "Object Detection": [[16, "object-detection"]], "Our Pledge": [[1, "our-pledge"]], "Our Standards": [[1, "our-standards"]], "Page": [[7, "page"]], "Preparing your model for inference": [[17, null]], "Prerequisites": [[3, "prerequisites"]], "Pretrained community models": [[14, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[14, "pushing-to-the-huggingface-hub"]], "Questions": [[2, "questions"]], "Recognition": [[14, "recognition"], [16, "recognition"]], "Recognition predictors": [[18, "recognition-predictors"]], "Returns:": [[6, "returns"], [7, "returns"], [7, "id11"], [7, "id13"], [7, "id15"], [7, "id19"], [7, "id23"], [7, "id27"], [7, "id31"], [8, "returns"], [8, "id6"], [8, "id11"], [8, "id16"], [8, "id20"], [8, "id24"], [8, "id29"], [8, "id34"], [8, "id39"], [8, "id44"], [8, "id49"], [8, "id53"], [8, "id57"], [8, "id62"], [8, "id66"], [8, "id71"], [8, "id76"], [8, "id80"], [8, "id84"], [8, "id88"], [8, "id93"], [8, "id98"], [8, "id102"], [8, "id107"], [8, "id112"], [8, "id117"], [8, "id122"], [8, "id126"], [8, "id130"], [8, "id135"], [8, "id140"], [8, "id145"], [8, "id149"], [8, "id153"], [8, "id158"], [8, "id162"], [8, "id166"], [8, "id168"], [8, "id170"], [8, "id172"], [10, "returns"]], "Scope": [[1, "scope"]], "Share your model with the community": [[14, null]], "Supported Vocabs": [[6, "supported-vocabs"]], "Supported contribution modules": [[5, "supported-contribution-modules"]], "Supported datasets": [[4, "supported-datasets"]], "Supported transformations": [[9, "supported-transformations"]], "Synthetic dataset generator": [[6, "synthetic-dataset-generator"], [16, "synthetic-dataset-generator"]], "Task evaluation": [[10, "task-evaluation"]], "Text Detection": [[18, "text-detection"]], "Text Recognition": [[18, "text-recognition"]], "Text detection models": [[4, "text-detection-models"]], "Text recognition models": [[4, "text-recognition-models"]], "Train your own model": [[12, null]], "Two-stage approaches": [[18, "two-stage-approaches"]], "Unit tests": [[2, "unit-tests"]], "Use your own datasets": [[16, "use-your-own-datasets"]], "Using your ONNX exported model": [[17, "using-your-onnx-exported-model"]], "Via Conda (Only for Linux)": [[3, "via-conda-only-for-linux"]], "Via Git": [[3, "via-git"]], "Via Python Package": [[3, "via-python-package"]], "Visualization": [[10, "visualization"]], "What should I do with the output?": [[18, "what-should-i-do-with-the-output"]], "Word": [[7, "word"]], "docTR Notebooks": [[11, null]], "docTR Vocabs": [[6, "id62"]], "docTR: Document Text Recognition": [[4, null]], "doctr.contrib": [[5, null]], "doctr.datasets": [[6, null], [6, "datasets"]], "doctr.io": [[7, null]], "doctr.models": [[8, null]], "doctr.models.classification": [[8, "doctr-models-classification"]], "doctr.models.detection": [[8, "doctr-models-detection"]], "doctr.models.factory": [[8, "doctr-models-factory"]], "doctr.models.recognition": [[8, "doctr-models-recognition"]], "doctr.models.zoo": [[8, "doctr-models-zoo"]], "doctr.transforms": [[9, null]], "doctr.utils": [[10, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]], "v0.7.0 (2023-09-09)": [[0, "v0-7-0-2023-09-09"]], "v0.8.0 (2024-02-28)": [[0, "v0-8-0-2024-02-28"]], "v0.8.1 (2024-03-04)": [[0, "v0-8-1-2024-03-04"]], "v0.9.0 (2024-08-08)": [[0, "v0-9-0-2024-08-08"]]}, "docnames": ["changelog", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/contrib", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_contrib_modules", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/contrib.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_contrib_modules.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[7, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[7, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[9, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[6, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[9, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[9, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[6, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[8, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[6, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[8, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[8, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[7, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[8, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[6, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[10, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[6, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[7, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[7, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[6, "doctr.datasets.encode_sequences", false]], "fast_base() (in module doctr.models.detection)": [[8, "doctr.models.detection.fast_base", false]], "fast_small() (in module doctr.models.detection)": [[8, "doctr.models.detection.fast_small", false]], "fast_tiny() (in module doctr.models.detection)": [[8, "doctr.models.detection.fast_tiny", false]], "from_hub() (in module doctr.models.factory)": [[8, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[7, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[7, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[7, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[6, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[9, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[9, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[6, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[6, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[6, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[6, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[6, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[8, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[9, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[7, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[8, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet34() (in module doctr.models.detection)": [[8, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[8, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[10, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[8, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[8, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[6, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_crop_orientation() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_small_crop_orientation", false]], "mobilenet_v3_small_page_orientation() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_small_page_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[9, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[8, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[6, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[10, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[9, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[7, "doctr.io.Page", false]], "page_orientation_predictor() (in module doctr.models.classification)": [[8, "doctr.models.classification.page_orientation_predictor", false]], "parseq() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[8, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[9, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[9, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[9, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[9, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[9, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[9, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[9, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[9, "doctr.transforms.RandomJpegQuality", false]], "randomresize (class in doctr.transforms)": [[9, "doctr.transforms.RandomResize", false]], "randomrotate (class in doctr.transforms)": [[9, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[9, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[9, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[7, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[7, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[7, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[7, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[6, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[9, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[8, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[8, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[8, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[8, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[7, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[7, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[6, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[10, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[10, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[10, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[10, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[6, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[6, "doctr.datasets.SVT", false]], "synthtext (class in doctr.datasets)": [[6, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[10, "doctr.utils.metrics.TextMatch", false]], "textnet_base() (in module doctr.models.classification)": [[8, "doctr.models.classification.textnet_base", false]], "textnet_small() (in module doctr.models.classification)": [[8, "doctr.models.classification.textnet_small", false]], "textnet_tiny() (in module doctr.models.classification)": [[8, "doctr.models.classification.textnet_tiny", false]], "togray (class in doctr.transforms)": [[9, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[10, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[10, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[10, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[10, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[8, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[10, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[8, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[8, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.vitstr_small", false]], "wildreceipt (class in doctr.datasets)": [[6, "doctr.datasets.WILDRECEIPT", false]], "word (class in doctr.io)": [[7, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[6, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[6, 0, 1, "", "CORD"], [6, 0, 1, "", "CharacterGenerator"], [6, 0, 1, "", "DetectionDataset"], [6, 0, 1, "", "DocArtefacts"], [6, 0, 1, "", "FUNSD"], [6, 0, 1, "", "IC03"], [6, 0, 1, "", "IC13"], [6, 0, 1, "", "IIIT5K"], [6, 0, 1, "", "IIITHWS"], [6, 0, 1, "", "IMGUR5K"], [6, 0, 1, "", "MJSynth"], [6, 0, 1, "", "OCRDataset"], [6, 0, 1, "", "RecognitionDataset"], [6, 0, 1, "", "SROIE"], [6, 0, 1, "", "SVHN"], [6, 0, 1, "", "SVT"], [6, 0, 1, "", "SynthText"], [6, 0, 1, "", "WILDRECEIPT"], [6, 0, 1, "", "WordGenerator"], [6, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[6, 0, 1, "", "DataLoader"]], "doctr.io": [[7, 0, 1, "", "Artefact"], [7, 0, 1, "", "Block"], [7, 0, 1, "", "Document"], [7, 0, 1, "", "DocumentFile"], [7, 0, 1, "", "Line"], [7, 0, 1, "", "Page"], [7, 0, 1, "", "Word"], [7, 1, 1, "", "decode_img_as_tensor"], [7, 1, 1, "", "read_html"], [7, 1, 1, "", "read_img_as_numpy"], [7, 1, 1, "", "read_img_as_tensor"], [7, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[7, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[7, 2, 1, "", "from_images"], [7, 2, 1, "", "from_pdf"], [7, 2, 1, "", "from_url"]], "doctr.io.Page": [[7, 2, 1, "", "show"]], "doctr.models": [[8, 1, 1, "", "kie_predictor"], [8, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[8, 1, 1, "", "crop_orientation_predictor"], [8, 1, 1, "", "magc_resnet31"], [8, 1, 1, "", "mobilenet_v3_large"], [8, 1, 1, "", "mobilenet_v3_large_r"], [8, 1, 1, "", "mobilenet_v3_small"], [8, 1, 1, "", "mobilenet_v3_small_crop_orientation"], [8, 1, 1, "", "mobilenet_v3_small_page_orientation"], [8, 1, 1, "", "mobilenet_v3_small_r"], [8, 1, 1, "", "page_orientation_predictor"], [8, 1, 1, "", "resnet18"], [8, 1, 1, "", "resnet31"], [8, 1, 1, "", "resnet34"], [8, 1, 1, "", "resnet50"], [8, 1, 1, "", "textnet_base"], [8, 1, 1, "", "textnet_small"], [8, 1, 1, "", "textnet_tiny"], [8, 1, 1, "", "vgg16_bn_r"], [8, 1, 1, "", "vit_b"], [8, 1, 1, "", "vit_s"]], "doctr.models.detection": [[8, 1, 1, "", "db_mobilenet_v3_large"], [8, 1, 1, "", "db_resnet50"], [8, 1, 1, "", "detection_predictor"], [8, 1, 1, "", "fast_base"], [8, 1, 1, "", "fast_small"], [8, 1, 1, "", "fast_tiny"], [8, 1, 1, "", "linknet_resnet18"], [8, 1, 1, "", "linknet_resnet34"], [8, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[8, 1, 1, "", "from_hub"], [8, 1, 1, "", "login_to_hub"], [8, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[8, 1, 1, "", "crnn_mobilenet_v3_large"], [8, 1, 1, "", "crnn_mobilenet_v3_small"], [8, 1, 1, "", "crnn_vgg16_bn"], [8, 1, 1, "", "master"], [8, 1, 1, "", "parseq"], [8, 1, 1, "", "recognition_predictor"], [8, 1, 1, "", "sar_resnet31"], [8, 1, 1, "", "vitstr_base"], [8, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[9, 0, 1, "", "ChannelShuffle"], [9, 0, 1, "", "ColorInversion"], [9, 0, 1, "", "Compose"], [9, 0, 1, "", "GaussianBlur"], [9, 0, 1, "", "GaussianNoise"], [9, 0, 1, "", "LambdaTransformation"], [9, 0, 1, "", "Normalize"], [9, 0, 1, "", "OneOf"], [9, 0, 1, "", "RandomApply"], [9, 0, 1, "", "RandomBrightness"], [9, 0, 1, "", "RandomContrast"], [9, 0, 1, "", "RandomCrop"], [9, 0, 1, "", "RandomGamma"], [9, 0, 1, "", "RandomHorizontalFlip"], [9, 0, 1, "", "RandomHue"], [9, 0, 1, "", "RandomJpegQuality"], [9, 0, 1, "", "RandomResize"], [9, 0, 1, "", "RandomRotate"], [9, 0, 1, "", "RandomSaturation"], [9, 0, 1, "", "RandomShadow"], [9, 0, 1, "", "Resize"], [9, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[10, 0, 1, "", "DetectionMetric"], [10, 0, 1, "", "LocalizationConfusion"], [10, 0, 1, "", "OCRMetric"], [10, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[10, 2, 1, "", "summary"], [10, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[10, 2, 1, "", "summary"], [10, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[10, 2, 1, "", "summary"], [10, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[10, 2, 1, "", "summary"], [10, 2, 1, "", "update"]], "doctr.utils.visualization": [[10, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [1, 7, 8, 10, 14, 17], "0": [1, 3, 6, 9, 10, 12, 15, 16, 18], "00": 18, "01": 18, "0123456789": 6, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 6, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 6, "02562": 8, "03": 18, "035": 18, "0361328125": 18, "04": 18, "05": 18, "06": 18, "06640625": 18, "07": 18, "08": [9, 18], "09": 18, "0966796875": 18, "1": [6, 7, 8, 9, 10, 12, 16, 18], "10": [6, 10, 18], "100": [6, 9, 10, 16, 18], "1000": 18, "101": 6, "1024": [8, 12, 18], "104": 6, "106": 6, "108": 6, "1095": 16, "11": 18, "110": 10, "1107": 16, "114": 6, "115": 6, "1156": 16, "116": 6, "118": 6, "11800h": 18, "11th": 18, "12": 18, "120": 6, "123": 6, "126": 6, "1268": 16, "128": [8, 12, 17, 18], "13": 18, "130": 6, "13068": 16, "131": 6, "1337891": 16, "1357421875": 18, "1396484375": 18, "14": 18, "1420": 18, "14470v1": 6, "149": 16, "15": 18, "150": [10, 18], "1552": 18, "16": [8, 17, 18], "1630859375": 18, "1684": 18, "16x16": 8, "17": 18, "1778": 18, "1782": 18, "18": [8, 18], "185546875": 18, "1900": 18, "1910": 8, "19342": 16, "19370": 16, "195": 6, "19598": 16, "199": 18, "1999": 18, "2": [3, 4, 6, 7, 9, 15, 18], "20": 18, "200": 10, "2000": 16, "2003": [4, 6], "2012": 6, "2013": [4, 6], "2015": 6, "2019": 4, "207901": 16, "21": 18, "2103": 6, "2186": 16, "21888": 16, "22": 18, "224": [8, 9], "225": 9, "22672": 16, "229": [9, 16], "23": 18, "233": 16, "236": 6, "24": 18, "246": 16, "249": 16, "25": 18, "2504": 18, "255": [7, 8, 9, 10, 18], "256": 8, "257": 16, "26": 18, "26032": 16, "264": 12, "27": 18, "2700": 16, "2710": 18, "2749": 12, "28": 18, "287": 12, "29": 18, "296": 12, "299": 12, "2d": 18, "3": [3, 4, 7, 8, 9, 10, 17, 18], "30": 18, "300": 16, "3000": 16, "301": 12, "30595": 18, "30ghz": 18, "31": 8, "32": [6, 8, 9, 12, 16, 17, 18], "3232421875": 18, "33": [9, 18], "33402": 16, "33608": 16, "34": [8, 18], "340": 18, "3456": 18, "3515625": 18, "36": 18, "360": 16, "37": [6, 18], "38": 18, "39": 18, "4": [8, 9, 10, 18], "40": 18, "406": 9, "41": 18, "42": 18, "43": 18, "44": 18, "45": 18, "456": 9, "46": 18, "47": 18, "472": 16, "48": [6, 18], "485": 9, "49": 18, "49377": 16, "5": [6, 9, 10, 15, 18], "50": [8, 16, 18], "51": 18, "51171875": 18, "512": 8, "52": [6, 18], "529": 18, "53": 18, "54": 18, "540": 18, "5478515625": 18, "55": 18, "56": 18, "57": 18, "58": [6, 18], "580": 18, "5810546875": 18, "583": 18, "59": 18, "597": 18, "5k": [4, 6], "5m": 18, "6": [9, 18], "60": 9, "600": [8, 10, 18], "61": 18, "62": 18, "626": 16, "63": 18, "64": [8, 9, 18], "641": 18, "647": 16, "65": 18, "66": 18, "67": 18, "68": 18, "69": 18, "693": 12, "694": 12, "695": 12, "6m": 18, "7": 18, "70": [6, 10, 18], "707470": 16, "71": [6, 18], "7100000": 16, "7141797": 16, "7149": 16, "72": 18, "72dpi": 7, "73": 18, "73257": 16, "74": 18, "75": [9, 18], "7581382": 16, "76": 18, "77": 18, "772": 12, "772875": 16, "78": 18, "785": 12, "79": 18, "793533": 16, "796": 16, "798": 12, "7m": 18, "8": [8, 9, 18], "80": 18, "800": [8, 10, 16, 18], "81": 18, "82": 18, "83": 18, "84": 18, "849": 16, "85": 18, "8564453125": 18, "857": 18, "85875": 16, "86": 18, "8603515625": 18, "87": 18, "8707": 16, "88": 18, "89": 18, "9": [3, 9, 18], "90": 18, "90k": 6, "90kdict32px": 6, "91": 18, "914085328578949": 18, "92": 18, "93": 18, "94": [6, 18], "95": [10, 18], "9578408598899841": 18, "96": 18, "97": 18, "98": 18, "99": 18, "9949972033500671": 18, "A": [1, 2, 4, 6, 7, 8, 11, 17], "As": 2, "Be": 18, "Being": 1, "By": 13, "For": [1, 2, 3, 12, 18], "If": [2, 7, 8, 12, 18], "In": [2, 6, 16], "It": [9, 14, 15, 17], "Its": [4, 8], "No": [1, 18], "Of": 6, "Or": [15, 17], "The": [1, 2, 6, 7, 10, 13, 15, 16, 17, 18], "Then": 8, "To": [2, 3, 13, 14, 15, 17, 18], "_": [1, 6, 8], "__call__": 18, "_build": 2, "_i": 10, "ab": 6, "abc": 17, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 6, "abdef": [6, 16], "abl": [16, 18], "about": [1, 16, 18], "abov": 18, "abstractdataset": 6, "abus": 1, "accept": 1, "access": [4, 7, 16, 18], "account": [1, 14], "accur": 18, "accuraci": 10, "achiev": 17, "act": 1, "action": 1, "activ": 4, "ad": [2, 8, 9], "adapt": 1, "add": [9, 10, 14, 18], "add_hook": 18, "add_label": 10, "addit": [2, 3, 7, 15, 18], "addition": [2, 18], "address": [1, 7], "adjust": 9, "advanc": 1, "advantag": 17, "advis": 2, "aesthet": [4, 6], "affect": 1, "after": [14, 18], "ag": 1, "again": 8, "aggreg": [10, 16], "aggress": 1, "align": [1, 7, 9], "all": [1, 2, 5, 6, 7, 9, 10, 15, 16, 18], "allow": [1, 17], "along": 18, "alreadi": [2, 17], "also": [1, 8, 14, 15, 16, 18], "alwai": 16, "an": [1, 2, 4, 6, 7, 8, 10, 15, 17, 18], "analysi": [7, 15], "ancient_greek": 6, "angl": [7, 9], "ani": [1, 6, 7, 8, 9, 10, 17, 18], "annot": 6, "anot": 16, "anoth": [8, 12, 16], "answer": 1, "anyascii": 10, "anyon": 4, "anyth": 15, "api": [2, 4], "apolog": 1, "apologi": 1, "app": 2, "appear": 1, "appli": [1, 6, 9], "applic": [4, 8], "appoint": 1, "appreci": 14, "appropri": [1, 2, 18], "ar": [1, 2, 3, 5, 6, 7, 9, 10, 11, 15, 16, 18], "arab": 6, "arabic_diacrit": 6, "arabic_lett": 6, "arabic_punctu": 6, "arbitrarili": [4, 8], "arch": [8, 14], "architectur": [4, 8, 14, 15], "area": 18, "argument": [6, 7, 8, 10, 12, 18], "around": 1, "arrai": [7, 9, 10], "art": [4, 15], "artefact": [10, 15, 18], "artefact_typ": 7, "artifici": [4, 6], "arxiv": [6, 8], "asarrai": 10, "ascii_lett": 6, "aspect": [4, 8, 9, 18], "assess": 10, "assign": 10, "associ": 7, "assum": 8, "assume_straight_pag": [8, 12, 18], "astyp": [8, 10, 18], "attack": 1, "attend": [4, 8], "attent": [1, 8], "autom": 4, "automat": 18, "autoregress": [4, 8], "avail": [1, 4, 5, 9], "averag": [9, 18], "avoid": [1, 3], "aw": [4, 18], "awar": 18, "azur": 18, "b": [8, 10, 18], "b_j": 10, "back": 2, "backbon": 8, "backend": 18, "background": 16, "bangla": 6, "bar": 15, "bar_cod": 16, "base": [4, 8, 15], "baselin": [4, 8, 18], "batch": [6, 8, 9, 15, 16, 18], "batch_siz": [6, 12, 15, 16, 17], "bblanchon": 3, "bbox": 18, "becaus": 13, "been": [2, 10, 16, 18], "befor": [6, 8, 9, 18], "begin": 10, "behavior": [1, 18], "being": [10, 18], "belong": 18, "benchmark": 18, "best": 1, "better": [11, 18], "between": [9, 10, 18], "bgr": 7, "bilinear": 9, "bin_thresh": 18, "binar": [4, 8, 18], "binari": [7, 17, 18], "bit": 17, "block": [10, 18], "block_1_1": 18, "blur": 9, "bmvc": 6, "bn": 14, "bodi": [1, 18], "bool": [6, 7, 8, 9, 10], "boolean": [8, 18], "both": [4, 6, 9, 16, 18], "bottom": [8, 18], "bound": [6, 7, 8, 9, 10, 15, 16, 18], "box": [6, 7, 8, 9, 10, 15, 16, 18], "box_thresh": 18, "bright": 9, "browser": [2, 4], "build": [2, 3, 17], "built": 2, "byte": [7, 18], "c": [3, 7, 10], "c_j": 10, "cach": [2, 6, 13], "cache_sampl": 6, "call": 17, "callabl": [6, 9], "can": [2, 3, 12, 13, 14, 15, 16, 18], "capabl": [2, 11, 18], "case": [6, 10], "cf": 18, "cfg": 18, "challeng": 6, "challenge2_test_task12_imag": 6, "challenge2_test_task1_gt": 6, "challenge2_training_task12_imag": 6, "challenge2_training_task1_gt": 6, "chang": [13, 18], "channel": [1, 2, 7, 9], "channel_prior": 3, "channelshuffl": 9, "charact": [4, 6, 7, 10, 16, 18], "charactergener": [6, 16], "characterist": 1, "charg": 18, "charset": 18, "chart": 7, "check": [2, 14, 18], "checkpoint": 8, "chip": 3, "ci": 2, "clarifi": 1, "clariti": 1, "class": [1, 6, 7, 9, 10, 18], "class_nam": 12, "classif": [16, 18], "classmethod": 7, "clear": 2, "clone": 3, "close": 2, "co": 14, "code": [4, 7, 15], "codecov": 2, "colab": 11, "collate_fn": 6, "collect": [7, 15], "color": 9, "colorinvers": 9, "column": 7, "com": [1, 3, 7, 8, 14], "combin": 18, "command": [2, 15], "comment": 1, "commit": 1, "common": [1, 9, 10, 17], "commun": 1, "compar": 4, "comparison": [10, 18], "competit": 6, "compil": [11, 18], "complaint": 1, "complementari": 10, "complet": 2, "compon": 18, "compos": [6, 18], "comprehens": 18, "comput": [6, 10, 17, 18], "conf_threshold": 15, "confid": [7, 18], "config": [3, 8], "configur": 8, "confus": 10, "consecut": [9, 18], "consequ": 1, "consid": [1, 2, 6, 7, 10, 18], "consist": 18, "consolid": [4, 6], "constant": 9, "construct": 1, "contact": 1, "contain": [5, 6, 11, 16, 18], "content": [6, 7, 18], "context": 8, "contib": 3, "continu": 1, "contrast": 9, "contrast_factor": 9, "contrib": [3, 15], "contribut": 1, "contributor": 2, "convers": 7, "convert": [7, 9], "convolut": 8, "coordin": [7, 18], "cord": [4, 6, 16, 18], "core": [10, 18], "corner": 18, "correct": 9, "correspond": [3, 7, 9, 18], "could": [1, 15], "counterpart": 10, "cover": 2, "coverag": 2, "cpu": [4, 12, 17], "creat": 14, "crnn": [4, 8, 14], "crnn_mobilenet_v3_larg": [8, 14, 18], "crnn_mobilenet_v3_smal": [8, 17, 18], "crnn_vgg16_bn": [8, 12, 14, 18], "crop": [7, 8, 9, 12, 16, 18], "crop_orient": [7, 18], "crop_orientation_predictor": [8, 12], "crop_param": 12, "cuda": 17, "currenc": 6, "current": [2, 12, 18], "custom": [14, 15, 17, 18], "custom_crop_orientation_model": 12, "custom_page_orientation_model": 12, "customhook": 18, "cvit": 4, "czczup": 8, "czech": 6, "d": [6, 16], "danish": 6, "data": [4, 6, 7, 9, 10, 12, 14], "dataload": 16, "dataset": [8, 12, 18], "dataset_info": 6, "date": [12, 18], "db": 14, "db_mobilenet_v3_larg": [8, 14, 18], "db_resnet34": 18, "db_resnet50": [8, 12, 14, 18], "dbnet": [4, 8], "deal": [11, 18], "decis": 1, "decod": 7, "decode_img_as_tensor": 7, "dedic": 17, "deem": 1, "deep": [8, 18], "def": 18, "default": [3, 7, 12, 13, 18], "defer": 16, "defin": [10, 17], "degre": [7, 9, 18], "degress": 7, "delet": 2, "delimit": 18, "delta": 9, "demo": [2, 4], "demonstr": 1, "depend": [2, 3, 4, 18], "deploi": 2, "deploy": 4, "derogatori": 1, "describ": 8, "descript": 11, "design": 9, "desir": 7, "det_arch": [8, 12, 14, 17], "det_b": 18, "det_model": [12, 14, 17], "det_param": 12, "det_predictor": [12, 18], "detail": [12, 18], "detect": [6, 7, 10, 11, 12, 15], "detect_languag": 8, "detect_orient": [8, 12, 18], "detection_predictor": [8, 18], "detection_task": [6, 16], "detectiondataset": [6, 16], "detectionmetr": 10, "detectionpredictor": [8, 12], "detector": [4, 8, 15], "deterior": 8, "determin": 1, "dev": [2, 13], "develop": 3, "deviat": 9, "devic": 17, "dict": [7, 10, 18], "dictionari": [7, 10], "differ": 1, "differenti": [4, 8], "digit": [4, 6, 16], "dimens": [7, 10, 18], "dimension": 9, "direct": 6, "directli": [14, 18], "directori": [2, 13], "disabl": [1, 13, 18], "disable_crop_orient": 18, "disable_page_orient": 18, "disclaim": 18, "discuss": 2, "disparag": 1, "displai": [7, 10], "display_artefact": 10, "distribut": 9, "div": 18, "divers": 1, "divid": 7, "do": [2, 3, 8], "doc": [2, 7, 15, 17, 18], "docartefact": [6, 16], "docstr": 2, "doctr": [3, 12, 13, 14, 15, 16, 17, 18], "doctr_cache_dir": 13, "doctr_multiprocessing_dis": 13, "document": [6, 8, 10, 11, 12, 15, 16, 17, 18], "documentbuild": 18, "documentfil": [7, 12, 14, 15, 17], "doesn": 17, "don": [12, 18], "done": 9, "download": [6, 16], "downsiz": 8, "draw": 9, "drop": 6, "drop_last": 6, "dtype": [7, 8, 9, 10, 17], "dual": [4, 6], "dummi": 14, "dummy_img": 18, "dummy_input": 17, "dure": 1, "dutch": 6, "dynam": [6, 15], "dynamic_seq_length": 6, "e": [1, 2, 3, 7, 8], "each": [4, 6, 7, 8, 9, 10, 16, 18], "eas": 2, "easi": [4, 10, 14, 17], "easili": [7, 10, 12, 14, 16, 18], "econom": 1, "edit": 1, "educ": 1, "effect": 18, "effici": [2, 4, 6, 8], "either": [10, 18], "element": [6, 7, 8, 18], "els": [2, 15], "email": 1, "empathi": 1, "en": 18, "enabl": [6, 7], "enclos": 7, "encod": [4, 6, 7, 8, 18], "encode_sequ": 6, "encount": 2, "encrypt": 7, "end": [4, 6, 8, 10], "english": [6, 16], "enough": [2, 18], "ensur": 2, "entri": 6, "environ": [1, 13], "eo": 6, "equiv": 18, "estim": 8, "etc": [7, 15], "ethnic": 1, "evalu": [16, 18], "event": 1, "everyon": 1, "everyth": [2, 18], "exact": [10, 18], "exampl": [1, 2, 4, 6, 8, 14, 18], "exchang": 17, "execut": 18, "exist": 14, "expand": 9, "expect": [7, 9, 10], "experi": 1, "explan": [1, 18], "explicit": 1, "exploit": [4, 8], "export": [7, 8, 10, 11, 15, 18], "export_as_straight_box": [8, 18], "export_as_xml": 18, "export_model_to_onnx": 17, "express": [1, 9], "extens": 7, "extern": [1, 16], "extract": [4, 6], "extractor": 8, "f_": 10, "f_a": 10, "factor": 9, "fair": 1, "fairli": 1, "fals": [6, 7, 8, 9, 10, 12, 18], "faq": 1, "fascan": 14, "fast": [4, 6, 8], "fast_bas": [8, 18], "fast_smal": [8, 18], "fast_tini": [8, 18], "faster": [4, 8, 17], "fasterrcnn_mobilenet_v3_large_fpn": 8, "favorit": 18, "featur": [3, 8, 10, 11, 12, 15], "feedback": 1, "feel": [2, 14], "felix92": 14, "few": [17, 18], "figsiz": 10, "figur": [10, 15], "file": [2, 6], "final": 8, "find": [2, 16], "finnish": 6, "first": [2, 6], "firsthand": 6, "fit": [8, 18], "flag": 18, "flip": 9, "float": [7, 9, 10, 17], "float32": [7, 8, 9, 17], "fn": 9, "focu": 14, "focus": [1, 6], "folder": 6, "follow": [1, 2, 3, 6, 9, 10, 12, 13, 14, 15, 18], "font": 6, "font_famili": 6, "foral": 10, "forc": 2, "forg": 3, "form": [4, 6, 18], "format": [7, 10, 12, 16, 17, 18], "forpost": [4, 6], "forum": 2, "fp16": 17, "frac": 10, "framework": [3, 14, 16, 18], "free": [1, 2, 14], "french": [6, 12, 14, 18], "friendli": 4, "from": [1, 4, 6, 7, 8, 9, 10, 11, 12, 15, 16, 17, 18], "from_hub": [8, 14], "from_imag": [7, 14, 15, 17], "from_pdf": 7, "from_url": 7, "full": [6, 10, 18], "function": [6, 9, 10, 15], "funsd": [4, 6, 16, 18], "further": 16, "futur": 6, "g": [7, 8], "g_": 10, "g_x": 10, "gamma": 9, "gaussian": 9, "gaussianblur": 9, "gaussiannois": 9, "gen": 18, "gender": 1, "gener": [2, 4, 7, 8], "generic_cyrillic_lett": 6, "geometri": [4, 7, 18], "geq": 10, "german": [6, 12, 14], "get": [17, 18], "git": 14, "github": [2, 3, 8, 14], "give": [1, 15], "given": [6, 7, 9, 10, 18], "global": 8, "go": 18, "good": 17, "googl": 2, "googlevis": 4, "gpu": [4, 15, 17], "gracefulli": 1, "graph": [4, 6, 7], "grayscal": 9, "ground": 10, "groung": 10, "group": [4, 18], "gt": 10, "gt_box": 10, "gt_label": 10, "guid": 2, "guidanc": 16, "gvision": 18, "h": [7, 8, 9], "h_": 10, "ha": [2, 6, 10, 16], "handl": [11, 16, 18], "handwrit": 6, "handwritten": 16, "harass": 1, "hardwar": 18, "harm": 1, "hat": 10, "have": [1, 2, 10, 12, 14, 16, 17, 18], "head": [8, 18], "healthi": 1, "hebrew": 6, "height": [7, 9], "hello": [10, 18], "help": 17, "here": [5, 9, 11, 15, 16, 18], "hf": 8, "hf_hub_download": 8, "high": 7, "higher": [3, 6, 18], "hindi": 6, "hindi_digit": 6, "hocr": 18, "hook": 18, "horizont": [7, 9, 18], "hous": 6, "how": [2, 11, 12, 14, 16], "howev": 16, "hsv": 9, "html": [1, 2, 3, 7, 18], "http": [1, 3, 6, 7, 8, 14, 18], "hub": 8, "hue": 9, "huggingfac": 8, "hw": 6, "i": [1, 2, 6, 7, 8, 9, 10, 13, 14, 15, 16, 17], "i7": 18, "ic03": [4, 6, 16], "ic13": [4, 6, 16], "icdar": [4, 6], "icdar2019": 6, "id": 18, "ident": 1, "identifi": 4, "iiit": [4, 6], "iiit5k": [6, 16], "iiithw": [4, 6, 16], "imag": [4, 6, 7, 8, 9, 10, 14, 15, 16, 18], "imagenet": 8, "imageri": 1, "images_90k_norm": 6, "img": [6, 9, 16, 17], "img_cont": 7, "img_fold": [6, 16], "img_path": 7, "img_transform": 6, "imgur5k": [4, 6, 16], "imgur5k_annot": 6, "imlist": 6, "impact": 1, "implement": [6, 7, 8, 9, 10, 18], "import": [6, 7, 8, 9, 10, 12, 14, 15, 16, 17, 18], "improv": 8, "inappropri": 1, "incid": 1, "includ": [1, 6, 16, 17], "inclus": 1, "increas": 9, "independ": 9, "index": [2, 7], "indic": 10, "individu": 1, "infer": [4, 8, 9, 15, 18], "inform": [1, 2, 4, 6, 16], "input": [2, 7, 8, 9, 17, 18], "input_crop": 8, "input_pag": [8, 10, 18], "input_shap": 17, "input_tensor": 8, "inspir": [1, 9], "instal": [14, 15, 17], "instanc": [1, 18], "instanti": [8, 18], "instead": [6, 7, 8], "insult": 1, "int": [6, 7, 9], "int64": 10, "integ": 10, "integr": [4, 14, 16], "intel": 18, "interact": [1, 7, 10], "interfac": [14, 17], "interoper": 17, "interpol": 9, "interpret": [6, 7], "intersect": 10, "invert": 9, "investig": 1, "invis": 1, "involv": [1, 18], "io": [12, 14, 15, 17], "iou": 10, "iou_thresh": 10, "iou_threshold": 15, "irregular": [4, 8, 16], "isn": 6, "issu": [1, 2, 14], "italian": 6, "iter": [6, 9, 16, 18], "its": [7, 8, 9, 10, 16, 18], "itself": [8, 14], "j": 10, "job": 2, "join": 2, "jpeg": 9, "jpegqual": 9, "jpg": [6, 7, 14, 17], "json": [6, 16, 18], "json_output": 18, "jump": 2, "just": 1, "kei": [4, 6], "kera": [8, 17], "kernel": [4, 8, 9], "kernel_shap": 9, "keywoard": 8, "keyword": [6, 7, 8, 10], "kie": [8, 12], "kie_predictor": [8, 12], "kiepredictor": 8, "kind": 1, "know": [2, 17], "kwarg": [6, 7, 8, 10], "l": 10, "l_j": 10, "label": [6, 10, 15, 16], "label_fil": [6, 16], "label_fold": 6, "label_path": [6, 16], "labels_path": [6, 16], "ladder": 1, "lambda": 9, "lambdatransform": 9, "lang": 18, "languag": [1, 4, 6, 7, 8, 14, 18], "larg": [8, 14], "largest": 10, "last": [3, 6], "latenc": 8, "later": 2, "latest": 18, "latin": 6, "layer": 17, "layout": 18, "lead": 1, "leader": 1, "learn": [1, 4, 8, 17, 18], "least": 3, "left": [10, 18], "legacy_french": 6, "length": [6, 18], "less": [17, 18], "level": [1, 6, 10, 18], "leverag": 11, "lf": 14, "librari": [2, 3, 11, 12], "light": 4, "lightweight": 17, "like": 1, "limits_": 10, "line": [4, 8, 10, 18], "line_1_1": 18, "link": 12, "linknet": [4, 8], "linknet_resnet18": [8, 12, 17, 18], "linknet_resnet34": [8, 17, 18], "linknet_resnet50": [8, 18], "list": [6, 7, 9, 10, 14], "ll": 10, "load": [4, 6, 8, 15, 17], "load_state_dict": 12, "load_weight": 12, "loc_pr": 18, "local": [2, 4, 6, 8, 10, 16, 18], "localis": 6, "localizationconfus": 10, "locat": [2, 7, 18], "login": 8, "login_to_hub": [8, 14], "logo": [7, 15, 16], "love": 14, "lower": [9, 10, 18], "m": [2, 10, 18], "m1": 3, "macbook": 3, "machin": 17, "made": 4, "magc_resnet31": 8, "mai": [1, 2], "mail": 1, "main": 11, "maintain": 4, "mainten": 2, "make": [1, 2, 10, 12, 13, 14, 17, 18], "mani": [16, 18], "manipul": 18, "map": [6, 8], "map_loc": 12, "master": [4, 8, 18], "match": [10, 18], "mathcal": 10, "matplotlib": [7, 10], "max": [6, 9, 10], "max_angl": 9, "max_area": 9, "max_char": [6, 16], "max_delta": 9, "max_gain": 9, "max_gamma": 9, "max_qual": 9, "max_ratio": 9, "maximum": [6, 9], "maxval": [8, 9], "mbox": 10, "mean": [9, 10, 12], "meaniou": 10, "meant": [7, 17], "measur": 18, "media": 1, "median": 8, "meet": 12, "member": 1, "memori": [13, 17], "mention": 18, "merg": 6, "messag": 2, "meta": 18, "metadata": 17, "metal": 3, "method": [7, 9, 18], "metric": [10, 18], "middl": 18, "might": [17, 18], "min": 9, "min_area": 9, "min_char": [6, 16], "min_gain": 9, "min_gamma": 9, "min_qual": 9, "min_ratio": 9, "min_val": 9, "minde": [1, 3, 4, 8], "minim": [2, 4], "minimalist": [4, 8], "minimum": [3, 6, 9, 10, 18], "minval": 9, "miss": 3, "mistak": 1, "mixed_float16": 17, "mixed_precis": 17, "mjsynth": [4, 6, 16], "mnt": 6, "mobilenet": [8, 14], "mobilenet_v3_larg": 8, "mobilenet_v3_large_r": 8, "mobilenet_v3_smal": [8, 12], "mobilenet_v3_small_crop_orient": [8, 12], "mobilenet_v3_small_page_orient": [8, 12], "mobilenet_v3_small_r": 8, "mobilenetv3": 8, "modal": [4, 6], "mode": 3, "model": [6, 10, 13, 15, 16], "model_nam": [8, 14, 17], "model_path": [15, 17], "moder": 1, "modif": 2, "modifi": [8, 13, 18], "modul": [3, 7, 8, 9, 10, 18], "more": [2, 16, 18], "most": 18, "mozilla": 1, "multi": [4, 8], "multilingu": [6, 14], "multipl": [6, 7, 9, 18], "multipli": 9, "multiprocess": 13, "my": 8, "my_awesome_model": 14, "my_hook": 18, "n": [6, 10], "name": [6, 8, 17, 18], "nation": 1, "natur": [1, 4, 6], "ndarrai": [6, 7, 9, 10], "necessari": [3, 12, 13], "need": [2, 3, 6, 10, 12, 13, 14, 15, 18], "neg": 9, "nest": 18, "network": [4, 6, 8, 17], "neural": [4, 6, 8, 17], "new": [2, 10], "next": [6, 16], "nois": 9, "noisi": [4, 6], "non": [4, 6, 7, 8, 9, 10], "none": [6, 7, 8, 9, 10, 18], "normal": [8, 9], "norwegian": 6, "note": [0, 2, 6, 8, 12, 14, 15, 17], "now": 2, "np": [8, 9, 10, 18], "num_output_channel": 9, "num_sampl": [6, 16], "number": [6, 9, 10, 18], "numpi": [7, 8, 10, 18], "o": 3, "obb": 15, "obj_detect": 14, "object": [6, 7, 10, 15, 18], "objectness_scor": [7, 18], "oblig": 1, "obtain": 18, "occupi": 17, "ocr": [4, 6, 8, 10, 14], "ocr_carea": 18, "ocr_db_crnn": 10, "ocr_lin": 18, "ocr_pag": 18, "ocr_par": 18, "ocr_predictor": [8, 12, 14, 17, 18], "ocrdataset": [6, 16], "ocrmetr": 10, "ocrpredictor": [8, 12], "ocrx_word": 18, "offens": 1, "offici": [1, 8], "offlin": 1, "offset": 9, "onc": 18, "one": [2, 6, 8, 9, 12, 14, 18], "oneof": 9, "ones": [6, 10], "onli": [2, 8, 9, 10, 12, 14, 16, 17, 18], "onlin": 1, "onnx": 15, "onnxruntim": [15, 17], "onnxtr": 17, "opac": 9, "opacity_rang": 9, "open": [1, 2, 14, 17], "opinion": 1, "optic": [4, 18], "optim": [4, 18], "option": [6, 8, 12], "order": [2, 6, 7, 9], "org": [1, 6, 8, 18], "organ": 7, "orient": [1, 7, 8, 11, 15, 18], "orientationpredictor": 8, "other": [1, 2], "otherwis": [1, 7, 10], "our": [2, 8, 18], "out": [2, 8, 9, 10, 18], "outpout": 18, "output": [7, 9, 17], "output_s": [7, 9], "outsid": 13, "over": [6, 10, 18], "overal": [1, 8], "overlai": 7, "overview": 15, "overwrit": 12, "overwritten": 14, "own": 4, "p": [9, 18], "packag": [2, 4, 10, 13, 15, 16, 17], "pad": [6, 8, 9, 18], "page": [3, 6, 8, 10, 12, 18], "page1": 7, "page2": 7, "page_1": 18, "page_idx": [7, 18], "page_orientation_predictor": [8, 12], "page_param": 12, "pair": 10, "paper": 8, "par_1_1": 18, "paragraph": 18, "paragraph_break": 18, "param": [9, 18], "paramet": [4, 7, 8, 17], "pars": [4, 6], "parseq": [4, 8, 14, 17, 18], "part": [6, 9, 18], "parti": 3, "partial": 18, "particip": 1, "pass": [6, 7, 8, 12, 18], "password": 7, "patch": [8, 10], "path": [6, 7, 15, 16, 17], "path_to_checkpoint": 12, "path_to_custom_model": 17, "path_to_pt": 12, "pattern": 1, "pdf": [7, 8, 11], "pdfpage": 7, "peopl": 1, "per": [9, 18], "perform": [4, 7, 8, 9, 10, 13, 17, 18], "period": 1, "permiss": 1, "permut": [4, 8], "persian_lett": 6, "person": [1, 16], "phase": 18, "photo": 16, "physic": [1, 7], "pick": 9, "pictur": 7, "pip": [2, 3, 15, 17], "pipelin": 18, "pixel": [7, 9, 18], "pleas": 2, "plot": 10, "plt": 10, "plug": 14, "plugin": 3, "png": 7, "point": 17, "polici": 13, "polish": 6, "polit": 1, "polygon": [6, 10, 18], "pool": 8, "portugues": 6, "posit": [1, 10], "possibl": [2, 10, 14, 18], "post": [1, 18], "postprocessor": 18, "potenti": 8, "power": 4, "ppageno": 18, "pre": [2, 8, 17], "precis": [10, 18], "pred": 10, "pred_box": 10, "pred_label": 10, "predefin": 16, "predict": [7, 8, 10, 18], "predictor": [4, 7, 8, 11, 12, 14, 17], "prefer": 16, "preinstal": 3, "preprocessor": [12, 18], "prerequisit": 14, "present": 11, "preserv": [8, 9, 18], "preserve_aspect_ratio": [7, 8, 9, 12, 18], "pretrain": [4, 8, 10, 12, 17, 18], "pretrained_backbon": [8, 12], "print": 18, "prior": 6, "privaci": 1, "privat": 1, "probabl": 9, "problem": 2, "procedur": 9, "process": [2, 4, 7, 12, 18], "processor": 18, "produc": [11, 18], "product": 17, "profession": 1, "project": [2, 16], "promptli": 1, "proper": 2, "properli": 6, "provid": [1, 2, 4, 14, 15, 16, 18], "public": [1, 4], "publicli": 18, "publish": 1, "pull": 14, "punctuat": 6, "pure": 6, "purpos": 2, "push_to_hf_hub": [8, 14], "py": 14, "pypdfium2": [3, 7], "pyplot": [7, 10], "python": [2, 15], "python3": 14, "pytorch": [3, 4, 8, 9, 12, 14, 17, 18], "q": 2, "qr": [7, 15], "qr_code": 16, "qualiti": 9, "question": 1, "quickli": 4, "quicktour": 11, "r": 18, "race": 1, "ramdisk": 6, "rand": [8, 9, 10, 17, 18], "random": [8, 9, 10, 18], "randomappli": 9, "randombright": 9, "randomcontrast": 9, "randomcrop": 9, "randomgamma": 9, "randomhorizontalflip": 9, "randomhu": 9, "randomjpegqu": 9, "randomli": 9, "randomres": 9, "randomrot": 9, "randomsatur": 9, "randomshadow": 9, "rang": 9, "rassi": 14, "ratio": [8, 9, 18], "raw": [7, 10], "re": 17, "read": [4, 6, 8], "read_html": 7, "read_img_as_numpi": 7, "read_img_as_tensor": 7, "read_pdf": 7, "readi": 17, "real": [4, 8, 9], "reason": [1, 4, 6], "rebuild": 2, "rebuilt": 2, "recal": [10, 18], "receipt": [4, 6, 18], "reco_arch": [8, 12, 14, 17], "reco_b": 18, "reco_model": [12, 14, 17], "reco_param": 12, "reco_predictor": 12, "recogn": 18, "recognit": [6, 10, 11, 12], "recognition_predictor": [8, 18], "recognition_task": [6, 16], "recognitiondataset": [6, 16], "recognitionpredictor": [8, 12], "rectangular": 8, "reduc": [3, 9], "refer": [2, 3, 12, 14, 15, 16, 18], "regardless": 1, "region": 18, "regroup": 10, "regular": 16, "reject": 1, "rel": [7, 9, 10, 18], "relat": 7, "releas": [0, 3], "relev": 15, "religion": 1, "remov": 1, "render": [7, 18], "repo": 8, "repo_id": [8, 14], "report": 1, "repositori": [6, 8, 14], "repres": [1, 17, 18], "represent": [4, 8], "request": [1, 14], "requir": [3, 9, 17], "research": 4, "residu": 8, "resiz": [9, 18], "resnet": 8, "resnet18": [8, 14], "resnet31": 8, "resnet34": 8, "resnet50": [8, 14], "resolv": 7, "resolve_block": 18, "resolve_lin": 18, "resourc": 16, "respect": 1, "rest": [2, 9, 10], "restrict": 13, "result": [2, 6, 7, 11, 14, 17, 18], "return": 18, "reusabl": 18, "review": 1, "rgb": [7, 9], "rgb_mode": 7, "rgb_output": 7, "right": [1, 8, 10], "robust": [4, 6], "root": 6, "rotat": [6, 7, 8, 9, 10, 11, 12, 16, 18], "run": [2, 3, 8], "same": [2, 7, 10, 16, 17, 18], "sampl": [6, 16, 18], "sample_transform": 6, "sar": [4, 8], "sar_resnet31": [8, 18], "satur": 9, "save": [8, 16], "scale": [7, 8, 9, 10], "scale_rang": 9, "scan": [4, 6], "scene": [4, 6, 8], "score": [7, 10], "script": [2, 16], "seamless": 4, "seamlessli": [4, 18], "search": 8, "searchabl": 11, "sec": 18, "second": 18, "section": [12, 14, 15, 17, 18], "secur": [1, 13], "see": [1, 2], "seen": 18, "segment": [4, 8, 18], "self": 18, "semant": [4, 8], "send": 18, "sens": 10, "sensit": 16, "separ": 18, "sequenc": [4, 6, 7, 8, 10, 18], "sequenti": [9, 18], "seri": 1, "seriou": 1, "set": [1, 3, 6, 8, 10, 13, 15, 18], "set_global_polici": 17, "sever": [7, 9, 18], "sex": 1, "sexual": 1, "shade": 9, "shape": [4, 7, 8, 9, 10, 18], "share": [13, 16], "shift": 9, "shm": 13, "should": [2, 6, 7, 9, 10], "show": [4, 7, 8, 10, 12, 14, 15], "showcas": [2, 11], "shuffl": [6, 9], "side": 10, "signatur": 7, "signific": 16, "simpl": [4, 8, 17], "simpler": 8, "sinc": [6, 16], "singl": [1, 2, 4, 6], "single_img_doc": 17, "size": [1, 6, 7, 9, 15, 18], "skew": 18, "slack": 2, "slightli": 8, "small": [2, 8, 18], "smallest": 7, "snapshot_download": 8, "snippet": 18, "so": [2, 3, 6, 8, 14, 16], "social": 1, "socio": 1, "some": [3, 11, 14, 16], "someth": 2, "somewher": 2, "sort": 1, "sourc": [6, 7, 8, 9, 10, 14], "space": [1, 18], "span": 18, "spanish": 6, "spatial": [4, 6, 7], "specif": [2, 3, 10, 12, 16, 18], "specifi": [1, 6, 7], "speed": [4, 8, 18], "sphinx": 2, "sroie": [4, 6, 16], "stabl": 3, "stackoverflow": 2, "stage": 4, "standalon": 11, "standard": 9, "start": 6, "state": [4, 10, 15], "static": 10, "statu": 1, "std": [9, 12], "step": 13, "still": 18, "str": [6, 7, 8, 9, 10], "straight": [6, 8, 16, 18], "straighten": 18, "straighten_pag": [8, 12, 18], "straigten_pag": 12, "stream": 7, "street": [4, 6], "strict": 3, "strictli": 10, "string": [6, 7, 10, 18], "strive": 3, "strong": [4, 8], "structur": [17, 18], "subset": [6, 18], "suggest": [2, 14], "sum": 10, "summari": 10, "support": [3, 12, 15, 17, 18], "sustain": 1, "svhn": [4, 6, 16], "svt": [6, 16], "swedish": 6, "symmetr": [8, 9, 18], "symmetric_pad": [8, 9, 18], "synthet": 4, "synthtext": [4, 6, 16], "system": 18, "t": [2, 6, 12, 17, 18], "tabl": [14, 15, 16], "take": [1, 6, 18], "target": [6, 7, 9, 10, 16], "target_s": 6, "task": [4, 6, 8, 14, 16, 18], "task2": 6, "team": 3, "techminde": 3, "templat": [2, 4], "tensor": [6, 7, 9, 18], "tensorflow": [3, 4, 7, 8, 9, 12, 14, 17, 18], "tensorspec": 17, "term": 1, "test": [6, 16], "test_set": 6, "text": [6, 7, 8, 10, 16], "text_output": 18, "textmatch": 10, "textnet": 8, "textnet_bas": 8, "textnet_smal": 8, "textnet_tini": 8, "textract": [4, 18], "textstylebrush": [4, 6], "textual": [4, 6, 7, 8, 18], "tf": [3, 7, 8, 9, 14, 17], "than": [2, 10, 14], "thank": 2, "thei": [1, 10], "them": [6, 18], "thi": [1, 2, 3, 5, 6, 9, 10, 12, 13, 14, 16, 17, 18], "thing": [17, 18], "third": 3, "those": [1, 7, 18], "threaten": 1, "threshold": 18, "through": [1, 9, 15, 16], "tilman": 14, "time": [1, 4, 8, 10, 16], "tini": 8, "titl": [7, 18], "tm": 18, "tmp": 13, "togeth": [2, 7], "tograi": 9, "tool": 16, "top": [10, 17, 18], "topic": 2, "torch": [3, 9, 12, 14, 17], "torchvis": 9, "total": 12, "toward": [1, 3], "train": [2, 6, 8, 9, 14, 15, 16, 17, 18], "train_it": [6, 16], "train_load": [6, 16], "train_pytorch": 14, "train_set": [6, 16], "train_tensorflow": 14, "trainabl": [4, 8], "tranform": 9, "transcrib": 18, "transfer": [4, 6], "transfo": 9, "transform": [4, 6, 8], "translat": 1, "troll": 1, "true": [6, 7, 8, 9, 10, 12, 13, 14, 16, 17, 18], "truth": 10, "tune": 17, "tupl": [6, 7, 9, 10], "two": [7, 13], "txt": 6, "type": [7, 10, 14, 17, 18], "typic": 18, "u": [1, 2], "ucsd": 6, "udac": 2, "uint8": [7, 8, 10, 18], "ukrainian": 6, "unaccept": 1, "underli": [16, 18], "underneath": 7, "understand": [4, 6, 18], "uniform": [8, 9], "uniformli": 9, "uninterrupt": [7, 18], "union": 10, "unittest": 2, "unlock": 7, "unoffici": 8, "unprofession": 1, "unsolicit": 1, "unsupervis": 4, "unwelcom": 1, "up": [8, 18], "updat": 10, "upgrad": 2, "upper": [6, 9], "uppercas": 16, "url": 7, "us": [1, 2, 3, 6, 8, 10, 11, 12, 13, 14, 15, 18], "usabl": 18, "usag": [13, 17], "use_polygon": [6, 10, 16], "useabl": 18, "user": [4, 7, 11], "utf": 18, "util": 17, "v1": 14, "v3": [8, 14, 18], "valid": 16, "valu": [2, 7, 9, 18], "valuabl": 4, "variabl": 13, "varieti": 6, "veri": 8, "version": [1, 2, 3, 17, 18], "vgg": 8, "vgg16": 14, "vgg16_bn_r": 8, "via": 1, "vietnames": 6, "view": [4, 6], "viewpoint": 1, "violat": 1, "visibl": 1, "vision": [4, 6, 8], "visiondataset": 6, "visiontransform": 8, "visual": [3, 4, 15], "visualize_pag": 10, "vit_": 8, "vit_b": 8, "vitstr": [4, 8, 17], "vitstr_bas": [8, 18], "vitstr_smal": [8, 12, 17, 18], "viz": 3, "vocab": [12, 14, 16, 17, 18], "vocabulari": [6, 12, 14], "w": [7, 8, 9, 10], "w3": 18, "wa": 1, "wai": [1, 4, 16], "want": [2, 17, 18], "warmup": 18, "wasn": 2, "we": [1, 2, 3, 4, 7, 9, 12, 14, 16, 17, 18], "weasyprint": 7, "web": [2, 7], "websit": 6, "welcom": 1, "well": [1, 17], "were": [1, 7, 18], "what": 1, "when": [1, 2, 8], "whenev": 2, "where": [2, 7, 9, 10], "whether": [2, 6, 7, 9, 10, 16, 18], "which": [1, 8, 13, 15, 16, 18], "whichev": 3, "while": [9, 18], "why": 1, "width": [7, 9], "wiki": 1, "wildreceipt": [4, 6, 16], "window": [8, 10], "wish": 2, "within": 1, "without": [1, 6, 8], "wonder": 2, "word": [4, 6, 8, 10, 18], "word_1_1": 18, "word_1_2": 18, "word_1_3": 18, "wordgener": [6, 16], "words_onli": 10, "work": [12, 13, 18], "workflow": 2, "worklow": 2, "world": [10, 18], "worth": 8, "wrap": 18, "wrapper": [6, 9], "write": 13, "written": [1, 7], "www": [1, 7, 18], "x": [7, 9, 10], "x_ascend": 18, "x_descend": 18, "x_i": 10, "x_size": 18, "x_wconf": 18, "xhtml": 18, "xmax": 7, "xmin": 7, "xml": 18, "xml_bytes_str": 18, "xml_element": 18, "xml_output": 18, "xmln": 18, "y": 10, "y_i": 10, "y_j": 10, "yet": 15, "ymax": 7, "ymin": 7, "yolov8": 15, "you": [2, 3, 6, 7, 8, 12, 13, 14, 15, 16, 17, 18], "your": [2, 4, 7, 10, 18], "yoursit": 7, "zero": [9, 10], "zoo": 12, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 6, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 6, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": 6, "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 6, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 6, "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 6, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 6, "\u00e4\u00f6\u00e4\u00f6": 6, "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 6, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": 6, "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": 6, "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": 6, "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 6, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": 6, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": 6, "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": 6, "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": 6, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 6, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 6, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 6, "\u067e\u0686\u06a2\u06a4\u06af": 6, "\u0905": 6, "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": 6, "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": 6, "\u0950": 6, "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": 6, "\u09bd": 6, "\u09ce": 6, "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": 6}, "titles": ["Changelog", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.contrib", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Integrate contributions into your pipeline", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 2, "0": 0, "01": 0, "02": 0, "03": 0, "04": 0, "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 1], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 1], "2021": 0, "2022": 0, "2023": 0, "2024": 0, "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 1], "31": 0, "4": [0, 1], "5": 0, "6": 0, "7": 0, "8": 0, "9": 0, "advanc": 18, "approach": 18, "architectur": 18, "arg": [6, 7, 8, 9, 10], "artefact": 7, "artefactdetect": 15, "attribut": 1, "avail": [15, 16, 18], "aw": 13, "ban": 1, "block": 7, "bug": 2, "changelog": 0, "choos": [16, 18], "classif": [8, 12, 14], "code": [1, 2], "codebas": 2, "commit": 2, "commun": 14, "compos": 9, "conda": 3, "conduct": 1, "connect": 2, "continu": 2, "contrib": 5, "contribut": [2, 5, 15], "contributor": 1, "convent": 14, "correct": 1, "coven": 1, "custom": [6, 12], "data": 16, "dataload": 6, "dataset": [4, 6, 16], "detect": [4, 8, 14, 16, 18], "develop": 2, "do": 18, "doctr": [2, 4, 5, 6, 7, 8, 9, 10, 11], "document": [2, 4, 7], "end": 18, "enforc": 1, "evalu": 10, "export": 17, "factori": 8, "featur": [2, 4], "feedback": 2, "file": 7, "from": 14, "gener": [6, 16], "git": 3, "guidelin": 1, "half": 17, "hub": 14, "huggingfac": 14, "i": 18, "infer": 17, "instal": [2, 3], "integr": [2, 15], "io": 7, "lambda": 13, "let": 2, "line": 7, "linux": 3, "load": [12, 14, 16], "loader": 6, "main": 4, "mode": 2, "model": [4, 8, 12, 14, 17, 18], "modifi": 2, "modul": [5, 15], "name": 14, "notebook": 11, "object": 16, "ocr": [16, 18], "onli": 3, "onnx": 17, "optim": 17, "option": 18, "orient": 12, "our": 1, "output": 18, "own": [12, 16], "packag": 3, "page": 7, "perman": 1, "pipelin": 15, "pledg": 1, "precis": 17, "predictor": 18, "prepar": 17, "prerequisit": 3, "pretrain": 14, "push": 14, "python": 3, "qualiti": 2, "question": 2, "read": 7, "readi": 16, "recognit": [4, 8, 14, 16, 18], "report": 2, "request": 2, "respons": 1, "return": [6, 7, 8, 10], "right": 18, "scope": 1, "share": 14, "should": 18, "stage": 18, "standard": 1, "structur": [2, 7], "style": 2, "support": [4, 5, 6, 9], "synthet": [6, 16], "task": 10, "temporari": 1, "test": 2, "text": [4, 18], "train": 12, "transform": 9, "two": 18, "unit": 2, "us": [16, 17], "util": 10, "v0": 0, "verif": 2, "via": 3, "visual": 10, "vocab": 6, "warn": 1, "what": 18, "word": 7, "your": [12, 14, 15, 16, 17], "zoo": [4, 8]}}) \ No newline at end of file diff --git a/using_doctr/custom_models_training.html b/using_doctr/custom_models_training.html index 580b4368b7..e664c6a950 100644 --- a/using_doctr/custom_models_training.html +++ b/using_doctr/custom_models_training.html @@ -14,7 +14,7 @@ - + Train your own model - docTR documentation @@ -615,7 +615,7 @@

    Loading your custom trained orientation classification model - + diff --git a/using_doctr/running_on_aws.html b/using_doctr/running_on_aws.html index ddb0c3c80f..81c38b49f5 100644 --- a/using_doctr/running_on_aws.html +++ b/using_doctr/running_on_aws.html @@ -14,7 +14,7 @@ - + AWS Lambda - docTR documentation @@ -358,7 +358,7 @@

    AWS Lambda - + diff --git a/using_doctr/sharing_models.html b/using_doctr/sharing_models.html index 07a3b2f2a3..4f5d1d68a5 100644 --- a/using_doctr/sharing_models.html +++ b/using_doctr/sharing_models.html @@ -14,7 +14,7 @@ - + Share your model with the community - docTR documentation @@ -540,7 +540,7 @@

    Recognition - + diff --git a/using_doctr/using_contrib_modules.html b/using_doctr/using_contrib_modules.html index b4a10925e6..cf282ff3a4 100644 --- a/using_doctr/using_contrib_modules.html +++ b/using_doctr/using_contrib_modules.html @@ -14,7 +14,7 @@ - + Integrate contributions into your pipeline - docTR documentation @@ -411,7 +411,7 @@

    ArtefactDetection - + diff --git a/using_doctr/using_datasets.html b/using_doctr/using_datasets.html index 4a52df36ba..e30b6d6459 100644 --- a/using_doctr/using_datasets.html +++ b/using_doctr/using_datasets.html @@ -14,7 +14,7 @@ - + Choose a ready to use dataset - docTR documentation @@ -638,7 +638,7 @@

    Data Loading - + diff --git a/using_doctr/using_model_export.html b/using_doctr/using_model_export.html index 2b30ee63a1..ad9d09ed4c 100644 --- a/using_doctr/using_model_export.html +++ b/using_doctr/using_model_export.html @@ -14,7 +14,7 @@ - + Preparing your model for inference - docTR documentation @@ -463,7 +463,7 @@

    Using your ONNX exported model - + diff --git a/using_doctr/using_models.html b/using_doctr/using_models.html index 13cb06116b..5c80dbf62d 100644 --- a/using_doctr/using_models.html +++ b/using_doctr/using_models.html @@ -14,7 +14,7 @@ - + Choosing the right model - docTR documentation @@ -1249,7 +1249,7 @@

    Advanced options - + diff --git a/v0.1.0/_modules/doctr/datasets/cord.html b/v0.1.0/_modules/doctr/datasets/cord.html index 78e70014e3..55b0584830 100644 --- a/v0.1.0/_modules/doctr/datasets/cord.html +++ b/v0.1.0/_modules/doctr/datasets/cord.html @@ -13,7 +13,7 @@ - + doctr.datasets.cord - docTR documentation @@ -462,7 +462,7 @@

    Source code for doctr.datasets.cord

         
       
    - + diff --git a/v0.1.0/_modules/doctr/datasets/detection.html b/v0.1.0/_modules/doctr/datasets/detection.html index 739563e466..718001e4cf 100644 --- a/v0.1.0/_modules/doctr/datasets/detection.html +++ b/v0.1.0/_modules/doctr/datasets/detection.html @@ -13,7 +13,7 @@ - + doctr.datasets.detection - docTR documentation @@ -430,7 +430,7 @@

    Source code for doctr.datasets.detection

         
       
    - + diff --git a/v0.1.0/_modules/doctr/datasets/doc_artefacts.html b/v0.1.0/_modules/doctr/datasets/doc_artefacts.html index 3313ae4660..94c32aaa0f 100644 --- a/v0.1.0/_modules/doctr/datasets/doc_artefacts.html +++ b/v0.1.0/_modules/doctr/datasets/doc_artefacts.html @@ -13,7 +13,7 @@ - + doctr.datasets.doc_artefacts - docTR documentation @@ -414,7 +414,7 @@

    Source code for doctr.datasets.doc_artefacts

       
    - + diff --git a/v0.1.0/_modules/doctr/datasets/funsd.html b/v0.1.0/_modules/doctr/datasets/funsd.html index e52abc5428..f08612f9fa 100644 --- a/v0.1.0/_modules/doctr/datasets/funsd.html +++ b/v0.1.0/_modules/doctr/datasets/funsd.html @@ -13,7 +13,7 @@ - + doctr.datasets.funsd - docTR documentation @@ -454,7 +454,7 @@

    Source code for doctr.datasets.funsd

         
       
    - + diff --git a/v0.1.0/_modules/doctr/datasets/generator/tensorflow.html b/v0.1.0/_modules/doctr/datasets/generator/tensorflow.html index 9f562582d9..a3e619f720 100644 --- a/v0.1.0/_modules/doctr/datasets/generator/tensorflow.html +++ b/v0.1.0/_modules/doctr/datasets/generator/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.datasets.generator.tensorflow - docTR documentation @@ -395,7 +395,7 @@

    Source code for doctr.datasets.generator.tensorflow

    - + diff --git a/v0.1.0/_modules/doctr/datasets/ic03.html b/v0.1.0/_modules/doctr/datasets/ic03.html index 3d221d07de..60e54a8a4b 100644 --- a/v0.1.0/_modules/doctr/datasets/ic03.html +++ b/v0.1.0/_modules/doctr/datasets/ic03.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic03 - docTR documentation @@ -468,7 +468,7 @@

    Source code for doctr.datasets.ic03

         
       
    - + diff --git a/v0.1.0/_modules/doctr/datasets/ic13.html b/v0.1.0/_modules/doctr/datasets/ic13.html index 8137e08e9f..219c98dcd1 100644 --- a/v0.1.0/_modules/doctr/datasets/ic13.html +++ b/v0.1.0/_modules/doctr/datasets/ic13.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic13 - docTR documentation @@ -440,7 +440,7 @@

    Source code for doctr.datasets.ic13

         
       
    - + diff --git a/v0.1.0/_modules/doctr/datasets/iiit5k.html b/v0.1.0/_modules/doctr/datasets/iiit5k.html index 1fc8ecfb27..b49c80fe18 100644 --- a/v0.1.0/_modules/doctr/datasets/iiit5k.html +++ b/v0.1.0/_modules/doctr/datasets/iiit5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiit5k - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.datasets.iiit5k

         
       
    - + diff --git a/v0.1.0/_modules/doctr/datasets/iiithws.html b/v0.1.0/_modules/doctr/datasets/iiithws.html index 07f5b13685..f7220afbc7 100644 --- a/v0.1.0/_modules/doctr/datasets/iiithws.html +++ b/v0.1.0/_modules/doctr/datasets/iiithws.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiithws - docTR documentation @@ -407,7 +407,7 @@

    Source code for doctr.datasets.iiithws

         
       
    - + diff --git a/v0.1.0/_modules/doctr/datasets/imgur5k.html b/v0.1.0/_modules/doctr/datasets/imgur5k.html index 68d433ca62..51c6545db4 100644 --- a/v0.1.0/_modules/doctr/datasets/imgur5k.html +++ b/v0.1.0/_modules/doctr/datasets/imgur5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.imgur5k - docTR documentation @@ -488,7 +488,7 @@

    Source code for doctr.datasets.imgur5k

         
       
    - + diff --git a/v0.1.0/_modules/doctr/datasets/loader.html b/v0.1.0/_modules/doctr/datasets/loader.html index d1785caa1c..ed80350ef0 100644 --- a/v0.1.0/_modules/doctr/datasets/loader.html +++ b/v0.1.0/_modules/doctr/datasets/loader.html @@ -13,7 +13,7 @@ - + doctr.datasets.loader - docTR documentation @@ -429,7 +429,7 @@

    Source code for doctr.datasets.loader

         
       
    - + diff --git a/v0.1.0/_modules/doctr/datasets/mjsynth.html b/v0.1.0/_modules/doctr/datasets/mjsynth.html index 77bb01d523..df34e49cf9 100644 --- a/v0.1.0/_modules/doctr/datasets/mjsynth.html +++ b/v0.1.0/_modules/doctr/datasets/mjsynth.html @@ -13,7 +13,7 @@ - + doctr.datasets.mjsynth - docTR documentation @@ -438,7 +438,7 @@

    Source code for doctr.datasets.mjsynth

         
       
    - + diff --git a/v0.1.0/_modules/doctr/datasets/ocr.html b/v0.1.0/_modules/doctr/datasets/ocr.html index 5832933ea5..ce1ed8b0d4 100644 --- a/v0.1.0/_modules/doctr/datasets/ocr.html +++ b/v0.1.0/_modules/doctr/datasets/ocr.html @@ -13,7 +13,7 @@ - + doctr.datasets.ocr - docTR documentation @@ -403,7 +403,7 @@

    Source code for doctr.datasets.ocr

         
       
    - + diff --git a/v0.1.0/_modules/doctr/datasets/recognition.html b/v0.1.0/_modules/doctr/datasets/recognition.html index 512c70c308..1754789364 100644 --- a/v0.1.0/_modules/doctr/datasets/recognition.html +++ b/v0.1.0/_modules/doctr/datasets/recognition.html @@ -13,7 +13,7 @@ - + doctr.datasets.recognition - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.datasets.recognition

         
       
    - + diff --git a/v0.1.0/_modules/doctr/datasets/sroie.html b/v0.1.0/_modules/doctr/datasets/sroie.html index 94c963390e..04cf10bda2 100644 --- a/v0.1.0/_modules/doctr/datasets/sroie.html +++ b/v0.1.0/_modules/doctr/datasets/sroie.html @@ -13,7 +13,7 @@ - + doctr.datasets.sroie - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.datasets.sroie

         
       
    - + diff --git a/v0.1.0/_modules/doctr/datasets/svhn.html b/v0.1.0/_modules/doctr/datasets/svhn.html index 48e4e4d210..60e02b1b3b 100644 --- a/v0.1.0/_modules/doctr/datasets/svhn.html +++ b/v0.1.0/_modules/doctr/datasets/svhn.html @@ -13,7 +13,7 @@ - + doctr.datasets.svhn - docTR documentation @@ -473,7 +473,7 @@

    Source code for doctr.datasets.svhn

         
       
    - + diff --git a/v0.1.0/_modules/doctr/datasets/svt.html b/v0.1.0/_modules/doctr/datasets/svt.html index 4144dc6b9b..a997fcbb50 100644 --- a/v0.1.0/_modules/doctr/datasets/svt.html +++ b/v0.1.0/_modules/doctr/datasets/svt.html @@ -13,7 +13,7 @@ - + doctr.datasets.svt - docTR documentation @@ -459,7 +459,7 @@

    Source code for doctr.datasets.svt

         
       
    - + diff --git a/v0.1.0/_modules/doctr/datasets/synthtext.html b/v0.1.0/_modules/doctr/datasets/synthtext.html index 3b9de506a7..c776e1d673 100644 --- a/v0.1.0/_modules/doctr/datasets/synthtext.html +++ b/v0.1.0/_modules/doctr/datasets/synthtext.html @@ -13,7 +13,7 @@ - + doctr.datasets.synthtext - docTR documentation @@ -470,7 +470,7 @@

    Source code for doctr.datasets.synthtext

         
       
    - + diff --git a/v0.1.0/_modules/doctr/datasets/utils.html b/v0.1.0/_modules/doctr/datasets/utils.html index 9defb17ba5..bde9304597 100644 --- a/v0.1.0/_modules/doctr/datasets/utils.html +++ b/v0.1.0/_modules/doctr/datasets/utils.html @@ -13,7 +13,7 @@ - + doctr.datasets.utils - docTR documentation @@ -554,7 +554,7 @@

    Source code for doctr.datasets.utils

         
       
    - + diff --git a/v0.1.0/_modules/doctr/datasets/wildreceipt.html b/v0.1.0/_modules/doctr/datasets/wildreceipt.html index c543ee7cac..12c6aebd14 100644 --- a/v0.1.0/_modules/doctr/datasets/wildreceipt.html +++ b/v0.1.0/_modules/doctr/datasets/wildreceipt.html @@ -13,7 +13,7 @@ - + doctr.datasets.wildreceipt - docTR documentation @@ -454,7 +454,7 @@

    Source code for doctr.datasets.wildreceipt

         
       
    - + diff --git a/v0.1.0/_modules/doctr/io/elements.html b/v0.1.0/_modules/doctr/io/elements.html index 753a47455c..e049d6ce30 100644 --- a/v0.1.0/_modules/doctr/io/elements.html +++ b/v0.1.0/_modules/doctr/io/elements.html @@ -13,7 +13,7 @@ - + doctr.io.elements - docTR documentation @@ -1008,7 +1008,7 @@

    Source code for doctr.io.elements

         
       
    - + diff --git a/v0.1.0/_modules/doctr/io/html.html b/v0.1.0/_modules/doctr/io/html.html index 7ad5b97031..be73631500 100644 --- a/v0.1.0/_modules/doctr/io/html.html +++ b/v0.1.0/_modules/doctr/io/html.html @@ -13,7 +13,7 @@ - + doctr.io.html - docTR documentation @@ -360,7 +360,7 @@

    Source code for doctr.io.html

         
       
    - + diff --git a/v0.1.0/_modules/doctr/io/image/base.html b/v0.1.0/_modules/doctr/io/image/base.html index 336b4bff0e..a50c95d595 100644 --- a/v0.1.0/_modules/doctr/io/image/base.html +++ b/v0.1.0/_modules/doctr/io/image/base.html @@ -13,7 +13,7 @@ - + doctr.io.image.base - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.io.image.base

         
       
    - + diff --git a/v0.1.0/_modules/doctr/io/image/tensorflow.html b/v0.1.0/_modules/doctr/io/image/tensorflow.html index f1846820a3..3b9e731756 100644 --- a/v0.1.0/_modules/doctr/io/image/tensorflow.html +++ b/v0.1.0/_modules/doctr/io/image/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.io.image.tensorflow - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.io.image.tensorflow

         
       
    - + diff --git a/v0.1.0/_modules/doctr/io/pdf.html b/v0.1.0/_modules/doctr/io/pdf.html index e3abf6960b..e5b94811c3 100644 --- a/v0.1.0/_modules/doctr/io/pdf.html +++ b/v0.1.0/_modules/doctr/io/pdf.html @@ -13,7 +13,7 @@ - + doctr.io.pdf - docTR documentation @@ -377,7 +377,7 @@

    Source code for doctr.io.pdf

         
       
    - + diff --git a/v0.1.0/_modules/doctr/io/reader.html b/v0.1.0/_modules/doctr/io/reader.html index c1ddc26edd..d36e5bb553 100644 --- a/v0.1.0/_modules/doctr/io/reader.html +++ b/v0.1.0/_modules/doctr/io/reader.html @@ -13,7 +13,7 @@ - + doctr.io.reader - docTR documentation @@ -426,7 +426,7 @@

    Source code for doctr.io.reader

         
       
    - + diff --git a/v0.1.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html b/v0.1.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html index 9f074805c1..61a010d548 100644 --- a/v0.1.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html +++ b/v0.1.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.magc_resnet.tensorflow - docTR documentation @@ -531,7 +531,7 @@

    Source code for doctr.models.classification.magc_resnet.tensorflow

    - + diff --git a/v0.1.0/_modules/doctr/models/classification/mobilenet/tensorflow.html b/v0.1.0/_modules/doctr/models/classification/mobilenet/tensorflow.html index 6a63851276..7c448394ad 100644 --- a/v0.1.0/_modules/doctr/models/classification/mobilenet/tensorflow.html +++ b/v0.1.0/_modules/doctr/models/classification/mobilenet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.mobilenet.tensorflow - docTR documentation @@ -793,7 +793,7 @@

    Source code for doctr.models.classification.mobilenet.tensorflow

    - + diff --git a/v0.1.0/_modules/doctr/models/classification/resnet/tensorflow.html b/v0.1.0/_modules/doctr/models/classification/resnet/tensorflow.html index 095d377f31..aed4343741 100644 --- a/v0.1.0/_modules/doctr/models/classification/resnet/tensorflow.html +++ b/v0.1.0/_modules/doctr/models/classification/resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.resnet.tensorflow - docTR documentation @@ -749,7 +749,7 @@

    Source code for doctr.models.classification.resnet.tensorflow

    - + diff --git a/v0.1.0/_modules/doctr/models/classification/textnet/tensorflow.html b/v0.1.0/_modules/doctr/models/classification/textnet/tensorflow.html index ad254ebbfb..c5567d7d67 100644 --- a/v0.1.0/_modules/doctr/models/classification/textnet/tensorflow.html +++ b/v0.1.0/_modules/doctr/models/classification/textnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.textnet.tensorflow - docTR documentation @@ -611,7 +611,7 @@

    Source code for doctr.models.classification.textnet.tensorflow

    - + diff --git a/v0.1.0/_modules/doctr/models/classification/vgg/tensorflow.html b/v0.1.0/_modules/doctr/models/classification/vgg/tensorflow.html index 01ae452624..788111ae87 100644 --- a/v0.1.0/_modules/doctr/models/classification/vgg/tensorflow.html +++ b/v0.1.0/_modules/doctr/models/classification/vgg/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vgg.tensorflow - docTR documentation @@ -451,7 +451,7 @@

    Source code for doctr.models.classification.vgg.tensorflow

    - + diff --git a/v0.1.0/_modules/doctr/models/classification/vit/tensorflow.html b/v0.1.0/_modules/doctr/models/classification/vit/tensorflow.html index 1333cf6045..971ba5abe9 100644 --- a/v0.1.0/_modules/doctr/models/classification/vit/tensorflow.html +++ b/v0.1.0/_modules/doctr/models/classification/vit/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vit.tensorflow - docTR documentation @@ -533,7 +533,7 @@

    Source code for doctr.models.classification.vit.tensorflow

    - + diff --git a/v0.1.0/_modules/doctr/models/classification/zoo.html b/v0.1.0/_modules/doctr/models/classification/zoo.html index f7796a7522..3eb2a3ec4e 100644 --- a/v0.1.0/_modules/doctr/models/classification/zoo.html +++ b/v0.1.0/_modules/doctr/models/classification/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.classification.zoo - docTR documentation @@ -447,7 +447,7 @@

    Source code for doctr.models.classification.zoo

    <
    - + diff --git a/v0.1.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html b/v0.1.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html index 4325d0b74a..66cef8663d 100644 --- a/v0.1.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html +++ b/v0.1.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.differentiable_binarization.tensorflow - docTR documentation @@ -759,7 +759,7 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo

    - + diff --git a/v0.1.0/_modules/doctr/models/detection/fast/tensorflow.html b/v0.1.0/_modules/doctr/models/detection/fast/tensorflow.html index 5b84d2dea1..65e1a77af8 100644 --- a/v0.1.0/_modules/doctr/models/detection/fast/tensorflow.html +++ b/v0.1.0/_modules/doctr/models/detection/fast/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.fast.tensorflow - docTR documentation @@ -769,7 +769,7 @@

    Source code for doctr.models.detection.fast.tensorflow

    - + diff --git a/v0.1.0/_modules/doctr/models/detection/linknet/tensorflow.html b/v0.1.0/_modules/doctr/models/detection/linknet/tensorflow.html index dbb58e37cf..ce995f99d4 100644 --- a/v0.1.0/_modules/doctr/models/detection/linknet/tensorflow.html +++ b/v0.1.0/_modules/doctr/models/detection/linknet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.linknet.tensorflow - docTR documentation @@ -716,7 +716,7 @@

    Source code for doctr.models.detection.linknet.tensorflow

    - + diff --git a/v0.1.0/_modules/doctr/models/detection/zoo.html b/v0.1.0/_modules/doctr/models/detection/zoo.html index 312f4584ab..3651c4e2d3 100644 --- a/v0.1.0/_modules/doctr/models/detection/zoo.html +++ b/v0.1.0/_modules/doctr/models/detection/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.detection.zoo - docTR documentation @@ -450,7 +450,7 @@

    Source code for doctr.models.detection.zoo

         
       
    - + diff --git a/v0.1.0/_modules/doctr/models/factory/hub.html b/v0.1.0/_modules/doctr/models/factory/hub.html index 8274a809f5..756b2c7a17 100644 --- a/v0.1.0/_modules/doctr/models/factory/hub.html +++ b/v0.1.0/_modules/doctr/models/factory/hub.html @@ -13,7 +13,7 @@ - + doctr.models.factory.hub - docTR documentation @@ -568,7 +568,7 @@

    Source code for doctr.models.factory.hub

         
       
    - + diff --git a/v0.1.0/_modules/doctr/models/recognition/crnn/tensorflow.html b/v0.1.0/_modules/doctr/models/recognition/crnn/tensorflow.html index e50c245923..bc64da9a1b 100644 --- a/v0.1.0/_modules/doctr/models/recognition/crnn/tensorflow.html +++ b/v0.1.0/_modules/doctr/models/recognition/crnn/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.crnn.tensorflow - docTR documentation @@ -658,7 +658,7 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    - + diff --git a/v0.1.0/_modules/doctr/models/recognition/master/tensorflow.html b/v0.1.0/_modules/doctr/models/recognition/master/tensorflow.html index 152ebb7e59..aa6aa69325 100644 --- a/v0.1.0/_modules/doctr/models/recognition/master/tensorflow.html +++ b/v0.1.0/_modules/doctr/models/recognition/master/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.master.tensorflow - docTR documentation @@ -655,7 +655,7 @@

    Source code for doctr.models.recognition.master.tensorflow

    - + diff --git a/v0.1.0/_modules/doctr/models/recognition/parseq/tensorflow.html b/v0.1.0/_modules/doctr/models/recognition/parseq/tensorflow.html index 0819737dfc..b181acef53 100644 --- a/v0.1.0/_modules/doctr/models/recognition/parseq/tensorflow.html +++ b/v0.1.0/_modules/doctr/models/recognition/parseq/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.parseq.tensorflow - docTR documentation @@ -845,7 +845,7 @@

    Source code for doctr.models.recognition.parseq.tensorflow

    - + diff --git a/v0.1.0/_modules/doctr/models/recognition/sar/tensorflow.html b/v0.1.0/_modules/doctr/models/recognition/sar/tensorflow.html index 010bc2bc54..4a591e6451 100644 --- a/v0.1.0/_modules/doctr/models/recognition/sar/tensorflow.html +++ b/v0.1.0/_modules/doctr/models/recognition/sar/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.sar.tensorflow - docTR documentation @@ -757,7 +757,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    - + diff --git a/v0.1.0/_modules/doctr/models/recognition/vitstr/tensorflow.html b/v0.1.0/_modules/doctr/models/recognition/vitstr/tensorflow.html index 6e101893bf..c594d40a56 100644 --- a/v0.1.0/_modules/doctr/models/recognition/vitstr/tensorflow.html +++ b/v0.1.0/_modules/doctr/models/recognition/vitstr/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.vitstr.tensorflow - docTR documentation @@ -621,7 +621,7 @@

    Source code for doctr.models.recognition.vitstr.tensorflow

    - + diff --git a/v0.1.0/_modules/doctr/models/recognition/zoo.html b/v0.1.0/_modules/doctr/models/recognition/zoo.html index 2c47f88de4..f664304019 100644 --- a/v0.1.0/_modules/doctr/models/recognition/zoo.html +++ b/v0.1.0/_modules/doctr/models/recognition/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.zoo - docTR documentation @@ -415,7 +415,7 @@

    Source code for doctr.models.recognition.zoo

       
    - + diff --git a/v0.1.0/_modules/doctr/models/zoo.html b/v0.1.0/_modules/doctr/models/zoo.html index 5b22f2c79f..d459671648 100644 --- a/v0.1.0/_modules/doctr/models/zoo.html +++ b/v0.1.0/_modules/doctr/models/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.zoo - docTR documentation @@ -576,7 +576,7 @@

    Source code for doctr.models.zoo

         
       
    - + diff --git a/v0.1.0/_modules/doctr/transforms/modules/base.html b/v0.1.0/_modules/doctr/transforms/modules/base.html index 96ebd680b7..4596df3848 100644 --- a/v0.1.0/_modules/doctr/transforms/modules/base.html +++ b/v0.1.0/_modules/doctr/transforms/modules/base.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.base - docTR documentation @@ -643,7 +643,7 @@

    Source code for doctr.transforms.modules.base

    - + diff --git a/v0.1.0/_modules/doctr/transforms/modules/tensorflow.html b/v0.1.0/_modules/doctr/transforms/modules/tensorflow.html index 0e18bcc922..acbbe96225 100644 --- a/v0.1.0/_modules/doctr/transforms/modules/tensorflow.html +++ b/v0.1.0/_modules/doctr/transforms/modules/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.tensorflow - docTR documentation @@ -956,7 +956,7 @@

    Source code for doctr.transforms.modules.tensorflow

    - + diff --git a/v0.1.0/_modules/doctr/utils/metrics.html b/v0.1.0/_modules/doctr/utils/metrics.html index d35d7e9672..8a37d5949a 100644 --- a/v0.1.0/_modules/doctr/utils/metrics.html +++ b/v0.1.0/_modules/doctr/utils/metrics.html @@ -13,7 +13,7 @@ - + doctr.utils.metrics - docTR documentation @@ -936,7 +936,7 @@

    Source code for doctr.utils.metrics

         
       
    - + diff --git a/v0.1.0/_modules/doctr/utils/visualization.html b/v0.1.0/_modules/doctr/utils/visualization.html index e608d492a4..c818be6d7b 100644 --- a/v0.1.0/_modules/doctr/utils/visualization.html +++ b/v0.1.0/_modules/doctr/utils/visualization.html @@ -13,7 +13,7 @@ - + doctr.utils.visualization - docTR documentation @@ -720,7 +720,7 @@

    Source code for doctr.utils.visualization

         
       
    - + diff --git a/v0.1.0/_modules/index.html b/v0.1.0/_modules/index.html index 758ef41bd0..5793c44f20 100644 --- a/v0.1.0/_modules/index.html +++ b/v0.1.0/_modules/index.html @@ -13,7 +13,7 @@ - + Overview: module code - docTR documentation @@ -378,7 +378,7 @@

    All modules for which code is available

    - + diff --git a/v0.1.0/_sources/getting_started/installing.rst.txt b/v0.1.0/_sources/getting_started/installing.rst.txt index e764e734a7..39e79aa3dd 100644 --- a/v0.1.0/_sources/getting_started/installing.rst.txt +++ b/v0.1.0/_sources/getting_started/installing.rst.txt @@ -3,7 +3,7 @@ Installation ************ -This library requires `Python `_ 3.9 or higher. +This library requires `Python `_ 3.10 or higher. Prerequisites diff --git a/v0.1.0/_static/basic.css b/v0.1.0/_static/basic.css index f316efcb47..7ebbd6d07b 100644 --- a/v0.1.0/_static/basic.css +++ b/v0.1.0/_static/basic.css @@ -1,12 +1,5 @@ /* - * basic.css - * ~~~~~~~~~ - * * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ /* -- main layout ----------------------------------------------------------- */ @@ -115,15 +108,11 @@ img { /* -- search page ----------------------------------------------------------- */ ul.search { - margin: 10px 0 0 20px; - padding: 0; + margin-top: 10px; } ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; + padding: 5px 0; } ul.search li a { diff --git a/v0.1.0/_static/doctools.js b/v0.1.0/_static/doctools.js index 4d67807d17..0398ebb9f0 100644 --- a/v0.1.0/_static/doctools.js +++ b/v0.1.0/_static/doctools.js @@ -1,12 +1,5 @@ /* - * doctools.js - * ~~~~~~~~~~~ - * * Base JavaScript utilities for all Sphinx HTML documentation. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; diff --git a/v0.1.0/_static/language_data.js b/v0.1.0/_static/language_data.js index 367b8ed81b..c7fe6c6faf 100644 --- a/v0.1.0/_static/language_data.js +++ b/v0.1.0/_static/language_data.js @@ -1,13 +1,6 @@ /* - * language_data.js - * ~~~~~~~~~~~~~~~~ - * * This script contains the language-specific data used by searchtools.js, * namely the list of stopwords, stemmer, scorer and splitter. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; diff --git a/v0.1.0/_static/searchtools.js b/v0.1.0/_static/searchtools.js index b08d58c9b9..2c774d17af 100644 --- a/v0.1.0/_static/searchtools.js +++ b/v0.1.0/_static/searchtools.js @@ -1,12 +1,5 @@ /* - * searchtools.js - * ~~~~~~~~~~~~~~~~ - * * Sphinx JavaScript utilities for the full-text search. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; @@ -20,7 +13,7 @@ if (typeof Scorer === "undefined") { // and returns the new score. /* score: result => { - const [docname, title, anchor, descr, score, filename] = result + const [docname, title, anchor, descr, score, filename, kind] = result return score }, */ @@ -47,6 +40,14 @@ if (typeof Scorer === "undefined") { }; } +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + const _removeChildren = (element) => { while (element && element.lastChild) element.removeChild(element.lastChild); }; @@ -64,9 +65,13 @@ const _displayItem = (item, searchTerms, highlightTerms) => { const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; const contentRoot = document.documentElement.dataset.content_root; - const [docName, title, anchor, descr, score, _filename] = item; + const [docName, title, anchor, descr, score, _filename, kind] = item; let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); let requestUrl; let linkUrl; if (docBuilder === "dirhtml") { @@ -115,8 +120,10 @@ const _finishSearch = (resultCount) => { "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." ); else - Search.status.innerText = _( - "Search finished, found ${resultCount} page(s) matching the search query." + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, ).replace('${resultCount}', resultCount); }; const _displayNextItem = ( @@ -138,7 +145,7 @@ const _displayNextItem = ( else _finishSearch(resultCount); }; // Helper function used by query() to order search results. -// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. // Order the results by score (in opposite order of appearance, since the // `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. const _orderResultsByScoreThenName = (a, b) => { @@ -248,6 +255,7 @@ const Search = { searchSummary.classList.add("search-summary"); searchSummary.innerText = ""; const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); searchList.classList.add("search"); const out = document.getElementById("search-results"); @@ -318,7 +326,7 @@ const Search = { const indexEntries = Search._index.indexentries; // Collect multiple result groups to be sorted separately and then ordered. - // Each is an array of [docname, title, anchor, descr, score, filename]. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. const normalResults = []; const nonMainIndexResults = []; @@ -337,6 +345,7 @@ const Search = { null, score + boost, filenames[file], + SearchResultKind.title, ]); } } @@ -354,6 +363,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.index, ]; if (isMain) { normalResults.push(result); @@ -475,6 +485,7 @@ const Search = { descr, score, filenames[match[0]], + SearchResultKind.object, ]); }; Object.keys(objects).forEach((prefix) => @@ -585,6 +596,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.text, ]); } return results; diff --git a/v0.1.0/changelog.html b/v0.1.0/changelog.html index ac81a6f231..fc45a50384 100644 --- a/v0.1.0/changelog.html +++ b/v0.1.0/changelog.html @@ -14,7 +14,7 @@ - + Changelog - docTR documentation @@ -446,7 +446,7 @@

    v0.1.0 (2021-03-05) - + diff --git a/v0.1.0/community/resources.html b/v0.1.0/community/resources.html index 2564037893..9a1988258c 100644 --- a/v0.1.0/community/resources.html +++ b/v0.1.0/community/resources.html @@ -14,7 +14,7 @@ - + Community resources - docTR documentation @@ -389,7 +389,7 @@

    Community resources - + diff --git a/v0.1.0/contributing/code_of_conduct.html b/v0.1.0/contributing/code_of_conduct.html index 5ea4a1f99d..03422dbb4d 100644 --- a/v0.1.0/contributing/code_of_conduct.html +++ b/v0.1.0/contributing/code_of_conduct.html @@ -14,7 +14,7 @@ - + Contributor Covenant Code of Conduct - docTR documentation @@ -504,7 +504,7 @@

    Attribution - + diff --git a/v0.1.0/contributing/contributing.html b/v0.1.0/contributing/contributing.html index e5a85682c6..05e2b3641b 100644 --- a/v0.1.0/contributing/contributing.html +++ b/v0.1.0/contributing/contributing.html @@ -14,7 +14,7 @@ - + Contributing to docTR - docTR documentation @@ -481,7 +481,7 @@

    Let’s connect - + diff --git a/v0.1.0/genindex.html b/v0.1.0/genindex.html index cbb43f08d8..21520455b4 100644 --- a/v0.1.0/genindex.html +++ b/v0.1.0/genindex.html @@ -13,7 +13,7 @@ - Index - docTR documentation + Index - docTR documentation @@ -756,7 +756,7 @@

    W

    - + diff --git a/v0.1.0/getting_started/installing.html b/v0.1.0/getting_started/installing.html index a488e9a030..af3b58193e 100644 --- a/v0.1.0/getting_started/installing.html +++ b/v0.1.0/getting_started/installing.html @@ -14,7 +14,7 @@ - + Installation - docTR documentation @@ -305,7 +305,7 @@

    Installation

    -

    This library requires Python 3.9 or higher.

    +

    This library requires Python 3.10 or higher.

    Prerequisites

    Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so:

    @@ -435,7 +435,7 @@

    Via Git - + diff --git a/v0.1.0/index.html b/v0.1.0/index.html index 76509686f5..3a06afc6d9 100644 --- a/v0.1.0/index.html +++ b/v0.1.0/index.html @@ -14,7 +14,7 @@ - + docTR documentation @@ -445,7 +445,7 @@

    Supported datasets - + diff --git a/v0.1.0/modules/contrib.html b/v0.1.0/modules/contrib.html index e99f6b3f74..7fb86b8b38 100644 --- a/v0.1.0/modules/contrib.html +++ b/v0.1.0/modules/contrib.html @@ -14,7 +14,7 @@ - + doctr.contrib - docTR documentation @@ -380,7 +380,7 @@

    Supported contribution modules - + diff --git a/v0.1.0/modules/datasets.html b/v0.1.0/modules/datasets.html index 456e10b172..380a986793 100644 --- a/v0.1.0/modules/datasets.html +++ b/v0.1.0/modules/datasets.html @@ -14,7 +14,7 @@ - + doctr.datasets - docTR documentation @@ -1081,7 +1081,7 @@

    Returns: - + diff --git a/v0.1.0/modules/io.html b/v0.1.0/modules/io.html index 01eadaa4b8..24c41954be 100644 --- a/v0.1.0/modules/io.html +++ b/v0.1.0/modules/io.html @@ -14,7 +14,7 @@ - + doctr.io - docTR documentation @@ -760,7 +760,7 @@

    Returns: - + diff --git a/v0.1.0/modules/models.html b/v0.1.0/modules/models.html index c465cc0586..91b8810a6a 100644 --- a/v0.1.0/modules/models.html +++ b/v0.1.0/modules/models.html @@ -14,7 +14,7 @@ - + doctr.models - docTR documentation @@ -1612,7 +1612,7 @@

    Args: - + diff --git a/v0.1.0/modules/transforms.html b/v0.1.0/modules/transforms.html index 30f7a2631a..c5ead3f3ce 100644 --- a/v0.1.0/modules/transforms.html +++ b/v0.1.0/modules/transforms.html @@ -14,7 +14,7 @@ - + doctr.transforms - docTR documentation @@ -835,7 +835,7 @@

    Args:< - + diff --git a/v0.1.0/modules/utils.html b/v0.1.0/modules/utils.html index 888a32c321..b7f6fc570b 100644 --- a/v0.1.0/modules/utils.html +++ b/v0.1.0/modules/utils.html @@ -14,7 +14,7 @@ - + doctr.utils - docTR documentation @@ -715,7 +715,7 @@

    Args: - + diff --git a/v0.1.0/notebooks.html b/v0.1.0/notebooks.html index f97771aebb..d36539f59e 100644 --- a/v0.1.0/notebooks.html +++ b/v0.1.0/notebooks.html @@ -14,7 +14,7 @@ - + docTR Notebooks - docTR documentation @@ -391,7 +391,7 @@

    docTR Notebooks - + diff --git a/v0.1.0/search.html b/v0.1.0/search.html index 82b8bd6950..d050f5eac7 100644 --- a/v0.1.0/search.html +++ b/v0.1.0/search.html @@ -14,7 +14,7 @@ - + Search - docTR documentation @@ -340,7 +340,7 @@ - + diff --git a/v0.1.0/searchindex.js b/v0.1.0/searchindex.js index bfa546d0e9..6f154115ab 100644 --- a/v0.1.0/searchindex.js +++ b/v0.1.0/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles": {"1. Correction": [[2, "correction"]], "2. Warning": [[2, "warning"]], "3. Temporary Ban": [[2, "temporary-ban"]], "4. Permanent Ban": [[2, "permanent-ban"]], "AWS Lambda": [[14, null]], "Advanced options": [[19, "advanced-options"]], "Args:": [[7, "args"], [7, "id4"], [7, "id7"], [7, "id10"], [7, "id13"], [7, "id16"], [7, "id19"], [7, "id22"], [7, "id25"], [7, "id29"], [7, "id32"], [7, "id37"], [7, "id40"], [7, "id46"], [7, "id49"], [7, "id50"], [7, "id51"], [7, "id54"], [7, "id57"], [7, "id60"], [7, "id61"], [8, "args"], [8, "id2"], [8, "id3"], [8, "id4"], [8, "id5"], [8, "id6"], [8, "id7"], [8, "id10"], [8, "id12"], [8, "id14"], [8, "id16"], [8, "id20"], [8, "id24"], [8, "id28"], [9, "args"], [9, "id3"], [9, "id8"], [9, "id13"], [9, "id17"], [9, "id21"], [9, "id26"], [9, "id31"], [9, "id36"], [9, "id41"], [9, "id46"], [9, "id50"], [9, "id54"], [9, "id59"], [9, "id63"], [9, "id68"], [9, "id73"], [9, "id77"], [9, "id81"], [9, "id85"], [9, "id90"], [9, "id95"], [9, "id99"], [9, "id104"], [9, "id109"], [9, "id114"], [9, "id119"], [9, "id123"], [9, "id127"], [9, "id132"], [9, "id137"], [9, "id142"], [9, "id146"], [9, "id150"], [9, "id155"], [9, "id159"], [9, "id163"], [9, "id167"], [9, "id169"], [9, "id171"], [9, "id173"], [10, "args"], [10, "id1"], [10, "id2"], [10, "id3"], [10, "id4"], [10, "id5"], [10, "id6"], [10, "id7"], [10, "id8"], [10, "id9"], [10, "id10"], [10, "id11"], [10, "id12"], [10, "id13"], [10, "id14"], [10, "id15"], [10, "id16"], [10, "id17"], [10, "id18"], [10, "id19"], [11, "args"], [11, "id3"], [11, "id4"], [11, "id5"], [11, "id6"], [11, "id7"], [11, "id8"], [11, "id9"]], "Artefact": [[8, "artefact"]], "ArtefactDetection": [[16, "artefactdetection"]], "Attribution": [[2, "attribution"]], "Available Datasets": [[17, "available-datasets"]], "Available architectures": [[19, "available-architectures"], [19, "id1"], [19, "id2"]], "Available contribution modules": [[16, "available-contribution-modules"]], "Block": [[8, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[17, null]], "Choosing the right model": [[19, null]], "Classification": [[15, "classification"]], "Code quality": [[3, "code-quality"]], "Code style verification": [[3, "code-style-verification"]], "Codebase structure": [[3, "codebase-structure"]], "Commits": [[3, "commits"]], "Community resources": [[1, null]], "Composing transformations": [[10, "composing-transformations"]], "Continuous Integration": [[3, "continuous-integration"]], "Contributing to docTR": [[3, null]], "Contributor Covenant Code of Conduct": [[2, null]], "Custom dataset loader": [[7, "custom-dataset-loader"]], "Custom orientation classification models": [[13, "custom-orientation-classification-models"]], "Data Loading": [[17, "data-loading"]], "Dataloader": [[7, "dataloader"]], "Detection": [[15, "detection"], [17, "detection"]], "Detection predictors": [[19, "detection-predictors"]], "Developer mode installation": [[3, "developer-mode-installation"]], "Developing docTR": [[3, "developing-doctr"]], "Document": [[8, "document"]], "Document structure": [[8, "document-structure"]], "End-to-End OCR": [[19, "end-to-end-ocr"]], "Enforcement": [[2, "enforcement"]], "Enforcement Guidelines": [[2, "enforcement-guidelines"]], "Enforcement Responsibilities": [[2, "enforcement-responsibilities"]], "Export to ONNX": [[18, "export-to-onnx"]], "Feature requests & bug report": [[3, "feature-requests-bug-report"]], "Feedback": [[3, "feedback"]], "File reading": [[8, "file-reading"]], "Half-precision": [[18, "half-precision"]], "Installation": [[4, null]], "Integrate contributions into your pipeline": [[16, null]], "Let\u2019s connect": [[3, "let-s-connect"]], "Line": [[8, "line"]], "Loading from Huggingface Hub": [[15, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[13, "loading-your-custom-trained-model"]], "Loading your custom trained orientation classification model": [[13, "loading-your-custom-trained-orientation-classification-model"]], "Main Features": [[5, "main-features"]], "Model optimization": [[18, "model-optimization"]], "Model zoo": [[5, "model-zoo"]], "Modifying the documentation": [[3, "modifying-the-documentation"]], "Naming conventions": [[15, "naming-conventions"]], "OCR": [[17, "ocr"]], "Object Detection": [[17, "object-detection"]], "Our Pledge": [[2, "our-pledge"]], "Our Standards": [[2, "our-standards"]], "Page": [[8, "page"]], "Preparing your model for inference": [[18, null]], "Prerequisites": [[4, "prerequisites"]], "Pretrained community models": [[15, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[15, "pushing-to-the-huggingface-hub"]], "Questions": [[3, "questions"]], "Recognition": [[15, "recognition"], [17, "recognition"]], "Recognition predictors": [[19, "recognition-predictors"]], "Returns:": [[7, "returns"], [8, "returns"], [8, "id11"], [8, "id13"], [8, "id15"], [8, "id19"], [8, "id23"], [8, "id27"], [8, "id31"], [9, "returns"], [9, "id6"], [9, "id11"], [9, "id16"], [9, "id20"], [9, "id24"], [9, "id29"], [9, "id34"], [9, "id39"], [9, "id44"], [9, "id49"], [9, "id53"], [9, "id57"], [9, "id62"], [9, "id66"], [9, "id71"], [9, "id76"], [9, "id80"], [9, "id84"], [9, "id88"], [9, "id93"], [9, "id98"], [9, "id102"], [9, "id107"], [9, "id112"], [9, "id117"], [9, "id122"], [9, "id126"], [9, "id130"], [9, "id135"], [9, "id140"], [9, "id145"], [9, "id149"], [9, "id153"], [9, "id158"], [9, "id162"], [9, "id166"], [9, "id168"], [9, "id170"], [9, "id172"], [11, "returns"]], "Scope": [[2, "scope"]], "Share your model with the community": [[15, null]], "Supported Vocabs": [[7, "supported-vocabs"]], "Supported contribution modules": [[6, "supported-contribution-modules"]], "Supported datasets": [[5, "supported-datasets"]], "Supported transformations": [[10, "supported-transformations"]], "Synthetic dataset generator": [[7, "synthetic-dataset-generator"], [17, "synthetic-dataset-generator"]], "Task evaluation": [[11, "task-evaluation"]], "Text Detection": [[19, "text-detection"]], "Text Recognition": [[19, "text-recognition"]], "Text detection models": [[5, "text-detection-models"]], "Text recognition models": [[5, "text-recognition-models"]], "Train your own model": [[13, null]], "Two-stage approaches": [[19, "two-stage-approaches"]], "Unit tests": [[3, "unit-tests"]], "Use your own datasets": [[17, "use-your-own-datasets"]], "Using your ONNX exported model": [[18, "using-your-onnx-exported-model"]], "Via Conda (Only for Linux)": [[4, "via-conda-only-for-linux"]], "Via Git": [[4, "via-git"]], "Via Python Package": [[4, "via-python-package"]], "Visualization": [[11, "visualization"]], "What should I do with the output?": [[19, "what-should-i-do-with-the-output"]], "Word": [[8, "word"]], "docTR Notebooks": [[12, null]], "docTR Vocabs": [[7, "id62"]], "docTR: Document Text Recognition": [[5, null]], "doctr.contrib": [[6, null]], "doctr.datasets": [[7, null], [7, "datasets"]], "doctr.io": [[8, null]], "doctr.models": [[9, null]], "doctr.models.classification": [[9, "doctr-models-classification"]], "doctr.models.detection": [[9, "doctr-models-detection"]], "doctr.models.factory": [[9, "doctr-models-factory"]], "doctr.models.recognition": [[9, "doctr-models-recognition"]], "doctr.models.zoo": [[9, "doctr-models-zoo"]], "doctr.transforms": [[10, null]], "doctr.utils": [[11, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.10.0 (2024-10-21)": [[0, "v0-10-0-2024-10-21"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]], "v0.7.0 (2023-09-09)": [[0, "v0-7-0-2023-09-09"]], "v0.8.0 (2024-02-28)": [[0, "v0-8-0-2024-02-28"]], "v0.8.1 (2024-03-04)": [[0, "v0-8-1-2024-03-04"]], "v0.9.0 (2024-08-08)": [[0, "v0-9-0-2024-08-08"]]}, "docnames": ["changelog", "community/resources", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/contrib", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_contrib_modules", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "community/resources.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/contrib.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_contrib_modules.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[8, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[8, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[10, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[7, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[10, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[10, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[7, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[7, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[9, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[7, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[7, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[8, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[8, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[7, "doctr.datasets.encode_sequences", false]], "fast_base() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_base", false]], "fast_small() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_small", false]], "fast_tiny() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_tiny", false]], "from_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[7, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[10, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[10, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[7, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[7, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[7, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[7, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[7, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[9, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[10, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[8, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet34() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[7, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_crop_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_crop_orientation", false]], "mobilenet_v3_small_page_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_page_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[10, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[9, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[7, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[10, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[8, "doctr.io.Page", false]], "page_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.page_orientation_predictor", false]], "parseq() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[10, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[10, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[10, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[10, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[10, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[10, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[10, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[10, "doctr.transforms.RandomJpegQuality", false]], "randomresize (class in doctr.transforms)": [[10, "doctr.transforms.RandomResize", false]], "randomrotate (class in doctr.transforms)": [[10, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[10, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[10, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[8, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[8, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[8, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[7, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[10, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[8, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[8, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[7, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[7, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[7, "doctr.datasets.SVT", false]], "synthtext (class in doctr.datasets)": [[7, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.TextMatch", false]], "textnet_base() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_base", false]], "textnet_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_small", false]], "textnet_tiny() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_tiny", false]], "togray (class in doctr.transforms)": [[10, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[11, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_small", false]], "wildreceipt (class in doctr.datasets)": [[7, "doctr.datasets.WILDRECEIPT", false]], "word (class in doctr.io)": [[8, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[7, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[7, 0, 1, "", "CORD"], [7, 0, 1, "", "CharacterGenerator"], [7, 0, 1, "", "DetectionDataset"], [7, 0, 1, "", "DocArtefacts"], [7, 0, 1, "", "FUNSD"], [7, 0, 1, "", "IC03"], [7, 0, 1, "", "IC13"], [7, 0, 1, "", "IIIT5K"], [7, 0, 1, "", "IIITHWS"], [7, 0, 1, "", "IMGUR5K"], [7, 0, 1, "", "MJSynth"], [7, 0, 1, "", "OCRDataset"], [7, 0, 1, "", "RecognitionDataset"], [7, 0, 1, "", "SROIE"], [7, 0, 1, "", "SVHN"], [7, 0, 1, "", "SVT"], [7, 0, 1, "", "SynthText"], [7, 0, 1, "", "WILDRECEIPT"], [7, 0, 1, "", "WordGenerator"], [7, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[7, 0, 1, "", "DataLoader"]], "doctr.io": [[8, 0, 1, "", "Artefact"], [8, 0, 1, "", "Block"], [8, 0, 1, "", "Document"], [8, 0, 1, "", "DocumentFile"], [8, 0, 1, "", "Line"], [8, 0, 1, "", "Page"], [8, 0, 1, "", "Word"], [8, 1, 1, "", "decode_img_as_tensor"], [8, 1, 1, "", "read_html"], [8, 1, 1, "", "read_img_as_numpy"], [8, 1, 1, "", "read_img_as_tensor"], [8, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[8, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[8, 2, 1, "", "from_images"], [8, 2, 1, "", "from_pdf"], [8, 2, 1, "", "from_url"]], "doctr.io.Page": [[8, 2, 1, "", "show"]], "doctr.models": [[9, 1, 1, "", "kie_predictor"], [9, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[9, 1, 1, "", "crop_orientation_predictor"], [9, 1, 1, "", "magc_resnet31"], [9, 1, 1, "", "mobilenet_v3_large"], [9, 1, 1, "", "mobilenet_v3_large_r"], [9, 1, 1, "", "mobilenet_v3_small"], [9, 1, 1, "", "mobilenet_v3_small_crop_orientation"], [9, 1, 1, "", "mobilenet_v3_small_page_orientation"], [9, 1, 1, "", "mobilenet_v3_small_r"], [9, 1, 1, "", "page_orientation_predictor"], [9, 1, 1, "", "resnet18"], [9, 1, 1, "", "resnet31"], [9, 1, 1, "", "resnet34"], [9, 1, 1, "", "resnet50"], [9, 1, 1, "", "textnet_base"], [9, 1, 1, "", "textnet_small"], [9, 1, 1, "", "textnet_tiny"], [9, 1, 1, "", "vgg16_bn_r"], [9, 1, 1, "", "vit_b"], [9, 1, 1, "", "vit_s"]], "doctr.models.detection": [[9, 1, 1, "", "db_mobilenet_v3_large"], [9, 1, 1, "", "db_resnet50"], [9, 1, 1, "", "detection_predictor"], [9, 1, 1, "", "fast_base"], [9, 1, 1, "", "fast_small"], [9, 1, 1, "", "fast_tiny"], [9, 1, 1, "", "linknet_resnet18"], [9, 1, 1, "", "linknet_resnet34"], [9, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[9, 1, 1, "", "from_hub"], [9, 1, 1, "", "login_to_hub"], [9, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[9, 1, 1, "", "crnn_mobilenet_v3_large"], [9, 1, 1, "", "crnn_mobilenet_v3_small"], [9, 1, 1, "", "crnn_vgg16_bn"], [9, 1, 1, "", "master"], [9, 1, 1, "", "parseq"], [9, 1, 1, "", "recognition_predictor"], [9, 1, 1, "", "sar_resnet31"], [9, 1, 1, "", "vitstr_base"], [9, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[10, 0, 1, "", "ChannelShuffle"], [10, 0, 1, "", "ColorInversion"], [10, 0, 1, "", "Compose"], [10, 0, 1, "", "GaussianBlur"], [10, 0, 1, "", "GaussianNoise"], [10, 0, 1, "", "LambdaTransformation"], [10, 0, 1, "", "Normalize"], [10, 0, 1, "", "OneOf"], [10, 0, 1, "", "RandomApply"], [10, 0, 1, "", "RandomBrightness"], [10, 0, 1, "", "RandomContrast"], [10, 0, 1, "", "RandomCrop"], [10, 0, 1, "", "RandomGamma"], [10, 0, 1, "", "RandomHorizontalFlip"], [10, 0, 1, "", "RandomHue"], [10, 0, 1, "", "RandomJpegQuality"], [10, 0, 1, "", "RandomResize"], [10, 0, 1, "", "RandomRotate"], [10, 0, 1, "", "RandomSaturation"], [10, 0, 1, "", "RandomShadow"], [10, 0, 1, "", "Resize"], [10, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[11, 0, 1, "", "DetectionMetric"], [11, 0, 1, "", "LocalizationConfusion"], [11, 0, 1, "", "OCRMetric"], [11, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.visualization": [[11, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [2, 8, 9, 11, 15, 18], "0": [2, 4, 7, 10, 11, 13, 16, 17, 19], "00": 19, "01": 19, "0123456789": 7, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "02562": 9, "03": 19, "035": 19, "0361328125": 19, "04": 19, "05": 19, "06": 19, "06640625": 19, "07": 19, "08": [10, 19], "09": 19, "0966796875": 19, "1": [7, 8, 9, 10, 11, 13, 17, 19], "10": [7, 11, 19], "100": [7, 10, 11, 17, 19], "1000": 19, "101": 7, "1024": [9, 13, 19], "104": 7, "106": 7, "108": 7, "1095": 17, "11": 19, "110": 11, "1107": 17, "114": 7, "115": 7, "1156": 17, "116": 7, "118": 7, "11800h": 19, "11th": 19, "12": 19, "120": 7, "123": 7, "126": 7, "1268": 17, "128": [9, 13, 18, 19], "13": 19, "130": 7, "13068": 17, "131": 7, "1337891": 17, "1357421875": 19, "1396484375": 19, "14": 19, "1420": 19, "14470v1": 7, "149": 17, "15": 19, "150": [11, 19], "1552": 19, "16": [9, 18, 19], "1630859375": 19, "1684": 19, "16x16": 9, "17": 19, "1778": 19, "1782": 19, "18": [9, 19], "185546875": 19, "1900": 19, "1910": 9, "19342": 17, "19370": 17, "195": 7, "19598": 17, "199": 19, "1999": 19, "2": [4, 5, 7, 8, 9, 10, 16, 19], "20": 19, "200": 11, "2000": 17, "2003": [5, 7], "2012": 7, "2013": [5, 7], "2015": 7, "2019": 5, "2023": 1, "207901": 17, "21": 19, "2103": 7, "2186": 17, "21888": 17, "22": 19, "224": [9, 10], "225": 10, "22672": 17, "229": [10, 17], "23": 19, "233": 17, "236": 7, "24": 19, "246": 17, "249": 17, "25": 19, "2504": 19, "255": [8, 9, 10, 11, 19], "256": 9, "257": 17, "26": 19, "26032": 17, "264": 13, "27": 19, "2700": 17, "2710": 19, "2749": 13, "28": 19, "287": 13, "29": 19, "296": 13, "299": 13, "2d": 19, "3": [4, 5, 8, 9, 10, 11, 18, 19], "30": 19, "300": 17, "3000": 17, "301": 13, "30595": 19, "30ghz": 19, "31": 9, "32": [7, 9, 10, 13, 17, 18, 19], "3232421875": 19, "33": [10, 19], "33402": 17, "33608": 17, "34": [9, 19], "340": 19, "3456": 19, "3515625": 19, "36": 19, "360": 17, "37": [7, 19], "38": 19, "39": 19, "4": [9, 10, 11, 19], "40": 19, "406": 10, "41": 19, "42": 19, "43": 19, "44": 19, "45": 19, "456": 10, "46": 19, "47": 19, "472": 17, "48": [7, 19], "485": 10, "49": 19, "49377": 17, "5": [7, 10, 11, 16, 19], "50": [9, 17, 19], "51": 19, "51171875": 19, "512": 9, "52": [7, 19], "529": 19, "53": 19, "54": 19, "540": 19, "5478515625": 19, "55": 19, "56": 19, "57": 19, "58": [7, 19], "580": 19, "5810546875": 19, "583": 19, "59": 19, "597": 19, "5k": [5, 7], "5m": 19, "6": [10, 19], "60": 10, "600": [9, 11, 19], "61": 19, "62": 19, "626": 17, "63": 19, "64": [9, 10, 19], "641": 19, "647": 17, "65": 19, "66": 19, "67": 19, "68": 19, "69": 19, "693": 13, "694": 13, "695": 13, "6m": 19, "7": 19, "70": [7, 11, 19], "707470": 17, "71": [7, 19], "7100000": 17, "7141797": 17, "7149": 17, "72": 19, "72dpi": 8, "73": 19, "73257": 17, "74": 19, "75": [10, 19], "7581382": 17, "76": 19, "77": 19, "772": 13, "772875": 17, "78": 19, "785": 13, "79": 19, "793533": 17, "796": 17, "798": 13, "7m": 19, "8": [9, 10, 19], "80": 19, "800": [9, 11, 17, 19], "81": 19, "82": 19, "83": 19, "84": 19, "849": 17, "85": 19, "8564453125": 19, "857": 19, "85875": 17, "86": 19, "8603515625": 19, "87": 19, "8707": 17, "88": 19, "89": 19, "9": [4, 10, 19], "90": 19, "90k": 7, "90kdict32px": 7, "91": 19, "914085328578949": 19, "92": 19, "93": 19, "94": [7, 19], "95": [11, 19], "9578408598899841": 19, "96": 19, "97": 19, "98": 19, "99": 19, "9949972033500671": 19, "A": [2, 3, 5, 7, 8, 9, 12, 18], "As": 3, "Be": 19, "Being": 2, "By": 14, "For": [2, 3, 4, 13, 19], "If": [3, 8, 9, 13, 19], "In": [3, 7, 17], "It": [10, 15, 16, 18], "Its": [5, 9], "No": [2, 19], "Of": 7, "Or": [16, 18], "The": [2, 3, 7, 8, 11, 14, 16, 17, 18, 19], "Then": 9, "To": [3, 4, 14, 15, 16, 18, 19], "_": [2, 7, 9], "__call__": 19, "_build": 3, "_i": 11, "ab": 7, "abc": 18, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "abdef": [7, 17], "abl": [17, 19], "about": [2, 17, 19], "abov": 19, "abstract": 1, "abstractdataset": 7, "abus": 2, "accept": 2, "access": [5, 8, 17, 19], "account": [2, 15], "accur": 19, "accuraci": 11, "achiev": 18, "act": 2, "action": 2, "activ": 5, "ad": [3, 9, 10], "adapt": 2, "add": [10, 11, 15, 19], "add_hook": 19, "add_label": 11, "addit": [3, 4, 8, 16, 19], "addition": [3, 19], "address": [2, 8], "adjust": 10, "advanc": 2, "advantag": 18, "advis": 3, "aesthet": [5, 7], "affect": 2, "after": [15, 19], "ag": 2, "again": 9, "aggreg": [11, 17], "aggress": 2, "align": [2, 8, 10], "all": [2, 3, 6, 7, 8, 10, 11, 16, 17, 19], "allow": [2, 18], "along": 19, "alreadi": [3, 18], "also": [2, 9, 15, 16, 17, 19], "alwai": 17, "an": [2, 3, 5, 7, 8, 9, 11, 16, 18, 19], "analysi": [8, 16], "ancient_greek": 7, "andrej": 1, "angl": [8, 10], "ani": [2, 7, 8, 9, 10, 11, 18, 19], "annot": 7, "anot": 17, "anoth": [9, 13, 17], "answer": 2, "anyascii": 11, "anyon": 5, "anyth": 16, "api": [3, 5], "apolog": 2, "apologi": 2, "app": 3, "appear": 2, "appli": [2, 7, 10], "applic": [5, 9], "appoint": 2, "appreci": 15, "appropri": [2, 3, 19], "ar": [2, 3, 4, 6, 7, 8, 10, 11, 12, 16, 17, 19], "arab": 7, "arabic_diacrit": 7, "arabic_lett": 7, "arabic_punctu": 7, "arbitrarili": [5, 9], "arch": [9, 15], "architectur": [5, 9, 15, 16], "area": 19, "argument": [7, 8, 9, 11, 13, 19], "around": 2, "arrai": [8, 10, 11], "art": [5, 16], "artefact": [11, 16, 19], "artefact_typ": 8, "articl": 1, "artifici": [5, 7], "arxiv": [7, 9], "asarrai": 11, "ascii_lett": 7, "aspect": [5, 9, 10, 19], "assess": 11, "assign": 11, "associ": 8, "assum": 9, "assume_straight_pag": [9, 13, 19], "astyp": [9, 11, 19], "attack": 2, "attend": [5, 9], "attent": [2, 9], "autom": 5, "automat": 19, "autoregress": [5, 9], "avail": [2, 5, 6, 10], "averag": [10, 19], "avoid": [2, 4], "aw": [5, 19], "awar": 19, "azur": 19, "b": [9, 11, 19], "b_j": 11, "back": 3, "backbon": 9, "backend": 19, "background": 17, "bangla": 7, "bar": 16, "bar_cod": 17, "baranovskij": 1, "base": [5, 9, 16], "baselin": [5, 9, 19], "batch": [7, 9, 10, 16, 17, 19], "batch_siz": [7, 9, 13, 16, 17, 18], "bblanchon": 4, "bbox": 19, "becaus": 14, "been": [3, 11, 17, 19], "befor": [7, 9, 10, 19], "begin": 11, "behavior": [2, 19], "being": [11, 19], "belong": 19, "benchmark": 19, "best": [1, 2], "better": [12, 19], "between": [10, 11, 19], "bgr": 8, "bilinear": 10, "bin_thresh": 19, "binar": [5, 9, 19], "binari": [8, 18, 19], "bit": 18, "block": [11, 19], "block_1_1": 19, "blur": 10, "bmvc": 7, "bn": 15, "bodi": [2, 19], "bool": [7, 8, 9, 10, 11], "boolean": [9, 19], "both": [5, 7, 10, 17, 19], "bottom": [9, 19], "bound": [7, 8, 9, 10, 11, 16, 17, 19], "box": [7, 8, 9, 10, 11, 16, 17, 19], "box_thresh": 19, "bright": 10, "browser": [3, 5], "build": [3, 4, 18], "built": 3, "byte": [8, 19], "c": [4, 8, 11], "c_j": 11, "cach": [3, 7, 14], "cache_sampl": 7, "call": 18, "callabl": [7, 10], "can": [3, 4, 13, 14, 15, 16, 17, 19], "capabl": [3, 12, 19], "case": [7, 11], "cf": 19, "cfg": 19, "challeng": 7, "challenge2_test_task12_imag": 7, "challenge2_test_task1_gt": 7, "challenge2_training_task12_imag": 7, "challenge2_training_task1_gt": 7, "chang": [14, 19], "channel": [2, 3, 8, 10], "channel_prior": 4, "channelshuffl": 10, "charact": [5, 7, 8, 11, 17, 19], "charactergener": [7, 17], "characterist": 2, "charg": 19, "charset": 19, "chart": 8, "check": [3, 15, 19], "checkpoint": 9, "chip": 4, "christian": 1, "ci": 3, "clarifi": 2, "clariti": 2, "class": [2, 7, 8, 10, 11, 19], "class_nam": 13, "classif": [17, 19], "classmethod": 8, "clear": 3, "clone": 4, "close": 3, "co": 15, "code": [5, 8, 16], "codecov": 3, "colab": 12, "collate_fn": 7, "collect": [8, 16], "color": 10, "colorinvers": 10, "column": 8, "com": [2, 4, 8, 9, 15], "combin": 19, "command": [3, 16], "comment": 2, "commit": 2, "common": [2, 10, 11, 18], "commun": 2, "compar": 5, "comparison": [11, 19], "competit": 7, "compil": [12, 19], "complaint": 2, "complementari": 11, "complet": 3, "compon": 19, "compos": [7, 19], "comprehens": 19, "comput": [7, 11, 18, 19], "conf_threshold": 16, "confid": [8, 19], "config": [4, 9], "configur": 9, "confus": 11, "consecut": [10, 19], "consequ": 2, "consid": [2, 3, 7, 8, 11, 19], "consist": 19, "consolid": [5, 7], "constant": 10, "construct": 2, "contact": 2, "contain": [1, 6, 7, 12, 17, 19], "content": [7, 8, 19], "context": 9, "contib": 4, "continu": 2, "contrast": 10, "contrast_factor": 10, "contrib": [4, 16], "contribut": 2, "contributor": 3, "convers": 8, "convert": [8, 10], "convolut": 9, "cool": 1, "coordin": [8, 19], "cord": [5, 7, 17, 19], "core": [11, 19], "corner": 19, "correct": 10, "correspond": [4, 8, 10, 19], "could": [2, 16], "counterpart": 11, "cover": 3, "coverag": 3, "cpu": [5, 13, 18], "creat": [1, 15], "crnn": [5, 9, 15], "crnn_mobilenet_v3_larg": [9, 15, 19], "crnn_mobilenet_v3_smal": [9, 18, 19], "crnn_vgg16_bn": [9, 13, 15, 19], "crop": [8, 9, 10, 13, 17, 19], "crop_orient": [8, 19], "crop_orientation_predictor": [9, 13], "crop_param": 13, "cuda": 18, "currenc": 7, "current": [3, 13, 19], "custom": [15, 16, 18, 19], "custom_crop_orientation_model": 13, "custom_page_orientation_model": 13, "customhook": 19, "cvit": 5, "czczup": 9, "czech": 7, "d": [7, 17], "danish": 7, "data": [5, 7, 8, 10, 11, 13, 15], "dataload": 17, "dataset": [9, 13, 19], "dataset_info": 7, "date": [13, 19], "db": 15, "db_mobilenet_v3_larg": [9, 15, 19], "db_resnet34": 19, "db_resnet50": [9, 13, 15, 19], "dbnet": [5, 9], "deal": [12, 19], "decis": 2, "decod": 8, "decode_img_as_tensor": 8, "dedic": 18, "deem": 2, "deep": [9, 19], "def": 19, "default": [4, 8, 13, 14, 19], "defer": 17, "defin": [11, 18], "degre": [8, 10, 19], "degress": 8, "delet": 3, "delimit": 19, "delta": 10, "demo": [3, 5], "demonstr": 2, "depend": [3, 4, 5, 19], "deploi": 3, "deploy": 5, "derogatori": 2, "describ": 9, "descript": 12, "design": 10, "desir": 8, "det_arch": [9, 13, 15, 18], "det_b": 19, "det_model": [13, 15, 18], "det_param": 13, "det_predictor": [13, 19], "detail": [13, 19], "detect": [1, 7, 8, 11, 12, 13, 16], "detect_languag": 9, "detect_orient": [9, 13, 19], "detection_predictor": [9, 19], "detection_task": [7, 17], "detectiondataset": [7, 17], "detectionmetr": 11, "detectionpredictor": [9, 13], "detector": [5, 9, 16], "deterior": 9, "determin": 2, "dev": [3, 14], "develop": 4, "deviat": 10, "devic": 18, "dict": [8, 11, 19], "dictionari": [8, 11], "differ": 2, "differenti": [5, 9], "digit": [5, 7, 17], "dimens": [8, 11, 19], "dimension": 10, "direct": 7, "directli": [15, 19], "directori": [3, 14], "disabl": [2, 14, 19], "disable_crop_orient": 19, "disable_page_orient": 19, "disclaim": 19, "discuss": 3, "disparag": 2, "displai": [8, 11], "display_artefact": 11, "distribut": 10, "div": 19, "divers": 2, "divid": 8, "do": [3, 4, 9], "doc": [3, 8, 16, 18, 19], "docartefact": [7, 17], "docstr": 3, "doctr": [1, 4, 13, 14, 15, 16, 17, 18, 19], "doctr_cache_dir": 14, "doctr_multiprocessing_dis": 14, "document": [1, 7, 9, 11, 12, 13, 16, 17, 18, 19], "documentbuild": 19, "documentfil": [8, 13, 15, 16, 18], "doesn": 18, "don": [13, 19], "done": 10, "download": [7, 17], "downsiz": 9, "draw": 10, "drop": 7, "drop_last": 7, "dtype": [8, 9, 10, 11, 18], "dual": [5, 7], "dummi": 15, "dummy_img": 19, "dummy_input": 18, "dure": 2, "dutch": 7, "dynam": [7, 16], "dynamic_seq_length": 7, "e": [2, 3, 4, 8, 9], "each": [5, 7, 8, 9, 10, 11, 17, 19], "eas": 3, "easi": [5, 11, 15, 18], "easili": [8, 11, 13, 15, 17, 19], "econom": 2, "edit": 2, "educ": 2, "effect": 19, "effici": [3, 5, 7, 9], "either": [11, 19], "element": [7, 8, 9, 19], "els": [3, 16], "email": 2, "empathi": 2, "en": 19, "enabl": [7, 8], "enclos": 8, "encod": [5, 7, 8, 9, 19], "encode_sequ": 7, "encount": 3, "encrypt": 8, "end": [5, 7, 9, 11], "english": [7, 17], "enough": [3, 19], "ensur": 3, "entri": 7, "environ": [2, 14], "eo": 7, "equiv": 19, "estim": 9, "etc": [8, 16], "ethnic": 2, "evalu": [17, 19], "event": 2, "everyon": 2, "everyth": [3, 19], "exact": [11, 19], "exampl": [2, 3, 5, 7, 9, 15, 19], "exchang": 18, "execut": 19, "exist": 15, "expand": 10, "expect": [8, 10, 11], "experi": 2, "explan": [2, 19], "explicit": 2, "exploit": [5, 9], "export": [8, 9, 11, 12, 16, 19], "export_as_straight_box": [9, 19], "export_as_xml": 19, "export_model_to_onnx": 18, "express": [2, 10], "extens": 8, "extern": [2, 17], "extract": [1, 5, 7], "extractor": 9, "f_": 11, "f_a": 11, "factor": 10, "fair": 2, "fairli": 2, "fals": [7, 8, 9, 10, 11, 13, 19], "faq": 2, "fascan": 15, "fast": [5, 7, 9], "fast_bas": [9, 19], "fast_smal": [9, 19], "fast_tini": [9, 19], "faster": [5, 9, 18], "fasterrcnn_mobilenet_v3_large_fpn": 9, "favorit": 19, "featur": [4, 9, 11, 12, 13, 16], "feedback": 2, "feel": [3, 15], "felix92": 15, "few": [18, 19], "figsiz": 11, "figur": [11, 16], "file": [3, 7], "final": 9, "find": [3, 17], "fine": 1, "finnish": 7, "first": [3, 7], "firsthand": 7, "fit": [9, 19], "flag": 19, "flip": 10, "float": [8, 10, 11, 18], "float32": [8, 9, 10, 18], "fn": 10, "focu": 15, "focus": [2, 7], "folder": 7, "follow": [2, 3, 4, 7, 10, 11, 13, 14, 15, 16, 19], "font": 7, "font_famili": 7, "foral": 11, "forc": 3, "forg": 4, "form": [5, 7, 19], "format": [8, 11, 13, 17, 18, 19], "forpost": [5, 7], "forum": 3, "found": 1, "fp16": 18, "frac": 11, "framework": [4, 15, 17, 19], "free": [2, 3, 15], "french": [7, 13, 15, 19], "friendli": 5, "from": [1, 2, 5, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19], "from_hub": [9, 15], "from_imag": [8, 15, 16, 18], "from_pdf": 8, "from_url": 8, "full": [7, 11, 19], "function": [7, 10, 11, 16], "funsd": [5, 7, 17, 19], "further": 17, "futur": 7, "g": [8, 9], "g_": 11, "g_x": 11, "gallagh": 1, "gamma": 10, "gaussian": 10, "gaussianblur": 10, "gaussiannois": 10, "gen": 19, "gender": 2, "gener": [3, 5, 8, 9], "generic_cyrillic_lett": 7, "geometri": [5, 8, 19], "geq": 11, "german": [7, 13, 15], "get": [18, 19], "git": 15, "github": [3, 4, 9, 15], "give": [2, 16], "given": [7, 8, 10, 11, 19], "global": 9, "go": 19, "good": 18, "googl": 3, "googlevis": 5, "gpu": [5, 16, 18], "gracefulli": 2, "graph": [5, 7, 8], "grayscal": 10, "ground": 11, "groung": 11, "group": [5, 19], "gt": 11, "gt_box": 11, "gt_label": 11, "guid": 3, "guidanc": 17, "gvision": 19, "h": [8, 9, 10], "h_": 11, "ha": [3, 7, 11, 17], "handl": [12, 17, 19], "handwrit": 7, "handwritten": 17, "harass": 2, "hardwar": 19, "harm": 2, "hat": 11, "have": [2, 3, 11, 13, 15, 17, 18, 19], "head": [9, 19], "healthi": 2, "hebrew": 7, "height": [8, 10], "hello": [11, 19], "help": 18, "here": [6, 10, 12, 16, 17, 19], "hf": 9, "hf_hub_download": 9, "high": 8, "higher": [4, 7, 19], "hindi": 7, "hindi_digit": 7, "hocr": 19, "hook": 19, "horizont": [8, 10, 19], "hous": 7, "how": [1, 3, 12, 13, 15, 17], "howev": 17, "hsv": 10, "html": [2, 3, 4, 8, 19], "http": [2, 4, 7, 8, 9, 15, 19], "hub": 9, "hue": 10, "huggingfac": 9, "hw": 7, "i": [2, 3, 7, 8, 9, 10, 11, 14, 15, 16, 17, 18], "i7": 19, "ibrahimov": 1, "ic03": [5, 7, 17], "ic13": [5, 7, 17], "icdar": [5, 7], "icdar2019": 7, "id": 19, "ident": 2, "identifi": 5, "iiit": [5, 7], "iiit5k": [7, 17], "iiithw": [5, 7, 17], "imag": [1, 5, 7, 8, 9, 10, 11, 15, 16, 17, 19], "imagenet": 9, "imageri": 2, "images_90k_norm": 7, "img": [7, 10, 17, 18], "img_cont": 8, "img_fold": [7, 17], "img_path": 8, "img_transform": 7, "imgur5k": [5, 7, 17], "imgur5k_annot": 7, "imlist": 7, "impact": 2, "implement": [7, 8, 9, 10, 11, 19], "import": [7, 8, 9, 10, 11, 13, 15, 16, 17, 18, 19], "improv": 9, "inappropri": 2, "incid": 2, "includ": [2, 7, 17, 18], "inclus": 2, "increas": 10, "independ": 10, "index": [3, 8], "indic": 11, "individu": 2, "infer": [5, 9, 10, 16, 19], "inform": [1, 2, 3, 5, 7, 17], "input": [3, 8, 9, 10, 18, 19], "input_crop": 9, "input_pag": [9, 11, 19], "input_shap": 18, "input_tensor": 9, "inspir": [2, 10], "instal": [15, 16, 18], "instanc": [2, 19], "instanti": [9, 19], "instead": [7, 8, 9], "insult": 2, "int": [7, 8, 9, 10], "int64": 11, "integ": 11, "integr": [1, 5, 15, 17], "intel": 19, "interact": [2, 8, 11], "interfac": [15, 18], "interoper": 18, "interpol": 10, "interpret": [7, 8], "intersect": 11, "invert": 10, "investig": 2, "invis": 2, "involv": [2, 19], "io": [13, 15, 16, 18], "iou": 11, "iou_thresh": 11, "iou_threshold": 16, "irregular": [5, 9, 17], "isn": 7, "issu": [2, 3, 15], "italian": 7, "iter": [7, 10, 17, 19], "its": [8, 9, 10, 11, 17, 19], "itself": [9, 15], "j": 11, "jame": 1, "job": 3, "join": 3, "jpeg": 10, "jpegqual": 10, "jpg": [7, 8, 15, 18], "json": [7, 17, 19], "json_output": 19, "jump": 3, "just": 2, "kei": [5, 7], "kera": [9, 18], "kernel": [5, 9, 10], "kernel_shap": 10, "keywoard": 9, "keyword": [7, 8, 9, 11], "kie": [9, 13], "kie_predictor": [9, 13], "kiepredictor": 9, "kind": 2, "know": [3, 18], "kwarg": [7, 8, 9, 11], "l": 11, "l_j": 11, "label": [7, 11, 16, 17], "label_fil": [7, 17], "label_fold": 7, "label_path": [7, 17], "labels_path": [7, 17], "ladder": 2, "lambda": 10, "lambdatransform": 10, "lang": 19, "languag": [2, 5, 7, 8, 9, 15, 19], "larg": [9, 15], "largest": 11, "last": [4, 7], "latenc": 9, "later": 3, "latest": 19, "latin": 7, "layer": 18, "layout": 19, "lead": 2, "leader": 2, "learn": [2, 5, 9, 18, 19], "least": 4, "left": [11, 19], "legacy_french": 7, "length": [7, 19], "less": [18, 19], "level": [2, 7, 11, 19], "leverag": 12, "lf": 15, "librari": [3, 4, 12, 13], "light": 5, "lightweight": 18, "like": 2, "limits_": 11, "line": [5, 9, 11, 19], "line_1_1": 19, "link": 13, "linknet": [5, 9], "linknet_resnet18": [9, 13, 18, 19], "linknet_resnet34": [9, 18, 19], "linknet_resnet50": [9, 19], "list": [7, 8, 10, 11, 15], "ll": 11, "load": [5, 7, 9, 16, 18], "load_state_dict": 13, "load_weight": 13, "loc_pr": 19, "local": [3, 5, 7, 9, 11, 17, 19], "localis": 7, "localizationconfus": 11, "locat": [3, 8, 19], "login": 9, "login_to_hub": [9, 15], "logo": [8, 16, 17], "love": 15, "lower": [10, 11, 19], "m": [3, 11, 19], "m1": 4, "macbook": 4, "machin": 18, "made": 5, "magc_resnet31": 9, "mai": [2, 3], "mail": 2, "main": 12, "maintain": 5, "mainten": 3, "make": [2, 3, 11, 13, 14, 15, 18, 19], "mani": [17, 19], "manipul": 19, "map": [7, 9], "map_loc": 13, "master": [5, 9, 19], "match": [11, 19], "mathcal": 11, "matplotlib": [8, 11], "max": [7, 10, 11], "max_angl": 10, "max_area": 10, "max_char": [7, 17], "max_delta": 10, "max_gain": 10, "max_gamma": 10, "max_qual": 10, "max_ratio": 10, "maximum": [7, 10], "maxval": [9, 10], "mbox": 11, "mean": [10, 11, 13], "meaniou": 11, "meant": [8, 18], "measur": 19, "media": 2, "median": 9, "meet": 13, "member": 2, "memori": [14, 18], "mention": 19, "merg": 7, "messag": 3, "meta": 19, "metadata": 18, "metal": 4, "method": [8, 10, 19], "metric": [11, 19], "middl": 19, "might": [18, 19], "min": 10, "min_area": 10, "min_char": [7, 17], "min_gain": 10, "min_gamma": 10, "min_qual": 10, "min_ratio": 10, "min_val": 10, "minde": [1, 2, 4, 5, 9], "minim": [3, 5], "minimalist": [5, 9], "minimum": [4, 7, 10, 11, 19], "minval": 10, "miss": 4, "mistak": 2, "mixed_float16": 18, "mixed_precis": 18, "mjsynth": [5, 7, 17], "mnt": 7, "mobilenet": [9, 15], "mobilenet_v3_larg": 9, "mobilenet_v3_large_r": 9, "mobilenet_v3_smal": [9, 13], "mobilenet_v3_small_crop_orient": [9, 13], "mobilenet_v3_small_page_orient": [9, 13], "mobilenet_v3_small_r": 9, "mobilenetv3": 9, "modal": [5, 7], "mode": 4, "model": [7, 11, 14, 16, 17], "model_nam": [9, 15, 18], "model_path": [16, 18], "moder": 2, "modif": 3, "modifi": [9, 14, 19], "modul": [4, 8, 9, 10, 11, 19], "more": [3, 17, 19], "moscardi": 1, "most": 19, "mozilla": 2, "multi": [5, 9], "multilingu": [7, 15], "multipl": [7, 8, 10, 19], "multipli": 10, "multiprocess": 14, "my": 9, "my_awesome_model": 15, "my_hook": 19, "n": [7, 11], "name": [7, 9, 18, 19], "nation": 2, "natur": [2, 5, 7], "ndarrai": [7, 8, 10, 11], "necessari": [4, 13, 14], "need": [3, 4, 7, 11, 13, 14, 15, 16, 19], "neg": 10, "nest": 19, "netraj": 1, "network": [5, 7, 9, 18], "neural": [5, 7, 9, 18], "new": [3, 11], "next": [7, 17], "nois": 10, "noisi": [5, 7], "non": [5, 7, 8, 9, 10, 11], "none": [7, 8, 9, 10, 11, 19], "normal": [9, 10], "norwegian": 7, "note": [0, 3, 7, 9, 13, 15, 16, 18], "now": 3, "np": [9, 10, 11, 19], "num_output_channel": 10, "num_sampl": [7, 17], "number": [7, 9, 10, 11, 19], "numpi": [8, 9, 11, 19], "o": 4, "obb": 16, "obj_detect": 15, "object": [7, 8, 11, 16, 19], "objectness_scor": [8, 19], "oblig": 2, "obtain": 19, "occupi": 18, "ocr": [1, 5, 7, 9, 11, 15], "ocr_carea": 19, "ocr_db_crnn": 11, "ocr_lin": 19, "ocr_pag": 19, "ocr_par": 19, "ocr_predictor": [9, 13, 15, 18, 19], "ocrdataset": [7, 17], "ocrmetr": 11, "ocrpredictor": [9, 13], "ocrx_word": 19, "offens": 2, "offici": [2, 9], "offlin": 2, "offset": 10, "onc": 19, "one": [3, 7, 9, 10, 13, 15, 19], "oneof": 10, "ones": [7, 11], "onli": [3, 9, 10, 11, 13, 15, 17, 18, 19], "onlin": 2, "onnx": 16, "onnxruntim": [16, 18], "onnxtr": 18, "opac": 10, "opacity_rang": 10, "open": [1, 2, 3, 15, 18], "opinion": 2, "optic": [5, 19], "optim": [5, 19], "option": [7, 9, 13], "order": [3, 7, 8, 10], "org": [2, 7, 9, 19], "organ": 8, "orient": [2, 8, 9, 12, 16, 19], "orientationpredictor": 9, "other": [2, 3], "otherwis": [2, 8, 11], "our": [1, 3, 9, 19], "out": [3, 9, 10, 11, 19], "outpout": 19, "output": [8, 10, 18], "output_s": [8, 10], "outsid": 14, "over": [7, 11, 19], "overal": [2, 9], "overlai": 8, "overview": 16, "overwrit": 13, "overwritten": 15, "own": 5, "p": [10, 19], "packag": [3, 5, 11, 14, 16, 17, 18], "pad": [7, 9, 10, 19], "page": [4, 7, 9, 11, 13, 19], "page1": 8, "page2": 8, "page_1": 19, "page_idx": [8, 19], "page_orientation_predictor": [9, 13], "page_param": 13, "pair": 11, "paper": 9, "par_1_1": 19, "paragraph": 19, "paragraph_break": 19, "parallel": 9, "param": [10, 19], "paramet": [5, 8, 9, 18], "pars": [5, 7], "parseq": [5, 9, 15, 18, 19], "part": [7, 10, 19], "parti": 4, "partial": 19, "particip": 2, "pass": [7, 8, 9, 13, 19], "password": 8, "patch": [9, 11], "path": [7, 8, 16, 17, 18], "path_to_checkpoint": 13, "path_to_custom_model": 18, "path_to_pt": 13, "patil": 1, "pattern": 2, "pdf": [8, 9, 12], "pdfpage": 8, "peopl": 2, "per": [10, 19], "perform": [5, 8, 9, 10, 11, 14, 18, 19], "period": 2, "permiss": 2, "permut": [5, 9], "persian_lett": 7, "person": [2, 17], "phase": 19, "photo": 17, "physic": [2, 8], "pick": 10, "pictur": 8, "pip": [3, 4, 16, 18], "pipelin": 19, "pixel": [8, 10, 19], "pleas": 3, "plot": 11, "plt": 11, "plug": 15, "plugin": 4, "png": 8, "point": 18, "polici": 14, "polish": 7, "polit": 2, "polygon": [7, 11, 19], "pool": 9, "portugues": 7, "posit": [2, 11], "possibl": [3, 11, 15, 19], "post": [2, 19], "postprocessor": 19, "potenti": 9, "power": 5, "ppageno": 19, "pre": [3, 9, 18], "precis": [11, 19], "pred": 11, "pred_box": 11, "pred_label": 11, "predefin": 17, "predict": [8, 9, 11, 19], "predictor": [5, 8, 9, 12, 13, 15, 18], "prefer": 17, "preinstal": 4, "preprocessor": [13, 19], "prerequisit": 15, "present": 12, "preserv": [9, 10, 19], "preserve_aspect_ratio": [8, 9, 10, 13, 19], "pretrain": [5, 9, 11, 13, 18, 19], "pretrained_backbon": [9, 13], "print": 19, "prior": 7, "privaci": 2, "privat": 2, "probabl": [1, 10], "problem": 3, "procedur": 10, "process": [3, 5, 8, 9, 13, 19], "processor": 19, "produc": [12, 19], "product": 18, "profession": 2, "project": [3, 17], "promptli": 2, "proper": 3, "properli": 7, "provid": [2, 3, 5, 15, 16, 17, 19], "public": [2, 5], "publicli": 19, "publish": 2, "pull": 15, "punctuat": 7, "pure": 7, "purpos": 3, "push_to_hf_hub": [9, 15], "py": 15, "pypdfium2": [4, 8], "pyplot": [8, 11], "python": [1, 3, 16], "python3": 15, "pytorch": [4, 5, 9, 10, 13, 15, 18, 19], "q": 3, "qr": [8, 16], "qr_code": 17, "qualiti": 10, "question": 2, "quickli": 5, "quicktour": 12, "r": 19, "race": 2, "ramdisk": 7, "rand": [9, 10, 11, 18, 19], "random": [9, 10, 11, 19], "randomappli": 10, "randombright": 10, "randomcontrast": 10, "randomcrop": 10, "randomgamma": 10, "randomhorizontalflip": 10, "randomhu": 10, "randomjpegqu": 10, "randomli": 10, "randomres": 10, "randomrot": 10, "randomsatur": 10, "randomshadow": 10, "rang": 10, "rassi": 15, "ratio": [9, 10, 19], "raw": [8, 11], "re": 18, "read": [5, 7, 9], "read_html": 8, "read_img_as_numpi": 8, "read_img_as_tensor": 8, "read_pdf": 8, "readi": 18, "real": [1, 5, 9, 10], "realli": 1, "reason": [2, 5, 7], "rebuild": 3, "rebuilt": 3, "recal": [11, 19], "receipt": [5, 7, 19], "reco_arch": [9, 13, 15, 18], "reco_b": 19, "reco_model": [13, 15, 18], "reco_param": 13, "reco_predictor": 13, "recogn": 19, "recognit": [7, 11, 12, 13], "recognition_predictor": [9, 19], "recognition_task": [7, 17], "recognitiondataset": [7, 17], "recognitionpredictor": [9, 13], "rectangular": 9, "reduc": [4, 10], "refer": [3, 4, 13, 15, 16, 17, 19], "regardless": 2, "region": 19, "regroup": 11, "regular": 17, "reject": 2, "rel": [8, 10, 11, 19], "relat": 8, "releas": [0, 4], "relev": 16, "religion": 2, "remov": 2, "render": [8, 19], "repo": 9, "repo_id": [9, 15], "report": 2, "repositori": [7, 9, 15], "repres": [2, 18, 19], "represent": [5, 9], "request": [2, 15], "requir": [4, 10, 18], "research": 5, "residu": 9, "resiz": [10, 19], "resnet": 9, "resnet18": [9, 15], "resnet31": 9, "resnet34": 9, "resnet50": [9, 15], "resolv": 8, "resolve_block": 19, "resolve_lin": 19, "resourc": 17, "respect": 2, "rest": [3, 10, 11], "restrict": 14, "result": [3, 7, 8, 12, 15, 18, 19], "return": 19, "reusabl": 19, "review": 2, "rgb": [8, 10], "rgb_mode": 8, "rgb_output": 8, "right": [2, 9, 11], "roboflow": 1, "robust": [5, 7], "root": 7, "rotat": [7, 8, 9, 10, 11, 12, 13, 17, 19], "run": [3, 4, 9], "same": [3, 8, 11, 17, 18, 19], "sampl": [7, 9, 17, 19], "sample_transform": 7, "sanjin": 1, "sar": [5, 9], "sar_resnet31": [9, 19], "satur": 10, "save": [9, 17], "scale": [8, 9, 10, 11], "scale_rang": 10, "scan": [5, 7], "scene": [5, 7, 9], "score": [8, 11], "script": [3, 17], "seamless": 5, "seamlessli": [5, 19], "search": [1, 9], "searchabl": 12, "sec": 19, "second": 19, "section": [1, 13, 15, 16, 18, 19], "secur": [2, 14], "see": [2, 3], "seen": 19, "segment": [5, 9, 19], "self": 19, "semant": [5, 9], "send": 19, "sens": 11, "sensit": 17, "separ": 19, "sequenc": [5, 7, 8, 9, 11, 19], "sequenti": [10, 19], "seri": 2, "seriou": 2, "set": [2, 4, 7, 9, 11, 14, 16, 19], "set_global_polici": 18, "sever": [8, 10, 19], "sex": 2, "sexual": 2, "shade": 10, "shape": [5, 8, 9, 10, 11, 19], "share": [14, 17], "shift": 10, "shm": 14, "should": [3, 7, 8, 10, 11], "show": [5, 8, 9, 11, 13, 15, 16], "showcas": [3, 12], "shuffl": [7, 10], "side": 11, "signatur": 8, "signific": 17, "simpl": [5, 9, 18], "simpler": 9, "sinc": [7, 17], "singl": [2, 3, 5, 7], "single_img_doc": 18, "size": [2, 7, 8, 10, 16, 19], "skew": 19, "slack": 3, "slightli": 9, "small": [3, 9, 19], "smallest": 8, "snapshot_download": 9, "snippet": 19, "so": [3, 4, 7, 9, 15, 17], "social": 2, "socio": 2, "some": [1, 4, 12, 15, 17], "someth": 3, "somewher": 3, "sort": 2, "sourc": [1, 7, 8, 9, 10, 11, 15], "space": [2, 19], "span": 19, "spanish": 7, "spatial": [5, 7, 8], "specif": [3, 4, 11, 13, 17, 19], "specifi": [2, 7, 8], "speed": [5, 9, 19], "sphinx": 3, "sroie": [5, 7, 17], "stabl": 4, "stackoverflow": 3, "stage": 5, "standalon": 12, "standard": 10, "start": 7, "state": [1, 5, 11, 16], "static": 11, "statist": 1, "statu": 2, "std": [10, 13], "step": 14, "still": 19, "str": [7, 8, 9, 10, 11], "straight": [7, 9, 17, 19], "straighten": 19, "straighten_pag": [9, 13, 19], "straigten_pag": 13, "stream": 8, "street": [5, 7], "strict": 4, "strictli": 11, "string": [7, 8, 11, 19], "strive": 4, "strong": [5, 9], "structur": [18, 19], "subset": [7, 19], "suggest": [3, 15], "sum": 11, "summari": 11, "support": [4, 13, 16, 18, 19], "sustain": 2, "svhn": [5, 7, 17], "svt": [7, 17], "swedish": 7, "symmetr": [9, 10, 19], "symmetric_pad": [9, 10, 19], "synthet": 5, "synthtext": [5, 7, 17], "system": 19, "t": [3, 7, 13, 18, 19], "tabl": [15, 16, 17], "take": [2, 7, 19], "target": [7, 8, 10, 11, 17], "target_s": 7, "task": [5, 7, 9, 15, 17, 19], "task2": 7, "team": 4, "techminde": 4, "templat": [3, 5], "tensor": [7, 8, 10, 19], "tensorflow": [4, 5, 8, 9, 10, 13, 15, 18, 19], "tensorspec": 18, "term": 2, "test": [7, 17], "test_set": 7, "text": [1, 7, 8, 9, 11, 17], "text_output": 19, "textmatch": 11, "textnet": 9, "textnet_bas": 9, "textnet_smal": 9, "textnet_tini": 9, "textract": [5, 19], "textstylebrush": [5, 7], "textual": [5, 7, 8, 9, 19], "tf": [4, 8, 9, 10, 15, 18], "than": [3, 11, 15], "thank": 3, "thei": [2, 11], "them": [7, 19], "thi": [1, 2, 3, 4, 6, 7, 10, 11, 13, 14, 15, 17, 18, 19], "thing": [18, 19], "third": 4, "those": [2, 8, 19], "threaten": 2, "threshold": 19, "through": [2, 10, 16, 17], "tilman": 15, "time": [1, 2, 5, 9, 11, 17], "tini": 9, "titl": [8, 19], "tm": 19, "tmp": 14, "togeth": [3, 8], "tograi": 10, "tool": [1, 17], "top": [11, 18, 19], "topic": 3, "torch": [4, 10, 13, 15, 18], "torchvis": 10, "total": 13, "toward": [2, 4], "train": [3, 7, 9, 10, 15, 16, 17, 18, 19], "train_it": [7, 17], "train_load": [7, 17], "train_pytorch": 15, "train_set": [7, 17], "train_tensorflow": 15, "trainabl": [5, 9], "tranform": 10, "transcrib": 19, "transfer": [5, 7], "transfo": 10, "transform": [5, 7, 9], "translat": 2, "troll": 2, "true": [7, 8, 9, 10, 11, 13, 14, 15, 17, 18, 19], "truth": 11, "tune": [1, 18], "tupl": [7, 8, 10, 11], "two": [8, 14], "txt": 7, "type": [8, 11, 15, 18, 19], "typic": 19, "u": [2, 3], "ucsd": 7, "udac": 3, "uint8": [8, 9, 11, 19], "ukrainian": 7, "unaccept": 2, "underli": [17, 19], "underneath": 8, "understand": [5, 7, 19], "uniform": [9, 10], "uniformli": 10, "uninterrupt": [8, 19], "union": 11, "unit": 1, "unittest": 3, "unlock": 8, "unoffici": 9, "unprofession": 2, "unsolicit": 2, "unsupervis": 5, "unwelcom": 2, "up": [9, 19], "updat": 11, "upgrad": 3, "upper": [7, 10], "uppercas": 17, "url": 8, "us": [2, 3, 4, 7, 9, 11, 12, 13, 14, 15, 16, 19], "usabl": 19, "usag": [14, 18], "use_polygon": [7, 11, 17], "useabl": 19, "user": [5, 8, 12], "utf": 19, "util": 18, "v1": 15, "v3": [9, 15, 19], "valid": 17, "valu": [3, 8, 10, 19], "valuabl": 5, "variabl": 14, "varieti": 7, "veri": 9, "verma": 1, "version": [2, 3, 4, 18, 19], "vgg": 9, "vgg16": 15, "vgg16_bn_r": 9, "via": 2, "video": 1, "vietnames": 7, "view": [5, 7], "viewpoint": 2, "violat": 2, "visibl": 2, "vision": [5, 7, 9], "visiondataset": 7, "visiontransform": 9, "visual": [4, 5, 16], "visualize_pag": 11, "vit_": 9, "vit_b": 9, "vitstr": [5, 9, 18], "vitstr_bas": [9, 19], "vitstr_smal": [9, 13, 18, 19], "viz": 4, "vocab": [13, 15, 17, 18, 19], "vocabulari": [7, 13, 15], "w": [8, 9, 10, 11], "w3": 19, "wa": 2, "wai": [2, 5, 17], "want": [3, 18, 19], "warmup": 19, "wasn": 3, "we": [1, 2, 3, 4, 5, 8, 10, 13, 15, 17, 18, 19], "weasyprint": 8, "web": [3, 8], "websit": 7, "welcom": 2, "well": [1, 2, 18], "were": [2, 8, 19], "what": [1, 2], "when": [2, 3, 9], "whenev": 3, "where": [3, 8, 10, 11], "whether": [3, 7, 8, 10, 11, 17, 19], "which": [2, 9, 14, 16, 17, 19], "whichev": 4, "while": [10, 19], "why": 2, "width": [8, 10], "wiki": 2, "wildreceipt": [5, 7, 17], "window": [9, 11], "wish": 3, "within": 2, "without": [2, 7, 9], "wonder": 3, "word": [5, 7, 9, 11, 19], "word_1_1": 19, "word_1_2": 19, "word_1_3": 19, "wordgener": [7, 17], "words_onli": 11, "work": [1, 13, 14, 19], "workflow": 3, "worklow": 3, "world": [11, 19], "worth": 9, "wrap": 19, "wrapper": [7, 10], "write": 14, "written": [2, 8], "www": [2, 8, 19], "x": [8, 10, 11], "x_ascend": 19, "x_descend": 19, "x_i": 11, "x_size": 19, "x_wconf": 19, "xhtml": 19, "xmax": 8, "xmin": 8, "xml": 19, "xml_bytes_str": 19, "xml_element": 19, "xml_output": 19, "xmln": 19, "y": 11, "y_i": 11, "y_j": 11, "yet": 16, "ymax": 8, "ymin": 8, "yolov8": 16, "you": [3, 4, 7, 8, 9, 13, 14, 15, 16, 17, 18, 19], "your": [3, 5, 8, 11, 19], "yoursit": 8, "yugesh": 1, "zero": [10, 11], "zoo": 13, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 7, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 7, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": 7, "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 7, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 7, "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 7, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 7, "\u00e4\u00f6\u00e4\u00f6": 7, "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 7, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": 7, "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": 7, "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": 7, "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": 7, "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": 7, "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0905": 7, "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": 7, "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": 7, "\u0950": 7, "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": 7, "\u09bd": 7, "\u09ce": 7, "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": 7}, "titles": ["Changelog", "Community resources", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.contrib", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Integrate contributions into your pipeline", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 3, "0": 0, "01": 0, "02": 0, "03": 0, "04": 0, "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 2], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 2], "2021": 0, "2022": 0, "2023": 0, "2024": 0, "21": 0, "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 2], "31": 0, "4": [0, 2], "5": 0, "6": 0, "7": 0, "8": 0, "9": 0, "advanc": 19, "approach": 19, "architectur": 19, "arg": [7, 8, 9, 10, 11], "artefact": 8, "artefactdetect": 16, "attribut": 2, "avail": [16, 17, 19], "aw": 14, "ban": 2, "block": 8, "bug": 3, "changelog": 0, "choos": [17, 19], "classif": [9, 13, 15], "code": [2, 3], "codebas": 3, "commit": 3, "commun": [1, 15], "compos": 10, "conda": 4, "conduct": 2, "connect": 3, "continu": 3, "contrib": 6, "contribut": [3, 6, 16], "contributor": 2, "convent": 15, "correct": 2, "coven": 2, "custom": [7, 13], "data": 17, "dataload": 7, "dataset": [5, 7, 17], "detect": [5, 9, 15, 17, 19], "develop": 3, "do": 19, "doctr": [3, 5, 6, 7, 8, 9, 10, 11, 12], "document": [3, 5, 8], "end": 19, "enforc": 2, "evalu": 11, "export": 18, "factori": 9, "featur": [3, 5], "feedback": 3, "file": 8, "from": 15, "gener": [7, 17], "git": 4, "guidelin": 2, "half": 18, "hub": 15, "huggingfac": 15, "i": 19, "infer": 18, "instal": [3, 4], "integr": [3, 16], "io": 8, "lambda": 14, "let": 3, "line": 8, "linux": 4, "load": [13, 15, 17], "loader": 7, "main": 5, "mode": 3, "model": [5, 9, 13, 15, 18, 19], "modifi": 3, "modul": [6, 16], "name": 15, "notebook": 12, "object": 17, "ocr": [17, 19], "onli": 4, "onnx": 18, "optim": 18, "option": 19, "orient": 13, "our": 2, "output": 19, "own": [13, 17], "packag": 4, "page": 8, "perman": 2, "pipelin": 16, "pledg": 2, "precis": 18, "predictor": 19, "prepar": 18, "prerequisit": 4, "pretrain": 15, "push": 15, "python": 4, "qualiti": 3, "question": 3, "read": 8, "readi": 17, "recognit": [5, 9, 15, 17, 19], "report": 3, "request": 3, "resourc": 1, "respons": 2, "return": [7, 8, 9, 11], "right": 19, "scope": 2, "share": 15, "should": 19, "stage": 19, "standard": 2, "structur": [3, 8], "style": 3, "support": [5, 6, 7, 10], "synthet": [7, 17], "task": 11, "temporari": 2, "test": 3, "text": [5, 19], "train": 13, "transform": 10, "two": 19, "unit": 3, "us": [17, 18], "util": 11, "v0": 0, "verif": 3, "via": 4, "visual": 11, "vocab": 7, "warn": 2, "what": 19, "word": 8, "your": [13, 15, 16, 17, 18], "zoo": [5, 9]}}) \ No newline at end of file +Search.setIndex({"alltitles": {"1. Correction": [[2, "correction"]], "2. Warning": [[2, "warning"]], "3. Temporary Ban": [[2, "temporary-ban"]], "4. Permanent Ban": [[2, "permanent-ban"]], "AWS Lambda": [[14, null]], "Advanced options": [[19, "advanced-options"]], "Args:": [[7, "args"], [7, "id4"], [7, "id7"], [7, "id10"], [7, "id13"], [7, "id16"], [7, "id19"], [7, "id22"], [7, "id25"], [7, "id29"], [7, "id32"], [7, "id37"], [7, "id40"], [7, "id46"], [7, "id49"], [7, "id50"], [7, "id51"], [7, "id54"], [7, "id57"], [7, "id60"], [7, "id61"], [8, "args"], [8, "id2"], [8, "id3"], [8, "id4"], [8, "id5"], [8, "id6"], [8, "id7"], [8, "id10"], [8, "id12"], [8, "id14"], [8, "id16"], [8, "id20"], [8, "id24"], [8, "id28"], [9, "args"], [9, "id3"], [9, "id8"], [9, "id13"], [9, "id17"], [9, "id21"], [9, "id26"], [9, "id31"], [9, "id36"], [9, "id41"], [9, "id46"], [9, "id50"], [9, "id54"], [9, "id59"], [9, "id63"], [9, "id68"], [9, "id73"], [9, "id77"], [9, "id81"], [9, "id85"], [9, "id90"], [9, "id95"], [9, "id99"], [9, "id104"], [9, "id109"], [9, "id114"], [9, "id119"], [9, "id123"], [9, "id127"], [9, "id132"], [9, "id137"], [9, "id142"], [9, "id146"], [9, "id150"], [9, "id155"], [9, "id159"], [9, "id163"], [9, "id167"], [9, "id169"], [9, "id171"], [9, "id173"], [10, "args"], [10, "id1"], [10, "id2"], [10, "id3"], [10, "id4"], [10, "id5"], [10, "id6"], [10, "id7"], [10, "id8"], [10, "id9"], [10, "id10"], [10, "id11"], [10, "id12"], [10, "id13"], [10, "id14"], [10, "id15"], [10, "id16"], [10, "id17"], [10, "id18"], [10, "id19"], [11, "args"], [11, "id3"], [11, "id4"], [11, "id5"], [11, "id6"], [11, "id7"], [11, "id8"], [11, "id9"]], "Artefact": [[8, "artefact"]], "ArtefactDetection": [[16, "artefactdetection"]], "Attribution": [[2, "attribution"]], "Available Datasets": [[17, "available-datasets"]], "Available architectures": [[19, "available-architectures"], [19, "id1"], [19, "id2"]], "Available contribution modules": [[16, "available-contribution-modules"]], "Block": [[8, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[17, null]], "Choosing the right model": [[19, null]], "Classification": [[15, "classification"]], "Code quality": [[3, "code-quality"]], "Code style verification": [[3, "code-style-verification"]], "Codebase structure": [[3, "codebase-structure"]], "Commits": [[3, "commits"]], "Community resources": [[1, null]], "Composing transformations": [[10, "composing-transformations"]], "Continuous Integration": [[3, "continuous-integration"]], "Contributing to docTR": [[3, null]], "Contributor Covenant Code of Conduct": [[2, null]], "Custom dataset loader": [[7, "custom-dataset-loader"]], "Custom orientation classification models": [[13, "custom-orientation-classification-models"]], "Data Loading": [[17, "data-loading"]], "Dataloader": [[7, "dataloader"]], "Detection": [[15, "detection"], [17, "detection"]], "Detection predictors": [[19, "detection-predictors"]], "Developer mode installation": [[3, "developer-mode-installation"]], "Developing docTR": [[3, "developing-doctr"]], "Document": [[8, "document"]], "Document structure": [[8, "document-structure"]], "End-to-End OCR": [[19, "end-to-end-ocr"]], "Enforcement": [[2, "enforcement"]], "Enforcement Guidelines": [[2, "enforcement-guidelines"]], "Enforcement Responsibilities": [[2, "enforcement-responsibilities"]], "Export to ONNX": [[18, "export-to-onnx"]], "Feature requests & bug report": [[3, "feature-requests-bug-report"]], "Feedback": [[3, "feedback"]], "File reading": [[8, "file-reading"]], "Half-precision": [[18, "half-precision"]], "Installation": [[4, null]], "Integrate contributions into your pipeline": [[16, null]], "Let\u2019s connect": [[3, "let-s-connect"]], "Line": [[8, "line"]], "Loading from Huggingface Hub": [[15, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[13, "loading-your-custom-trained-model"]], "Loading your custom trained orientation classification model": [[13, "loading-your-custom-trained-orientation-classification-model"]], "Main Features": [[5, "main-features"]], "Model optimization": [[18, "model-optimization"]], "Model zoo": [[5, "model-zoo"]], "Modifying the documentation": [[3, "modifying-the-documentation"]], "Naming conventions": [[15, "naming-conventions"]], "OCR": [[17, "ocr"]], "Object Detection": [[17, "object-detection"]], "Our Pledge": [[2, "our-pledge"]], "Our Standards": [[2, "our-standards"]], "Page": [[8, "page"]], "Preparing your model for inference": [[18, null]], "Prerequisites": [[4, "prerequisites"]], "Pretrained community models": [[15, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[15, "pushing-to-the-huggingface-hub"]], "Questions": [[3, "questions"]], "Recognition": [[15, "recognition"], [17, "recognition"]], "Recognition predictors": [[19, "recognition-predictors"]], "Returns:": [[7, "returns"], [8, "returns"], [8, "id11"], [8, "id13"], [8, "id15"], [8, "id19"], [8, "id23"], [8, "id27"], [8, "id31"], [9, "returns"], [9, "id6"], [9, "id11"], [9, "id16"], [9, "id20"], [9, "id24"], [9, "id29"], [9, "id34"], [9, "id39"], [9, "id44"], [9, "id49"], [9, "id53"], [9, "id57"], [9, "id62"], [9, "id66"], [9, "id71"], [9, "id76"], [9, "id80"], [9, "id84"], [9, "id88"], [9, "id93"], [9, "id98"], [9, "id102"], [9, "id107"], [9, "id112"], [9, "id117"], [9, "id122"], [9, "id126"], [9, "id130"], [9, "id135"], [9, "id140"], [9, "id145"], [9, "id149"], [9, "id153"], [9, "id158"], [9, "id162"], [9, "id166"], [9, "id168"], [9, "id170"], [9, "id172"], [11, "returns"]], "Scope": [[2, "scope"]], "Share your model with the community": [[15, null]], "Supported Vocabs": [[7, "supported-vocabs"]], "Supported contribution modules": [[6, "supported-contribution-modules"]], "Supported datasets": [[5, "supported-datasets"]], "Supported transformations": [[10, "supported-transformations"]], "Synthetic dataset generator": [[7, "synthetic-dataset-generator"], [17, "synthetic-dataset-generator"]], "Task evaluation": [[11, "task-evaluation"]], "Text Detection": [[19, "text-detection"]], "Text Recognition": [[19, "text-recognition"]], "Text detection models": [[5, "text-detection-models"]], "Text recognition models": [[5, "text-recognition-models"]], "Train your own model": [[13, null]], "Two-stage approaches": [[19, "two-stage-approaches"]], "Unit tests": [[3, "unit-tests"]], "Use your own datasets": [[17, "use-your-own-datasets"]], "Using your ONNX exported model": [[18, "using-your-onnx-exported-model"]], "Via Conda (Only for Linux)": [[4, "via-conda-only-for-linux"]], "Via Git": [[4, "via-git"]], "Via Python Package": [[4, "via-python-package"]], "Visualization": [[11, "visualization"]], "What should I do with the output?": [[19, "what-should-i-do-with-the-output"]], "Word": [[8, "word"]], "docTR Notebooks": [[12, null]], "docTR Vocabs": [[7, "id62"]], "docTR: Document Text Recognition": [[5, null]], "doctr.contrib": [[6, null]], "doctr.datasets": [[7, null], [7, "datasets"]], "doctr.io": [[8, null]], "doctr.models": [[9, null]], "doctr.models.classification": [[9, "doctr-models-classification"]], "doctr.models.detection": [[9, "doctr-models-detection"]], "doctr.models.factory": [[9, "doctr-models-factory"]], "doctr.models.recognition": [[9, "doctr-models-recognition"]], "doctr.models.zoo": [[9, "doctr-models-zoo"]], "doctr.transforms": [[10, null]], "doctr.utils": [[11, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.10.0 (2024-10-21)": [[0, "v0-10-0-2024-10-21"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]], "v0.7.0 (2023-09-09)": [[0, "v0-7-0-2023-09-09"]], "v0.8.0 (2024-02-28)": [[0, "v0-8-0-2024-02-28"]], "v0.8.1 (2024-03-04)": [[0, "v0-8-1-2024-03-04"]], "v0.9.0 (2024-08-08)": [[0, "v0-9-0-2024-08-08"]]}, "docnames": ["changelog", "community/resources", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/contrib", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_contrib_modules", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "community/resources.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/contrib.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_contrib_modules.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[8, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[8, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[10, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[7, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[10, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[10, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[7, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[7, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[9, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[7, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[7, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[8, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[8, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[7, "doctr.datasets.encode_sequences", false]], "fast_base() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_base", false]], "fast_small() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_small", false]], "fast_tiny() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_tiny", false]], "from_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[7, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[10, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[10, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[7, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[7, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[7, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[7, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[7, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[9, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[10, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[8, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet34() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[7, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_crop_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_crop_orientation", false]], "mobilenet_v3_small_page_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_page_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[10, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[9, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[7, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[10, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[8, "doctr.io.Page", false]], "page_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.page_orientation_predictor", false]], "parseq() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[10, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[10, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[10, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[10, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[10, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[10, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[10, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[10, "doctr.transforms.RandomJpegQuality", false]], "randomresize (class in doctr.transforms)": [[10, "doctr.transforms.RandomResize", false]], "randomrotate (class in doctr.transforms)": [[10, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[10, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[10, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[8, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[8, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[8, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[7, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[10, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[8, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[8, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[7, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[7, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[7, "doctr.datasets.SVT", false]], "synthtext (class in doctr.datasets)": [[7, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.TextMatch", false]], "textnet_base() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_base", false]], "textnet_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_small", false]], "textnet_tiny() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_tiny", false]], "togray (class in doctr.transforms)": [[10, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[11, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_small", false]], "wildreceipt (class in doctr.datasets)": [[7, "doctr.datasets.WILDRECEIPT", false]], "word (class in doctr.io)": [[8, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[7, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[7, 0, 1, "", "CORD"], [7, 0, 1, "", "CharacterGenerator"], [7, 0, 1, "", "DetectionDataset"], [7, 0, 1, "", "DocArtefacts"], [7, 0, 1, "", "FUNSD"], [7, 0, 1, "", "IC03"], [7, 0, 1, "", "IC13"], [7, 0, 1, "", "IIIT5K"], [7, 0, 1, "", "IIITHWS"], [7, 0, 1, "", "IMGUR5K"], [7, 0, 1, "", "MJSynth"], [7, 0, 1, "", "OCRDataset"], [7, 0, 1, "", "RecognitionDataset"], [7, 0, 1, "", "SROIE"], [7, 0, 1, "", "SVHN"], [7, 0, 1, "", "SVT"], [7, 0, 1, "", "SynthText"], [7, 0, 1, "", "WILDRECEIPT"], [7, 0, 1, "", "WordGenerator"], [7, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[7, 0, 1, "", "DataLoader"]], "doctr.io": [[8, 0, 1, "", "Artefact"], [8, 0, 1, "", "Block"], [8, 0, 1, "", "Document"], [8, 0, 1, "", "DocumentFile"], [8, 0, 1, "", "Line"], [8, 0, 1, "", "Page"], [8, 0, 1, "", "Word"], [8, 1, 1, "", "decode_img_as_tensor"], [8, 1, 1, "", "read_html"], [8, 1, 1, "", "read_img_as_numpy"], [8, 1, 1, "", "read_img_as_tensor"], [8, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[8, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[8, 2, 1, "", "from_images"], [8, 2, 1, "", "from_pdf"], [8, 2, 1, "", "from_url"]], "doctr.io.Page": [[8, 2, 1, "", "show"]], "doctr.models": [[9, 1, 1, "", "kie_predictor"], [9, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[9, 1, 1, "", "crop_orientation_predictor"], [9, 1, 1, "", "magc_resnet31"], [9, 1, 1, "", "mobilenet_v3_large"], [9, 1, 1, "", "mobilenet_v3_large_r"], [9, 1, 1, "", "mobilenet_v3_small"], [9, 1, 1, "", "mobilenet_v3_small_crop_orientation"], [9, 1, 1, "", "mobilenet_v3_small_page_orientation"], [9, 1, 1, "", "mobilenet_v3_small_r"], [9, 1, 1, "", "page_orientation_predictor"], [9, 1, 1, "", "resnet18"], [9, 1, 1, "", "resnet31"], [9, 1, 1, "", "resnet34"], [9, 1, 1, "", "resnet50"], [9, 1, 1, "", "textnet_base"], [9, 1, 1, "", "textnet_small"], [9, 1, 1, "", "textnet_tiny"], [9, 1, 1, "", "vgg16_bn_r"], [9, 1, 1, "", "vit_b"], [9, 1, 1, "", "vit_s"]], "doctr.models.detection": [[9, 1, 1, "", "db_mobilenet_v3_large"], [9, 1, 1, "", "db_resnet50"], [9, 1, 1, "", "detection_predictor"], [9, 1, 1, "", "fast_base"], [9, 1, 1, "", "fast_small"], [9, 1, 1, "", "fast_tiny"], [9, 1, 1, "", "linknet_resnet18"], [9, 1, 1, "", "linknet_resnet34"], [9, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[9, 1, 1, "", "from_hub"], [9, 1, 1, "", "login_to_hub"], [9, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[9, 1, 1, "", "crnn_mobilenet_v3_large"], [9, 1, 1, "", "crnn_mobilenet_v3_small"], [9, 1, 1, "", "crnn_vgg16_bn"], [9, 1, 1, "", "master"], [9, 1, 1, "", "parseq"], [9, 1, 1, "", "recognition_predictor"], [9, 1, 1, "", "sar_resnet31"], [9, 1, 1, "", "vitstr_base"], [9, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[10, 0, 1, "", "ChannelShuffle"], [10, 0, 1, "", "ColorInversion"], [10, 0, 1, "", "Compose"], [10, 0, 1, "", "GaussianBlur"], [10, 0, 1, "", "GaussianNoise"], [10, 0, 1, "", "LambdaTransformation"], [10, 0, 1, "", "Normalize"], [10, 0, 1, "", "OneOf"], [10, 0, 1, "", "RandomApply"], [10, 0, 1, "", "RandomBrightness"], [10, 0, 1, "", "RandomContrast"], [10, 0, 1, "", "RandomCrop"], [10, 0, 1, "", "RandomGamma"], [10, 0, 1, "", "RandomHorizontalFlip"], [10, 0, 1, "", "RandomHue"], [10, 0, 1, "", "RandomJpegQuality"], [10, 0, 1, "", "RandomResize"], [10, 0, 1, "", "RandomRotate"], [10, 0, 1, "", "RandomSaturation"], [10, 0, 1, "", "RandomShadow"], [10, 0, 1, "", "Resize"], [10, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[11, 0, 1, "", "DetectionMetric"], [11, 0, 1, "", "LocalizationConfusion"], [11, 0, 1, "", "OCRMetric"], [11, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.visualization": [[11, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [2, 8, 9, 11, 15, 18], "0": [2, 4, 7, 10, 11, 13, 16, 17, 19], "00": 19, "01": 19, "0123456789": 7, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "02562": 9, "03": 19, "035": 19, "0361328125": 19, "04": 19, "05": 19, "06": 19, "06640625": 19, "07": 19, "08": [10, 19], "09": 19, "0966796875": 19, "1": [7, 8, 9, 10, 11, 13, 17, 19], "10": [4, 7, 11, 19], "100": [7, 10, 11, 17, 19], "1000": 19, "101": 7, "1024": [9, 13, 19], "104": 7, "106": 7, "108": 7, "1095": 17, "11": 19, "110": 11, "1107": 17, "114": 7, "115": 7, "1156": 17, "116": 7, "118": 7, "11800h": 19, "11th": 19, "12": 19, "120": 7, "123": 7, "126": 7, "1268": 17, "128": [9, 13, 18, 19], "13": 19, "130": 7, "13068": 17, "131": 7, "1337891": 17, "1357421875": 19, "1396484375": 19, "14": 19, "1420": 19, "14470v1": 7, "149": 17, "15": 19, "150": [11, 19], "1552": 19, "16": [9, 18, 19], "1630859375": 19, "1684": 19, "16x16": 9, "17": 19, "1778": 19, "1782": 19, "18": [9, 19], "185546875": 19, "1900": 19, "1910": 9, "19342": 17, "19370": 17, "195": 7, "19598": 17, "199": 19, "1999": 19, "2": [4, 5, 7, 8, 9, 10, 16, 19], "20": 19, "200": 11, "2000": 17, "2003": [5, 7], "2012": 7, "2013": [5, 7], "2015": 7, "2019": 5, "2023": 1, "207901": 17, "21": 19, "2103": 7, "2186": 17, "21888": 17, "22": 19, "224": [9, 10], "225": 10, "22672": 17, "229": [10, 17], "23": 19, "233": 17, "236": 7, "24": 19, "246": 17, "249": 17, "25": 19, "2504": 19, "255": [8, 9, 10, 11, 19], "256": 9, "257": 17, "26": 19, "26032": 17, "264": 13, "27": 19, "2700": 17, "2710": 19, "2749": 13, "28": 19, "287": 13, "29": 19, "296": 13, "299": 13, "2d": 19, "3": [4, 5, 8, 9, 10, 11, 18, 19], "30": 19, "300": 17, "3000": 17, "301": 13, "30595": 19, "30ghz": 19, "31": 9, "32": [7, 9, 10, 13, 17, 18, 19], "3232421875": 19, "33": [10, 19], "33402": 17, "33608": 17, "34": [9, 19], "340": 19, "3456": 19, "3515625": 19, "36": 19, "360": 17, "37": [7, 19], "38": 19, "39": 19, "4": [9, 10, 11, 19], "40": 19, "406": 10, "41": 19, "42": 19, "43": 19, "44": 19, "45": 19, "456": 10, "46": 19, "47": 19, "472": 17, "48": [7, 19], "485": 10, "49": 19, "49377": 17, "5": [7, 10, 11, 16, 19], "50": [9, 17, 19], "51": 19, "51171875": 19, "512": 9, "52": [7, 19], "529": 19, "53": 19, "54": 19, "540": 19, "5478515625": 19, "55": 19, "56": 19, "57": 19, "58": [7, 19], "580": 19, "5810546875": 19, "583": 19, "59": 19, "597": 19, "5k": [5, 7], "5m": 19, "6": [10, 19], "60": 10, "600": [9, 11, 19], "61": 19, "62": 19, "626": 17, "63": 19, "64": [9, 10, 19], "641": 19, "647": 17, "65": 19, "66": 19, "67": 19, "68": 19, "69": 19, "693": 13, "694": 13, "695": 13, "6m": 19, "7": 19, "70": [7, 11, 19], "707470": 17, "71": [7, 19], "7100000": 17, "7141797": 17, "7149": 17, "72": 19, "72dpi": 8, "73": 19, "73257": 17, "74": 19, "75": [10, 19], "7581382": 17, "76": 19, "77": 19, "772": 13, "772875": 17, "78": 19, "785": 13, "79": 19, "793533": 17, "796": 17, "798": 13, "7m": 19, "8": [9, 10, 19], "80": 19, "800": [9, 11, 17, 19], "81": 19, "82": 19, "83": 19, "84": 19, "849": 17, "85": 19, "8564453125": 19, "857": 19, "85875": 17, "86": 19, "8603515625": 19, "87": 19, "8707": 17, "88": 19, "89": 19, "9": [10, 19], "90": 19, "90k": 7, "90kdict32px": 7, "91": 19, "914085328578949": 19, "92": 19, "93": 19, "94": [7, 19], "95": [11, 19], "9578408598899841": 19, "96": 19, "97": 19, "98": 19, "99": 19, "9949972033500671": 19, "A": [2, 3, 5, 7, 8, 9, 12, 18], "As": 3, "Be": 19, "Being": 2, "By": 14, "For": [2, 3, 4, 13, 19], "If": [3, 8, 9, 13, 19], "In": [3, 7, 17], "It": [10, 15, 16, 18], "Its": [5, 9], "No": [2, 19], "Of": 7, "Or": [16, 18], "The": [2, 3, 7, 8, 11, 14, 16, 17, 18, 19], "Then": 9, "To": [3, 4, 14, 15, 16, 18, 19], "_": [2, 7, 9], "__call__": 19, "_build": 3, "_i": 11, "ab": 7, "abc": 18, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "abdef": [7, 17], "abl": [17, 19], "about": [2, 17, 19], "abov": 19, "abstract": 1, "abstractdataset": 7, "abus": 2, "accept": 2, "access": [5, 8, 17, 19], "account": [2, 15], "accur": 19, "accuraci": 11, "achiev": 18, "act": 2, "action": 2, "activ": 5, "ad": [3, 9, 10], "adapt": 2, "add": [10, 11, 15, 19], "add_hook": 19, "add_label": 11, "addit": [3, 4, 8, 16, 19], "addition": [3, 19], "address": [2, 8], "adjust": 10, "advanc": 2, "advantag": 18, "advis": 3, "aesthet": [5, 7], "affect": 2, "after": [15, 19], "ag": 2, "again": 9, "aggreg": [11, 17], "aggress": 2, "align": [2, 8, 10], "all": [2, 3, 6, 7, 8, 10, 11, 16, 17, 19], "allow": [2, 18], "along": 19, "alreadi": [3, 18], "also": [2, 9, 15, 16, 17, 19], "alwai": 17, "an": [2, 3, 5, 7, 8, 9, 11, 16, 18, 19], "analysi": [8, 16], "ancient_greek": 7, "andrej": 1, "angl": [8, 10], "ani": [2, 7, 8, 9, 10, 11, 18, 19], "annot": 7, "anot": 17, "anoth": [9, 13, 17], "answer": 2, "anyascii": 11, "anyon": 5, "anyth": 16, "api": [3, 5], "apolog": 2, "apologi": 2, "app": 3, "appear": 2, "appli": [2, 7, 10], "applic": [5, 9], "appoint": 2, "appreci": 15, "appropri": [2, 3, 19], "ar": [2, 3, 4, 6, 7, 8, 10, 11, 12, 16, 17, 19], "arab": 7, "arabic_diacrit": 7, "arabic_lett": 7, "arabic_punctu": 7, "arbitrarili": [5, 9], "arch": [9, 15], "architectur": [5, 9, 15, 16], "area": 19, "argument": [7, 8, 9, 11, 13, 19], "around": 2, "arrai": [8, 10, 11], "art": [5, 16], "artefact": [11, 16, 19], "artefact_typ": 8, "articl": 1, "artifici": [5, 7], "arxiv": [7, 9], "asarrai": 11, "ascii_lett": 7, "aspect": [5, 9, 10, 19], "assess": 11, "assign": 11, "associ": 8, "assum": 9, "assume_straight_pag": [9, 13, 19], "astyp": [9, 11, 19], "attack": 2, "attend": [5, 9], "attent": [2, 9], "autom": 5, "automat": 19, "autoregress": [5, 9], "avail": [2, 5, 6, 10], "averag": [10, 19], "avoid": [2, 4], "aw": [5, 19], "awar": 19, "azur": 19, "b": [9, 11, 19], "b_j": 11, "back": 3, "backbon": 9, "backend": 19, "background": 17, "bangla": 7, "bar": 16, "bar_cod": 17, "baranovskij": 1, "base": [5, 9, 16], "baselin": [5, 9, 19], "batch": [7, 9, 10, 16, 17, 19], "batch_siz": [7, 9, 13, 16, 17, 18], "bblanchon": 4, "bbox": 19, "becaus": 14, "been": [3, 11, 17, 19], "befor": [7, 9, 10, 19], "begin": 11, "behavior": [2, 19], "being": [11, 19], "belong": 19, "benchmark": 19, "best": [1, 2], "better": [12, 19], "between": [10, 11, 19], "bgr": 8, "bilinear": 10, "bin_thresh": 19, "binar": [5, 9, 19], "binari": [8, 18, 19], "bit": 18, "block": [11, 19], "block_1_1": 19, "blur": 10, "bmvc": 7, "bn": 15, "bodi": [2, 19], "bool": [7, 8, 9, 10, 11], "boolean": [9, 19], "both": [5, 7, 10, 17, 19], "bottom": [9, 19], "bound": [7, 8, 9, 10, 11, 16, 17, 19], "box": [7, 8, 9, 10, 11, 16, 17, 19], "box_thresh": 19, "bright": 10, "browser": [3, 5], "build": [3, 4, 18], "built": 3, "byte": [8, 19], "c": [4, 8, 11], "c_j": 11, "cach": [3, 7, 14], "cache_sampl": 7, "call": 18, "callabl": [7, 10], "can": [3, 4, 13, 14, 15, 16, 17, 19], "capabl": [3, 12, 19], "case": [7, 11], "cf": 19, "cfg": 19, "challeng": 7, "challenge2_test_task12_imag": 7, "challenge2_test_task1_gt": 7, "challenge2_training_task12_imag": 7, "challenge2_training_task1_gt": 7, "chang": [14, 19], "channel": [2, 3, 8, 10], "channel_prior": 4, "channelshuffl": 10, "charact": [5, 7, 8, 11, 17, 19], "charactergener": [7, 17], "characterist": 2, "charg": 19, "charset": 19, "chart": 8, "check": [3, 15, 19], "checkpoint": 9, "chip": 4, "christian": 1, "ci": 3, "clarifi": 2, "clariti": 2, "class": [2, 7, 8, 10, 11, 19], "class_nam": 13, "classif": [17, 19], "classmethod": 8, "clear": 3, "clone": 4, "close": 3, "co": 15, "code": [5, 8, 16], "codecov": 3, "colab": 12, "collate_fn": 7, "collect": [8, 16], "color": 10, "colorinvers": 10, "column": 8, "com": [2, 4, 8, 9, 15], "combin": 19, "command": [3, 16], "comment": 2, "commit": 2, "common": [2, 10, 11, 18], "commun": 2, "compar": 5, "comparison": [11, 19], "competit": 7, "compil": [12, 19], "complaint": 2, "complementari": 11, "complet": 3, "compon": 19, "compos": [7, 19], "comprehens": 19, "comput": [7, 11, 18, 19], "conf_threshold": 16, "confid": [8, 19], "config": [4, 9], "configur": 9, "confus": 11, "consecut": [10, 19], "consequ": 2, "consid": [2, 3, 7, 8, 11, 19], "consist": 19, "consolid": [5, 7], "constant": 10, "construct": 2, "contact": 2, "contain": [1, 6, 7, 12, 17, 19], "content": [7, 8, 19], "context": 9, "contib": 4, "continu": 2, "contrast": 10, "contrast_factor": 10, "contrib": [4, 16], "contribut": 2, "contributor": 3, "convers": 8, "convert": [8, 10], "convolut": 9, "cool": 1, "coordin": [8, 19], "cord": [5, 7, 17, 19], "core": [11, 19], "corner": 19, "correct": 10, "correspond": [4, 8, 10, 19], "could": [2, 16], "counterpart": 11, "cover": 3, "coverag": 3, "cpu": [5, 13, 18], "creat": [1, 15], "crnn": [5, 9, 15], "crnn_mobilenet_v3_larg": [9, 15, 19], "crnn_mobilenet_v3_smal": [9, 18, 19], "crnn_vgg16_bn": [9, 13, 15, 19], "crop": [8, 9, 10, 13, 17, 19], "crop_orient": [8, 19], "crop_orientation_predictor": [9, 13], "crop_param": 13, "cuda": 18, "currenc": 7, "current": [3, 13, 19], "custom": [15, 16, 18, 19], "custom_crop_orientation_model": 13, "custom_page_orientation_model": 13, "customhook": 19, "cvit": 5, "czczup": 9, "czech": 7, "d": [7, 17], "danish": 7, "data": [5, 7, 8, 10, 11, 13, 15], "dataload": 17, "dataset": [9, 13, 19], "dataset_info": 7, "date": [13, 19], "db": 15, "db_mobilenet_v3_larg": [9, 15, 19], "db_resnet34": 19, "db_resnet50": [9, 13, 15, 19], "dbnet": [5, 9], "deal": [12, 19], "decis": 2, "decod": 8, "decode_img_as_tensor": 8, "dedic": 18, "deem": 2, "deep": [9, 19], "def": 19, "default": [4, 8, 13, 14, 19], "defer": 17, "defin": [11, 18], "degre": [8, 10, 19], "degress": 8, "delet": 3, "delimit": 19, "delta": 10, "demo": [3, 5], "demonstr": 2, "depend": [3, 4, 5, 19], "deploi": 3, "deploy": 5, "derogatori": 2, "describ": 9, "descript": 12, "design": 10, "desir": 8, "det_arch": [9, 13, 15, 18], "det_b": 19, "det_model": [13, 15, 18], "det_param": 13, "det_predictor": [13, 19], "detail": [13, 19], "detect": [1, 7, 8, 11, 12, 13, 16], "detect_languag": 9, "detect_orient": [9, 13, 19], "detection_predictor": [9, 19], "detection_task": [7, 17], "detectiondataset": [7, 17], "detectionmetr": 11, "detectionpredictor": [9, 13], "detector": [5, 9, 16], "deterior": 9, "determin": 2, "dev": [3, 14], "develop": 4, "deviat": 10, "devic": 18, "dict": [8, 11, 19], "dictionari": [8, 11], "differ": 2, "differenti": [5, 9], "digit": [5, 7, 17], "dimens": [8, 11, 19], "dimension": 10, "direct": 7, "directli": [15, 19], "directori": [3, 14], "disabl": [2, 14, 19], "disable_crop_orient": 19, "disable_page_orient": 19, "disclaim": 19, "discuss": 3, "disparag": 2, "displai": [8, 11], "display_artefact": 11, "distribut": 10, "div": 19, "divers": 2, "divid": 8, "do": [3, 4, 9], "doc": [3, 8, 16, 18, 19], "docartefact": [7, 17], "docstr": 3, "doctr": [1, 4, 13, 14, 15, 16, 17, 18, 19], "doctr_cache_dir": 14, "doctr_multiprocessing_dis": 14, "document": [1, 7, 9, 11, 12, 13, 16, 17, 18, 19], "documentbuild": 19, "documentfil": [8, 13, 15, 16, 18], "doesn": 18, "don": [13, 19], "done": 10, "download": [7, 17], "downsiz": 9, "draw": 10, "drop": 7, "drop_last": 7, "dtype": [8, 9, 10, 11, 18], "dual": [5, 7], "dummi": 15, "dummy_img": 19, "dummy_input": 18, "dure": 2, "dutch": 7, "dynam": [7, 16], "dynamic_seq_length": 7, "e": [2, 3, 4, 8, 9], "each": [5, 7, 8, 9, 10, 11, 17, 19], "eas": 3, "easi": [5, 11, 15, 18], "easili": [8, 11, 13, 15, 17, 19], "econom": 2, "edit": 2, "educ": 2, "effect": 19, "effici": [3, 5, 7, 9], "either": [11, 19], "element": [7, 8, 9, 19], "els": [3, 16], "email": 2, "empathi": 2, "en": 19, "enabl": [7, 8], "enclos": 8, "encod": [5, 7, 8, 9, 19], "encode_sequ": 7, "encount": 3, "encrypt": 8, "end": [5, 7, 9, 11], "english": [7, 17], "enough": [3, 19], "ensur": 3, "entri": 7, "environ": [2, 14], "eo": 7, "equiv": 19, "estim": 9, "etc": [8, 16], "ethnic": 2, "evalu": [17, 19], "event": 2, "everyon": 2, "everyth": [3, 19], "exact": [11, 19], "exampl": [2, 3, 5, 7, 9, 15, 19], "exchang": 18, "execut": 19, "exist": 15, "expand": 10, "expect": [8, 10, 11], "experi": 2, "explan": [2, 19], "explicit": 2, "exploit": [5, 9], "export": [8, 9, 11, 12, 16, 19], "export_as_straight_box": [9, 19], "export_as_xml": 19, "export_model_to_onnx": 18, "express": [2, 10], "extens": 8, "extern": [2, 17], "extract": [1, 5, 7], "extractor": 9, "f_": 11, "f_a": 11, "factor": 10, "fair": 2, "fairli": 2, "fals": [7, 8, 9, 10, 11, 13, 19], "faq": 2, "fascan": 15, "fast": [5, 7, 9], "fast_bas": [9, 19], "fast_smal": [9, 19], "fast_tini": [9, 19], "faster": [5, 9, 18], "fasterrcnn_mobilenet_v3_large_fpn": 9, "favorit": 19, "featur": [4, 9, 11, 12, 13, 16], "feedback": 2, "feel": [3, 15], "felix92": 15, "few": [18, 19], "figsiz": 11, "figur": [11, 16], "file": [3, 7], "final": 9, "find": [3, 17], "fine": 1, "finnish": 7, "first": [3, 7], "firsthand": 7, "fit": [9, 19], "flag": 19, "flip": 10, "float": [8, 10, 11, 18], "float32": [8, 9, 10, 18], "fn": 10, "focu": 15, "focus": [2, 7], "folder": 7, "follow": [2, 3, 4, 7, 10, 11, 13, 14, 15, 16, 19], "font": 7, "font_famili": 7, "foral": 11, "forc": 3, "forg": 4, "form": [5, 7, 19], "format": [8, 11, 13, 17, 18, 19], "forpost": [5, 7], "forum": 3, "found": 1, "fp16": 18, "frac": 11, "framework": [4, 15, 17, 19], "free": [2, 3, 15], "french": [7, 13, 15, 19], "friendli": 5, "from": [1, 2, 5, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19], "from_hub": [9, 15], "from_imag": [8, 15, 16, 18], "from_pdf": 8, "from_url": 8, "full": [7, 11, 19], "function": [7, 10, 11, 16], "funsd": [5, 7, 17, 19], "further": 17, "futur": 7, "g": [8, 9], "g_": 11, "g_x": 11, "gallagh": 1, "gamma": 10, "gaussian": 10, "gaussianblur": 10, "gaussiannois": 10, "gen": 19, "gender": 2, "gener": [3, 5, 8, 9], "generic_cyrillic_lett": 7, "geometri": [5, 8, 19], "geq": 11, "german": [7, 13, 15], "get": [18, 19], "git": 15, "github": [3, 4, 9, 15], "give": [2, 16], "given": [7, 8, 10, 11, 19], "global": 9, "go": 19, "good": 18, "googl": 3, "googlevis": 5, "gpu": [5, 16, 18], "gracefulli": 2, "graph": [5, 7, 8], "grayscal": 10, "ground": 11, "groung": 11, "group": [5, 19], "gt": 11, "gt_box": 11, "gt_label": 11, "guid": 3, "guidanc": 17, "gvision": 19, "h": [8, 9, 10], "h_": 11, "ha": [3, 7, 11, 17], "handl": [12, 17, 19], "handwrit": 7, "handwritten": 17, "harass": 2, "hardwar": 19, "harm": 2, "hat": 11, "have": [2, 3, 11, 13, 15, 17, 18, 19], "head": [9, 19], "healthi": 2, "hebrew": 7, "height": [8, 10], "hello": [11, 19], "help": 18, "here": [6, 10, 12, 16, 17, 19], "hf": 9, "hf_hub_download": 9, "high": 8, "higher": [4, 7, 19], "hindi": 7, "hindi_digit": 7, "hocr": 19, "hook": 19, "horizont": [8, 10, 19], "hous": 7, "how": [1, 3, 12, 13, 15, 17], "howev": 17, "hsv": 10, "html": [2, 3, 4, 8, 19], "http": [2, 4, 7, 8, 9, 15, 19], "hub": 9, "hue": 10, "huggingfac": 9, "hw": 7, "i": [2, 3, 7, 8, 9, 10, 11, 14, 15, 16, 17, 18], "i7": 19, "ibrahimov": 1, "ic03": [5, 7, 17], "ic13": [5, 7, 17], "icdar": [5, 7], "icdar2019": 7, "id": 19, "ident": 2, "identifi": 5, "iiit": [5, 7], "iiit5k": [7, 17], "iiithw": [5, 7, 17], "imag": [1, 5, 7, 8, 9, 10, 11, 15, 16, 17, 19], "imagenet": 9, "imageri": 2, "images_90k_norm": 7, "img": [7, 10, 17, 18], "img_cont": 8, "img_fold": [7, 17], "img_path": 8, "img_transform": 7, "imgur5k": [5, 7, 17], "imgur5k_annot": 7, "imlist": 7, "impact": 2, "implement": [7, 8, 9, 10, 11, 19], "import": [7, 8, 9, 10, 11, 13, 15, 16, 17, 18, 19], "improv": 9, "inappropri": 2, "incid": 2, "includ": [2, 7, 17, 18], "inclus": 2, "increas": 10, "independ": 10, "index": [3, 8], "indic": 11, "individu": 2, "infer": [5, 9, 10, 16, 19], "inform": [1, 2, 3, 5, 7, 17], "input": [3, 8, 9, 10, 18, 19], "input_crop": 9, "input_pag": [9, 11, 19], "input_shap": 18, "input_tensor": 9, "inspir": [2, 10], "instal": [15, 16, 18], "instanc": [2, 19], "instanti": [9, 19], "instead": [7, 8, 9], "insult": 2, "int": [7, 8, 9, 10], "int64": 11, "integ": 11, "integr": [1, 5, 15, 17], "intel": 19, "interact": [2, 8, 11], "interfac": [15, 18], "interoper": 18, "interpol": 10, "interpret": [7, 8], "intersect": 11, "invert": 10, "investig": 2, "invis": 2, "involv": [2, 19], "io": [13, 15, 16, 18], "iou": 11, "iou_thresh": 11, "iou_threshold": 16, "irregular": [5, 9, 17], "isn": 7, "issu": [2, 3, 15], "italian": 7, "iter": [7, 10, 17, 19], "its": [8, 9, 10, 11, 17, 19], "itself": [9, 15], "j": 11, "jame": 1, "job": 3, "join": 3, "jpeg": 10, "jpegqual": 10, "jpg": [7, 8, 15, 18], "json": [7, 17, 19], "json_output": 19, "jump": 3, "just": 2, "kei": [5, 7], "kera": [9, 18], "kernel": [5, 9, 10], "kernel_shap": 10, "keywoard": 9, "keyword": [7, 8, 9, 11], "kie": [9, 13], "kie_predictor": [9, 13], "kiepredictor": 9, "kind": 2, "know": [3, 18], "kwarg": [7, 8, 9, 11], "l": 11, "l_j": 11, "label": [7, 11, 16, 17], "label_fil": [7, 17], "label_fold": 7, "label_path": [7, 17], "labels_path": [7, 17], "ladder": 2, "lambda": 10, "lambdatransform": 10, "lang": 19, "languag": [2, 5, 7, 8, 9, 15, 19], "larg": [9, 15], "largest": 11, "last": [4, 7], "latenc": 9, "later": 3, "latest": 19, "latin": 7, "layer": 18, "layout": 19, "lead": 2, "leader": 2, "learn": [2, 5, 9, 18, 19], "least": 4, "left": [11, 19], "legacy_french": 7, "length": [7, 19], "less": [18, 19], "level": [2, 7, 11, 19], "leverag": 12, "lf": 15, "librari": [3, 4, 12, 13], "light": 5, "lightweight": 18, "like": 2, "limits_": 11, "line": [5, 9, 11, 19], "line_1_1": 19, "link": 13, "linknet": [5, 9], "linknet_resnet18": [9, 13, 18, 19], "linknet_resnet34": [9, 18, 19], "linknet_resnet50": [9, 19], "list": [7, 8, 10, 11, 15], "ll": 11, "load": [5, 7, 9, 16, 18], "load_state_dict": 13, "load_weight": 13, "loc_pr": 19, "local": [3, 5, 7, 9, 11, 17, 19], "localis": 7, "localizationconfus": 11, "locat": [3, 8, 19], "login": 9, "login_to_hub": [9, 15], "logo": [8, 16, 17], "love": 15, "lower": [10, 11, 19], "m": [3, 11, 19], "m1": 4, "macbook": 4, "machin": 18, "made": 5, "magc_resnet31": 9, "mai": [2, 3], "mail": 2, "main": 12, "maintain": 5, "mainten": 3, "make": [2, 3, 11, 13, 14, 15, 18, 19], "mani": [17, 19], "manipul": 19, "map": [7, 9], "map_loc": 13, "master": [5, 9, 19], "match": [11, 19], "mathcal": 11, "matplotlib": [8, 11], "max": [7, 10, 11], "max_angl": 10, "max_area": 10, "max_char": [7, 17], "max_delta": 10, "max_gain": 10, "max_gamma": 10, "max_qual": 10, "max_ratio": 10, "maximum": [7, 10], "maxval": [9, 10], "mbox": 11, "mean": [10, 11, 13], "meaniou": 11, "meant": [8, 18], "measur": 19, "media": 2, "median": 9, "meet": 13, "member": 2, "memori": [14, 18], "mention": 19, "merg": 7, "messag": 3, "meta": 19, "metadata": 18, "metal": 4, "method": [8, 10, 19], "metric": [11, 19], "middl": 19, "might": [18, 19], "min": 10, "min_area": 10, "min_char": [7, 17], "min_gain": 10, "min_gamma": 10, "min_qual": 10, "min_ratio": 10, "min_val": 10, "minde": [1, 2, 4, 5, 9], "minim": [3, 5], "minimalist": [5, 9], "minimum": [4, 7, 10, 11, 19], "minval": 10, "miss": 4, "mistak": 2, "mixed_float16": 18, "mixed_precis": 18, "mjsynth": [5, 7, 17], "mnt": 7, "mobilenet": [9, 15], "mobilenet_v3_larg": 9, "mobilenet_v3_large_r": 9, "mobilenet_v3_smal": [9, 13], "mobilenet_v3_small_crop_orient": [9, 13], "mobilenet_v3_small_page_orient": [9, 13], "mobilenet_v3_small_r": 9, "mobilenetv3": 9, "modal": [5, 7], "mode": 4, "model": [7, 11, 14, 16, 17], "model_nam": [9, 15, 18], "model_path": [16, 18], "moder": 2, "modif": 3, "modifi": [9, 14, 19], "modul": [4, 8, 9, 10, 11, 19], "more": [3, 17, 19], "moscardi": 1, "most": 19, "mozilla": 2, "multi": [5, 9], "multilingu": [7, 15], "multipl": [7, 8, 10, 19], "multipli": 10, "multiprocess": 14, "my": 9, "my_awesome_model": 15, "my_hook": 19, "n": [7, 11], "name": [7, 9, 18, 19], "nation": 2, "natur": [2, 5, 7], "ndarrai": [7, 8, 10, 11], "necessari": [4, 13, 14], "need": [3, 4, 7, 11, 13, 14, 15, 16, 19], "neg": 10, "nest": 19, "netraj": 1, "network": [5, 7, 9, 18], "neural": [5, 7, 9, 18], "new": [3, 11], "next": [7, 17], "nois": 10, "noisi": [5, 7], "non": [5, 7, 8, 9, 10, 11], "none": [7, 8, 9, 10, 11, 19], "normal": [9, 10], "norwegian": 7, "note": [0, 3, 7, 9, 13, 15, 16, 18], "now": 3, "np": [9, 10, 11, 19], "num_output_channel": 10, "num_sampl": [7, 17], "number": [7, 9, 10, 11, 19], "numpi": [8, 9, 11, 19], "o": 4, "obb": 16, "obj_detect": 15, "object": [7, 8, 11, 16, 19], "objectness_scor": [8, 19], "oblig": 2, "obtain": 19, "occupi": 18, "ocr": [1, 5, 7, 9, 11, 15], "ocr_carea": 19, "ocr_db_crnn": 11, "ocr_lin": 19, "ocr_pag": 19, "ocr_par": 19, "ocr_predictor": [9, 13, 15, 18, 19], "ocrdataset": [7, 17], "ocrmetr": 11, "ocrpredictor": [9, 13], "ocrx_word": 19, "offens": 2, "offici": [2, 9], "offlin": 2, "offset": 10, "onc": 19, "one": [3, 7, 9, 10, 13, 15, 19], "oneof": 10, "ones": [7, 11], "onli": [3, 9, 10, 11, 13, 15, 17, 18, 19], "onlin": 2, "onnx": 16, "onnxruntim": [16, 18], "onnxtr": 18, "opac": 10, "opacity_rang": 10, "open": [1, 2, 3, 15, 18], "opinion": 2, "optic": [5, 19], "optim": [5, 19], "option": [7, 9, 13], "order": [3, 7, 8, 10], "org": [2, 7, 9, 19], "organ": 8, "orient": [2, 8, 9, 12, 16, 19], "orientationpredictor": 9, "other": [2, 3], "otherwis": [2, 8, 11], "our": [1, 3, 9, 19], "out": [3, 9, 10, 11, 19], "outpout": 19, "output": [8, 10, 18], "output_s": [8, 10], "outsid": 14, "over": [7, 11, 19], "overal": [2, 9], "overlai": 8, "overview": 16, "overwrit": 13, "overwritten": 15, "own": 5, "p": [10, 19], "packag": [3, 5, 11, 14, 16, 17, 18], "pad": [7, 9, 10, 19], "page": [4, 7, 9, 11, 13, 19], "page1": 8, "page2": 8, "page_1": 19, "page_idx": [8, 19], "page_orientation_predictor": [9, 13], "page_param": 13, "pair": 11, "paper": 9, "par_1_1": 19, "paragraph": 19, "paragraph_break": 19, "parallel": 9, "param": [10, 19], "paramet": [5, 8, 9, 18], "pars": [5, 7], "parseq": [5, 9, 15, 18, 19], "part": [7, 10, 19], "parti": 4, "partial": 19, "particip": 2, "pass": [7, 8, 9, 13, 19], "password": 8, "patch": [9, 11], "path": [7, 8, 16, 17, 18], "path_to_checkpoint": 13, "path_to_custom_model": 18, "path_to_pt": 13, "patil": 1, "pattern": 2, "pdf": [8, 9, 12], "pdfpage": 8, "peopl": 2, "per": [10, 19], "perform": [5, 8, 9, 10, 11, 14, 18, 19], "period": 2, "permiss": 2, "permut": [5, 9], "persian_lett": 7, "person": [2, 17], "phase": 19, "photo": 17, "physic": [2, 8], "pick": 10, "pictur": 8, "pip": [3, 4, 16, 18], "pipelin": 19, "pixel": [8, 10, 19], "pleas": 3, "plot": 11, "plt": 11, "plug": 15, "plugin": 4, "png": 8, "point": 18, "polici": 14, "polish": 7, "polit": 2, "polygon": [7, 11, 19], "pool": 9, "portugues": 7, "posit": [2, 11], "possibl": [3, 11, 15, 19], "post": [2, 19], "postprocessor": 19, "potenti": 9, "power": 5, "ppageno": 19, "pre": [3, 9, 18], "precis": [11, 19], "pred": 11, "pred_box": 11, "pred_label": 11, "predefin": 17, "predict": [8, 9, 11, 19], "predictor": [5, 8, 9, 12, 13, 15, 18], "prefer": 17, "preinstal": 4, "preprocessor": [13, 19], "prerequisit": 15, "present": 12, "preserv": [9, 10, 19], "preserve_aspect_ratio": [8, 9, 10, 13, 19], "pretrain": [5, 9, 11, 13, 18, 19], "pretrained_backbon": [9, 13], "print": 19, "prior": 7, "privaci": 2, "privat": 2, "probabl": [1, 10], "problem": 3, "procedur": 10, "process": [3, 5, 8, 9, 13, 19], "processor": 19, "produc": [12, 19], "product": 18, "profession": 2, "project": [3, 17], "promptli": 2, "proper": 3, "properli": 7, "provid": [2, 3, 5, 15, 16, 17, 19], "public": [2, 5], "publicli": 19, "publish": 2, "pull": 15, "punctuat": 7, "pure": 7, "purpos": 3, "push_to_hf_hub": [9, 15], "py": 15, "pypdfium2": [4, 8], "pyplot": [8, 11], "python": [1, 3, 16], "python3": 15, "pytorch": [4, 5, 9, 10, 13, 15, 18, 19], "q": 3, "qr": [8, 16], "qr_code": 17, "qualiti": 10, "question": 2, "quickli": 5, "quicktour": 12, "r": 19, "race": 2, "ramdisk": 7, "rand": [9, 10, 11, 18, 19], "random": [9, 10, 11, 19], "randomappli": 10, "randombright": 10, "randomcontrast": 10, "randomcrop": 10, "randomgamma": 10, "randomhorizontalflip": 10, "randomhu": 10, "randomjpegqu": 10, "randomli": 10, "randomres": 10, "randomrot": 10, "randomsatur": 10, "randomshadow": 10, "rang": 10, "rassi": 15, "ratio": [9, 10, 19], "raw": [8, 11], "re": 18, "read": [5, 7, 9], "read_html": 8, "read_img_as_numpi": 8, "read_img_as_tensor": 8, "read_pdf": 8, "readi": 18, "real": [1, 5, 9, 10], "realli": 1, "reason": [2, 5, 7], "rebuild": 3, "rebuilt": 3, "recal": [11, 19], "receipt": [5, 7, 19], "reco_arch": [9, 13, 15, 18], "reco_b": 19, "reco_model": [13, 15, 18], "reco_param": 13, "reco_predictor": 13, "recogn": 19, "recognit": [7, 11, 12, 13], "recognition_predictor": [9, 19], "recognition_task": [7, 17], "recognitiondataset": [7, 17], "recognitionpredictor": [9, 13], "rectangular": 9, "reduc": [4, 10], "refer": [3, 4, 13, 15, 16, 17, 19], "regardless": 2, "region": 19, "regroup": 11, "regular": 17, "reject": 2, "rel": [8, 10, 11, 19], "relat": 8, "releas": [0, 4], "relev": 16, "religion": 2, "remov": 2, "render": [8, 19], "repo": 9, "repo_id": [9, 15], "report": 2, "repositori": [7, 9, 15], "repres": [2, 18, 19], "represent": [5, 9], "request": [2, 15], "requir": [4, 10, 18], "research": 5, "residu": 9, "resiz": [10, 19], "resnet": 9, "resnet18": [9, 15], "resnet31": 9, "resnet34": 9, "resnet50": [9, 15], "resolv": 8, "resolve_block": 19, "resolve_lin": 19, "resourc": 17, "respect": 2, "rest": [3, 10, 11], "restrict": 14, "result": [3, 7, 8, 12, 15, 18, 19], "return": 19, "reusabl": 19, "review": 2, "rgb": [8, 10], "rgb_mode": 8, "rgb_output": 8, "right": [2, 9, 11], "roboflow": 1, "robust": [5, 7], "root": 7, "rotat": [7, 8, 9, 10, 11, 12, 13, 17, 19], "run": [3, 4, 9], "same": [3, 8, 11, 17, 18, 19], "sampl": [7, 9, 17, 19], "sample_transform": 7, "sanjin": 1, "sar": [5, 9], "sar_resnet31": [9, 19], "satur": 10, "save": [9, 17], "scale": [8, 9, 10, 11], "scale_rang": 10, "scan": [5, 7], "scene": [5, 7, 9], "score": [8, 11], "script": [3, 17], "seamless": 5, "seamlessli": [5, 19], "search": [1, 9], "searchabl": 12, "sec": 19, "second": 19, "section": [1, 13, 15, 16, 18, 19], "secur": [2, 14], "see": [2, 3], "seen": 19, "segment": [5, 9, 19], "self": 19, "semant": [5, 9], "send": 19, "sens": 11, "sensit": 17, "separ": 19, "sequenc": [5, 7, 8, 9, 11, 19], "sequenti": [10, 19], "seri": 2, "seriou": 2, "set": [2, 4, 7, 9, 11, 14, 16, 19], "set_global_polici": 18, "sever": [8, 10, 19], "sex": 2, "sexual": 2, "shade": 10, "shape": [5, 8, 9, 10, 11, 19], "share": [14, 17], "shift": 10, "shm": 14, "should": [3, 7, 8, 10, 11], "show": [5, 8, 9, 11, 13, 15, 16], "showcas": [3, 12], "shuffl": [7, 10], "side": 11, "signatur": 8, "signific": 17, "simpl": [5, 9, 18], "simpler": 9, "sinc": [7, 17], "singl": [2, 3, 5, 7], "single_img_doc": 18, "size": [2, 7, 8, 10, 16, 19], "skew": 19, "slack": 3, "slightli": 9, "small": [3, 9, 19], "smallest": 8, "snapshot_download": 9, "snippet": 19, "so": [3, 4, 7, 9, 15, 17], "social": 2, "socio": 2, "some": [1, 4, 12, 15, 17], "someth": 3, "somewher": 3, "sort": 2, "sourc": [1, 7, 8, 9, 10, 11, 15], "space": [2, 19], "span": 19, "spanish": 7, "spatial": [5, 7, 8], "specif": [3, 4, 11, 13, 17, 19], "specifi": [2, 7, 8], "speed": [5, 9, 19], "sphinx": 3, "sroie": [5, 7, 17], "stabl": 4, "stackoverflow": 3, "stage": 5, "standalon": 12, "standard": 10, "start": 7, "state": [1, 5, 11, 16], "static": 11, "statist": 1, "statu": 2, "std": [10, 13], "step": 14, "still": 19, "str": [7, 8, 9, 10, 11], "straight": [7, 9, 17, 19], "straighten": 19, "straighten_pag": [9, 13, 19], "straigten_pag": 13, "stream": 8, "street": [5, 7], "strict": 4, "strictli": 11, "string": [7, 8, 11, 19], "strive": 4, "strong": [5, 9], "structur": [18, 19], "subset": [7, 19], "suggest": [3, 15], "sum": 11, "summari": 11, "support": [4, 13, 16, 18, 19], "sustain": 2, "svhn": [5, 7, 17], "svt": [7, 17], "swedish": 7, "symmetr": [9, 10, 19], "symmetric_pad": [9, 10, 19], "synthet": 5, "synthtext": [5, 7, 17], "system": 19, "t": [3, 7, 13, 18, 19], "tabl": [15, 16, 17], "take": [2, 7, 19], "target": [7, 8, 10, 11, 17], "target_s": 7, "task": [5, 7, 9, 15, 17, 19], "task2": 7, "team": 4, "techminde": 4, "templat": [3, 5], "tensor": [7, 8, 10, 19], "tensorflow": [4, 5, 8, 9, 10, 13, 15, 18, 19], "tensorspec": 18, "term": 2, "test": [7, 17], "test_set": 7, "text": [1, 7, 8, 9, 11, 17], "text_output": 19, "textmatch": 11, "textnet": 9, "textnet_bas": 9, "textnet_smal": 9, "textnet_tini": 9, "textract": [5, 19], "textstylebrush": [5, 7], "textual": [5, 7, 8, 9, 19], "tf": [4, 8, 9, 10, 15, 18], "than": [3, 11, 15], "thank": 3, "thei": [2, 11], "them": [7, 19], "thi": [1, 2, 3, 4, 6, 7, 10, 11, 13, 14, 15, 17, 18, 19], "thing": [18, 19], "third": 4, "those": [2, 8, 19], "threaten": 2, "threshold": 19, "through": [2, 10, 16, 17], "tilman": 15, "time": [1, 2, 5, 9, 11, 17], "tini": 9, "titl": [8, 19], "tm": 19, "tmp": 14, "togeth": [3, 8], "tograi": 10, "tool": [1, 17], "top": [11, 18, 19], "topic": 3, "torch": [4, 10, 13, 15, 18], "torchvis": 10, "total": 13, "toward": [2, 4], "train": [3, 7, 9, 10, 15, 16, 17, 18, 19], "train_it": [7, 17], "train_load": [7, 17], "train_pytorch": 15, "train_set": [7, 17], "train_tensorflow": 15, "trainabl": [5, 9], "tranform": 10, "transcrib": 19, "transfer": [5, 7], "transfo": 10, "transform": [5, 7, 9], "translat": 2, "troll": 2, "true": [7, 8, 9, 10, 11, 13, 14, 15, 17, 18, 19], "truth": 11, "tune": [1, 18], "tupl": [7, 8, 10, 11], "two": [8, 14], "txt": 7, "type": [8, 11, 15, 18, 19], "typic": 19, "u": [2, 3], "ucsd": 7, "udac": 3, "uint8": [8, 9, 11, 19], "ukrainian": 7, "unaccept": 2, "underli": [17, 19], "underneath": 8, "understand": [5, 7, 19], "uniform": [9, 10], "uniformli": 10, "uninterrupt": [8, 19], "union": 11, "unit": 1, "unittest": 3, "unlock": 8, "unoffici": 9, "unprofession": 2, "unsolicit": 2, "unsupervis": 5, "unwelcom": 2, "up": [9, 19], "updat": 11, "upgrad": 3, "upper": [7, 10], "uppercas": 17, "url": 8, "us": [2, 3, 4, 7, 9, 11, 12, 13, 14, 15, 16, 19], "usabl": 19, "usag": [14, 18], "use_polygon": [7, 11, 17], "useabl": 19, "user": [5, 8, 12], "utf": 19, "util": 18, "v1": 15, "v3": [9, 15, 19], "valid": 17, "valu": [3, 8, 10, 19], "valuabl": 5, "variabl": 14, "varieti": 7, "veri": 9, "verma": 1, "version": [2, 3, 4, 18, 19], "vgg": 9, "vgg16": 15, "vgg16_bn_r": 9, "via": 2, "video": 1, "vietnames": 7, "view": [5, 7], "viewpoint": 2, "violat": 2, "visibl": 2, "vision": [5, 7, 9], "visiondataset": 7, "visiontransform": 9, "visual": [4, 5, 16], "visualize_pag": 11, "vit_": 9, "vit_b": 9, "vitstr": [5, 9, 18], "vitstr_bas": [9, 19], "vitstr_smal": [9, 13, 18, 19], "viz": 4, "vocab": [13, 15, 17, 18, 19], "vocabulari": [7, 13, 15], "w": [8, 9, 10, 11], "w3": 19, "wa": 2, "wai": [2, 5, 17], "want": [3, 18, 19], "warmup": 19, "wasn": 3, "we": [1, 2, 3, 4, 5, 8, 10, 13, 15, 17, 18, 19], "weasyprint": 8, "web": [3, 8], "websit": 7, "welcom": 2, "well": [1, 2, 18], "were": [2, 8, 19], "what": [1, 2], "when": [2, 3, 9], "whenev": 3, "where": [3, 8, 10, 11], "whether": [3, 7, 8, 10, 11, 17, 19], "which": [2, 9, 14, 16, 17, 19], "whichev": 4, "while": [10, 19], "why": 2, "width": [8, 10], "wiki": 2, "wildreceipt": [5, 7, 17], "window": [9, 11], "wish": 3, "within": 2, "without": [2, 7, 9], "wonder": 3, "word": [5, 7, 9, 11, 19], "word_1_1": 19, "word_1_2": 19, "word_1_3": 19, "wordgener": [7, 17], "words_onli": 11, "work": [1, 13, 14, 19], "workflow": 3, "worklow": 3, "world": [11, 19], "worth": 9, "wrap": 19, "wrapper": [7, 10], "write": 14, "written": [2, 8], "www": [2, 8, 19], "x": [8, 10, 11], "x_ascend": 19, "x_descend": 19, "x_i": 11, "x_size": 19, "x_wconf": 19, "xhtml": 19, "xmax": 8, "xmin": 8, "xml": 19, "xml_bytes_str": 19, "xml_element": 19, "xml_output": 19, "xmln": 19, "y": 11, "y_i": 11, "y_j": 11, "yet": 16, "ymax": 8, "ymin": 8, "yolov8": 16, "you": [3, 4, 7, 8, 9, 13, 14, 15, 16, 17, 18, 19], "your": [3, 5, 8, 11, 19], "yoursit": 8, "yugesh": 1, "zero": [10, 11], "zoo": 13, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 7, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 7, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": 7, "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 7, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 7, "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 7, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 7, "\u00e4\u00f6\u00e4\u00f6": 7, "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 7, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": 7, "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": 7, "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": 7, "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": 7, "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": 7, "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0905": 7, "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": 7, "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": 7, "\u0950": 7, "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": 7, "\u09bd": 7, "\u09ce": 7, "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": 7}, "titles": ["Changelog", "Community resources", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.contrib", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Integrate contributions into your pipeline", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 3, "0": 0, "01": 0, "02": 0, "03": 0, "04": 0, "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 2], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 2], "2021": 0, "2022": 0, "2023": 0, "2024": 0, "21": 0, "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 2], "31": 0, "4": [0, 2], "5": 0, "6": 0, "7": 0, "8": 0, "9": 0, "advanc": 19, "approach": 19, "architectur": 19, "arg": [7, 8, 9, 10, 11], "artefact": 8, "artefactdetect": 16, "attribut": 2, "avail": [16, 17, 19], "aw": 14, "ban": 2, "block": 8, "bug": 3, "changelog": 0, "choos": [17, 19], "classif": [9, 13, 15], "code": [2, 3], "codebas": 3, "commit": 3, "commun": [1, 15], "compos": 10, "conda": 4, "conduct": 2, "connect": 3, "continu": 3, "contrib": 6, "contribut": [3, 6, 16], "contributor": 2, "convent": 15, "correct": 2, "coven": 2, "custom": [7, 13], "data": 17, "dataload": 7, "dataset": [5, 7, 17], "detect": [5, 9, 15, 17, 19], "develop": 3, "do": 19, "doctr": [3, 5, 6, 7, 8, 9, 10, 11, 12], "document": [3, 5, 8], "end": 19, "enforc": 2, "evalu": 11, "export": 18, "factori": 9, "featur": [3, 5], "feedback": 3, "file": 8, "from": 15, "gener": [7, 17], "git": 4, "guidelin": 2, "half": 18, "hub": 15, "huggingfac": 15, "i": 19, "infer": 18, "instal": [3, 4], "integr": [3, 16], "io": 8, "lambda": 14, "let": 3, "line": 8, "linux": 4, "load": [13, 15, 17], "loader": 7, "main": 5, "mode": 3, "model": [5, 9, 13, 15, 18, 19], "modifi": 3, "modul": [6, 16], "name": 15, "notebook": 12, "object": 17, "ocr": [17, 19], "onli": 4, "onnx": 18, "optim": 18, "option": 19, "orient": 13, "our": 2, "output": 19, "own": [13, 17], "packag": 4, "page": 8, "perman": 2, "pipelin": 16, "pledg": 2, "precis": 18, "predictor": 19, "prepar": 18, "prerequisit": 4, "pretrain": 15, "push": 15, "python": 4, "qualiti": 3, "question": 3, "read": 8, "readi": 17, "recognit": [5, 9, 15, 17, 19], "report": 3, "request": 3, "resourc": 1, "respons": 2, "return": [7, 8, 9, 11], "right": 19, "scope": 2, "share": 15, "should": 19, "stage": 19, "standard": 2, "structur": [3, 8], "style": 3, "support": [5, 6, 7, 10], "synthet": [7, 17], "task": 11, "temporari": 2, "test": 3, "text": [5, 19], "train": 13, "transform": 10, "two": 19, "unit": 3, "us": [17, 18], "util": 11, "v0": 0, "verif": 3, "via": 4, "visual": 11, "vocab": 7, "warn": 2, "what": 19, "word": 8, "your": [13, 15, 16, 17, 18], "zoo": [5, 9]}}) \ No newline at end of file diff --git a/v0.1.0/using_doctr/custom_models_training.html b/v0.1.0/using_doctr/custom_models_training.html index df39d8d568..b714c1f971 100644 --- a/v0.1.0/using_doctr/custom_models_training.html +++ b/v0.1.0/using_doctr/custom_models_training.html @@ -14,7 +14,7 @@ - + Train your own model - docTR documentation @@ -619,7 +619,7 @@

    Loading your custom trained orientation classification model - + diff --git a/v0.1.0/using_doctr/running_on_aws.html b/v0.1.0/using_doctr/running_on_aws.html index 16ceaca7a1..808ea541cd 100644 --- a/v0.1.0/using_doctr/running_on_aws.html +++ b/v0.1.0/using_doctr/running_on_aws.html @@ -14,7 +14,7 @@ - + AWS Lambda - docTR documentation @@ -362,7 +362,7 @@

    AWS Lambda - + diff --git a/v0.1.0/using_doctr/sharing_models.html b/v0.1.0/using_doctr/sharing_models.html index d76b4017f4..c9e978400a 100644 --- a/v0.1.0/using_doctr/sharing_models.html +++ b/v0.1.0/using_doctr/sharing_models.html @@ -14,7 +14,7 @@ - + Share your model with the community - docTR documentation @@ -544,7 +544,7 @@

    Recognition - + diff --git a/v0.1.0/using_doctr/using_contrib_modules.html b/v0.1.0/using_doctr/using_contrib_modules.html index 50598dae5d..0c5fffdf9f 100644 --- a/v0.1.0/using_doctr/using_contrib_modules.html +++ b/v0.1.0/using_doctr/using_contrib_modules.html @@ -14,7 +14,7 @@ - + Integrate contributions into your pipeline - docTR documentation @@ -415,7 +415,7 @@

    ArtefactDetection - + diff --git a/v0.1.0/using_doctr/using_datasets.html b/v0.1.0/using_doctr/using_datasets.html index 460476dbbf..8a7d4f0a64 100644 --- a/v0.1.0/using_doctr/using_datasets.html +++ b/v0.1.0/using_doctr/using_datasets.html @@ -14,7 +14,7 @@ - + Choose a ready to use dataset - docTR documentation @@ -642,7 +642,7 @@

    Data Loading - + diff --git a/v0.1.0/using_doctr/using_model_export.html b/v0.1.0/using_doctr/using_model_export.html index 6124c00ebe..6790dd0642 100644 --- a/v0.1.0/using_doctr/using_model_export.html +++ b/v0.1.0/using_doctr/using_model_export.html @@ -14,7 +14,7 @@ - + Preparing your model for inference - docTR documentation @@ -467,7 +467,7 @@

    Using your ONNX exported model - + diff --git a/v0.1.0/using_doctr/using_models.html b/v0.1.0/using_doctr/using_models.html index 61f1f5ab7a..9ead8498e1 100644 --- a/v0.1.0/using_doctr/using_models.html +++ b/v0.1.0/using_doctr/using_models.html @@ -14,7 +14,7 @@ - + Choosing the right model - docTR documentation @@ -1253,7 +1253,7 @@

    Advanced options - + diff --git a/v0.1.1/_modules/doctr/datasets/cord.html b/v0.1.1/_modules/doctr/datasets/cord.html index 78e70014e3..55b0584830 100644 --- a/v0.1.1/_modules/doctr/datasets/cord.html +++ b/v0.1.1/_modules/doctr/datasets/cord.html @@ -13,7 +13,7 @@ - + doctr.datasets.cord - docTR documentation @@ -462,7 +462,7 @@

    Source code for doctr.datasets.cord

         
       
    - + diff --git a/v0.1.1/_modules/doctr/datasets/detection.html b/v0.1.1/_modules/doctr/datasets/detection.html index 739563e466..718001e4cf 100644 --- a/v0.1.1/_modules/doctr/datasets/detection.html +++ b/v0.1.1/_modules/doctr/datasets/detection.html @@ -13,7 +13,7 @@ - + doctr.datasets.detection - docTR documentation @@ -430,7 +430,7 @@

    Source code for doctr.datasets.detection

         
       
    - + diff --git a/v0.1.1/_modules/doctr/datasets/doc_artefacts.html b/v0.1.1/_modules/doctr/datasets/doc_artefacts.html index 3313ae4660..94c32aaa0f 100644 --- a/v0.1.1/_modules/doctr/datasets/doc_artefacts.html +++ b/v0.1.1/_modules/doctr/datasets/doc_artefacts.html @@ -13,7 +13,7 @@ - + doctr.datasets.doc_artefacts - docTR documentation @@ -414,7 +414,7 @@

    Source code for doctr.datasets.doc_artefacts

       
    - + diff --git a/v0.1.1/_modules/doctr/datasets/funsd.html b/v0.1.1/_modules/doctr/datasets/funsd.html index e52abc5428..f08612f9fa 100644 --- a/v0.1.1/_modules/doctr/datasets/funsd.html +++ b/v0.1.1/_modules/doctr/datasets/funsd.html @@ -13,7 +13,7 @@ - + doctr.datasets.funsd - docTR documentation @@ -454,7 +454,7 @@

    Source code for doctr.datasets.funsd

         
       
    - + diff --git a/v0.1.1/_modules/doctr/datasets/generator/tensorflow.html b/v0.1.1/_modules/doctr/datasets/generator/tensorflow.html index 9f562582d9..a3e619f720 100644 --- a/v0.1.1/_modules/doctr/datasets/generator/tensorflow.html +++ b/v0.1.1/_modules/doctr/datasets/generator/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.datasets.generator.tensorflow - docTR documentation @@ -395,7 +395,7 @@

    Source code for doctr.datasets.generator.tensorflow

    - + diff --git a/v0.1.1/_modules/doctr/datasets/ic03.html b/v0.1.1/_modules/doctr/datasets/ic03.html index 3d221d07de..60e54a8a4b 100644 --- a/v0.1.1/_modules/doctr/datasets/ic03.html +++ b/v0.1.1/_modules/doctr/datasets/ic03.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic03 - docTR documentation @@ -468,7 +468,7 @@

    Source code for doctr.datasets.ic03

         
       
    - + diff --git a/v0.1.1/_modules/doctr/datasets/ic13.html b/v0.1.1/_modules/doctr/datasets/ic13.html index 8137e08e9f..219c98dcd1 100644 --- a/v0.1.1/_modules/doctr/datasets/ic13.html +++ b/v0.1.1/_modules/doctr/datasets/ic13.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic13 - docTR documentation @@ -440,7 +440,7 @@

    Source code for doctr.datasets.ic13

         
       
    - + diff --git a/v0.1.1/_modules/doctr/datasets/iiit5k.html b/v0.1.1/_modules/doctr/datasets/iiit5k.html index 1fc8ecfb27..b49c80fe18 100644 --- a/v0.1.1/_modules/doctr/datasets/iiit5k.html +++ b/v0.1.1/_modules/doctr/datasets/iiit5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiit5k - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.datasets.iiit5k

         
       
    - + diff --git a/v0.1.1/_modules/doctr/datasets/iiithws.html b/v0.1.1/_modules/doctr/datasets/iiithws.html index 07f5b13685..f7220afbc7 100644 --- a/v0.1.1/_modules/doctr/datasets/iiithws.html +++ b/v0.1.1/_modules/doctr/datasets/iiithws.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiithws - docTR documentation @@ -407,7 +407,7 @@

    Source code for doctr.datasets.iiithws

         
       
    - + diff --git a/v0.1.1/_modules/doctr/datasets/imgur5k.html b/v0.1.1/_modules/doctr/datasets/imgur5k.html index 68d433ca62..51c6545db4 100644 --- a/v0.1.1/_modules/doctr/datasets/imgur5k.html +++ b/v0.1.1/_modules/doctr/datasets/imgur5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.imgur5k - docTR documentation @@ -488,7 +488,7 @@

    Source code for doctr.datasets.imgur5k

         
       
    - + diff --git a/v0.1.1/_modules/doctr/datasets/loader.html b/v0.1.1/_modules/doctr/datasets/loader.html index d1785caa1c..ed80350ef0 100644 --- a/v0.1.1/_modules/doctr/datasets/loader.html +++ b/v0.1.1/_modules/doctr/datasets/loader.html @@ -13,7 +13,7 @@ - + doctr.datasets.loader - docTR documentation @@ -429,7 +429,7 @@

    Source code for doctr.datasets.loader

         
       
    - + diff --git a/v0.1.1/_modules/doctr/datasets/mjsynth.html b/v0.1.1/_modules/doctr/datasets/mjsynth.html index 77bb01d523..df34e49cf9 100644 --- a/v0.1.1/_modules/doctr/datasets/mjsynth.html +++ b/v0.1.1/_modules/doctr/datasets/mjsynth.html @@ -13,7 +13,7 @@ - + doctr.datasets.mjsynth - docTR documentation @@ -438,7 +438,7 @@

    Source code for doctr.datasets.mjsynth

         
       
    - + diff --git a/v0.1.1/_modules/doctr/datasets/ocr.html b/v0.1.1/_modules/doctr/datasets/ocr.html index 5832933ea5..ce1ed8b0d4 100644 --- a/v0.1.1/_modules/doctr/datasets/ocr.html +++ b/v0.1.1/_modules/doctr/datasets/ocr.html @@ -13,7 +13,7 @@ - + doctr.datasets.ocr - docTR documentation @@ -403,7 +403,7 @@

    Source code for doctr.datasets.ocr

         
       
    - + diff --git a/v0.1.1/_modules/doctr/datasets/recognition.html b/v0.1.1/_modules/doctr/datasets/recognition.html index 512c70c308..1754789364 100644 --- a/v0.1.1/_modules/doctr/datasets/recognition.html +++ b/v0.1.1/_modules/doctr/datasets/recognition.html @@ -13,7 +13,7 @@ - + doctr.datasets.recognition - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.datasets.recognition

         
       
    - + diff --git a/v0.1.1/_modules/doctr/datasets/sroie.html b/v0.1.1/_modules/doctr/datasets/sroie.html index 94c963390e..04cf10bda2 100644 --- a/v0.1.1/_modules/doctr/datasets/sroie.html +++ b/v0.1.1/_modules/doctr/datasets/sroie.html @@ -13,7 +13,7 @@ - + doctr.datasets.sroie - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.datasets.sroie

         
       
    - + diff --git a/v0.1.1/_modules/doctr/datasets/svhn.html b/v0.1.1/_modules/doctr/datasets/svhn.html index 48e4e4d210..60e02b1b3b 100644 --- a/v0.1.1/_modules/doctr/datasets/svhn.html +++ b/v0.1.1/_modules/doctr/datasets/svhn.html @@ -13,7 +13,7 @@ - + doctr.datasets.svhn - docTR documentation @@ -473,7 +473,7 @@

    Source code for doctr.datasets.svhn

         
       
    - + diff --git a/v0.1.1/_modules/doctr/datasets/svt.html b/v0.1.1/_modules/doctr/datasets/svt.html index 4144dc6b9b..a997fcbb50 100644 --- a/v0.1.1/_modules/doctr/datasets/svt.html +++ b/v0.1.1/_modules/doctr/datasets/svt.html @@ -13,7 +13,7 @@ - + doctr.datasets.svt - docTR documentation @@ -459,7 +459,7 @@

    Source code for doctr.datasets.svt

         
       
    - + diff --git a/v0.1.1/_modules/doctr/datasets/synthtext.html b/v0.1.1/_modules/doctr/datasets/synthtext.html index 3b9de506a7..c776e1d673 100644 --- a/v0.1.1/_modules/doctr/datasets/synthtext.html +++ b/v0.1.1/_modules/doctr/datasets/synthtext.html @@ -13,7 +13,7 @@ - + doctr.datasets.synthtext - docTR documentation @@ -470,7 +470,7 @@

    Source code for doctr.datasets.synthtext

         
       
    - + diff --git a/v0.1.1/_modules/doctr/datasets/utils.html b/v0.1.1/_modules/doctr/datasets/utils.html index 9defb17ba5..bde9304597 100644 --- a/v0.1.1/_modules/doctr/datasets/utils.html +++ b/v0.1.1/_modules/doctr/datasets/utils.html @@ -13,7 +13,7 @@ - + doctr.datasets.utils - docTR documentation @@ -554,7 +554,7 @@

    Source code for doctr.datasets.utils

         
       
    - + diff --git a/v0.1.1/_modules/doctr/datasets/wildreceipt.html b/v0.1.1/_modules/doctr/datasets/wildreceipt.html index c543ee7cac..12c6aebd14 100644 --- a/v0.1.1/_modules/doctr/datasets/wildreceipt.html +++ b/v0.1.1/_modules/doctr/datasets/wildreceipt.html @@ -13,7 +13,7 @@ - + doctr.datasets.wildreceipt - docTR documentation @@ -454,7 +454,7 @@

    Source code for doctr.datasets.wildreceipt

         
       
    - + diff --git a/v0.1.1/_modules/doctr/io/elements.html b/v0.1.1/_modules/doctr/io/elements.html index 753a47455c..e049d6ce30 100644 --- a/v0.1.1/_modules/doctr/io/elements.html +++ b/v0.1.1/_modules/doctr/io/elements.html @@ -13,7 +13,7 @@ - + doctr.io.elements - docTR documentation @@ -1008,7 +1008,7 @@

    Source code for doctr.io.elements

         
       
    - + diff --git a/v0.1.1/_modules/doctr/io/html.html b/v0.1.1/_modules/doctr/io/html.html index 7ad5b97031..be73631500 100644 --- a/v0.1.1/_modules/doctr/io/html.html +++ b/v0.1.1/_modules/doctr/io/html.html @@ -13,7 +13,7 @@ - + doctr.io.html - docTR documentation @@ -360,7 +360,7 @@

    Source code for doctr.io.html

         
       
    - + diff --git a/v0.1.1/_modules/doctr/io/image/base.html b/v0.1.1/_modules/doctr/io/image/base.html index 336b4bff0e..a50c95d595 100644 --- a/v0.1.1/_modules/doctr/io/image/base.html +++ b/v0.1.1/_modules/doctr/io/image/base.html @@ -13,7 +13,7 @@ - + doctr.io.image.base - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.io.image.base

         
       
    - + diff --git a/v0.1.1/_modules/doctr/io/image/tensorflow.html b/v0.1.1/_modules/doctr/io/image/tensorflow.html index f1846820a3..3b9e731756 100644 --- a/v0.1.1/_modules/doctr/io/image/tensorflow.html +++ b/v0.1.1/_modules/doctr/io/image/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.io.image.tensorflow - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.io.image.tensorflow

         
       
    - + diff --git a/v0.1.1/_modules/doctr/io/pdf.html b/v0.1.1/_modules/doctr/io/pdf.html index e3abf6960b..e5b94811c3 100644 --- a/v0.1.1/_modules/doctr/io/pdf.html +++ b/v0.1.1/_modules/doctr/io/pdf.html @@ -13,7 +13,7 @@ - + doctr.io.pdf - docTR documentation @@ -377,7 +377,7 @@

    Source code for doctr.io.pdf

         
       
    - + diff --git a/v0.1.1/_modules/doctr/io/reader.html b/v0.1.1/_modules/doctr/io/reader.html index c1ddc26edd..d36e5bb553 100644 --- a/v0.1.1/_modules/doctr/io/reader.html +++ b/v0.1.1/_modules/doctr/io/reader.html @@ -13,7 +13,7 @@ - + doctr.io.reader - docTR documentation @@ -426,7 +426,7 @@

    Source code for doctr.io.reader

         
       
    - + diff --git a/v0.1.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html b/v0.1.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html index 9f074805c1..61a010d548 100644 --- a/v0.1.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html +++ b/v0.1.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.magc_resnet.tensorflow - docTR documentation @@ -531,7 +531,7 @@

    Source code for doctr.models.classification.magc_resnet.tensorflow

    - + diff --git a/v0.1.1/_modules/doctr/models/classification/mobilenet/tensorflow.html b/v0.1.1/_modules/doctr/models/classification/mobilenet/tensorflow.html index 6a63851276..7c448394ad 100644 --- a/v0.1.1/_modules/doctr/models/classification/mobilenet/tensorflow.html +++ b/v0.1.1/_modules/doctr/models/classification/mobilenet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.mobilenet.tensorflow - docTR documentation @@ -793,7 +793,7 @@

    Source code for doctr.models.classification.mobilenet.tensorflow

    - + diff --git a/v0.1.1/_modules/doctr/models/classification/resnet/tensorflow.html b/v0.1.1/_modules/doctr/models/classification/resnet/tensorflow.html index 095d377f31..aed4343741 100644 --- a/v0.1.1/_modules/doctr/models/classification/resnet/tensorflow.html +++ b/v0.1.1/_modules/doctr/models/classification/resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.resnet.tensorflow - docTR documentation @@ -749,7 +749,7 @@

    Source code for doctr.models.classification.resnet.tensorflow

    - + diff --git a/v0.1.1/_modules/doctr/models/classification/textnet/tensorflow.html b/v0.1.1/_modules/doctr/models/classification/textnet/tensorflow.html index ad254ebbfb..c5567d7d67 100644 --- a/v0.1.1/_modules/doctr/models/classification/textnet/tensorflow.html +++ b/v0.1.1/_modules/doctr/models/classification/textnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.textnet.tensorflow - docTR documentation @@ -611,7 +611,7 @@

    Source code for doctr.models.classification.textnet.tensorflow

    - + diff --git a/v0.1.1/_modules/doctr/models/classification/vgg/tensorflow.html b/v0.1.1/_modules/doctr/models/classification/vgg/tensorflow.html index 01ae452624..788111ae87 100644 --- a/v0.1.1/_modules/doctr/models/classification/vgg/tensorflow.html +++ b/v0.1.1/_modules/doctr/models/classification/vgg/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vgg.tensorflow - docTR documentation @@ -451,7 +451,7 @@

    Source code for doctr.models.classification.vgg.tensorflow

    - + diff --git a/v0.1.1/_modules/doctr/models/classification/vit/tensorflow.html b/v0.1.1/_modules/doctr/models/classification/vit/tensorflow.html index 1333cf6045..971ba5abe9 100644 --- a/v0.1.1/_modules/doctr/models/classification/vit/tensorflow.html +++ b/v0.1.1/_modules/doctr/models/classification/vit/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vit.tensorflow - docTR documentation @@ -533,7 +533,7 @@

    Source code for doctr.models.classification.vit.tensorflow

    - + diff --git a/v0.1.1/_modules/doctr/models/classification/zoo.html b/v0.1.1/_modules/doctr/models/classification/zoo.html index f7796a7522..3eb2a3ec4e 100644 --- a/v0.1.1/_modules/doctr/models/classification/zoo.html +++ b/v0.1.1/_modules/doctr/models/classification/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.classification.zoo - docTR documentation @@ -447,7 +447,7 @@

    Source code for doctr.models.classification.zoo

    <
    - + diff --git a/v0.1.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html b/v0.1.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html index 4325d0b74a..66cef8663d 100644 --- a/v0.1.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html +++ b/v0.1.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.differentiable_binarization.tensorflow - docTR documentation @@ -759,7 +759,7 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo

    - + diff --git a/v0.1.1/_modules/doctr/models/detection/fast/tensorflow.html b/v0.1.1/_modules/doctr/models/detection/fast/tensorflow.html index 5b84d2dea1..65e1a77af8 100644 --- a/v0.1.1/_modules/doctr/models/detection/fast/tensorflow.html +++ b/v0.1.1/_modules/doctr/models/detection/fast/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.fast.tensorflow - docTR documentation @@ -769,7 +769,7 @@

    Source code for doctr.models.detection.fast.tensorflow

    - + diff --git a/v0.1.1/_modules/doctr/models/detection/linknet/tensorflow.html b/v0.1.1/_modules/doctr/models/detection/linknet/tensorflow.html index dbb58e37cf..ce995f99d4 100644 --- a/v0.1.1/_modules/doctr/models/detection/linknet/tensorflow.html +++ b/v0.1.1/_modules/doctr/models/detection/linknet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.linknet.tensorflow - docTR documentation @@ -716,7 +716,7 @@

    Source code for doctr.models.detection.linknet.tensorflow

    - + diff --git a/v0.1.1/_modules/doctr/models/detection/zoo.html b/v0.1.1/_modules/doctr/models/detection/zoo.html index 312f4584ab..3651c4e2d3 100644 --- a/v0.1.1/_modules/doctr/models/detection/zoo.html +++ b/v0.1.1/_modules/doctr/models/detection/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.detection.zoo - docTR documentation @@ -450,7 +450,7 @@

    Source code for doctr.models.detection.zoo

         
       
    - + diff --git a/v0.1.1/_modules/doctr/models/factory/hub.html b/v0.1.1/_modules/doctr/models/factory/hub.html index 8274a809f5..756b2c7a17 100644 --- a/v0.1.1/_modules/doctr/models/factory/hub.html +++ b/v0.1.1/_modules/doctr/models/factory/hub.html @@ -13,7 +13,7 @@ - + doctr.models.factory.hub - docTR documentation @@ -568,7 +568,7 @@

    Source code for doctr.models.factory.hub

         
       
    - + diff --git a/v0.1.1/_modules/doctr/models/recognition/crnn/tensorflow.html b/v0.1.1/_modules/doctr/models/recognition/crnn/tensorflow.html index e50c245923..bc64da9a1b 100644 --- a/v0.1.1/_modules/doctr/models/recognition/crnn/tensorflow.html +++ b/v0.1.1/_modules/doctr/models/recognition/crnn/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.crnn.tensorflow - docTR documentation @@ -658,7 +658,7 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    - + diff --git a/v0.1.1/_modules/doctr/models/recognition/master/tensorflow.html b/v0.1.1/_modules/doctr/models/recognition/master/tensorflow.html index 152ebb7e59..aa6aa69325 100644 --- a/v0.1.1/_modules/doctr/models/recognition/master/tensorflow.html +++ b/v0.1.1/_modules/doctr/models/recognition/master/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.master.tensorflow - docTR documentation @@ -655,7 +655,7 @@

    Source code for doctr.models.recognition.master.tensorflow

    - + diff --git a/v0.1.1/_modules/doctr/models/recognition/parseq/tensorflow.html b/v0.1.1/_modules/doctr/models/recognition/parseq/tensorflow.html index 0819737dfc..b181acef53 100644 --- a/v0.1.1/_modules/doctr/models/recognition/parseq/tensorflow.html +++ b/v0.1.1/_modules/doctr/models/recognition/parseq/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.parseq.tensorflow - docTR documentation @@ -845,7 +845,7 @@

    Source code for doctr.models.recognition.parseq.tensorflow

    - + diff --git a/v0.1.1/_modules/doctr/models/recognition/sar/tensorflow.html b/v0.1.1/_modules/doctr/models/recognition/sar/tensorflow.html index 010bc2bc54..4a591e6451 100644 --- a/v0.1.1/_modules/doctr/models/recognition/sar/tensorflow.html +++ b/v0.1.1/_modules/doctr/models/recognition/sar/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.sar.tensorflow - docTR documentation @@ -757,7 +757,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    - + diff --git a/v0.1.1/_modules/doctr/models/recognition/vitstr/tensorflow.html b/v0.1.1/_modules/doctr/models/recognition/vitstr/tensorflow.html index 6e101893bf..c594d40a56 100644 --- a/v0.1.1/_modules/doctr/models/recognition/vitstr/tensorflow.html +++ b/v0.1.1/_modules/doctr/models/recognition/vitstr/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.vitstr.tensorflow - docTR documentation @@ -621,7 +621,7 @@

    Source code for doctr.models.recognition.vitstr.tensorflow

    - + diff --git a/v0.1.1/_modules/doctr/models/recognition/zoo.html b/v0.1.1/_modules/doctr/models/recognition/zoo.html index 2c47f88de4..f664304019 100644 --- a/v0.1.1/_modules/doctr/models/recognition/zoo.html +++ b/v0.1.1/_modules/doctr/models/recognition/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.zoo - docTR documentation @@ -415,7 +415,7 @@

    Source code for doctr.models.recognition.zoo

       
    - + diff --git a/v0.1.1/_modules/doctr/models/zoo.html b/v0.1.1/_modules/doctr/models/zoo.html index 5b22f2c79f..d459671648 100644 --- a/v0.1.1/_modules/doctr/models/zoo.html +++ b/v0.1.1/_modules/doctr/models/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.zoo - docTR documentation @@ -576,7 +576,7 @@

    Source code for doctr.models.zoo

         
       
    - + diff --git a/v0.1.1/_modules/doctr/transforms/modules/base.html b/v0.1.1/_modules/doctr/transforms/modules/base.html index 96ebd680b7..4596df3848 100644 --- a/v0.1.1/_modules/doctr/transforms/modules/base.html +++ b/v0.1.1/_modules/doctr/transforms/modules/base.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.base - docTR documentation @@ -643,7 +643,7 @@

    Source code for doctr.transforms.modules.base

    - + diff --git a/v0.1.1/_modules/doctr/transforms/modules/tensorflow.html b/v0.1.1/_modules/doctr/transforms/modules/tensorflow.html index 0e18bcc922..acbbe96225 100644 --- a/v0.1.1/_modules/doctr/transforms/modules/tensorflow.html +++ b/v0.1.1/_modules/doctr/transforms/modules/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.tensorflow - docTR documentation @@ -956,7 +956,7 @@

    Source code for doctr.transforms.modules.tensorflow

    - + diff --git a/v0.1.1/_modules/doctr/utils/metrics.html b/v0.1.1/_modules/doctr/utils/metrics.html index d35d7e9672..8a37d5949a 100644 --- a/v0.1.1/_modules/doctr/utils/metrics.html +++ b/v0.1.1/_modules/doctr/utils/metrics.html @@ -13,7 +13,7 @@ - + doctr.utils.metrics - docTR documentation @@ -936,7 +936,7 @@

    Source code for doctr.utils.metrics

         
       
    - + diff --git a/v0.1.1/_modules/doctr/utils/visualization.html b/v0.1.1/_modules/doctr/utils/visualization.html index e608d492a4..c818be6d7b 100644 --- a/v0.1.1/_modules/doctr/utils/visualization.html +++ b/v0.1.1/_modules/doctr/utils/visualization.html @@ -13,7 +13,7 @@ - + doctr.utils.visualization - docTR documentation @@ -720,7 +720,7 @@

    Source code for doctr.utils.visualization

         
       
    - + diff --git a/v0.1.1/_modules/index.html b/v0.1.1/_modules/index.html index 758ef41bd0..5793c44f20 100644 --- a/v0.1.1/_modules/index.html +++ b/v0.1.1/_modules/index.html @@ -13,7 +13,7 @@ - + Overview: module code - docTR documentation @@ -378,7 +378,7 @@

    All modules for which code is available

    - + diff --git a/v0.1.1/_sources/getting_started/installing.rst.txt b/v0.1.1/_sources/getting_started/installing.rst.txt index e764e734a7..39e79aa3dd 100644 --- a/v0.1.1/_sources/getting_started/installing.rst.txt +++ b/v0.1.1/_sources/getting_started/installing.rst.txt @@ -3,7 +3,7 @@ Installation ************ -This library requires `Python `_ 3.9 or higher. +This library requires `Python `_ 3.10 or higher. Prerequisites diff --git a/v0.1.1/_static/basic.css b/v0.1.1/_static/basic.css index f316efcb47..7ebbd6d07b 100644 --- a/v0.1.1/_static/basic.css +++ b/v0.1.1/_static/basic.css @@ -1,12 +1,5 @@ /* - * basic.css - * ~~~~~~~~~ - * * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ /* -- main layout ----------------------------------------------------------- */ @@ -115,15 +108,11 @@ img { /* -- search page ----------------------------------------------------------- */ ul.search { - margin: 10px 0 0 20px; - padding: 0; + margin-top: 10px; } ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; + padding: 5px 0; } ul.search li a { diff --git a/v0.1.1/_static/doctools.js b/v0.1.1/_static/doctools.js index 4d67807d17..0398ebb9f0 100644 --- a/v0.1.1/_static/doctools.js +++ b/v0.1.1/_static/doctools.js @@ -1,12 +1,5 @@ /* - * doctools.js - * ~~~~~~~~~~~ - * * Base JavaScript utilities for all Sphinx HTML documentation. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; diff --git a/v0.1.1/_static/language_data.js b/v0.1.1/_static/language_data.js index 367b8ed81b..c7fe6c6faf 100644 --- a/v0.1.1/_static/language_data.js +++ b/v0.1.1/_static/language_data.js @@ -1,13 +1,6 @@ /* - * language_data.js - * ~~~~~~~~~~~~~~~~ - * * This script contains the language-specific data used by searchtools.js, * namely the list of stopwords, stemmer, scorer and splitter. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; diff --git a/v0.1.1/_static/searchtools.js b/v0.1.1/_static/searchtools.js index b08d58c9b9..2c774d17af 100644 --- a/v0.1.1/_static/searchtools.js +++ b/v0.1.1/_static/searchtools.js @@ -1,12 +1,5 @@ /* - * searchtools.js - * ~~~~~~~~~~~~~~~~ - * * Sphinx JavaScript utilities for the full-text search. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; @@ -20,7 +13,7 @@ if (typeof Scorer === "undefined") { // and returns the new score. /* score: result => { - const [docname, title, anchor, descr, score, filename] = result + const [docname, title, anchor, descr, score, filename, kind] = result return score }, */ @@ -47,6 +40,14 @@ if (typeof Scorer === "undefined") { }; } +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + const _removeChildren = (element) => { while (element && element.lastChild) element.removeChild(element.lastChild); }; @@ -64,9 +65,13 @@ const _displayItem = (item, searchTerms, highlightTerms) => { const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; const contentRoot = document.documentElement.dataset.content_root; - const [docName, title, anchor, descr, score, _filename] = item; + const [docName, title, anchor, descr, score, _filename, kind] = item; let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); let requestUrl; let linkUrl; if (docBuilder === "dirhtml") { @@ -115,8 +120,10 @@ const _finishSearch = (resultCount) => { "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." ); else - Search.status.innerText = _( - "Search finished, found ${resultCount} page(s) matching the search query." + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, ).replace('${resultCount}', resultCount); }; const _displayNextItem = ( @@ -138,7 +145,7 @@ const _displayNextItem = ( else _finishSearch(resultCount); }; // Helper function used by query() to order search results. -// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. // Order the results by score (in opposite order of appearance, since the // `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. const _orderResultsByScoreThenName = (a, b) => { @@ -248,6 +255,7 @@ const Search = { searchSummary.classList.add("search-summary"); searchSummary.innerText = ""; const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); searchList.classList.add("search"); const out = document.getElementById("search-results"); @@ -318,7 +326,7 @@ const Search = { const indexEntries = Search._index.indexentries; // Collect multiple result groups to be sorted separately and then ordered. - // Each is an array of [docname, title, anchor, descr, score, filename]. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. const normalResults = []; const nonMainIndexResults = []; @@ -337,6 +345,7 @@ const Search = { null, score + boost, filenames[file], + SearchResultKind.title, ]); } } @@ -354,6 +363,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.index, ]; if (isMain) { normalResults.push(result); @@ -475,6 +485,7 @@ const Search = { descr, score, filenames[match[0]], + SearchResultKind.object, ]); }; Object.keys(objects).forEach((prefix) => @@ -585,6 +596,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.text, ]); } return results; diff --git a/v0.1.1/changelog.html b/v0.1.1/changelog.html index ac81a6f231..fc45a50384 100644 --- a/v0.1.1/changelog.html +++ b/v0.1.1/changelog.html @@ -14,7 +14,7 @@ - + Changelog - docTR documentation @@ -446,7 +446,7 @@

    v0.1.0 (2021-03-05) - + diff --git a/v0.1.1/community/resources.html b/v0.1.1/community/resources.html index 2564037893..9a1988258c 100644 --- a/v0.1.1/community/resources.html +++ b/v0.1.1/community/resources.html @@ -14,7 +14,7 @@ - + Community resources - docTR documentation @@ -389,7 +389,7 @@

    Community resources - + diff --git a/v0.1.1/contributing/code_of_conduct.html b/v0.1.1/contributing/code_of_conduct.html index 5ea4a1f99d..03422dbb4d 100644 --- a/v0.1.1/contributing/code_of_conduct.html +++ b/v0.1.1/contributing/code_of_conduct.html @@ -14,7 +14,7 @@ - + Contributor Covenant Code of Conduct - docTR documentation @@ -504,7 +504,7 @@

    Attribution - + diff --git a/v0.1.1/contributing/contributing.html b/v0.1.1/contributing/contributing.html index e5a85682c6..05e2b3641b 100644 --- a/v0.1.1/contributing/contributing.html +++ b/v0.1.1/contributing/contributing.html @@ -14,7 +14,7 @@ - + Contributing to docTR - docTR documentation @@ -481,7 +481,7 @@

    Let’s connect - + diff --git a/v0.1.1/genindex.html b/v0.1.1/genindex.html index cbb43f08d8..21520455b4 100644 --- a/v0.1.1/genindex.html +++ b/v0.1.1/genindex.html @@ -13,7 +13,7 @@ - Index - docTR documentation + Index - docTR documentation @@ -756,7 +756,7 @@

    W

    - + diff --git a/v0.1.1/getting_started/installing.html b/v0.1.1/getting_started/installing.html index a488e9a030..af3b58193e 100644 --- a/v0.1.1/getting_started/installing.html +++ b/v0.1.1/getting_started/installing.html @@ -14,7 +14,7 @@ - + Installation - docTR documentation @@ -305,7 +305,7 @@

    Installation

    -

    This library requires Python 3.9 or higher.

    +

    This library requires Python 3.10 or higher.

    Prerequisites

    Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so:

    @@ -435,7 +435,7 @@

    Via Git - + diff --git a/v0.1.1/index.html b/v0.1.1/index.html index 76509686f5..3a06afc6d9 100644 --- a/v0.1.1/index.html +++ b/v0.1.1/index.html @@ -14,7 +14,7 @@ - + docTR documentation @@ -445,7 +445,7 @@

    Supported datasets - + diff --git a/v0.1.1/modules/contrib.html b/v0.1.1/modules/contrib.html index e99f6b3f74..7fb86b8b38 100644 --- a/v0.1.1/modules/contrib.html +++ b/v0.1.1/modules/contrib.html @@ -14,7 +14,7 @@ - + doctr.contrib - docTR documentation @@ -380,7 +380,7 @@

    Supported contribution modules - + diff --git a/v0.1.1/modules/datasets.html b/v0.1.1/modules/datasets.html index 456e10b172..380a986793 100644 --- a/v0.1.1/modules/datasets.html +++ b/v0.1.1/modules/datasets.html @@ -14,7 +14,7 @@ - + doctr.datasets - docTR documentation @@ -1081,7 +1081,7 @@

    Returns: - + diff --git a/v0.1.1/modules/io.html b/v0.1.1/modules/io.html index 01eadaa4b8..24c41954be 100644 --- a/v0.1.1/modules/io.html +++ b/v0.1.1/modules/io.html @@ -14,7 +14,7 @@ - + doctr.io - docTR documentation @@ -760,7 +760,7 @@

    Returns: - + diff --git a/v0.1.1/modules/models.html b/v0.1.1/modules/models.html index c465cc0586..91b8810a6a 100644 --- a/v0.1.1/modules/models.html +++ b/v0.1.1/modules/models.html @@ -14,7 +14,7 @@ - + doctr.models - docTR documentation @@ -1612,7 +1612,7 @@

    Args: - + diff --git a/v0.1.1/modules/transforms.html b/v0.1.1/modules/transforms.html index 30f7a2631a..c5ead3f3ce 100644 --- a/v0.1.1/modules/transforms.html +++ b/v0.1.1/modules/transforms.html @@ -14,7 +14,7 @@ - + doctr.transforms - docTR documentation @@ -835,7 +835,7 @@

    Args:< - + diff --git a/v0.1.1/modules/utils.html b/v0.1.1/modules/utils.html index 888a32c321..b7f6fc570b 100644 --- a/v0.1.1/modules/utils.html +++ b/v0.1.1/modules/utils.html @@ -14,7 +14,7 @@ - + doctr.utils - docTR documentation @@ -715,7 +715,7 @@

    Args: - + diff --git a/v0.1.1/notebooks.html b/v0.1.1/notebooks.html index f97771aebb..d36539f59e 100644 --- a/v0.1.1/notebooks.html +++ b/v0.1.1/notebooks.html @@ -14,7 +14,7 @@ - + docTR Notebooks - docTR documentation @@ -391,7 +391,7 @@

    docTR Notebooks - + diff --git a/v0.1.1/search.html b/v0.1.1/search.html index 82b8bd6950..d050f5eac7 100644 --- a/v0.1.1/search.html +++ b/v0.1.1/search.html @@ -14,7 +14,7 @@ - + Search - docTR documentation @@ -340,7 +340,7 @@ - + diff --git a/v0.1.1/searchindex.js b/v0.1.1/searchindex.js index bfa546d0e9..6f154115ab 100644 --- a/v0.1.1/searchindex.js +++ b/v0.1.1/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles": {"1. Correction": [[2, "correction"]], "2. Warning": [[2, "warning"]], "3. Temporary Ban": [[2, "temporary-ban"]], "4. Permanent Ban": [[2, "permanent-ban"]], "AWS Lambda": [[14, null]], "Advanced options": [[19, "advanced-options"]], "Args:": [[7, "args"], [7, "id4"], [7, "id7"], [7, "id10"], [7, "id13"], [7, "id16"], [7, "id19"], [7, "id22"], [7, "id25"], [7, "id29"], [7, "id32"], [7, "id37"], [7, "id40"], [7, "id46"], [7, "id49"], [7, "id50"], [7, "id51"], [7, "id54"], [7, "id57"], [7, "id60"], [7, "id61"], [8, "args"], [8, "id2"], [8, "id3"], [8, "id4"], [8, "id5"], [8, "id6"], [8, "id7"], [8, "id10"], [8, "id12"], [8, "id14"], [8, "id16"], [8, "id20"], [8, "id24"], [8, "id28"], [9, "args"], [9, "id3"], [9, "id8"], [9, "id13"], [9, "id17"], [9, "id21"], [9, "id26"], [9, "id31"], [9, "id36"], [9, "id41"], [9, "id46"], [9, "id50"], [9, "id54"], [9, "id59"], [9, "id63"], [9, "id68"], [9, "id73"], [9, "id77"], [9, "id81"], [9, "id85"], [9, "id90"], [9, "id95"], [9, "id99"], [9, "id104"], [9, "id109"], [9, "id114"], [9, "id119"], [9, "id123"], [9, "id127"], [9, "id132"], [9, "id137"], [9, "id142"], [9, "id146"], [9, "id150"], [9, "id155"], [9, "id159"], [9, "id163"], [9, "id167"], [9, "id169"], [9, "id171"], [9, "id173"], [10, "args"], [10, "id1"], [10, "id2"], [10, "id3"], [10, "id4"], [10, "id5"], [10, "id6"], [10, "id7"], [10, "id8"], [10, "id9"], [10, "id10"], [10, "id11"], [10, "id12"], [10, "id13"], [10, "id14"], [10, "id15"], [10, "id16"], [10, "id17"], [10, "id18"], [10, "id19"], [11, "args"], [11, "id3"], [11, "id4"], [11, "id5"], [11, "id6"], [11, "id7"], [11, "id8"], [11, "id9"]], "Artefact": [[8, "artefact"]], "ArtefactDetection": [[16, "artefactdetection"]], "Attribution": [[2, "attribution"]], "Available Datasets": [[17, "available-datasets"]], "Available architectures": [[19, "available-architectures"], [19, "id1"], [19, "id2"]], "Available contribution modules": [[16, "available-contribution-modules"]], "Block": [[8, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[17, null]], "Choosing the right model": [[19, null]], "Classification": [[15, "classification"]], "Code quality": [[3, "code-quality"]], "Code style verification": [[3, "code-style-verification"]], "Codebase structure": [[3, "codebase-structure"]], "Commits": [[3, "commits"]], "Community resources": [[1, null]], "Composing transformations": [[10, "composing-transformations"]], "Continuous Integration": [[3, "continuous-integration"]], "Contributing to docTR": [[3, null]], "Contributor Covenant Code of Conduct": [[2, null]], "Custom dataset loader": [[7, "custom-dataset-loader"]], "Custom orientation classification models": [[13, "custom-orientation-classification-models"]], "Data Loading": [[17, "data-loading"]], "Dataloader": [[7, "dataloader"]], "Detection": [[15, "detection"], [17, "detection"]], "Detection predictors": [[19, "detection-predictors"]], "Developer mode installation": [[3, "developer-mode-installation"]], "Developing docTR": [[3, "developing-doctr"]], "Document": [[8, "document"]], "Document structure": [[8, "document-structure"]], "End-to-End OCR": [[19, "end-to-end-ocr"]], "Enforcement": [[2, "enforcement"]], "Enforcement Guidelines": [[2, "enforcement-guidelines"]], "Enforcement Responsibilities": [[2, "enforcement-responsibilities"]], "Export to ONNX": [[18, "export-to-onnx"]], "Feature requests & bug report": [[3, "feature-requests-bug-report"]], "Feedback": [[3, "feedback"]], "File reading": [[8, "file-reading"]], "Half-precision": [[18, "half-precision"]], "Installation": [[4, null]], "Integrate contributions into your pipeline": [[16, null]], "Let\u2019s connect": [[3, "let-s-connect"]], "Line": [[8, "line"]], "Loading from Huggingface Hub": [[15, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[13, "loading-your-custom-trained-model"]], "Loading your custom trained orientation classification model": [[13, "loading-your-custom-trained-orientation-classification-model"]], "Main Features": [[5, "main-features"]], "Model optimization": [[18, "model-optimization"]], "Model zoo": [[5, "model-zoo"]], "Modifying the documentation": [[3, "modifying-the-documentation"]], "Naming conventions": [[15, "naming-conventions"]], "OCR": [[17, "ocr"]], "Object Detection": [[17, "object-detection"]], "Our Pledge": [[2, "our-pledge"]], "Our Standards": [[2, "our-standards"]], "Page": [[8, "page"]], "Preparing your model for inference": [[18, null]], "Prerequisites": [[4, "prerequisites"]], "Pretrained community models": [[15, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[15, "pushing-to-the-huggingface-hub"]], "Questions": [[3, "questions"]], "Recognition": [[15, "recognition"], [17, "recognition"]], "Recognition predictors": [[19, "recognition-predictors"]], "Returns:": [[7, "returns"], [8, "returns"], [8, "id11"], [8, "id13"], [8, "id15"], [8, "id19"], [8, "id23"], [8, "id27"], [8, "id31"], [9, "returns"], [9, "id6"], [9, "id11"], [9, "id16"], [9, "id20"], [9, "id24"], [9, "id29"], [9, "id34"], [9, "id39"], [9, "id44"], [9, "id49"], [9, "id53"], [9, "id57"], [9, "id62"], [9, "id66"], [9, "id71"], [9, "id76"], [9, "id80"], [9, "id84"], [9, "id88"], [9, "id93"], [9, "id98"], [9, "id102"], [9, "id107"], [9, "id112"], [9, "id117"], [9, "id122"], [9, "id126"], [9, "id130"], [9, "id135"], [9, "id140"], [9, "id145"], [9, "id149"], [9, "id153"], [9, "id158"], [9, "id162"], [9, "id166"], [9, "id168"], [9, "id170"], [9, "id172"], [11, "returns"]], "Scope": [[2, "scope"]], "Share your model with the community": [[15, null]], "Supported Vocabs": [[7, "supported-vocabs"]], "Supported contribution modules": [[6, "supported-contribution-modules"]], "Supported datasets": [[5, "supported-datasets"]], "Supported transformations": [[10, "supported-transformations"]], "Synthetic dataset generator": [[7, "synthetic-dataset-generator"], [17, "synthetic-dataset-generator"]], "Task evaluation": [[11, "task-evaluation"]], "Text Detection": [[19, "text-detection"]], "Text Recognition": [[19, "text-recognition"]], "Text detection models": [[5, "text-detection-models"]], "Text recognition models": [[5, "text-recognition-models"]], "Train your own model": [[13, null]], "Two-stage approaches": [[19, "two-stage-approaches"]], "Unit tests": [[3, "unit-tests"]], "Use your own datasets": [[17, "use-your-own-datasets"]], "Using your ONNX exported model": [[18, "using-your-onnx-exported-model"]], "Via Conda (Only for Linux)": [[4, "via-conda-only-for-linux"]], "Via Git": [[4, "via-git"]], "Via Python Package": [[4, "via-python-package"]], "Visualization": [[11, "visualization"]], "What should I do with the output?": [[19, "what-should-i-do-with-the-output"]], "Word": [[8, "word"]], "docTR Notebooks": [[12, null]], "docTR Vocabs": [[7, "id62"]], "docTR: Document Text Recognition": [[5, null]], "doctr.contrib": [[6, null]], "doctr.datasets": [[7, null], [7, "datasets"]], "doctr.io": [[8, null]], "doctr.models": [[9, null]], "doctr.models.classification": [[9, "doctr-models-classification"]], "doctr.models.detection": [[9, "doctr-models-detection"]], "doctr.models.factory": [[9, "doctr-models-factory"]], "doctr.models.recognition": [[9, "doctr-models-recognition"]], "doctr.models.zoo": [[9, "doctr-models-zoo"]], "doctr.transforms": [[10, null]], "doctr.utils": [[11, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.10.0 (2024-10-21)": [[0, "v0-10-0-2024-10-21"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]], "v0.7.0 (2023-09-09)": [[0, "v0-7-0-2023-09-09"]], "v0.8.0 (2024-02-28)": [[0, "v0-8-0-2024-02-28"]], "v0.8.1 (2024-03-04)": [[0, "v0-8-1-2024-03-04"]], "v0.9.0 (2024-08-08)": [[0, "v0-9-0-2024-08-08"]]}, "docnames": ["changelog", "community/resources", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/contrib", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_contrib_modules", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "community/resources.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/contrib.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_contrib_modules.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[8, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[8, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[10, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[7, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[10, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[10, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[7, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[7, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[9, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[7, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[7, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[8, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[8, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[7, "doctr.datasets.encode_sequences", false]], "fast_base() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_base", false]], "fast_small() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_small", false]], "fast_tiny() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_tiny", false]], "from_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[7, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[10, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[10, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[7, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[7, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[7, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[7, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[7, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[9, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[10, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[8, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet34() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[7, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_crop_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_crop_orientation", false]], "mobilenet_v3_small_page_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_page_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[10, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[9, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[7, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[10, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[8, "doctr.io.Page", false]], "page_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.page_orientation_predictor", false]], "parseq() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[10, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[10, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[10, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[10, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[10, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[10, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[10, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[10, "doctr.transforms.RandomJpegQuality", false]], "randomresize (class in doctr.transforms)": [[10, "doctr.transforms.RandomResize", false]], "randomrotate (class in doctr.transforms)": [[10, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[10, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[10, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[8, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[8, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[8, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[7, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[10, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[8, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[8, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[7, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[7, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[7, "doctr.datasets.SVT", false]], "synthtext (class in doctr.datasets)": [[7, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.TextMatch", false]], "textnet_base() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_base", false]], "textnet_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_small", false]], "textnet_tiny() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_tiny", false]], "togray (class in doctr.transforms)": [[10, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[11, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_small", false]], "wildreceipt (class in doctr.datasets)": [[7, "doctr.datasets.WILDRECEIPT", false]], "word (class in doctr.io)": [[8, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[7, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[7, 0, 1, "", "CORD"], [7, 0, 1, "", "CharacterGenerator"], [7, 0, 1, "", "DetectionDataset"], [7, 0, 1, "", "DocArtefacts"], [7, 0, 1, "", "FUNSD"], [7, 0, 1, "", "IC03"], [7, 0, 1, "", "IC13"], [7, 0, 1, "", "IIIT5K"], [7, 0, 1, "", "IIITHWS"], [7, 0, 1, "", "IMGUR5K"], [7, 0, 1, "", "MJSynth"], [7, 0, 1, "", "OCRDataset"], [7, 0, 1, "", "RecognitionDataset"], [7, 0, 1, "", "SROIE"], [7, 0, 1, "", "SVHN"], [7, 0, 1, "", "SVT"], [7, 0, 1, "", "SynthText"], [7, 0, 1, "", "WILDRECEIPT"], [7, 0, 1, "", "WordGenerator"], [7, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[7, 0, 1, "", "DataLoader"]], "doctr.io": [[8, 0, 1, "", "Artefact"], [8, 0, 1, "", "Block"], [8, 0, 1, "", "Document"], [8, 0, 1, "", "DocumentFile"], [8, 0, 1, "", "Line"], [8, 0, 1, "", "Page"], [8, 0, 1, "", "Word"], [8, 1, 1, "", "decode_img_as_tensor"], [8, 1, 1, "", "read_html"], [8, 1, 1, "", "read_img_as_numpy"], [8, 1, 1, "", "read_img_as_tensor"], [8, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[8, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[8, 2, 1, "", "from_images"], [8, 2, 1, "", "from_pdf"], [8, 2, 1, "", "from_url"]], "doctr.io.Page": [[8, 2, 1, "", "show"]], "doctr.models": [[9, 1, 1, "", "kie_predictor"], [9, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[9, 1, 1, "", "crop_orientation_predictor"], [9, 1, 1, "", "magc_resnet31"], [9, 1, 1, "", "mobilenet_v3_large"], [9, 1, 1, "", "mobilenet_v3_large_r"], [9, 1, 1, "", "mobilenet_v3_small"], [9, 1, 1, "", "mobilenet_v3_small_crop_orientation"], [9, 1, 1, "", "mobilenet_v3_small_page_orientation"], [9, 1, 1, "", "mobilenet_v3_small_r"], [9, 1, 1, "", "page_orientation_predictor"], [9, 1, 1, "", "resnet18"], [9, 1, 1, "", "resnet31"], [9, 1, 1, "", "resnet34"], [9, 1, 1, "", "resnet50"], [9, 1, 1, "", "textnet_base"], [9, 1, 1, "", "textnet_small"], [9, 1, 1, "", "textnet_tiny"], [9, 1, 1, "", "vgg16_bn_r"], [9, 1, 1, "", "vit_b"], [9, 1, 1, "", "vit_s"]], "doctr.models.detection": [[9, 1, 1, "", "db_mobilenet_v3_large"], [9, 1, 1, "", "db_resnet50"], [9, 1, 1, "", "detection_predictor"], [9, 1, 1, "", "fast_base"], [9, 1, 1, "", "fast_small"], [9, 1, 1, "", "fast_tiny"], [9, 1, 1, "", "linknet_resnet18"], [9, 1, 1, "", "linknet_resnet34"], [9, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[9, 1, 1, "", "from_hub"], [9, 1, 1, "", "login_to_hub"], [9, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[9, 1, 1, "", "crnn_mobilenet_v3_large"], [9, 1, 1, "", "crnn_mobilenet_v3_small"], [9, 1, 1, "", "crnn_vgg16_bn"], [9, 1, 1, "", "master"], [9, 1, 1, "", "parseq"], [9, 1, 1, "", "recognition_predictor"], [9, 1, 1, "", "sar_resnet31"], [9, 1, 1, "", "vitstr_base"], [9, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[10, 0, 1, "", "ChannelShuffle"], [10, 0, 1, "", "ColorInversion"], [10, 0, 1, "", "Compose"], [10, 0, 1, "", "GaussianBlur"], [10, 0, 1, "", "GaussianNoise"], [10, 0, 1, "", "LambdaTransformation"], [10, 0, 1, "", "Normalize"], [10, 0, 1, "", "OneOf"], [10, 0, 1, "", "RandomApply"], [10, 0, 1, "", "RandomBrightness"], [10, 0, 1, "", "RandomContrast"], [10, 0, 1, "", "RandomCrop"], [10, 0, 1, "", "RandomGamma"], [10, 0, 1, "", "RandomHorizontalFlip"], [10, 0, 1, "", "RandomHue"], [10, 0, 1, "", "RandomJpegQuality"], [10, 0, 1, "", "RandomResize"], [10, 0, 1, "", "RandomRotate"], [10, 0, 1, "", "RandomSaturation"], [10, 0, 1, "", "RandomShadow"], [10, 0, 1, "", "Resize"], [10, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[11, 0, 1, "", "DetectionMetric"], [11, 0, 1, "", "LocalizationConfusion"], [11, 0, 1, "", "OCRMetric"], [11, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.visualization": [[11, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [2, 8, 9, 11, 15, 18], "0": [2, 4, 7, 10, 11, 13, 16, 17, 19], "00": 19, "01": 19, "0123456789": 7, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "02562": 9, "03": 19, "035": 19, "0361328125": 19, "04": 19, "05": 19, "06": 19, "06640625": 19, "07": 19, "08": [10, 19], "09": 19, "0966796875": 19, "1": [7, 8, 9, 10, 11, 13, 17, 19], "10": [7, 11, 19], "100": [7, 10, 11, 17, 19], "1000": 19, "101": 7, "1024": [9, 13, 19], "104": 7, "106": 7, "108": 7, "1095": 17, "11": 19, "110": 11, "1107": 17, "114": 7, "115": 7, "1156": 17, "116": 7, "118": 7, "11800h": 19, "11th": 19, "12": 19, "120": 7, "123": 7, "126": 7, "1268": 17, "128": [9, 13, 18, 19], "13": 19, "130": 7, "13068": 17, "131": 7, "1337891": 17, "1357421875": 19, "1396484375": 19, "14": 19, "1420": 19, "14470v1": 7, "149": 17, "15": 19, "150": [11, 19], "1552": 19, "16": [9, 18, 19], "1630859375": 19, "1684": 19, "16x16": 9, "17": 19, "1778": 19, "1782": 19, "18": [9, 19], "185546875": 19, "1900": 19, "1910": 9, "19342": 17, "19370": 17, "195": 7, "19598": 17, "199": 19, "1999": 19, "2": [4, 5, 7, 8, 9, 10, 16, 19], "20": 19, "200": 11, "2000": 17, "2003": [5, 7], "2012": 7, "2013": [5, 7], "2015": 7, "2019": 5, "2023": 1, "207901": 17, "21": 19, "2103": 7, "2186": 17, "21888": 17, "22": 19, "224": [9, 10], "225": 10, "22672": 17, "229": [10, 17], "23": 19, "233": 17, "236": 7, "24": 19, "246": 17, "249": 17, "25": 19, "2504": 19, "255": [8, 9, 10, 11, 19], "256": 9, "257": 17, "26": 19, "26032": 17, "264": 13, "27": 19, "2700": 17, "2710": 19, "2749": 13, "28": 19, "287": 13, "29": 19, "296": 13, "299": 13, "2d": 19, "3": [4, 5, 8, 9, 10, 11, 18, 19], "30": 19, "300": 17, "3000": 17, "301": 13, "30595": 19, "30ghz": 19, "31": 9, "32": [7, 9, 10, 13, 17, 18, 19], "3232421875": 19, "33": [10, 19], "33402": 17, "33608": 17, "34": [9, 19], "340": 19, "3456": 19, "3515625": 19, "36": 19, "360": 17, "37": [7, 19], "38": 19, "39": 19, "4": [9, 10, 11, 19], "40": 19, "406": 10, "41": 19, "42": 19, "43": 19, "44": 19, "45": 19, "456": 10, "46": 19, "47": 19, "472": 17, "48": [7, 19], "485": 10, "49": 19, "49377": 17, "5": [7, 10, 11, 16, 19], "50": [9, 17, 19], "51": 19, "51171875": 19, "512": 9, "52": [7, 19], "529": 19, "53": 19, "54": 19, "540": 19, "5478515625": 19, "55": 19, "56": 19, "57": 19, "58": [7, 19], "580": 19, "5810546875": 19, "583": 19, "59": 19, "597": 19, "5k": [5, 7], "5m": 19, "6": [10, 19], "60": 10, "600": [9, 11, 19], "61": 19, "62": 19, "626": 17, "63": 19, "64": [9, 10, 19], "641": 19, "647": 17, "65": 19, "66": 19, "67": 19, "68": 19, "69": 19, "693": 13, "694": 13, "695": 13, "6m": 19, "7": 19, "70": [7, 11, 19], "707470": 17, "71": [7, 19], "7100000": 17, "7141797": 17, "7149": 17, "72": 19, "72dpi": 8, "73": 19, "73257": 17, "74": 19, "75": [10, 19], "7581382": 17, "76": 19, "77": 19, "772": 13, "772875": 17, "78": 19, "785": 13, "79": 19, "793533": 17, "796": 17, "798": 13, "7m": 19, "8": [9, 10, 19], "80": 19, "800": [9, 11, 17, 19], "81": 19, "82": 19, "83": 19, "84": 19, "849": 17, "85": 19, "8564453125": 19, "857": 19, "85875": 17, "86": 19, "8603515625": 19, "87": 19, "8707": 17, "88": 19, "89": 19, "9": [4, 10, 19], "90": 19, "90k": 7, "90kdict32px": 7, "91": 19, "914085328578949": 19, "92": 19, "93": 19, "94": [7, 19], "95": [11, 19], "9578408598899841": 19, "96": 19, "97": 19, "98": 19, "99": 19, "9949972033500671": 19, "A": [2, 3, 5, 7, 8, 9, 12, 18], "As": 3, "Be": 19, "Being": 2, "By": 14, "For": [2, 3, 4, 13, 19], "If": [3, 8, 9, 13, 19], "In": [3, 7, 17], "It": [10, 15, 16, 18], "Its": [5, 9], "No": [2, 19], "Of": 7, "Or": [16, 18], "The": [2, 3, 7, 8, 11, 14, 16, 17, 18, 19], "Then": 9, "To": [3, 4, 14, 15, 16, 18, 19], "_": [2, 7, 9], "__call__": 19, "_build": 3, "_i": 11, "ab": 7, "abc": 18, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "abdef": [7, 17], "abl": [17, 19], "about": [2, 17, 19], "abov": 19, "abstract": 1, "abstractdataset": 7, "abus": 2, "accept": 2, "access": [5, 8, 17, 19], "account": [2, 15], "accur": 19, "accuraci": 11, "achiev": 18, "act": 2, "action": 2, "activ": 5, "ad": [3, 9, 10], "adapt": 2, "add": [10, 11, 15, 19], "add_hook": 19, "add_label": 11, "addit": [3, 4, 8, 16, 19], "addition": [3, 19], "address": [2, 8], "adjust": 10, "advanc": 2, "advantag": 18, "advis": 3, "aesthet": [5, 7], "affect": 2, "after": [15, 19], "ag": 2, "again": 9, "aggreg": [11, 17], "aggress": 2, "align": [2, 8, 10], "all": [2, 3, 6, 7, 8, 10, 11, 16, 17, 19], "allow": [2, 18], "along": 19, "alreadi": [3, 18], "also": [2, 9, 15, 16, 17, 19], "alwai": 17, "an": [2, 3, 5, 7, 8, 9, 11, 16, 18, 19], "analysi": [8, 16], "ancient_greek": 7, "andrej": 1, "angl": [8, 10], "ani": [2, 7, 8, 9, 10, 11, 18, 19], "annot": 7, "anot": 17, "anoth": [9, 13, 17], "answer": 2, "anyascii": 11, "anyon": 5, "anyth": 16, "api": [3, 5], "apolog": 2, "apologi": 2, "app": 3, "appear": 2, "appli": [2, 7, 10], "applic": [5, 9], "appoint": 2, "appreci": 15, "appropri": [2, 3, 19], "ar": [2, 3, 4, 6, 7, 8, 10, 11, 12, 16, 17, 19], "arab": 7, "arabic_diacrit": 7, "arabic_lett": 7, "arabic_punctu": 7, "arbitrarili": [5, 9], "arch": [9, 15], "architectur": [5, 9, 15, 16], "area": 19, "argument": [7, 8, 9, 11, 13, 19], "around": 2, "arrai": [8, 10, 11], "art": [5, 16], "artefact": [11, 16, 19], "artefact_typ": 8, "articl": 1, "artifici": [5, 7], "arxiv": [7, 9], "asarrai": 11, "ascii_lett": 7, "aspect": [5, 9, 10, 19], "assess": 11, "assign": 11, "associ": 8, "assum": 9, "assume_straight_pag": [9, 13, 19], "astyp": [9, 11, 19], "attack": 2, "attend": [5, 9], "attent": [2, 9], "autom": 5, "automat": 19, "autoregress": [5, 9], "avail": [2, 5, 6, 10], "averag": [10, 19], "avoid": [2, 4], "aw": [5, 19], "awar": 19, "azur": 19, "b": [9, 11, 19], "b_j": 11, "back": 3, "backbon": 9, "backend": 19, "background": 17, "bangla": 7, "bar": 16, "bar_cod": 17, "baranovskij": 1, "base": [5, 9, 16], "baselin": [5, 9, 19], "batch": [7, 9, 10, 16, 17, 19], "batch_siz": [7, 9, 13, 16, 17, 18], "bblanchon": 4, "bbox": 19, "becaus": 14, "been": [3, 11, 17, 19], "befor": [7, 9, 10, 19], "begin": 11, "behavior": [2, 19], "being": [11, 19], "belong": 19, "benchmark": 19, "best": [1, 2], "better": [12, 19], "between": [10, 11, 19], "bgr": 8, "bilinear": 10, "bin_thresh": 19, "binar": [5, 9, 19], "binari": [8, 18, 19], "bit": 18, "block": [11, 19], "block_1_1": 19, "blur": 10, "bmvc": 7, "bn": 15, "bodi": [2, 19], "bool": [7, 8, 9, 10, 11], "boolean": [9, 19], "both": [5, 7, 10, 17, 19], "bottom": [9, 19], "bound": [7, 8, 9, 10, 11, 16, 17, 19], "box": [7, 8, 9, 10, 11, 16, 17, 19], "box_thresh": 19, "bright": 10, "browser": [3, 5], "build": [3, 4, 18], "built": 3, "byte": [8, 19], "c": [4, 8, 11], "c_j": 11, "cach": [3, 7, 14], "cache_sampl": 7, "call": 18, "callabl": [7, 10], "can": [3, 4, 13, 14, 15, 16, 17, 19], "capabl": [3, 12, 19], "case": [7, 11], "cf": 19, "cfg": 19, "challeng": 7, "challenge2_test_task12_imag": 7, "challenge2_test_task1_gt": 7, "challenge2_training_task12_imag": 7, "challenge2_training_task1_gt": 7, "chang": [14, 19], "channel": [2, 3, 8, 10], "channel_prior": 4, "channelshuffl": 10, "charact": [5, 7, 8, 11, 17, 19], "charactergener": [7, 17], "characterist": 2, "charg": 19, "charset": 19, "chart": 8, "check": [3, 15, 19], "checkpoint": 9, "chip": 4, "christian": 1, "ci": 3, "clarifi": 2, "clariti": 2, "class": [2, 7, 8, 10, 11, 19], "class_nam": 13, "classif": [17, 19], "classmethod": 8, "clear": 3, "clone": 4, "close": 3, "co": 15, "code": [5, 8, 16], "codecov": 3, "colab": 12, "collate_fn": 7, "collect": [8, 16], "color": 10, "colorinvers": 10, "column": 8, "com": [2, 4, 8, 9, 15], "combin": 19, "command": [3, 16], "comment": 2, "commit": 2, "common": [2, 10, 11, 18], "commun": 2, "compar": 5, "comparison": [11, 19], "competit": 7, "compil": [12, 19], "complaint": 2, "complementari": 11, "complet": 3, "compon": 19, "compos": [7, 19], "comprehens": 19, "comput": [7, 11, 18, 19], "conf_threshold": 16, "confid": [8, 19], "config": [4, 9], "configur": 9, "confus": 11, "consecut": [10, 19], "consequ": 2, "consid": [2, 3, 7, 8, 11, 19], "consist": 19, "consolid": [5, 7], "constant": 10, "construct": 2, "contact": 2, "contain": [1, 6, 7, 12, 17, 19], "content": [7, 8, 19], "context": 9, "contib": 4, "continu": 2, "contrast": 10, "contrast_factor": 10, "contrib": [4, 16], "contribut": 2, "contributor": 3, "convers": 8, "convert": [8, 10], "convolut": 9, "cool": 1, "coordin": [8, 19], "cord": [5, 7, 17, 19], "core": [11, 19], "corner": 19, "correct": 10, "correspond": [4, 8, 10, 19], "could": [2, 16], "counterpart": 11, "cover": 3, "coverag": 3, "cpu": [5, 13, 18], "creat": [1, 15], "crnn": [5, 9, 15], "crnn_mobilenet_v3_larg": [9, 15, 19], "crnn_mobilenet_v3_smal": [9, 18, 19], "crnn_vgg16_bn": [9, 13, 15, 19], "crop": [8, 9, 10, 13, 17, 19], "crop_orient": [8, 19], "crop_orientation_predictor": [9, 13], "crop_param": 13, "cuda": 18, "currenc": 7, "current": [3, 13, 19], "custom": [15, 16, 18, 19], "custom_crop_orientation_model": 13, "custom_page_orientation_model": 13, "customhook": 19, "cvit": 5, "czczup": 9, "czech": 7, "d": [7, 17], "danish": 7, "data": [5, 7, 8, 10, 11, 13, 15], "dataload": 17, "dataset": [9, 13, 19], "dataset_info": 7, "date": [13, 19], "db": 15, "db_mobilenet_v3_larg": [9, 15, 19], "db_resnet34": 19, "db_resnet50": [9, 13, 15, 19], "dbnet": [5, 9], "deal": [12, 19], "decis": 2, "decod": 8, "decode_img_as_tensor": 8, "dedic": 18, "deem": 2, "deep": [9, 19], "def": 19, "default": [4, 8, 13, 14, 19], "defer": 17, "defin": [11, 18], "degre": [8, 10, 19], "degress": 8, "delet": 3, "delimit": 19, "delta": 10, "demo": [3, 5], "demonstr": 2, "depend": [3, 4, 5, 19], "deploi": 3, "deploy": 5, "derogatori": 2, "describ": 9, "descript": 12, "design": 10, "desir": 8, "det_arch": [9, 13, 15, 18], "det_b": 19, "det_model": [13, 15, 18], "det_param": 13, "det_predictor": [13, 19], "detail": [13, 19], "detect": [1, 7, 8, 11, 12, 13, 16], "detect_languag": 9, "detect_orient": [9, 13, 19], "detection_predictor": [9, 19], "detection_task": [7, 17], "detectiondataset": [7, 17], "detectionmetr": 11, "detectionpredictor": [9, 13], "detector": [5, 9, 16], "deterior": 9, "determin": 2, "dev": [3, 14], "develop": 4, "deviat": 10, "devic": 18, "dict": [8, 11, 19], "dictionari": [8, 11], "differ": 2, "differenti": [5, 9], "digit": [5, 7, 17], "dimens": [8, 11, 19], "dimension": 10, "direct": 7, "directli": [15, 19], "directori": [3, 14], "disabl": [2, 14, 19], "disable_crop_orient": 19, "disable_page_orient": 19, "disclaim": 19, "discuss": 3, "disparag": 2, "displai": [8, 11], "display_artefact": 11, "distribut": 10, "div": 19, "divers": 2, "divid": 8, "do": [3, 4, 9], "doc": [3, 8, 16, 18, 19], "docartefact": [7, 17], "docstr": 3, "doctr": [1, 4, 13, 14, 15, 16, 17, 18, 19], "doctr_cache_dir": 14, "doctr_multiprocessing_dis": 14, "document": [1, 7, 9, 11, 12, 13, 16, 17, 18, 19], "documentbuild": 19, "documentfil": [8, 13, 15, 16, 18], "doesn": 18, "don": [13, 19], "done": 10, "download": [7, 17], "downsiz": 9, "draw": 10, "drop": 7, "drop_last": 7, "dtype": [8, 9, 10, 11, 18], "dual": [5, 7], "dummi": 15, "dummy_img": 19, "dummy_input": 18, "dure": 2, "dutch": 7, "dynam": [7, 16], "dynamic_seq_length": 7, "e": [2, 3, 4, 8, 9], "each": [5, 7, 8, 9, 10, 11, 17, 19], "eas": 3, "easi": [5, 11, 15, 18], "easili": [8, 11, 13, 15, 17, 19], "econom": 2, "edit": 2, "educ": 2, "effect": 19, "effici": [3, 5, 7, 9], "either": [11, 19], "element": [7, 8, 9, 19], "els": [3, 16], "email": 2, "empathi": 2, "en": 19, "enabl": [7, 8], "enclos": 8, "encod": [5, 7, 8, 9, 19], "encode_sequ": 7, "encount": 3, "encrypt": 8, "end": [5, 7, 9, 11], "english": [7, 17], "enough": [3, 19], "ensur": 3, "entri": 7, "environ": [2, 14], "eo": 7, "equiv": 19, "estim": 9, "etc": [8, 16], "ethnic": 2, "evalu": [17, 19], "event": 2, "everyon": 2, "everyth": [3, 19], "exact": [11, 19], "exampl": [2, 3, 5, 7, 9, 15, 19], "exchang": 18, "execut": 19, "exist": 15, "expand": 10, "expect": [8, 10, 11], "experi": 2, "explan": [2, 19], "explicit": 2, "exploit": [5, 9], "export": [8, 9, 11, 12, 16, 19], "export_as_straight_box": [9, 19], "export_as_xml": 19, "export_model_to_onnx": 18, "express": [2, 10], "extens": 8, "extern": [2, 17], "extract": [1, 5, 7], "extractor": 9, "f_": 11, "f_a": 11, "factor": 10, "fair": 2, "fairli": 2, "fals": [7, 8, 9, 10, 11, 13, 19], "faq": 2, "fascan": 15, "fast": [5, 7, 9], "fast_bas": [9, 19], "fast_smal": [9, 19], "fast_tini": [9, 19], "faster": [5, 9, 18], "fasterrcnn_mobilenet_v3_large_fpn": 9, "favorit": 19, "featur": [4, 9, 11, 12, 13, 16], "feedback": 2, "feel": [3, 15], "felix92": 15, "few": [18, 19], "figsiz": 11, "figur": [11, 16], "file": [3, 7], "final": 9, "find": [3, 17], "fine": 1, "finnish": 7, "first": [3, 7], "firsthand": 7, "fit": [9, 19], "flag": 19, "flip": 10, "float": [8, 10, 11, 18], "float32": [8, 9, 10, 18], "fn": 10, "focu": 15, "focus": [2, 7], "folder": 7, "follow": [2, 3, 4, 7, 10, 11, 13, 14, 15, 16, 19], "font": 7, "font_famili": 7, "foral": 11, "forc": 3, "forg": 4, "form": [5, 7, 19], "format": [8, 11, 13, 17, 18, 19], "forpost": [5, 7], "forum": 3, "found": 1, "fp16": 18, "frac": 11, "framework": [4, 15, 17, 19], "free": [2, 3, 15], "french": [7, 13, 15, 19], "friendli": 5, "from": [1, 2, 5, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19], "from_hub": [9, 15], "from_imag": [8, 15, 16, 18], "from_pdf": 8, "from_url": 8, "full": [7, 11, 19], "function": [7, 10, 11, 16], "funsd": [5, 7, 17, 19], "further": 17, "futur": 7, "g": [8, 9], "g_": 11, "g_x": 11, "gallagh": 1, "gamma": 10, "gaussian": 10, "gaussianblur": 10, "gaussiannois": 10, "gen": 19, "gender": 2, "gener": [3, 5, 8, 9], "generic_cyrillic_lett": 7, "geometri": [5, 8, 19], "geq": 11, "german": [7, 13, 15], "get": [18, 19], "git": 15, "github": [3, 4, 9, 15], "give": [2, 16], "given": [7, 8, 10, 11, 19], "global": 9, "go": 19, "good": 18, "googl": 3, "googlevis": 5, "gpu": [5, 16, 18], "gracefulli": 2, "graph": [5, 7, 8], "grayscal": 10, "ground": 11, "groung": 11, "group": [5, 19], "gt": 11, "gt_box": 11, "gt_label": 11, "guid": 3, "guidanc": 17, "gvision": 19, "h": [8, 9, 10], "h_": 11, "ha": [3, 7, 11, 17], "handl": [12, 17, 19], "handwrit": 7, "handwritten": 17, "harass": 2, "hardwar": 19, "harm": 2, "hat": 11, "have": [2, 3, 11, 13, 15, 17, 18, 19], "head": [9, 19], "healthi": 2, "hebrew": 7, "height": [8, 10], "hello": [11, 19], "help": 18, "here": [6, 10, 12, 16, 17, 19], "hf": 9, "hf_hub_download": 9, "high": 8, "higher": [4, 7, 19], "hindi": 7, "hindi_digit": 7, "hocr": 19, "hook": 19, "horizont": [8, 10, 19], "hous": 7, "how": [1, 3, 12, 13, 15, 17], "howev": 17, "hsv": 10, "html": [2, 3, 4, 8, 19], "http": [2, 4, 7, 8, 9, 15, 19], "hub": 9, "hue": 10, "huggingfac": 9, "hw": 7, "i": [2, 3, 7, 8, 9, 10, 11, 14, 15, 16, 17, 18], "i7": 19, "ibrahimov": 1, "ic03": [5, 7, 17], "ic13": [5, 7, 17], "icdar": [5, 7], "icdar2019": 7, "id": 19, "ident": 2, "identifi": 5, "iiit": [5, 7], "iiit5k": [7, 17], "iiithw": [5, 7, 17], "imag": [1, 5, 7, 8, 9, 10, 11, 15, 16, 17, 19], "imagenet": 9, "imageri": 2, "images_90k_norm": 7, "img": [7, 10, 17, 18], "img_cont": 8, "img_fold": [7, 17], "img_path": 8, "img_transform": 7, "imgur5k": [5, 7, 17], "imgur5k_annot": 7, "imlist": 7, "impact": 2, "implement": [7, 8, 9, 10, 11, 19], "import": [7, 8, 9, 10, 11, 13, 15, 16, 17, 18, 19], "improv": 9, "inappropri": 2, "incid": 2, "includ": [2, 7, 17, 18], "inclus": 2, "increas": 10, "independ": 10, "index": [3, 8], "indic": 11, "individu": 2, "infer": [5, 9, 10, 16, 19], "inform": [1, 2, 3, 5, 7, 17], "input": [3, 8, 9, 10, 18, 19], "input_crop": 9, "input_pag": [9, 11, 19], "input_shap": 18, "input_tensor": 9, "inspir": [2, 10], "instal": [15, 16, 18], "instanc": [2, 19], "instanti": [9, 19], "instead": [7, 8, 9], "insult": 2, "int": [7, 8, 9, 10], "int64": 11, "integ": 11, "integr": [1, 5, 15, 17], "intel": 19, "interact": [2, 8, 11], "interfac": [15, 18], "interoper": 18, "interpol": 10, "interpret": [7, 8], "intersect": 11, "invert": 10, "investig": 2, "invis": 2, "involv": [2, 19], "io": [13, 15, 16, 18], "iou": 11, "iou_thresh": 11, "iou_threshold": 16, "irregular": [5, 9, 17], "isn": 7, "issu": [2, 3, 15], "italian": 7, "iter": [7, 10, 17, 19], "its": [8, 9, 10, 11, 17, 19], "itself": [9, 15], "j": 11, "jame": 1, "job": 3, "join": 3, "jpeg": 10, "jpegqual": 10, "jpg": [7, 8, 15, 18], "json": [7, 17, 19], "json_output": 19, "jump": 3, "just": 2, "kei": [5, 7], "kera": [9, 18], "kernel": [5, 9, 10], "kernel_shap": 10, "keywoard": 9, "keyword": [7, 8, 9, 11], "kie": [9, 13], "kie_predictor": [9, 13], "kiepredictor": 9, "kind": 2, "know": [3, 18], "kwarg": [7, 8, 9, 11], "l": 11, "l_j": 11, "label": [7, 11, 16, 17], "label_fil": [7, 17], "label_fold": 7, "label_path": [7, 17], "labels_path": [7, 17], "ladder": 2, "lambda": 10, "lambdatransform": 10, "lang": 19, "languag": [2, 5, 7, 8, 9, 15, 19], "larg": [9, 15], "largest": 11, "last": [4, 7], "latenc": 9, "later": 3, "latest": 19, "latin": 7, "layer": 18, "layout": 19, "lead": 2, "leader": 2, "learn": [2, 5, 9, 18, 19], "least": 4, "left": [11, 19], "legacy_french": 7, "length": [7, 19], "less": [18, 19], "level": [2, 7, 11, 19], "leverag": 12, "lf": 15, "librari": [3, 4, 12, 13], "light": 5, "lightweight": 18, "like": 2, "limits_": 11, "line": [5, 9, 11, 19], "line_1_1": 19, "link": 13, "linknet": [5, 9], "linknet_resnet18": [9, 13, 18, 19], "linknet_resnet34": [9, 18, 19], "linknet_resnet50": [9, 19], "list": [7, 8, 10, 11, 15], "ll": 11, "load": [5, 7, 9, 16, 18], "load_state_dict": 13, "load_weight": 13, "loc_pr": 19, "local": [3, 5, 7, 9, 11, 17, 19], "localis": 7, "localizationconfus": 11, "locat": [3, 8, 19], "login": 9, "login_to_hub": [9, 15], "logo": [8, 16, 17], "love": 15, "lower": [10, 11, 19], "m": [3, 11, 19], "m1": 4, "macbook": 4, "machin": 18, "made": 5, "magc_resnet31": 9, "mai": [2, 3], "mail": 2, "main": 12, "maintain": 5, "mainten": 3, "make": [2, 3, 11, 13, 14, 15, 18, 19], "mani": [17, 19], "manipul": 19, "map": [7, 9], "map_loc": 13, "master": [5, 9, 19], "match": [11, 19], "mathcal": 11, "matplotlib": [8, 11], "max": [7, 10, 11], "max_angl": 10, "max_area": 10, "max_char": [7, 17], "max_delta": 10, "max_gain": 10, "max_gamma": 10, "max_qual": 10, "max_ratio": 10, "maximum": [7, 10], "maxval": [9, 10], "mbox": 11, "mean": [10, 11, 13], "meaniou": 11, "meant": [8, 18], "measur": 19, "media": 2, "median": 9, "meet": 13, "member": 2, "memori": [14, 18], "mention": 19, "merg": 7, "messag": 3, "meta": 19, "metadata": 18, "metal": 4, "method": [8, 10, 19], "metric": [11, 19], "middl": 19, "might": [18, 19], "min": 10, "min_area": 10, "min_char": [7, 17], "min_gain": 10, "min_gamma": 10, "min_qual": 10, "min_ratio": 10, "min_val": 10, "minde": [1, 2, 4, 5, 9], "minim": [3, 5], "minimalist": [5, 9], "minimum": [4, 7, 10, 11, 19], "minval": 10, "miss": 4, "mistak": 2, "mixed_float16": 18, "mixed_precis": 18, "mjsynth": [5, 7, 17], "mnt": 7, "mobilenet": [9, 15], "mobilenet_v3_larg": 9, "mobilenet_v3_large_r": 9, "mobilenet_v3_smal": [9, 13], "mobilenet_v3_small_crop_orient": [9, 13], "mobilenet_v3_small_page_orient": [9, 13], "mobilenet_v3_small_r": 9, "mobilenetv3": 9, "modal": [5, 7], "mode": 4, "model": [7, 11, 14, 16, 17], "model_nam": [9, 15, 18], "model_path": [16, 18], "moder": 2, "modif": 3, "modifi": [9, 14, 19], "modul": [4, 8, 9, 10, 11, 19], "more": [3, 17, 19], "moscardi": 1, "most": 19, "mozilla": 2, "multi": [5, 9], "multilingu": [7, 15], "multipl": [7, 8, 10, 19], "multipli": 10, "multiprocess": 14, "my": 9, "my_awesome_model": 15, "my_hook": 19, "n": [7, 11], "name": [7, 9, 18, 19], "nation": 2, "natur": [2, 5, 7], "ndarrai": [7, 8, 10, 11], "necessari": [4, 13, 14], "need": [3, 4, 7, 11, 13, 14, 15, 16, 19], "neg": 10, "nest": 19, "netraj": 1, "network": [5, 7, 9, 18], "neural": [5, 7, 9, 18], "new": [3, 11], "next": [7, 17], "nois": 10, "noisi": [5, 7], "non": [5, 7, 8, 9, 10, 11], "none": [7, 8, 9, 10, 11, 19], "normal": [9, 10], "norwegian": 7, "note": [0, 3, 7, 9, 13, 15, 16, 18], "now": 3, "np": [9, 10, 11, 19], "num_output_channel": 10, "num_sampl": [7, 17], "number": [7, 9, 10, 11, 19], "numpi": [8, 9, 11, 19], "o": 4, "obb": 16, "obj_detect": 15, "object": [7, 8, 11, 16, 19], "objectness_scor": [8, 19], "oblig": 2, "obtain": 19, "occupi": 18, "ocr": [1, 5, 7, 9, 11, 15], "ocr_carea": 19, "ocr_db_crnn": 11, "ocr_lin": 19, "ocr_pag": 19, "ocr_par": 19, "ocr_predictor": [9, 13, 15, 18, 19], "ocrdataset": [7, 17], "ocrmetr": 11, "ocrpredictor": [9, 13], "ocrx_word": 19, "offens": 2, "offici": [2, 9], "offlin": 2, "offset": 10, "onc": 19, "one": [3, 7, 9, 10, 13, 15, 19], "oneof": 10, "ones": [7, 11], "onli": [3, 9, 10, 11, 13, 15, 17, 18, 19], "onlin": 2, "onnx": 16, "onnxruntim": [16, 18], "onnxtr": 18, "opac": 10, "opacity_rang": 10, "open": [1, 2, 3, 15, 18], "opinion": 2, "optic": [5, 19], "optim": [5, 19], "option": [7, 9, 13], "order": [3, 7, 8, 10], "org": [2, 7, 9, 19], "organ": 8, "orient": [2, 8, 9, 12, 16, 19], "orientationpredictor": 9, "other": [2, 3], "otherwis": [2, 8, 11], "our": [1, 3, 9, 19], "out": [3, 9, 10, 11, 19], "outpout": 19, "output": [8, 10, 18], "output_s": [8, 10], "outsid": 14, "over": [7, 11, 19], "overal": [2, 9], "overlai": 8, "overview": 16, "overwrit": 13, "overwritten": 15, "own": 5, "p": [10, 19], "packag": [3, 5, 11, 14, 16, 17, 18], "pad": [7, 9, 10, 19], "page": [4, 7, 9, 11, 13, 19], "page1": 8, "page2": 8, "page_1": 19, "page_idx": [8, 19], "page_orientation_predictor": [9, 13], "page_param": 13, "pair": 11, "paper": 9, "par_1_1": 19, "paragraph": 19, "paragraph_break": 19, "parallel": 9, "param": [10, 19], "paramet": [5, 8, 9, 18], "pars": [5, 7], "parseq": [5, 9, 15, 18, 19], "part": [7, 10, 19], "parti": 4, "partial": 19, "particip": 2, "pass": [7, 8, 9, 13, 19], "password": 8, "patch": [9, 11], "path": [7, 8, 16, 17, 18], "path_to_checkpoint": 13, "path_to_custom_model": 18, "path_to_pt": 13, "patil": 1, "pattern": 2, "pdf": [8, 9, 12], "pdfpage": 8, "peopl": 2, "per": [10, 19], "perform": [5, 8, 9, 10, 11, 14, 18, 19], "period": 2, "permiss": 2, "permut": [5, 9], "persian_lett": 7, "person": [2, 17], "phase": 19, "photo": 17, "physic": [2, 8], "pick": 10, "pictur": 8, "pip": [3, 4, 16, 18], "pipelin": 19, "pixel": [8, 10, 19], "pleas": 3, "plot": 11, "plt": 11, "plug": 15, "plugin": 4, "png": 8, "point": 18, "polici": 14, "polish": 7, "polit": 2, "polygon": [7, 11, 19], "pool": 9, "portugues": 7, "posit": [2, 11], "possibl": [3, 11, 15, 19], "post": [2, 19], "postprocessor": 19, "potenti": 9, "power": 5, "ppageno": 19, "pre": [3, 9, 18], "precis": [11, 19], "pred": 11, "pred_box": 11, "pred_label": 11, "predefin": 17, "predict": [8, 9, 11, 19], "predictor": [5, 8, 9, 12, 13, 15, 18], "prefer": 17, "preinstal": 4, "preprocessor": [13, 19], "prerequisit": 15, "present": 12, "preserv": [9, 10, 19], "preserve_aspect_ratio": [8, 9, 10, 13, 19], "pretrain": [5, 9, 11, 13, 18, 19], "pretrained_backbon": [9, 13], "print": 19, "prior": 7, "privaci": 2, "privat": 2, "probabl": [1, 10], "problem": 3, "procedur": 10, "process": [3, 5, 8, 9, 13, 19], "processor": 19, "produc": [12, 19], "product": 18, "profession": 2, "project": [3, 17], "promptli": 2, "proper": 3, "properli": 7, "provid": [2, 3, 5, 15, 16, 17, 19], "public": [2, 5], "publicli": 19, "publish": 2, "pull": 15, "punctuat": 7, "pure": 7, "purpos": 3, "push_to_hf_hub": [9, 15], "py": 15, "pypdfium2": [4, 8], "pyplot": [8, 11], "python": [1, 3, 16], "python3": 15, "pytorch": [4, 5, 9, 10, 13, 15, 18, 19], "q": 3, "qr": [8, 16], "qr_code": 17, "qualiti": 10, "question": 2, "quickli": 5, "quicktour": 12, "r": 19, "race": 2, "ramdisk": 7, "rand": [9, 10, 11, 18, 19], "random": [9, 10, 11, 19], "randomappli": 10, "randombright": 10, "randomcontrast": 10, "randomcrop": 10, "randomgamma": 10, "randomhorizontalflip": 10, "randomhu": 10, "randomjpegqu": 10, "randomli": 10, "randomres": 10, "randomrot": 10, "randomsatur": 10, "randomshadow": 10, "rang": 10, "rassi": 15, "ratio": [9, 10, 19], "raw": [8, 11], "re": 18, "read": [5, 7, 9], "read_html": 8, "read_img_as_numpi": 8, "read_img_as_tensor": 8, "read_pdf": 8, "readi": 18, "real": [1, 5, 9, 10], "realli": 1, "reason": [2, 5, 7], "rebuild": 3, "rebuilt": 3, "recal": [11, 19], "receipt": [5, 7, 19], "reco_arch": [9, 13, 15, 18], "reco_b": 19, "reco_model": [13, 15, 18], "reco_param": 13, "reco_predictor": 13, "recogn": 19, "recognit": [7, 11, 12, 13], "recognition_predictor": [9, 19], "recognition_task": [7, 17], "recognitiondataset": [7, 17], "recognitionpredictor": [9, 13], "rectangular": 9, "reduc": [4, 10], "refer": [3, 4, 13, 15, 16, 17, 19], "regardless": 2, "region": 19, "regroup": 11, "regular": 17, "reject": 2, "rel": [8, 10, 11, 19], "relat": 8, "releas": [0, 4], "relev": 16, "religion": 2, "remov": 2, "render": [8, 19], "repo": 9, "repo_id": [9, 15], "report": 2, "repositori": [7, 9, 15], "repres": [2, 18, 19], "represent": [5, 9], "request": [2, 15], "requir": [4, 10, 18], "research": 5, "residu": 9, "resiz": [10, 19], "resnet": 9, "resnet18": [9, 15], "resnet31": 9, "resnet34": 9, "resnet50": [9, 15], "resolv": 8, "resolve_block": 19, "resolve_lin": 19, "resourc": 17, "respect": 2, "rest": [3, 10, 11], "restrict": 14, "result": [3, 7, 8, 12, 15, 18, 19], "return": 19, "reusabl": 19, "review": 2, "rgb": [8, 10], "rgb_mode": 8, "rgb_output": 8, "right": [2, 9, 11], "roboflow": 1, "robust": [5, 7], "root": 7, "rotat": [7, 8, 9, 10, 11, 12, 13, 17, 19], "run": [3, 4, 9], "same": [3, 8, 11, 17, 18, 19], "sampl": [7, 9, 17, 19], "sample_transform": 7, "sanjin": 1, "sar": [5, 9], "sar_resnet31": [9, 19], "satur": 10, "save": [9, 17], "scale": [8, 9, 10, 11], "scale_rang": 10, "scan": [5, 7], "scene": [5, 7, 9], "score": [8, 11], "script": [3, 17], "seamless": 5, "seamlessli": [5, 19], "search": [1, 9], "searchabl": 12, "sec": 19, "second": 19, "section": [1, 13, 15, 16, 18, 19], "secur": [2, 14], "see": [2, 3], "seen": 19, "segment": [5, 9, 19], "self": 19, "semant": [5, 9], "send": 19, "sens": 11, "sensit": 17, "separ": 19, "sequenc": [5, 7, 8, 9, 11, 19], "sequenti": [10, 19], "seri": 2, "seriou": 2, "set": [2, 4, 7, 9, 11, 14, 16, 19], "set_global_polici": 18, "sever": [8, 10, 19], "sex": 2, "sexual": 2, "shade": 10, "shape": [5, 8, 9, 10, 11, 19], "share": [14, 17], "shift": 10, "shm": 14, "should": [3, 7, 8, 10, 11], "show": [5, 8, 9, 11, 13, 15, 16], "showcas": [3, 12], "shuffl": [7, 10], "side": 11, "signatur": 8, "signific": 17, "simpl": [5, 9, 18], "simpler": 9, "sinc": [7, 17], "singl": [2, 3, 5, 7], "single_img_doc": 18, "size": [2, 7, 8, 10, 16, 19], "skew": 19, "slack": 3, "slightli": 9, "small": [3, 9, 19], "smallest": 8, "snapshot_download": 9, "snippet": 19, "so": [3, 4, 7, 9, 15, 17], "social": 2, "socio": 2, "some": [1, 4, 12, 15, 17], "someth": 3, "somewher": 3, "sort": 2, "sourc": [1, 7, 8, 9, 10, 11, 15], "space": [2, 19], "span": 19, "spanish": 7, "spatial": [5, 7, 8], "specif": [3, 4, 11, 13, 17, 19], "specifi": [2, 7, 8], "speed": [5, 9, 19], "sphinx": 3, "sroie": [5, 7, 17], "stabl": 4, "stackoverflow": 3, "stage": 5, "standalon": 12, "standard": 10, "start": 7, "state": [1, 5, 11, 16], "static": 11, "statist": 1, "statu": 2, "std": [10, 13], "step": 14, "still": 19, "str": [7, 8, 9, 10, 11], "straight": [7, 9, 17, 19], "straighten": 19, "straighten_pag": [9, 13, 19], "straigten_pag": 13, "stream": 8, "street": [5, 7], "strict": 4, "strictli": 11, "string": [7, 8, 11, 19], "strive": 4, "strong": [5, 9], "structur": [18, 19], "subset": [7, 19], "suggest": [3, 15], "sum": 11, "summari": 11, "support": [4, 13, 16, 18, 19], "sustain": 2, "svhn": [5, 7, 17], "svt": [7, 17], "swedish": 7, "symmetr": [9, 10, 19], "symmetric_pad": [9, 10, 19], "synthet": 5, "synthtext": [5, 7, 17], "system": 19, "t": [3, 7, 13, 18, 19], "tabl": [15, 16, 17], "take": [2, 7, 19], "target": [7, 8, 10, 11, 17], "target_s": 7, "task": [5, 7, 9, 15, 17, 19], "task2": 7, "team": 4, "techminde": 4, "templat": [3, 5], "tensor": [7, 8, 10, 19], "tensorflow": [4, 5, 8, 9, 10, 13, 15, 18, 19], "tensorspec": 18, "term": 2, "test": [7, 17], "test_set": 7, "text": [1, 7, 8, 9, 11, 17], "text_output": 19, "textmatch": 11, "textnet": 9, "textnet_bas": 9, "textnet_smal": 9, "textnet_tini": 9, "textract": [5, 19], "textstylebrush": [5, 7], "textual": [5, 7, 8, 9, 19], "tf": [4, 8, 9, 10, 15, 18], "than": [3, 11, 15], "thank": 3, "thei": [2, 11], "them": [7, 19], "thi": [1, 2, 3, 4, 6, 7, 10, 11, 13, 14, 15, 17, 18, 19], "thing": [18, 19], "third": 4, "those": [2, 8, 19], "threaten": 2, "threshold": 19, "through": [2, 10, 16, 17], "tilman": 15, "time": [1, 2, 5, 9, 11, 17], "tini": 9, "titl": [8, 19], "tm": 19, "tmp": 14, "togeth": [3, 8], "tograi": 10, "tool": [1, 17], "top": [11, 18, 19], "topic": 3, "torch": [4, 10, 13, 15, 18], "torchvis": 10, "total": 13, "toward": [2, 4], "train": [3, 7, 9, 10, 15, 16, 17, 18, 19], "train_it": [7, 17], "train_load": [7, 17], "train_pytorch": 15, "train_set": [7, 17], "train_tensorflow": 15, "trainabl": [5, 9], "tranform": 10, "transcrib": 19, "transfer": [5, 7], "transfo": 10, "transform": [5, 7, 9], "translat": 2, "troll": 2, "true": [7, 8, 9, 10, 11, 13, 14, 15, 17, 18, 19], "truth": 11, "tune": [1, 18], "tupl": [7, 8, 10, 11], "two": [8, 14], "txt": 7, "type": [8, 11, 15, 18, 19], "typic": 19, "u": [2, 3], "ucsd": 7, "udac": 3, "uint8": [8, 9, 11, 19], "ukrainian": 7, "unaccept": 2, "underli": [17, 19], "underneath": 8, "understand": [5, 7, 19], "uniform": [9, 10], "uniformli": 10, "uninterrupt": [8, 19], "union": 11, "unit": 1, "unittest": 3, "unlock": 8, "unoffici": 9, "unprofession": 2, "unsolicit": 2, "unsupervis": 5, "unwelcom": 2, "up": [9, 19], "updat": 11, "upgrad": 3, "upper": [7, 10], "uppercas": 17, "url": 8, "us": [2, 3, 4, 7, 9, 11, 12, 13, 14, 15, 16, 19], "usabl": 19, "usag": [14, 18], "use_polygon": [7, 11, 17], "useabl": 19, "user": [5, 8, 12], "utf": 19, "util": 18, "v1": 15, "v3": [9, 15, 19], "valid": 17, "valu": [3, 8, 10, 19], "valuabl": 5, "variabl": 14, "varieti": 7, "veri": 9, "verma": 1, "version": [2, 3, 4, 18, 19], "vgg": 9, "vgg16": 15, "vgg16_bn_r": 9, "via": 2, "video": 1, "vietnames": 7, "view": [5, 7], "viewpoint": 2, "violat": 2, "visibl": 2, "vision": [5, 7, 9], "visiondataset": 7, "visiontransform": 9, "visual": [4, 5, 16], "visualize_pag": 11, "vit_": 9, "vit_b": 9, "vitstr": [5, 9, 18], "vitstr_bas": [9, 19], "vitstr_smal": [9, 13, 18, 19], "viz": 4, "vocab": [13, 15, 17, 18, 19], "vocabulari": [7, 13, 15], "w": [8, 9, 10, 11], "w3": 19, "wa": 2, "wai": [2, 5, 17], "want": [3, 18, 19], "warmup": 19, "wasn": 3, "we": [1, 2, 3, 4, 5, 8, 10, 13, 15, 17, 18, 19], "weasyprint": 8, "web": [3, 8], "websit": 7, "welcom": 2, "well": [1, 2, 18], "were": [2, 8, 19], "what": [1, 2], "when": [2, 3, 9], "whenev": 3, "where": [3, 8, 10, 11], "whether": [3, 7, 8, 10, 11, 17, 19], "which": [2, 9, 14, 16, 17, 19], "whichev": 4, "while": [10, 19], "why": 2, "width": [8, 10], "wiki": 2, "wildreceipt": [5, 7, 17], "window": [9, 11], "wish": 3, "within": 2, "without": [2, 7, 9], "wonder": 3, "word": [5, 7, 9, 11, 19], "word_1_1": 19, "word_1_2": 19, "word_1_3": 19, "wordgener": [7, 17], "words_onli": 11, "work": [1, 13, 14, 19], "workflow": 3, "worklow": 3, "world": [11, 19], "worth": 9, "wrap": 19, "wrapper": [7, 10], "write": 14, "written": [2, 8], "www": [2, 8, 19], "x": [8, 10, 11], "x_ascend": 19, "x_descend": 19, "x_i": 11, "x_size": 19, "x_wconf": 19, "xhtml": 19, "xmax": 8, "xmin": 8, "xml": 19, "xml_bytes_str": 19, "xml_element": 19, "xml_output": 19, "xmln": 19, "y": 11, "y_i": 11, "y_j": 11, "yet": 16, "ymax": 8, "ymin": 8, "yolov8": 16, "you": [3, 4, 7, 8, 9, 13, 14, 15, 16, 17, 18, 19], "your": [3, 5, 8, 11, 19], "yoursit": 8, "yugesh": 1, "zero": [10, 11], "zoo": 13, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 7, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 7, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": 7, "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 7, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 7, "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 7, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 7, "\u00e4\u00f6\u00e4\u00f6": 7, "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 7, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": 7, "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": 7, "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": 7, "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": 7, "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": 7, "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0905": 7, "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": 7, "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": 7, "\u0950": 7, "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": 7, "\u09bd": 7, "\u09ce": 7, "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": 7}, "titles": ["Changelog", "Community resources", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.contrib", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Integrate contributions into your pipeline", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 3, "0": 0, "01": 0, "02": 0, "03": 0, "04": 0, "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 2], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 2], "2021": 0, "2022": 0, "2023": 0, "2024": 0, "21": 0, "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 2], "31": 0, "4": [0, 2], "5": 0, "6": 0, "7": 0, "8": 0, "9": 0, "advanc": 19, "approach": 19, "architectur": 19, "arg": [7, 8, 9, 10, 11], "artefact": 8, "artefactdetect": 16, "attribut": 2, "avail": [16, 17, 19], "aw": 14, "ban": 2, "block": 8, "bug": 3, "changelog": 0, "choos": [17, 19], "classif": [9, 13, 15], "code": [2, 3], "codebas": 3, "commit": 3, "commun": [1, 15], "compos": 10, "conda": 4, "conduct": 2, "connect": 3, "continu": 3, "contrib": 6, "contribut": [3, 6, 16], "contributor": 2, "convent": 15, "correct": 2, "coven": 2, "custom": [7, 13], "data": 17, "dataload": 7, "dataset": [5, 7, 17], "detect": [5, 9, 15, 17, 19], "develop": 3, "do": 19, "doctr": [3, 5, 6, 7, 8, 9, 10, 11, 12], "document": [3, 5, 8], "end": 19, "enforc": 2, "evalu": 11, "export": 18, "factori": 9, "featur": [3, 5], "feedback": 3, "file": 8, "from": 15, "gener": [7, 17], "git": 4, "guidelin": 2, "half": 18, "hub": 15, "huggingfac": 15, "i": 19, "infer": 18, "instal": [3, 4], "integr": [3, 16], "io": 8, "lambda": 14, "let": 3, "line": 8, "linux": 4, "load": [13, 15, 17], "loader": 7, "main": 5, "mode": 3, "model": [5, 9, 13, 15, 18, 19], "modifi": 3, "modul": [6, 16], "name": 15, "notebook": 12, "object": 17, "ocr": [17, 19], "onli": 4, "onnx": 18, "optim": 18, "option": 19, "orient": 13, "our": 2, "output": 19, "own": [13, 17], "packag": 4, "page": 8, "perman": 2, "pipelin": 16, "pledg": 2, "precis": 18, "predictor": 19, "prepar": 18, "prerequisit": 4, "pretrain": 15, "push": 15, "python": 4, "qualiti": 3, "question": 3, "read": 8, "readi": 17, "recognit": [5, 9, 15, 17, 19], "report": 3, "request": 3, "resourc": 1, "respons": 2, "return": [7, 8, 9, 11], "right": 19, "scope": 2, "share": 15, "should": 19, "stage": 19, "standard": 2, "structur": [3, 8], "style": 3, "support": [5, 6, 7, 10], "synthet": [7, 17], "task": 11, "temporari": 2, "test": 3, "text": [5, 19], "train": 13, "transform": 10, "two": 19, "unit": 3, "us": [17, 18], "util": 11, "v0": 0, "verif": 3, "via": 4, "visual": 11, "vocab": 7, "warn": 2, "what": 19, "word": 8, "your": [13, 15, 16, 17, 18], "zoo": [5, 9]}}) \ No newline at end of file +Search.setIndex({"alltitles": {"1. Correction": [[2, "correction"]], "2. Warning": [[2, "warning"]], "3. Temporary Ban": [[2, "temporary-ban"]], "4. Permanent Ban": [[2, "permanent-ban"]], "AWS Lambda": [[14, null]], "Advanced options": [[19, "advanced-options"]], "Args:": [[7, "args"], [7, "id4"], [7, "id7"], [7, "id10"], [7, "id13"], [7, "id16"], [7, "id19"], [7, "id22"], [7, "id25"], [7, "id29"], [7, "id32"], [7, "id37"], [7, "id40"], [7, "id46"], [7, "id49"], [7, "id50"], [7, "id51"], [7, "id54"], [7, "id57"], [7, "id60"], [7, "id61"], [8, "args"], [8, "id2"], [8, "id3"], [8, "id4"], [8, "id5"], [8, "id6"], [8, "id7"], [8, "id10"], [8, "id12"], [8, "id14"], [8, "id16"], [8, "id20"], [8, "id24"], [8, "id28"], [9, "args"], [9, "id3"], [9, "id8"], [9, "id13"], [9, "id17"], [9, "id21"], [9, "id26"], [9, "id31"], [9, "id36"], [9, "id41"], [9, "id46"], [9, "id50"], [9, "id54"], [9, "id59"], [9, "id63"], [9, "id68"], [9, "id73"], [9, "id77"], [9, "id81"], [9, "id85"], [9, "id90"], [9, "id95"], [9, "id99"], [9, "id104"], [9, "id109"], [9, "id114"], [9, "id119"], [9, "id123"], [9, "id127"], [9, "id132"], [9, "id137"], [9, "id142"], [9, "id146"], [9, "id150"], [9, "id155"], [9, "id159"], [9, "id163"], [9, "id167"], [9, "id169"], [9, "id171"], [9, "id173"], [10, "args"], [10, "id1"], [10, "id2"], [10, "id3"], [10, "id4"], [10, "id5"], [10, "id6"], [10, "id7"], [10, "id8"], [10, "id9"], [10, "id10"], [10, "id11"], [10, "id12"], [10, "id13"], [10, "id14"], [10, "id15"], [10, "id16"], [10, "id17"], [10, "id18"], [10, "id19"], [11, "args"], [11, "id3"], [11, "id4"], [11, "id5"], [11, "id6"], [11, "id7"], [11, "id8"], [11, "id9"]], "Artefact": [[8, "artefact"]], "ArtefactDetection": [[16, "artefactdetection"]], "Attribution": [[2, "attribution"]], "Available Datasets": [[17, "available-datasets"]], "Available architectures": [[19, "available-architectures"], [19, "id1"], [19, "id2"]], "Available contribution modules": [[16, "available-contribution-modules"]], "Block": [[8, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[17, null]], "Choosing the right model": [[19, null]], "Classification": [[15, "classification"]], "Code quality": [[3, "code-quality"]], "Code style verification": [[3, "code-style-verification"]], "Codebase structure": [[3, "codebase-structure"]], "Commits": [[3, "commits"]], "Community resources": [[1, null]], "Composing transformations": [[10, "composing-transformations"]], "Continuous Integration": [[3, "continuous-integration"]], "Contributing to docTR": [[3, null]], "Contributor Covenant Code of Conduct": [[2, null]], "Custom dataset loader": [[7, "custom-dataset-loader"]], "Custom orientation classification models": [[13, "custom-orientation-classification-models"]], "Data Loading": [[17, "data-loading"]], "Dataloader": [[7, "dataloader"]], "Detection": [[15, "detection"], [17, "detection"]], "Detection predictors": [[19, "detection-predictors"]], "Developer mode installation": [[3, "developer-mode-installation"]], "Developing docTR": [[3, "developing-doctr"]], "Document": [[8, "document"]], "Document structure": [[8, "document-structure"]], "End-to-End OCR": [[19, "end-to-end-ocr"]], "Enforcement": [[2, "enforcement"]], "Enforcement Guidelines": [[2, "enforcement-guidelines"]], "Enforcement Responsibilities": [[2, "enforcement-responsibilities"]], "Export to ONNX": [[18, "export-to-onnx"]], "Feature requests & bug report": [[3, "feature-requests-bug-report"]], "Feedback": [[3, "feedback"]], "File reading": [[8, "file-reading"]], "Half-precision": [[18, "half-precision"]], "Installation": [[4, null]], "Integrate contributions into your pipeline": [[16, null]], "Let\u2019s connect": [[3, "let-s-connect"]], "Line": [[8, "line"]], "Loading from Huggingface Hub": [[15, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[13, "loading-your-custom-trained-model"]], "Loading your custom trained orientation classification model": [[13, "loading-your-custom-trained-orientation-classification-model"]], "Main Features": [[5, "main-features"]], "Model optimization": [[18, "model-optimization"]], "Model zoo": [[5, "model-zoo"]], "Modifying the documentation": [[3, "modifying-the-documentation"]], "Naming conventions": [[15, "naming-conventions"]], "OCR": [[17, "ocr"]], "Object Detection": [[17, "object-detection"]], "Our Pledge": [[2, "our-pledge"]], "Our Standards": [[2, "our-standards"]], "Page": [[8, "page"]], "Preparing your model for inference": [[18, null]], "Prerequisites": [[4, "prerequisites"]], "Pretrained community models": [[15, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[15, "pushing-to-the-huggingface-hub"]], "Questions": [[3, "questions"]], "Recognition": [[15, "recognition"], [17, "recognition"]], "Recognition predictors": [[19, "recognition-predictors"]], "Returns:": [[7, "returns"], [8, "returns"], [8, "id11"], [8, "id13"], [8, "id15"], [8, "id19"], [8, "id23"], [8, "id27"], [8, "id31"], [9, "returns"], [9, "id6"], [9, "id11"], [9, "id16"], [9, "id20"], [9, "id24"], [9, "id29"], [9, "id34"], [9, "id39"], [9, "id44"], [9, "id49"], [9, "id53"], [9, "id57"], [9, "id62"], [9, "id66"], [9, "id71"], [9, "id76"], [9, "id80"], [9, "id84"], [9, "id88"], [9, "id93"], [9, "id98"], [9, "id102"], [9, "id107"], [9, "id112"], [9, "id117"], [9, "id122"], [9, "id126"], [9, "id130"], [9, "id135"], [9, "id140"], [9, "id145"], [9, "id149"], [9, "id153"], [9, "id158"], [9, "id162"], [9, "id166"], [9, "id168"], [9, "id170"], [9, "id172"], [11, "returns"]], "Scope": [[2, "scope"]], "Share your model with the community": [[15, null]], "Supported Vocabs": [[7, "supported-vocabs"]], "Supported contribution modules": [[6, "supported-contribution-modules"]], "Supported datasets": [[5, "supported-datasets"]], "Supported transformations": [[10, "supported-transformations"]], "Synthetic dataset generator": [[7, "synthetic-dataset-generator"], [17, "synthetic-dataset-generator"]], "Task evaluation": [[11, "task-evaluation"]], "Text Detection": [[19, "text-detection"]], "Text Recognition": [[19, "text-recognition"]], "Text detection models": [[5, "text-detection-models"]], "Text recognition models": [[5, "text-recognition-models"]], "Train your own model": [[13, null]], "Two-stage approaches": [[19, "two-stage-approaches"]], "Unit tests": [[3, "unit-tests"]], "Use your own datasets": [[17, "use-your-own-datasets"]], "Using your ONNX exported model": [[18, "using-your-onnx-exported-model"]], "Via Conda (Only for Linux)": [[4, "via-conda-only-for-linux"]], "Via Git": [[4, "via-git"]], "Via Python Package": [[4, "via-python-package"]], "Visualization": [[11, "visualization"]], "What should I do with the output?": [[19, "what-should-i-do-with-the-output"]], "Word": [[8, "word"]], "docTR Notebooks": [[12, null]], "docTR Vocabs": [[7, "id62"]], "docTR: Document Text Recognition": [[5, null]], "doctr.contrib": [[6, null]], "doctr.datasets": [[7, null], [7, "datasets"]], "doctr.io": [[8, null]], "doctr.models": [[9, null]], "doctr.models.classification": [[9, "doctr-models-classification"]], "doctr.models.detection": [[9, "doctr-models-detection"]], "doctr.models.factory": [[9, "doctr-models-factory"]], "doctr.models.recognition": [[9, "doctr-models-recognition"]], "doctr.models.zoo": [[9, "doctr-models-zoo"]], "doctr.transforms": [[10, null]], "doctr.utils": [[11, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.10.0 (2024-10-21)": [[0, "v0-10-0-2024-10-21"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]], "v0.7.0 (2023-09-09)": [[0, "v0-7-0-2023-09-09"]], "v0.8.0 (2024-02-28)": [[0, "v0-8-0-2024-02-28"]], "v0.8.1 (2024-03-04)": [[0, "v0-8-1-2024-03-04"]], "v0.9.0 (2024-08-08)": [[0, "v0-9-0-2024-08-08"]]}, "docnames": ["changelog", "community/resources", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/contrib", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_contrib_modules", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "community/resources.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/contrib.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_contrib_modules.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[8, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[8, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[10, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[7, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[10, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[10, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[7, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[7, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[9, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[7, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[7, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[8, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[8, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[7, "doctr.datasets.encode_sequences", false]], "fast_base() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_base", false]], "fast_small() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_small", false]], "fast_tiny() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_tiny", false]], "from_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[7, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[10, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[10, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[7, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[7, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[7, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[7, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[7, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[9, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[10, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[8, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet34() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[7, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_crop_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_crop_orientation", false]], "mobilenet_v3_small_page_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_page_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[10, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[9, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[7, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[10, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[8, "doctr.io.Page", false]], "page_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.page_orientation_predictor", false]], "parseq() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[10, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[10, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[10, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[10, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[10, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[10, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[10, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[10, "doctr.transforms.RandomJpegQuality", false]], "randomresize (class in doctr.transforms)": [[10, "doctr.transforms.RandomResize", false]], "randomrotate (class in doctr.transforms)": [[10, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[10, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[10, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[8, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[8, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[8, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[7, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[10, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[8, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[8, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[7, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[7, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[7, "doctr.datasets.SVT", false]], "synthtext (class in doctr.datasets)": [[7, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.TextMatch", false]], "textnet_base() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_base", false]], "textnet_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_small", false]], "textnet_tiny() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_tiny", false]], "togray (class in doctr.transforms)": [[10, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[11, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_small", false]], "wildreceipt (class in doctr.datasets)": [[7, "doctr.datasets.WILDRECEIPT", false]], "word (class in doctr.io)": [[8, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[7, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[7, 0, 1, "", "CORD"], [7, 0, 1, "", "CharacterGenerator"], [7, 0, 1, "", "DetectionDataset"], [7, 0, 1, "", "DocArtefacts"], [7, 0, 1, "", "FUNSD"], [7, 0, 1, "", "IC03"], [7, 0, 1, "", "IC13"], [7, 0, 1, "", "IIIT5K"], [7, 0, 1, "", "IIITHWS"], [7, 0, 1, "", "IMGUR5K"], [7, 0, 1, "", "MJSynth"], [7, 0, 1, "", "OCRDataset"], [7, 0, 1, "", "RecognitionDataset"], [7, 0, 1, "", "SROIE"], [7, 0, 1, "", "SVHN"], [7, 0, 1, "", "SVT"], [7, 0, 1, "", "SynthText"], [7, 0, 1, "", "WILDRECEIPT"], [7, 0, 1, "", "WordGenerator"], [7, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[7, 0, 1, "", "DataLoader"]], "doctr.io": [[8, 0, 1, "", "Artefact"], [8, 0, 1, "", "Block"], [8, 0, 1, "", "Document"], [8, 0, 1, "", "DocumentFile"], [8, 0, 1, "", "Line"], [8, 0, 1, "", "Page"], [8, 0, 1, "", "Word"], [8, 1, 1, "", "decode_img_as_tensor"], [8, 1, 1, "", "read_html"], [8, 1, 1, "", "read_img_as_numpy"], [8, 1, 1, "", "read_img_as_tensor"], [8, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[8, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[8, 2, 1, "", "from_images"], [8, 2, 1, "", "from_pdf"], [8, 2, 1, "", "from_url"]], "doctr.io.Page": [[8, 2, 1, "", "show"]], "doctr.models": [[9, 1, 1, "", "kie_predictor"], [9, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[9, 1, 1, "", "crop_orientation_predictor"], [9, 1, 1, "", "magc_resnet31"], [9, 1, 1, "", "mobilenet_v3_large"], [9, 1, 1, "", "mobilenet_v3_large_r"], [9, 1, 1, "", "mobilenet_v3_small"], [9, 1, 1, "", "mobilenet_v3_small_crop_orientation"], [9, 1, 1, "", "mobilenet_v3_small_page_orientation"], [9, 1, 1, "", "mobilenet_v3_small_r"], [9, 1, 1, "", "page_orientation_predictor"], [9, 1, 1, "", "resnet18"], [9, 1, 1, "", "resnet31"], [9, 1, 1, "", "resnet34"], [9, 1, 1, "", "resnet50"], [9, 1, 1, "", "textnet_base"], [9, 1, 1, "", "textnet_small"], [9, 1, 1, "", "textnet_tiny"], [9, 1, 1, "", "vgg16_bn_r"], [9, 1, 1, "", "vit_b"], [9, 1, 1, "", "vit_s"]], "doctr.models.detection": [[9, 1, 1, "", "db_mobilenet_v3_large"], [9, 1, 1, "", "db_resnet50"], [9, 1, 1, "", "detection_predictor"], [9, 1, 1, "", "fast_base"], [9, 1, 1, "", "fast_small"], [9, 1, 1, "", "fast_tiny"], [9, 1, 1, "", "linknet_resnet18"], [9, 1, 1, "", "linknet_resnet34"], [9, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[9, 1, 1, "", "from_hub"], [9, 1, 1, "", "login_to_hub"], [9, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[9, 1, 1, "", "crnn_mobilenet_v3_large"], [9, 1, 1, "", "crnn_mobilenet_v3_small"], [9, 1, 1, "", "crnn_vgg16_bn"], [9, 1, 1, "", "master"], [9, 1, 1, "", "parseq"], [9, 1, 1, "", "recognition_predictor"], [9, 1, 1, "", "sar_resnet31"], [9, 1, 1, "", "vitstr_base"], [9, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[10, 0, 1, "", "ChannelShuffle"], [10, 0, 1, "", "ColorInversion"], [10, 0, 1, "", "Compose"], [10, 0, 1, "", "GaussianBlur"], [10, 0, 1, "", "GaussianNoise"], [10, 0, 1, "", "LambdaTransformation"], [10, 0, 1, "", "Normalize"], [10, 0, 1, "", "OneOf"], [10, 0, 1, "", "RandomApply"], [10, 0, 1, "", "RandomBrightness"], [10, 0, 1, "", "RandomContrast"], [10, 0, 1, "", "RandomCrop"], [10, 0, 1, "", "RandomGamma"], [10, 0, 1, "", "RandomHorizontalFlip"], [10, 0, 1, "", "RandomHue"], [10, 0, 1, "", "RandomJpegQuality"], [10, 0, 1, "", "RandomResize"], [10, 0, 1, "", "RandomRotate"], [10, 0, 1, "", "RandomSaturation"], [10, 0, 1, "", "RandomShadow"], [10, 0, 1, "", "Resize"], [10, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[11, 0, 1, "", "DetectionMetric"], [11, 0, 1, "", "LocalizationConfusion"], [11, 0, 1, "", "OCRMetric"], [11, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.visualization": [[11, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [2, 8, 9, 11, 15, 18], "0": [2, 4, 7, 10, 11, 13, 16, 17, 19], "00": 19, "01": 19, "0123456789": 7, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "02562": 9, "03": 19, "035": 19, "0361328125": 19, "04": 19, "05": 19, "06": 19, "06640625": 19, "07": 19, "08": [10, 19], "09": 19, "0966796875": 19, "1": [7, 8, 9, 10, 11, 13, 17, 19], "10": [4, 7, 11, 19], "100": [7, 10, 11, 17, 19], "1000": 19, "101": 7, "1024": [9, 13, 19], "104": 7, "106": 7, "108": 7, "1095": 17, "11": 19, "110": 11, "1107": 17, "114": 7, "115": 7, "1156": 17, "116": 7, "118": 7, "11800h": 19, "11th": 19, "12": 19, "120": 7, "123": 7, "126": 7, "1268": 17, "128": [9, 13, 18, 19], "13": 19, "130": 7, "13068": 17, "131": 7, "1337891": 17, "1357421875": 19, "1396484375": 19, "14": 19, "1420": 19, "14470v1": 7, "149": 17, "15": 19, "150": [11, 19], "1552": 19, "16": [9, 18, 19], "1630859375": 19, "1684": 19, "16x16": 9, "17": 19, "1778": 19, "1782": 19, "18": [9, 19], "185546875": 19, "1900": 19, "1910": 9, "19342": 17, "19370": 17, "195": 7, "19598": 17, "199": 19, "1999": 19, "2": [4, 5, 7, 8, 9, 10, 16, 19], "20": 19, "200": 11, "2000": 17, "2003": [5, 7], "2012": 7, "2013": [5, 7], "2015": 7, "2019": 5, "2023": 1, "207901": 17, "21": 19, "2103": 7, "2186": 17, "21888": 17, "22": 19, "224": [9, 10], "225": 10, "22672": 17, "229": [10, 17], "23": 19, "233": 17, "236": 7, "24": 19, "246": 17, "249": 17, "25": 19, "2504": 19, "255": [8, 9, 10, 11, 19], "256": 9, "257": 17, "26": 19, "26032": 17, "264": 13, "27": 19, "2700": 17, "2710": 19, "2749": 13, "28": 19, "287": 13, "29": 19, "296": 13, "299": 13, "2d": 19, "3": [4, 5, 8, 9, 10, 11, 18, 19], "30": 19, "300": 17, "3000": 17, "301": 13, "30595": 19, "30ghz": 19, "31": 9, "32": [7, 9, 10, 13, 17, 18, 19], "3232421875": 19, "33": [10, 19], "33402": 17, "33608": 17, "34": [9, 19], "340": 19, "3456": 19, "3515625": 19, "36": 19, "360": 17, "37": [7, 19], "38": 19, "39": 19, "4": [9, 10, 11, 19], "40": 19, "406": 10, "41": 19, "42": 19, "43": 19, "44": 19, "45": 19, "456": 10, "46": 19, "47": 19, "472": 17, "48": [7, 19], "485": 10, "49": 19, "49377": 17, "5": [7, 10, 11, 16, 19], "50": [9, 17, 19], "51": 19, "51171875": 19, "512": 9, "52": [7, 19], "529": 19, "53": 19, "54": 19, "540": 19, "5478515625": 19, "55": 19, "56": 19, "57": 19, "58": [7, 19], "580": 19, "5810546875": 19, "583": 19, "59": 19, "597": 19, "5k": [5, 7], "5m": 19, "6": [10, 19], "60": 10, "600": [9, 11, 19], "61": 19, "62": 19, "626": 17, "63": 19, "64": [9, 10, 19], "641": 19, "647": 17, "65": 19, "66": 19, "67": 19, "68": 19, "69": 19, "693": 13, "694": 13, "695": 13, "6m": 19, "7": 19, "70": [7, 11, 19], "707470": 17, "71": [7, 19], "7100000": 17, "7141797": 17, "7149": 17, "72": 19, "72dpi": 8, "73": 19, "73257": 17, "74": 19, "75": [10, 19], "7581382": 17, "76": 19, "77": 19, "772": 13, "772875": 17, "78": 19, "785": 13, "79": 19, "793533": 17, "796": 17, "798": 13, "7m": 19, "8": [9, 10, 19], "80": 19, "800": [9, 11, 17, 19], "81": 19, "82": 19, "83": 19, "84": 19, "849": 17, "85": 19, "8564453125": 19, "857": 19, "85875": 17, "86": 19, "8603515625": 19, "87": 19, "8707": 17, "88": 19, "89": 19, "9": [10, 19], "90": 19, "90k": 7, "90kdict32px": 7, "91": 19, "914085328578949": 19, "92": 19, "93": 19, "94": [7, 19], "95": [11, 19], "9578408598899841": 19, "96": 19, "97": 19, "98": 19, "99": 19, "9949972033500671": 19, "A": [2, 3, 5, 7, 8, 9, 12, 18], "As": 3, "Be": 19, "Being": 2, "By": 14, "For": [2, 3, 4, 13, 19], "If": [3, 8, 9, 13, 19], "In": [3, 7, 17], "It": [10, 15, 16, 18], "Its": [5, 9], "No": [2, 19], "Of": 7, "Or": [16, 18], "The": [2, 3, 7, 8, 11, 14, 16, 17, 18, 19], "Then": 9, "To": [3, 4, 14, 15, 16, 18, 19], "_": [2, 7, 9], "__call__": 19, "_build": 3, "_i": 11, "ab": 7, "abc": 18, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "abdef": [7, 17], "abl": [17, 19], "about": [2, 17, 19], "abov": 19, "abstract": 1, "abstractdataset": 7, "abus": 2, "accept": 2, "access": [5, 8, 17, 19], "account": [2, 15], "accur": 19, "accuraci": 11, "achiev": 18, "act": 2, "action": 2, "activ": 5, "ad": [3, 9, 10], "adapt": 2, "add": [10, 11, 15, 19], "add_hook": 19, "add_label": 11, "addit": [3, 4, 8, 16, 19], "addition": [3, 19], "address": [2, 8], "adjust": 10, "advanc": 2, "advantag": 18, "advis": 3, "aesthet": [5, 7], "affect": 2, "after": [15, 19], "ag": 2, "again": 9, "aggreg": [11, 17], "aggress": 2, "align": [2, 8, 10], "all": [2, 3, 6, 7, 8, 10, 11, 16, 17, 19], "allow": [2, 18], "along": 19, "alreadi": [3, 18], "also": [2, 9, 15, 16, 17, 19], "alwai": 17, "an": [2, 3, 5, 7, 8, 9, 11, 16, 18, 19], "analysi": [8, 16], "ancient_greek": 7, "andrej": 1, "angl": [8, 10], "ani": [2, 7, 8, 9, 10, 11, 18, 19], "annot": 7, "anot": 17, "anoth": [9, 13, 17], "answer": 2, "anyascii": 11, "anyon": 5, "anyth": 16, "api": [3, 5], "apolog": 2, "apologi": 2, "app": 3, "appear": 2, "appli": [2, 7, 10], "applic": [5, 9], "appoint": 2, "appreci": 15, "appropri": [2, 3, 19], "ar": [2, 3, 4, 6, 7, 8, 10, 11, 12, 16, 17, 19], "arab": 7, "arabic_diacrit": 7, "arabic_lett": 7, "arabic_punctu": 7, "arbitrarili": [5, 9], "arch": [9, 15], "architectur": [5, 9, 15, 16], "area": 19, "argument": [7, 8, 9, 11, 13, 19], "around": 2, "arrai": [8, 10, 11], "art": [5, 16], "artefact": [11, 16, 19], "artefact_typ": 8, "articl": 1, "artifici": [5, 7], "arxiv": [7, 9], "asarrai": 11, "ascii_lett": 7, "aspect": [5, 9, 10, 19], "assess": 11, "assign": 11, "associ": 8, "assum": 9, "assume_straight_pag": [9, 13, 19], "astyp": [9, 11, 19], "attack": 2, "attend": [5, 9], "attent": [2, 9], "autom": 5, "automat": 19, "autoregress": [5, 9], "avail": [2, 5, 6, 10], "averag": [10, 19], "avoid": [2, 4], "aw": [5, 19], "awar": 19, "azur": 19, "b": [9, 11, 19], "b_j": 11, "back": 3, "backbon": 9, "backend": 19, "background": 17, "bangla": 7, "bar": 16, "bar_cod": 17, "baranovskij": 1, "base": [5, 9, 16], "baselin": [5, 9, 19], "batch": [7, 9, 10, 16, 17, 19], "batch_siz": [7, 9, 13, 16, 17, 18], "bblanchon": 4, "bbox": 19, "becaus": 14, "been": [3, 11, 17, 19], "befor": [7, 9, 10, 19], "begin": 11, "behavior": [2, 19], "being": [11, 19], "belong": 19, "benchmark": 19, "best": [1, 2], "better": [12, 19], "between": [10, 11, 19], "bgr": 8, "bilinear": 10, "bin_thresh": 19, "binar": [5, 9, 19], "binari": [8, 18, 19], "bit": 18, "block": [11, 19], "block_1_1": 19, "blur": 10, "bmvc": 7, "bn": 15, "bodi": [2, 19], "bool": [7, 8, 9, 10, 11], "boolean": [9, 19], "both": [5, 7, 10, 17, 19], "bottom": [9, 19], "bound": [7, 8, 9, 10, 11, 16, 17, 19], "box": [7, 8, 9, 10, 11, 16, 17, 19], "box_thresh": 19, "bright": 10, "browser": [3, 5], "build": [3, 4, 18], "built": 3, "byte": [8, 19], "c": [4, 8, 11], "c_j": 11, "cach": [3, 7, 14], "cache_sampl": 7, "call": 18, "callabl": [7, 10], "can": [3, 4, 13, 14, 15, 16, 17, 19], "capabl": [3, 12, 19], "case": [7, 11], "cf": 19, "cfg": 19, "challeng": 7, "challenge2_test_task12_imag": 7, "challenge2_test_task1_gt": 7, "challenge2_training_task12_imag": 7, "challenge2_training_task1_gt": 7, "chang": [14, 19], "channel": [2, 3, 8, 10], "channel_prior": 4, "channelshuffl": 10, "charact": [5, 7, 8, 11, 17, 19], "charactergener": [7, 17], "characterist": 2, "charg": 19, "charset": 19, "chart": 8, "check": [3, 15, 19], "checkpoint": 9, "chip": 4, "christian": 1, "ci": 3, "clarifi": 2, "clariti": 2, "class": [2, 7, 8, 10, 11, 19], "class_nam": 13, "classif": [17, 19], "classmethod": 8, "clear": 3, "clone": 4, "close": 3, "co": 15, "code": [5, 8, 16], "codecov": 3, "colab": 12, "collate_fn": 7, "collect": [8, 16], "color": 10, "colorinvers": 10, "column": 8, "com": [2, 4, 8, 9, 15], "combin": 19, "command": [3, 16], "comment": 2, "commit": 2, "common": [2, 10, 11, 18], "commun": 2, "compar": 5, "comparison": [11, 19], "competit": 7, "compil": [12, 19], "complaint": 2, "complementari": 11, "complet": 3, "compon": 19, "compos": [7, 19], "comprehens": 19, "comput": [7, 11, 18, 19], "conf_threshold": 16, "confid": [8, 19], "config": [4, 9], "configur": 9, "confus": 11, "consecut": [10, 19], "consequ": 2, "consid": [2, 3, 7, 8, 11, 19], "consist": 19, "consolid": [5, 7], "constant": 10, "construct": 2, "contact": 2, "contain": [1, 6, 7, 12, 17, 19], "content": [7, 8, 19], "context": 9, "contib": 4, "continu": 2, "contrast": 10, "contrast_factor": 10, "contrib": [4, 16], "contribut": 2, "contributor": 3, "convers": 8, "convert": [8, 10], "convolut": 9, "cool": 1, "coordin": [8, 19], "cord": [5, 7, 17, 19], "core": [11, 19], "corner": 19, "correct": 10, "correspond": [4, 8, 10, 19], "could": [2, 16], "counterpart": 11, "cover": 3, "coverag": 3, "cpu": [5, 13, 18], "creat": [1, 15], "crnn": [5, 9, 15], "crnn_mobilenet_v3_larg": [9, 15, 19], "crnn_mobilenet_v3_smal": [9, 18, 19], "crnn_vgg16_bn": [9, 13, 15, 19], "crop": [8, 9, 10, 13, 17, 19], "crop_orient": [8, 19], "crop_orientation_predictor": [9, 13], "crop_param": 13, "cuda": 18, "currenc": 7, "current": [3, 13, 19], "custom": [15, 16, 18, 19], "custom_crop_orientation_model": 13, "custom_page_orientation_model": 13, "customhook": 19, "cvit": 5, "czczup": 9, "czech": 7, "d": [7, 17], "danish": 7, "data": [5, 7, 8, 10, 11, 13, 15], "dataload": 17, "dataset": [9, 13, 19], "dataset_info": 7, "date": [13, 19], "db": 15, "db_mobilenet_v3_larg": [9, 15, 19], "db_resnet34": 19, "db_resnet50": [9, 13, 15, 19], "dbnet": [5, 9], "deal": [12, 19], "decis": 2, "decod": 8, "decode_img_as_tensor": 8, "dedic": 18, "deem": 2, "deep": [9, 19], "def": 19, "default": [4, 8, 13, 14, 19], "defer": 17, "defin": [11, 18], "degre": [8, 10, 19], "degress": 8, "delet": 3, "delimit": 19, "delta": 10, "demo": [3, 5], "demonstr": 2, "depend": [3, 4, 5, 19], "deploi": 3, "deploy": 5, "derogatori": 2, "describ": 9, "descript": 12, "design": 10, "desir": 8, "det_arch": [9, 13, 15, 18], "det_b": 19, "det_model": [13, 15, 18], "det_param": 13, "det_predictor": [13, 19], "detail": [13, 19], "detect": [1, 7, 8, 11, 12, 13, 16], "detect_languag": 9, "detect_orient": [9, 13, 19], "detection_predictor": [9, 19], "detection_task": [7, 17], "detectiondataset": [7, 17], "detectionmetr": 11, "detectionpredictor": [9, 13], "detector": [5, 9, 16], "deterior": 9, "determin": 2, "dev": [3, 14], "develop": 4, "deviat": 10, "devic": 18, "dict": [8, 11, 19], "dictionari": [8, 11], "differ": 2, "differenti": [5, 9], "digit": [5, 7, 17], "dimens": [8, 11, 19], "dimension": 10, "direct": 7, "directli": [15, 19], "directori": [3, 14], "disabl": [2, 14, 19], "disable_crop_orient": 19, "disable_page_orient": 19, "disclaim": 19, "discuss": 3, "disparag": 2, "displai": [8, 11], "display_artefact": 11, "distribut": 10, "div": 19, "divers": 2, "divid": 8, "do": [3, 4, 9], "doc": [3, 8, 16, 18, 19], "docartefact": [7, 17], "docstr": 3, "doctr": [1, 4, 13, 14, 15, 16, 17, 18, 19], "doctr_cache_dir": 14, "doctr_multiprocessing_dis": 14, "document": [1, 7, 9, 11, 12, 13, 16, 17, 18, 19], "documentbuild": 19, "documentfil": [8, 13, 15, 16, 18], "doesn": 18, "don": [13, 19], "done": 10, "download": [7, 17], "downsiz": 9, "draw": 10, "drop": 7, "drop_last": 7, "dtype": [8, 9, 10, 11, 18], "dual": [5, 7], "dummi": 15, "dummy_img": 19, "dummy_input": 18, "dure": 2, "dutch": 7, "dynam": [7, 16], "dynamic_seq_length": 7, "e": [2, 3, 4, 8, 9], "each": [5, 7, 8, 9, 10, 11, 17, 19], "eas": 3, "easi": [5, 11, 15, 18], "easili": [8, 11, 13, 15, 17, 19], "econom": 2, "edit": 2, "educ": 2, "effect": 19, "effici": [3, 5, 7, 9], "either": [11, 19], "element": [7, 8, 9, 19], "els": [3, 16], "email": 2, "empathi": 2, "en": 19, "enabl": [7, 8], "enclos": 8, "encod": [5, 7, 8, 9, 19], "encode_sequ": 7, "encount": 3, "encrypt": 8, "end": [5, 7, 9, 11], "english": [7, 17], "enough": [3, 19], "ensur": 3, "entri": 7, "environ": [2, 14], "eo": 7, "equiv": 19, "estim": 9, "etc": [8, 16], "ethnic": 2, "evalu": [17, 19], "event": 2, "everyon": 2, "everyth": [3, 19], "exact": [11, 19], "exampl": [2, 3, 5, 7, 9, 15, 19], "exchang": 18, "execut": 19, "exist": 15, "expand": 10, "expect": [8, 10, 11], "experi": 2, "explan": [2, 19], "explicit": 2, "exploit": [5, 9], "export": [8, 9, 11, 12, 16, 19], "export_as_straight_box": [9, 19], "export_as_xml": 19, "export_model_to_onnx": 18, "express": [2, 10], "extens": 8, "extern": [2, 17], "extract": [1, 5, 7], "extractor": 9, "f_": 11, "f_a": 11, "factor": 10, "fair": 2, "fairli": 2, "fals": [7, 8, 9, 10, 11, 13, 19], "faq": 2, "fascan": 15, "fast": [5, 7, 9], "fast_bas": [9, 19], "fast_smal": [9, 19], "fast_tini": [9, 19], "faster": [5, 9, 18], "fasterrcnn_mobilenet_v3_large_fpn": 9, "favorit": 19, "featur": [4, 9, 11, 12, 13, 16], "feedback": 2, "feel": [3, 15], "felix92": 15, "few": [18, 19], "figsiz": 11, "figur": [11, 16], "file": [3, 7], "final": 9, "find": [3, 17], "fine": 1, "finnish": 7, "first": [3, 7], "firsthand": 7, "fit": [9, 19], "flag": 19, "flip": 10, "float": [8, 10, 11, 18], "float32": [8, 9, 10, 18], "fn": 10, "focu": 15, "focus": [2, 7], "folder": 7, "follow": [2, 3, 4, 7, 10, 11, 13, 14, 15, 16, 19], "font": 7, "font_famili": 7, "foral": 11, "forc": 3, "forg": 4, "form": [5, 7, 19], "format": [8, 11, 13, 17, 18, 19], "forpost": [5, 7], "forum": 3, "found": 1, "fp16": 18, "frac": 11, "framework": [4, 15, 17, 19], "free": [2, 3, 15], "french": [7, 13, 15, 19], "friendli": 5, "from": [1, 2, 5, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19], "from_hub": [9, 15], "from_imag": [8, 15, 16, 18], "from_pdf": 8, "from_url": 8, "full": [7, 11, 19], "function": [7, 10, 11, 16], "funsd": [5, 7, 17, 19], "further": 17, "futur": 7, "g": [8, 9], "g_": 11, "g_x": 11, "gallagh": 1, "gamma": 10, "gaussian": 10, "gaussianblur": 10, "gaussiannois": 10, "gen": 19, "gender": 2, "gener": [3, 5, 8, 9], "generic_cyrillic_lett": 7, "geometri": [5, 8, 19], "geq": 11, "german": [7, 13, 15], "get": [18, 19], "git": 15, "github": [3, 4, 9, 15], "give": [2, 16], "given": [7, 8, 10, 11, 19], "global": 9, "go": 19, "good": 18, "googl": 3, "googlevis": 5, "gpu": [5, 16, 18], "gracefulli": 2, "graph": [5, 7, 8], "grayscal": 10, "ground": 11, "groung": 11, "group": [5, 19], "gt": 11, "gt_box": 11, "gt_label": 11, "guid": 3, "guidanc": 17, "gvision": 19, "h": [8, 9, 10], "h_": 11, "ha": [3, 7, 11, 17], "handl": [12, 17, 19], "handwrit": 7, "handwritten": 17, "harass": 2, "hardwar": 19, "harm": 2, "hat": 11, "have": [2, 3, 11, 13, 15, 17, 18, 19], "head": [9, 19], "healthi": 2, "hebrew": 7, "height": [8, 10], "hello": [11, 19], "help": 18, "here": [6, 10, 12, 16, 17, 19], "hf": 9, "hf_hub_download": 9, "high": 8, "higher": [4, 7, 19], "hindi": 7, "hindi_digit": 7, "hocr": 19, "hook": 19, "horizont": [8, 10, 19], "hous": 7, "how": [1, 3, 12, 13, 15, 17], "howev": 17, "hsv": 10, "html": [2, 3, 4, 8, 19], "http": [2, 4, 7, 8, 9, 15, 19], "hub": 9, "hue": 10, "huggingfac": 9, "hw": 7, "i": [2, 3, 7, 8, 9, 10, 11, 14, 15, 16, 17, 18], "i7": 19, "ibrahimov": 1, "ic03": [5, 7, 17], "ic13": [5, 7, 17], "icdar": [5, 7], "icdar2019": 7, "id": 19, "ident": 2, "identifi": 5, "iiit": [5, 7], "iiit5k": [7, 17], "iiithw": [5, 7, 17], "imag": [1, 5, 7, 8, 9, 10, 11, 15, 16, 17, 19], "imagenet": 9, "imageri": 2, "images_90k_norm": 7, "img": [7, 10, 17, 18], "img_cont": 8, "img_fold": [7, 17], "img_path": 8, "img_transform": 7, "imgur5k": [5, 7, 17], "imgur5k_annot": 7, "imlist": 7, "impact": 2, "implement": [7, 8, 9, 10, 11, 19], "import": [7, 8, 9, 10, 11, 13, 15, 16, 17, 18, 19], "improv": 9, "inappropri": 2, "incid": 2, "includ": [2, 7, 17, 18], "inclus": 2, "increas": 10, "independ": 10, "index": [3, 8], "indic": 11, "individu": 2, "infer": [5, 9, 10, 16, 19], "inform": [1, 2, 3, 5, 7, 17], "input": [3, 8, 9, 10, 18, 19], "input_crop": 9, "input_pag": [9, 11, 19], "input_shap": 18, "input_tensor": 9, "inspir": [2, 10], "instal": [15, 16, 18], "instanc": [2, 19], "instanti": [9, 19], "instead": [7, 8, 9], "insult": 2, "int": [7, 8, 9, 10], "int64": 11, "integ": 11, "integr": [1, 5, 15, 17], "intel": 19, "interact": [2, 8, 11], "interfac": [15, 18], "interoper": 18, "interpol": 10, "interpret": [7, 8], "intersect": 11, "invert": 10, "investig": 2, "invis": 2, "involv": [2, 19], "io": [13, 15, 16, 18], "iou": 11, "iou_thresh": 11, "iou_threshold": 16, "irregular": [5, 9, 17], "isn": 7, "issu": [2, 3, 15], "italian": 7, "iter": [7, 10, 17, 19], "its": [8, 9, 10, 11, 17, 19], "itself": [9, 15], "j": 11, "jame": 1, "job": 3, "join": 3, "jpeg": 10, "jpegqual": 10, "jpg": [7, 8, 15, 18], "json": [7, 17, 19], "json_output": 19, "jump": 3, "just": 2, "kei": [5, 7], "kera": [9, 18], "kernel": [5, 9, 10], "kernel_shap": 10, "keywoard": 9, "keyword": [7, 8, 9, 11], "kie": [9, 13], "kie_predictor": [9, 13], "kiepredictor": 9, "kind": 2, "know": [3, 18], "kwarg": [7, 8, 9, 11], "l": 11, "l_j": 11, "label": [7, 11, 16, 17], "label_fil": [7, 17], "label_fold": 7, "label_path": [7, 17], "labels_path": [7, 17], "ladder": 2, "lambda": 10, "lambdatransform": 10, "lang": 19, "languag": [2, 5, 7, 8, 9, 15, 19], "larg": [9, 15], "largest": 11, "last": [4, 7], "latenc": 9, "later": 3, "latest": 19, "latin": 7, "layer": 18, "layout": 19, "lead": 2, "leader": 2, "learn": [2, 5, 9, 18, 19], "least": 4, "left": [11, 19], "legacy_french": 7, "length": [7, 19], "less": [18, 19], "level": [2, 7, 11, 19], "leverag": 12, "lf": 15, "librari": [3, 4, 12, 13], "light": 5, "lightweight": 18, "like": 2, "limits_": 11, "line": [5, 9, 11, 19], "line_1_1": 19, "link": 13, "linknet": [5, 9], "linknet_resnet18": [9, 13, 18, 19], "linknet_resnet34": [9, 18, 19], "linknet_resnet50": [9, 19], "list": [7, 8, 10, 11, 15], "ll": 11, "load": [5, 7, 9, 16, 18], "load_state_dict": 13, "load_weight": 13, "loc_pr": 19, "local": [3, 5, 7, 9, 11, 17, 19], "localis": 7, "localizationconfus": 11, "locat": [3, 8, 19], "login": 9, "login_to_hub": [9, 15], "logo": [8, 16, 17], "love": 15, "lower": [10, 11, 19], "m": [3, 11, 19], "m1": 4, "macbook": 4, "machin": 18, "made": 5, "magc_resnet31": 9, "mai": [2, 3], "mail": 2, "main": 12, "maintain": 5, "mainten": 3, "make": [2, 3, 11, 13, 14, 15, 18, 19], "mani": [17, 19], "manipul": 19, "map": [7, 9], "map_loc": 13, "master": [5, 9, 19], "match": [11, 19], "mathcal": 11, "matplotlib": [8, 11], "max": [7, 10, 11], "max_angl": 10, "max_area": 10, "max_char": [7, 17], "max_delta": 10, "max_gain": 10, "max_gamma": 10, "max_qual": 10, "max_ratio": 10, "maximum": [7, 10], "maxval": [9, 10], "mbox": 11, "mean": [10, 11, 13], "meaniou": 11, "meant": [8, 18], "measur": 19, "media": 2, "median": 9, "meet": 13, "member": 2, "memori": [14, 18], "mention": 19, "merg": 7, "messag": 3, "meta": 19, "metadata": 18, "metal": 4, "method": [8, 10, 19], "metric": [11, 19], "middl": 19, "might": [18, 19], "min": 10, "min_area": 10, "min_char": [7, 17], "min_gain": 10, "min_gamma": 10, "min_qual": 10, "min_ratio": 10, "min_val": 10, "minde": [1, 2, 4, 5, 9], "minim": [3, 5], "minimalist": [5, 9], "minimum": [4, 7, 10, 11, 19], "minval": 10, "miss": 4, "mistak": 2, "mixed_float16": 18, "mixed_precis": 18, "mjsynth": [5, 7, 17], "mnt": 7, "mobilenet": [9, 15], "mobilenet_v3_larg": 9, "mobilenet_v3_large_r": 9, "mobilenet_v3_smal": [9, 13], "mobilenet_v3_small_crop_orient": [9, 13], "mobilenet_v3_small_page_orient": [9, 13], "mobilenet_v3_small_r": 9, "mobilenetv3": 9, "modal": [5, 7], "mode": 4, "model": [7, 11, 14, 16, 17], "model_nam": [9, 15, 18], "model_path": [16, 18], "moder": 2, "modif": 3, "modifi": [9, 14, 19], "modul": [4, 8, 9, 10, 11, 19], "more": [3, 17, 19], "moscardi": 1, "most": 19, "mozilla": 2, "multi": [5, 9], "multilingu": [7, 15], "multipl": [7, 8, 10, 19], "multipli": 10, "multiprocess": 14, "my": 9, "my_awesome_model": 15, "my_hook": 19, "n": [7, 11], "name": [7, 9, 18, 19], "nation": 2, "natur": [2, 5, 7], "ndarrai": [7, 8, 10, 11], "necessari": [4, 13, 14], "need": [3, 4, 7, 11, 13, 14, 15, 16, 19], "neg": 10, "nest": 19, "netraj": 1, "network": [5, 7, 9, 18], "neural": [5, 7, 9, 18], "new": [3, 11], "next": [7, 17], "nois": 10, "noisi": [5, 7], "non": [5, 7, 8, 9, 10, 11], "none": [7, 8, 9, 10, 11, 19], "normal": [9, 10], "norwegian": 7, "note": [0, 3, 7, 9, 13, 15, 16, 18], "now": 3, "np": [9, 10, 11, 19], "num_output_channel": 10, "num_sampl": [7, 17], "number": [7, 9, 10, 11, 19], "numpi": [8, 9, 11, 19], "o": 4, "obb": 16, "obj_detect": 15, "object": [7, 8, 11, 16, 19], "objectness_scor": [8, 19], "oblig": 2, "obtain": 19, "occupi": 18, "ocr": [1, 5, 7, 9, 11, 15], "ocr_carea": 19, "ocr_db_crnn": 11, "ocr_lin": 19, "ocr_pag": 19, "ocr_par": 19, "ocr_predictor": [9, 13, 15, 18, 19], "ocrdataset": [7, 17], "ocrmetr": 11, "ocrpredictor": [9, 13], "ocrx_word": 19, "offens": 2, "offici": [2, 9], "offlin": 2, "offset": 10, "onc": 19, "one": [3, 7, 9, 10, 13, 15, 19], "oneof": 10, "ones": [7, 11], "onli": [3, 9, 10, 11, 13, 15, 17, 18, 19], "onlin": 2, "onnx": 16, "onnxruntim": [16, 18], "onnxtr": 18, "opac": 10, "opacity_rang": 10, "open": [1, 2, 3, 15, 18], "opinion": 2, "optic": [5, 19], "optim": [5, 19], "option": [7, 9, 13], "order": [3, 7, 8, 10], "org": [2, 7, 9, 19], "organ": 8, "orient": [2, 8, 9, 12, 16, 19], "orientationpredictor": 9, "other": [2, 3], "otherwis": [2, 8, 11], "our": [1, 3, 9, 19], "out": [3, 9, 10, 11, 19], "outpout": 19, "output": [8, 10, 18], "output_s": [8, 10], "outsid": 14, "over": [7, 11, 19], "overal": [2, 9], "overlai": 8, "overview": 16, "overwrit": 13, "overwritten": 15, "own": 5, "p": [10, 19], "packag": [3, 5, 11, 14, 16, 17, 18], "pad": [7, 9, 10, 19], "page": [4, 7, 9, 11, 13, 19], "page1": 8, "page2": 8, "page_1": 19, "page_idx": [8, 19], "page_orientation_predictor": [9, 13], "page_param": 13, "pair": 11, "paper": 9, "par_1_1": 19, "paragraph": 19, "paragraph_break": 19, "parallel": 9, "param": [10, 19], "paramet": [5, 8, 9, 18], "pars": [5, 7], "parseq": [5, 9, 15, 18, 19], "part": [7, 10, 19], "parti": 4, "partial": 19, "particip": 2, "pass": [7, 8, 9, 13, 19], "password": 8, "patch": [9, 11], "path": [7, 8, 16, 17, 18], "path_to_checkpoint": 13, "path_to_custom_model": 18, "path_to_pt": 13, "patil": 1, "pattern": 2, "pdf": [8, 9, 12], "pdfpage": 8, "peopl": 2, "per": [10, 19], "perform": [5, 8, 9, 10, 11, 14, 18, 19], "period": 2, "permiss": 2, "permut": [5, 9], "persian_lett": 7, "person": [2, 17], "phase": 19, "photo": 17, "physic": [2, 8], "pick": 10, "pictur": 8, "pip": [3, 4, 16, 18], "pipelin": 19, "pixel": [8, 10, 19], "pleas": 3, "plot": 11, "plt": 11, "plug": 15, "plugin": 4, "png": 8, "point": 18, "polici": 14, "polish": 7, "polit": 2, "polygon": [7, 11, 19], "pool": 9, "portugues": 7, "posit": [2, 11], "possibl": [3, 11, 15, 19], "post": [2, 19], "postprocessor": 19, "potenti": 9, "power": 5, "ppageno": 19, "pre": [3, 9, 18], "precis": [11, 19], "pred": 11, "pred_box": 11, "pred_label": 11, "predefin": 17, "predict": [8, 9, 11, 19], "predictor": [5, 8, 9, 12, 13, 15, 18], "prefer": 17, "preinstal": 4, "preprocessor": [13, 19], "prerequisit": 15, "present": 12, "preserv": [9, 10, 19], "preserve_aspect_ratio": [8, 9, 10, 13, 19], "pretrain": [5, 9, 11, 13, 18, 19], "pretrained_backbon": [9, 13], "print": 19, "prior": 7, "privaci": 2, "privat": 2, "probabl": [1, 10], "problem": 3, "procedur": 10, "process": [3, 5, 8, 9, 13, 19], "processor": 19, "produc": [12, 19], "product": 18, "profession": 2, "project": [3, 17], "promptli": 2, "proper": 3, "properli": 7, "provid": [2, 3, 5, 15, 16, 17, 19], "public": [2, 5], "publicli": 19, "publish": 2, "pull": 15, "punctuat": 7, "pure": 7, "purpos": 3, "push_to_hf_hub": [9, 15], "py": 15, "pypdfium2": [4, 8], "pyplot": [8, 11], "python": [1, 3, 16], "python3": 15, "pytorch": [4, 5, 9, 10, 13, 15, 18, 19], "q": 3, "qr": [8, 16], "qr_code": 17, "qualiti": 10, "question": 2, "quickli": 5, "quicktour": 12, "r": 19, "race": 2, "ramdisk": 7, "rand": [9, 10, 11, 18, 19], "random": [9, 10, 11, 19], "randomappli": 10, "randombright": 10, "randomcontrast": 10, "randomcrop": 10, "randomgamma": 10, "randomhorizontalflip": 10, "randomhu": 10, "randomjpegqu": 10, "randomli": 10, "randomres": 10, "randomrot": 10, "randomsatur": 10, "randomshadow": 10, "rang": 10, "rassi": 15, "ratio": [9, 10, 19], "raw": [8, 11], "re": 18, "read": [5, 7, 9], "read_html": 8, "read_img_as_numpi": 8, "read_img_as_tensor": 8, "read_pdf": 8, "readi": 18, "real": [1, 5, 9, 10], "realli": 1, "reason": [2, 5, 7], "rebuild": 3, "rebuilt": 3, "recal": [11, 19], "receipt": [5, 7, 19], "reco_arch": [9, 13, 15, 18], "reco_b": 19, "reco_model": [13, 15, 18], "reco_param": 13, "reco_predictor": 13, "recogn": 19, "recognit": [7, 11, 12, 13], "recognition_predictor": [9, 19], "recognition_task": [7, 17], "recognitiondataset": [7, 17], "recognitionpredictor": [9, 13], "rectangular": 9, "reduc": [4, 10], "refer": [3, 4, 13, 15, 16, 17, 19], "regardless": 2, "region": 19, "regroup": 11, "regular": 17, "reject": 2, "rel": [8, 10, 11, 19], "relat": 8, "releas": [0, 4], "relev": 16, "religion": 2, "remov": 2, "render": [8, 19], "repo": 9, "repo_id": [9, 15], "report": 2, "repositori": [7, 9, 15], "repres": [2, 18, 19], "represent": [5, 9], "request": [2, 15], "requir": [4, 10, 18], "research": 5, "residu": 9, "resiz": [10, 19], "resnet": 9, "resnet18": [9, 15], "resnet31": 9, "resnet34": 9, "resnet50": [9, 15], "resolv": 8, "resolve_block": 19, "resolve_lin": 19, "resourc": 17, "respect": 2, "rest": [3, 10, 11], "restrict": 14, "result": [3, 7, 8, 12, 15, 18, 19], "return": 19, "reusabl": 19, "review": 2, "rgb": [8, 10], "rgb_mode": 8, "rgb_output": 8, "right": [2, 9, 11], "roboflow": 1, "robust": [5, 7], "root": 7, "rotat": [7, 8, 9, 10, 11, 12, 13, 17, 19], "run": [3, 4, 9], "same": [3, 8, 11, 17, 18, 19], "sampl": [7, 9, 17, 19], "sample_transform": 7, "sanjin": 1, "sar": [5, 9], "sar_resnet31": [9, 19], "satur": 10, "save": [9, 17], "scale": [8, 9, 10, 11], "scale_rang": 10, "scan": [5, 7], "scene": [5, 7, 9], "score": [8, 11], "script": [3, 17], "seamless": 5, "seamlessli": [5, 19], "search": [1, 9], "searchabl": 12, "sec": 19, "second": 19, "section": [1, 13, 15, 16, 18, 19], "secur": [2, 14], "see": [2, 3], "seen": 19, "segment": [5, 9, 19], "self": 19, "semant": [5, 9], "send": 19, "sens": 11, "sensit": 17, "separ": 19, "sequenc": [5, 7, 8, 9, 11, 19], "sequenti": [10, 19], "seri": 2, "seriou": 2, "set": [2, 4, 7, 9, 11, 14, 16, 19], "set_global_polici": 18, "sever": [8, 10, 19], "sex": 2, "sexual": 2, "shade": 10, "shape": [5, 8, 9, 10, 11, 19], "share": [14, 17], "shift": 10, "shm": 14, "should": [3, 7, 8, 10, 11], "show": [5, 8, 9, 11, 13, 15, 16], "showcas": [3, 12], "shuffl": [7, 10], "side": 11, "signatur": 8, "signific": 17, "simpl": [5, 9, 18], "simpler": 9, "sinc": [7, 17], "singl": [2, 3, 5, 7], "single_img_doc": 18, "size": [2, 7, 8, 10, 16, 19], "skew": 19, "slack": 3, "slightli": 9, "small": [3, 9, 19], "smallest": 8, "snapshot_download": 9, "snippet": 19, "so": [3, 4, 7, 9, 15, 17], "social": 2, "socio": 2, "some": [1, 4, 12, 15, 17], "someth": 3, "somewher": 3, "sort": 2, "sourc": [1, 7, 8, 9, 10, 11, 15], "space": [2, 19], "span": 19, "spanish": 7, "spatial": [5, 7, 8], "specif": [3, 4, 11, 13, 17, 19], "specifi": [2, 7, 8], "speed": [5, 9, 19], "sphinx": 3, "sroie": [5, 7, 17], "stabl": 4, "stackoverflow": 3, "stage": 5, "standalon": 12, "standard": 10, "start": 7, "state": [1, 5, 11, 16], "static": 11, "statist": 1, "statu": 2, "std": [10, 13], "step": 14, "still": 19, "str": [7, 8, 9, 10, 11], "straight": [7, 9, 17, 19], "straighten": 19, "straighten_pag": [9, 13, 19], "straigten_pag": 13, "stream": 8, "street": [5, 7], "strict": 4, "strictli": 11, "string": [7, 8, 11, 19], "strive": 4, "strong": [5, 9], "structur": [18, 19], "subset": [7, 19], "suggest": [3, 15], "sum": 11, "summari": 11, "support": [4, 13, 16, 18, 19], "sustain": 2, "svhn": [5, 7, 17], "svt": [7, 17], "swedish": 7, "symmetr": [9, 10, 19], "symmetric_pad": [9, 10, 19], "synthet": 5, "synthtext": [5, 7, 17], "system": 19, "t": [3, 7, 13, 18, 19], "tabl": [15, 16, 17], "take": [2, 7, 19], "target": [7, 8, 10, 11, 17], "target_s": 7, "task": [5, 7, 9, 15, 17, 19], "task2": 7, "team": 4, "techminde": 4, "templat": [3, 5], "tensor": [7, 8, 10, 19], "tensorflow": [4, 5, 8, 9, 10, 13, 15, 18, 19], "tensorspec": 18, "term": 2, "test": [7, 17], "test_set": 7, "text": [1, 7, 8, 9, 11, 17], "text_output": 19, "textmatch": 11, "textnet": 9, "textnet_bas": 9, "textnet_smal": 9, "textnet_tini": 9, "textract": [5, 19], "textstylebrush": [5, 7], "textual": [5, 7, 8, 9, 19], "tf": [4, 8, 9, 10, 15, 18], "than": [3, 11, 15], "thank": 3, "thei": [2, 11], "them": [7, 19], "thi": [1, 2, 3, 4, 6, 7, 10, 11, 13, 14, 15, 17, 18, 19], "thing": [18, 19], "third": 4, "those": [2, 8, 19], "threaten": 2, "threshold": 19, "through": [2, 10, 16, 17], "tilman": 15, "time": [1, 2, 5, 9, 11, 17], "tini": 9, "titl": [8, 19], "tm": 19, "tmp": 14, "togeth": [3, 8], "tograi": 10, "tool": [1, 17], "top": [11, 18, 19], "topic": 3, "torch": [4, 10, 13, 15, 18], "torchvis": 10, "total": 13, "toward": [2, 4], "train": [3, 7, 9, 10, 15, 16, 17, 18, 19], "train_it": [7, 17], "train_load": [7, 17], "train_pytorch": 15, "train_set": [7, 17], "train_tensorflow": 15, "trainabl": [5, 9], "tranform": 10, "transcrib": 19, "transfer": [5, 7], "transfo": 10, "transform": [5, 7, 9], "translat": 2, "troll": 2, "true": [7, 8, 9, 10, 11, 13, 14, 15, 17, 18, 19], "truth": 11, "tune": [1, 18], "tupl": [7, 8, 10, 11], "two": [8, 14], "txt": 7, "type": [8, 11, 15, 18, 19], "typic": 19, "u": [2, 3], "ucsd": 7, "udac": 3, "uint8": [8, 9, 11, 19], "ukrainian": 7, "unaccept": 2, "underli": [17, 19], "underneath": 8, "understand": [5, 7, 19], "uniform": [9, 10], "uniformli": 10, "uninterrupt": [8, 19], "union": 11, "unit": 1, "unittest": 3, "unlock": 8, "unoffici": 9, "unprofession": 2, "unsolicit": 2, "unsupervis": 5, "unwelcom": 2, "up": [9, 19], "updat": 11, "upgrad": 3, "upper": [7, 10], "uppercas": 17, "url": 8, "us": [2, 3, 4, 7, 9, 11, 12, 13, 14, 15, 16, 19], "usabl": 19, "usag": [14, 18], "use_polygon": [7, 11, 17], "useabl": 19, "user": [5, 8, 12], "utf": 19, "util": 18, "v1": 15, "v3": [9, 15, 19], "valid": 17, "valu": [3, 8, 10, 19], "valuabl": 5, "variabl": 14, "varieti": 7, "veri": 9, "verma": 1, "version": [2, 3, 4, 18, 19], "vgg": 9, "vgg16": 15, "vgg16_bn_r": 9, "via": 2, "video": 1, "vietnames": 7, "view": [5, 7], "viewpoint": 2, "violat": 2, "visibl": 2, "vision": [5, 7, 9], "visiondataset": 7, "visiontransform": 9, "visual": [4, 5, 16], "visualize_pag": 11, "vit_": 9, "vit_b": 9, "vitstr": [5, 9, 18], "vitstr_bas": [9, 19], "vitstr_smal": [9, 13, 18, 19], "viz": 4, "vocab": [13, 15, 17, 18, 19], "vocabulari": [7, 13, 15], "w": [8, 9, 10, 11], "w3": 19, "wa": 2, "wai": [2, 5, 17], "want": [3, 18, 19], "warmup": 19, "wasn": 3, "we": [1, 2, 3, 4, 5, 8, 10, 13, 15, 17, 18, 19], "weasyprint": 8, "web": [3, 8], "websit": 7, "welcom": 2, "well": [1, 2, 18], "were": [2, 8, 19], "what": [1, 2], "when": [2, 3, 9], "whenev": 3, "where": [3, 8, 10, 11], "whether": [3, 7, 8, 10, 11, 17, 19], "which": [2, 9, 14, 16, 17, 19], "whichev": 4, "while": [10, 19], "why": 2, "width": [8, 10], "wiki": 2, "wildreceipt": [5, 7, 17], "window": [9, 11], "wish": 3, "within": 2, "without": [2, 7, 9], "wonder": 3, "word": [5, 7, 9, 11, 19], "word_1_1": 19, "word_1_2": 19, "word_1_3": 19, "wordgener": [7, 17], "words_onli": 11, "work": [1, 13, 14, 19], "workflow": 3, "worklow": 3, "world": [11, 19], "worth": 9, "wrap": 19, "wrapper": [7, 10], "write": 14, "written": [2, 8], "www": [2, 8, 19], "x": [8, 10, 11], "x_ascend": 19, "x_descend": 19, "x_i": 11, "x_size": 19, "x_wconf": 19, "xhtml": 19, "xmax": 8, "xmin": 8, "xml": 19, "xml_bytes_str": 19, "xml_element": 19, "xml_output": 19, "xmln": 19, "y": 11, "y_i": 11, "y_j": 11, "yet": 16, "ymax": 8, "ymin": 8, "yolov8": 16, "you": [3, 4, 7, 8, 9, 13, 14, 15, 16, 17, 18, 19], "your": [3, 5, 8, 11, 19], "yoursit": 8, "yugesh": 1, "zero": [10, 11], "zoo": 13, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 7, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 7, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": 7, "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 7, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 7, "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 7, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 7, "\u00e4\u00f6\u00e4\u00f6": 7, "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 7, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": 7, "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": 7, "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": 7, "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": 7, "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": 7, "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0905": 7, "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": 7, "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": 7, "\u0950": 7, "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": 7, "\u09bd": 7, "\u09ce": 7, "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": 7}, "titles": ["Changelog", "Community resources", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.contrib", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Integrate contributions into your pipeline", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 3, "0": 0, "01": 0, "02": 0, "03": 0, "04": 0, "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 2], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 2], "2021": 0, "2022": 0, "2023": 0, "2024": 0, "21": 0, "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 2], "31": 0, "4": [0, 2], "5": 0, "6": 0, "7": 0, "8": 0, "9": 0, "advanc": 19, "approach": 19, "architectur": 19, "arg": [7, 8, 9, 10, 11], "artefact": 8, "artefactdetect": 16, "attribut": 2, "avail": [16, 17, 19], "aw": 14, "ban": 2, "block": 8, "bug": 3, "changelog": 0, "choos": [17, 19], "classif": [9, 13, 15], "code": [2, 3], "codebas": 3, "commit": 3, "commun": [1, 15], "compos": 10, "conda": 4, "conduct": 2, "connect": 3, "continu": 3, "contrib": 6, "contribut": [3, 6, 16], "contributor": 2, "convent": 15, "correct": 2, "coven": 2, "custom": [7, 13], "data": 17, "dataload": 7, "dataset": [5, 7, 17], "detect": [5, 9, 15, 17, 19], "develop": 3, "do": 19, "doctr": [3, 5, 6, 7, 8, 9, 10, 11, 12], "document": [3, 5, 8], "end": 19, "enforc": 2, "evalu": 11, "export": 18, "factori": 9, "featur": [3, 5], "feedback": 3, "file": 8, "from": 15, "gener": [7, 17], "git": 4, "guidelin": 2, "half": 18, "hub": 15, "huggingfac": 15, "i": 19, "infer": 18, "instal": [3, 4], "integr": [3, 16], "io": 8, "lambda": 14, "let": 3, "line": 8, "linux": 4, "load": [13, 15, 17], "loader": 7, "main": 5, "mode": 3, "model": [5, 9, 13, 15, 18, 19], "modifi": 3, "modul": [6, 16], "name": 15, "notebook": 12, "object": 17, "ocr": [17, 19], "onli": 4, "onnx": 18, "optim": 18, "option": 19, "orient": 13, "our": 2, "output": 19, "own": [13, 17], "packag": 4, "page": 8, "perman": 2, "pipelin": 16, "pledg": 2, "precis": 18, "predictor": 19, "prepar": 18, "prerequisit": 4, "pretrain": 15, "push": 15, "python": 4, "qualiti": 3, "question": 3, "read": 8, "readi": 17, "recognit": [5, 9, 15, 17, 19], "report": 3, "request": 3, "resourc": 1, "respons": 2, "return": [7, 8, 9, 11], "right": 19, "scope": 2, "share": 15, "should": 19, "stage": 19, "standard": 2, "structur": [3, 8], "style": 3, "support": [5, 6, 7, 10], "synthet": [7, 17], "task": 11, "temporari": 2, "test": 3, "text": [5, 19], "train": 13, "transform": 10, "two": 19, "unit": 3, "us": [17, 18], "util": 11, "v0": 0, "verif": 3, "via": 4, "visual": 11, "vocab": 7, "warn": 2, "what": 19, "word": 8, "your": [13, 15, 16, 17, 18], "zoo": [5, 9]}}) \ No newline at end of file diff --git a/v0.1.1/using_doctr/custom_models_training.html b/v0.1.1/using_doctr/custom_models_training.html index df39d8d568..b714c1f971 100644 --- a/v0.1.1/using_doctr/custom_models_training.html +++ b/v0.1.1/using_doctr/custom_models_training.html @@ -14,7 +14,7 @@ - + Train your own model - docTR documentation @@ -619,7 +619,7 @@

    Loading your custom trained orientation classification model - + diff --git a/v0.1.1/using_doctr/running_on_aws.html b/v0.1.1/using_doctr/running_on_aws.html index 16ceaca7a1..808ea541cd 100644 --- a/v0.1.1/using_doctr/running_on_aws.html +++ b/v0.1.1/using_doctr/running_on_aws.html @@ -14,7 +14,7 @@ - + AWS Lambda - docTR documentation @@ -362,7 +362,7 @@

    AWS Lambda - + diff --git a/v0.1.1/using_doctr/sharing_models.html b/v0.1.1/using_doctr/sharing_models.html index d76b4017f4..c9e978400a 100644 --- a/v0.1.1/using_doctr/sharing_models.html +++ b/v0.1.1/using_doctr/sharing_models.html @@ -14,7 +14,7 @@ - + Share your model with the community - docTR documentation @@ -544,7 +544,7 @@

    Recognition - + diff --git a/v0.1.1/using_doctr/using_contrib_modules.html b/v0.1.1/using_doctr/using_contrib_modules.html index 50598dae5d..0c5fffdf9f 100644 --- a/v0.1.1/using_doctr/using_contrib_modules.html +++ b/v0.1.1/using_doctr/using_contrib_modules.html @@ -14,7 +14,7 @@ - + Integrate contributions into your pipeline - docTR documentation @@ -415,7 +415,7 @@

    ArtefactDetection - + diff --git a/v0.1.1/using_doctr/using_datasets.html b/v0.1.1/using_doctr/using_datasets.html index 460476dbbf..8a7d4f0a64 100644 --- a/v0.1.1/using_doctr/using_datasets.html +++ b/v0.1.1/using_doctr/using_datasets.html @@ -14,7 +14,7 @@ - + Choose a ready to use dataset - docTR documentation @@ -642,7 +642,7 @@

    Data Loading - + diff --git a/v0.1.1/using_doctr/using_model_export.html b/v0.1.1/using_doctr/using_model_export.html index 6124c00ebe..6790dd0642 100644 --- a/v0.1.1/using_doctr/using_model_export.html +++ b/v0.1.1/using_doctr/using_model_export.html @@ -14,7 +14,7 @@ - + Preparing your model for inference - docTR documentation @@ -467,7 +467,7 @@

    Using your ONNX exported model - + diff --git a/v0.1.1/using_doctr/using_models.html b/v0.1.1/using_doctr/using_models.html index 61f1f5ab7a..9ead8498e1 100644 --- a/v0.1.1/using_doctr/using_models.html +++ b/v0.1.1/using_doctr/using_models.html @@ -14,7 +14,7 @@ - + Choosing the right model - docTR documentation @@ -1253,7 +1253,7 @@

    Advanced options - + diff --git a/v0.2.0/_modules/doctr/datasets/cord.html b/v0.2.0/_modules/doctr/datasets/cord.html index de8018d676..55b0584830 100644 --- a/v0.2.0/_modules/doctr/datasets/cord.html +++ b/v0.2.0/_modules/doctr/datasets/cord.html @@ -13,7 +13,7 @@ - + doctr.datasets.cord - docTR documentation @@ -225,15 +225,42 @@

    Source code for doctr.datasets.cord

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    -import tensorflow as tf
    +from typing import Any, Dict, List, Tuple, Union
     
    -from .core import VisionDataset
    +import numpy as np
    +from tqdm import tqdm
    +
    +from .datasets import VisionDataset
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['CORD']
    +__all__ = ["CORD"]
     
     
     
    -[docs] +[docs] class CORD(VisionDataset): """CORD dataset from `"CORD: A Consolidated Receipt Dataset forPost-OCR Parsing" <https://openreview.net/pdf?id=SJl3z659UH>`_. - Example:: - >>> from doctr.datasets import CORD - >>> train_set = CORD(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/cord-grid.png&src=0 + :align: center + + >>> from doctr.datasets import CORD + >>> train_set = CORD(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - TRAIN = ('https://github.com/mindee/doctr/releases/download/v0.1.1/cord_train.zip', - '45f9dc77f126490f3e52d7cb4f70ef3c57e649ea86d19d862a2757c9c455d7f8') - TEST = ('https://github.com/mindee/doctr/releases/download/v0.1.1/cord_test.zip', - '8c895e3d6f7e1161c5b7245e3723ce15c04d84be89eaa6093949b75a66fb3c58') + TRAIN = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/cord_train.zip&src=0", + "45f9dc77f126490f3e52d7cb4f70ef3c57e649ea86d19d862a2757c9c455d7f8", + "cord_train.zip", + ) + + TEST = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/cord_test.zip&src=0", + "8c895e3d6f7e1161c5b7245e3723ce15c04d84be89eaa6093949b75a66fb3c58", + "cord_test.zip", + ) def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[tf.Tensor], tf.Tensor]] = None, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: - - url, sha256 = self.TRAIN if train else self.TEST - super().__init__(url, None, sha256, True, **kwargs) - - # # List images - self.root = os.path.join(self._root, 'image') - self.data: List[Tuple[str, Dict[str, Any]]] = [] + url, sha256, name = self.TRAIN if train else self.TEST + super().__init__( + url, + name, + sha256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) + + # List images + tmp_root = os.path.join(self.root, "image") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] self.train = train - self.sample_transforms = (lambda x: x) if sample_transforms is None else sample_transforms - for img_path in os.listdir(self.root): + np_dtype = np.float32 + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking CORD", total=len(os.listdir(tmp_root))): + # File existence check + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem _targets = [] - with open(os.path.join(self._root, 'json', f"{stem}.json"), 'rb') as f: + with open(os.path.join(self.root, "json", f"{stem}.json"), "rb") as f: label = json.load(f) for line in label["valid_line"]: for word in line["words"]: - x = word["quad"]["x1"], word["quad"]["x2"], word["quad"]["x3"], word["quad"]["x4"] - y = word["quad"]["y1"], word["quad"]["y2"], word["quad"]["y3"], word["quad"]["y4"] - # Reduce 8 coords to 4 - left, right = min(x), max(x) - top, bot = min(y), max(y) if len(word["text"]) > 0: - _targets.append((word["text"], [left, top, right, bot])) + x = word["quad"]["x1"], word["quad"]["x2"], word["quad"]["x3"], word["quad"]["x4"] + y = word["quad"]["y1"], word["quad"]["y2"], word["quad"]["y3"], word["quad"]["y4"] + box: Union[List[float], np.ndarray] + if use_polygons: + # (x, y) coordinates of top left, top right, bottom right, bottom left corners + box = np.array( + [ + [x[0], y[0]], + [x[1], y[1]], + [x[2], y[2]], + [x[3], y[3]], + ], + dtype=np_dtype, + ) + else: + # Reduce 8 coords to 4 -> xmin, ymin, xmax, ymax + box = [min(x), min(y), max(x), max(y)] + _targets.append((word["text"], box)) text_targets, box_targets = zip(*_targets) - self.data.append((img_path, dict(boxes=np.asarray(box_targets, dtype=np.int), labels=text_targets))) + if recognition_task: + crops = crop_bboxes_from_image( + img_path=os.path.join(tmp_root, img_path), geoms=np.asarray(box_targets, dtype=int).clip(min=0) + ) + for crop, label in zip(crops, list(text_targets)): + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, np.asarray(box_targets, dtype=int).clip(min=0))) + else: + self.data.append(( + img_path, + dict(boxes=np.asarray(box_targets, dtype=int).clip(min=0), labels=list(text_targets)), + )) + + self.root = tmp_root def extra_repr(self) -> str: - return f"train={self.train}" - - def __getitem__(self, index: int) -> Tuple[tf.Tensor, Dict[str, Any]]: - img_name, target = self.data[index] - # Read image - img = tf.io.read_file(os.path.join(self.root, img_name)) - img = tf.image.decode_jpeg(img, channels=3) - img = self.sample_transforms(img) - - return img, target - - @staticmethod - def collate_fn(samples: List[Tuple[tf.Tensor, Dict[str, Any]]]) -> Tuple[tf.Tensor, List[Dict[str, Any]]]: - - images, targets = zip(*samples) - images = tf.stack(images, axis=0) - - return images, list(targets)
    + return f"train={self.train}"
    @@ -394,8 +461,8 @@

    Source code for doctr.datasets.cord

           
         
       
    - - + + diff --git a/v0.2.0/_modules/doctr/datasets/core.html b/v0.2.0/_modules/doctr/datasets/core.html deleted file mode 100644 index a1d2ee62ad..0000000000 --- a/v0.2.0/_modules/doctr/datasets/core.html +++ /dev/null @@ -1,392 +0,0 @@ - - - - - - - - - - - - doctr.datasets.core - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.core

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from pathlib import Path
    -from zipfile import ZipFile
    -from typing import List, Any, Optional
    -
    -from doctr.models.utils import download_from_url
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset:
    -
    -    data: List[Any] = []
    -
    -    def __len__(self):
    -        return len(self.data)
    -
    -    def __getitem__(self, index: int) -> Any:
    -        raise NotImplementedError
    -
    -    def extra_repr(self) -> str:
    -        return ""
    -
    -    def __repr__(self) -> str:
    -        return f"{self.__class__.__name__}({self.extra_repr()})"
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset): - """Implements an abstract dataset - - Args: - url: URL of the dataset - file_name: name of the file once downloaded - file_hash: expected SHA256 of the file - extract_archive: whether the downloaded file is an archive to be extracted - download: whether the dataset should be downloaded if not present on disk - overwrite: whether the archive should be re-extracted - """ - - def __init__( - self, - url: str, - file_name: Optional[str] = None, - file_hash: Optional[str] = None, - extract_archive: bool = False, - download: bool = False, - overwrite: bool = False, - ) -> None: - - dataset_cache = os.path.join(os.path.expanduser('~'), '.cache', 'doctr', 'datasets') - - file_name = file_name if isinstance(file_name, str) else os.path.basename(url) - # Download the file if not present - archive_path = os.path.join(dataset_cache, file_name) - - if not os.path.exists(archive_path) and not download: - raise ValueError("the dataset needs to be downloaded first with download=True") - - archive_path = download_from_url(url, file_name, file_hash, cache_subdir='datasets') - - # Extract the archive - if extract_archive: - archive_path = Path(archive_path) - dataset_path = archive_path.parent.joinpath(archive_path.stem) - if not dataset_path.is_dir() or overwrite: - with ZipFile(archive_path, 'r') as f: - f.extractall(path=dataset_path) - - # List images - self._root = dataset_path if extract_archive else archive_path - self.data: List[Any] = []
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.0/_modules/doctr/datasets/detection.html b/v0.2.0/_modules/doctr/datasets/detection.html index 739563e466..718001e4cf 100644 --- a/v0.2.0/_modules/doctr/datasets/detection.html +++ b/v0.2.0/_modules/doctr/datasets/detection.html @@ -13,7 +13,7 @@ - + doctr.datasets.detection - docTR documentation @@ -430,7 +430,7 @@

    Source code for doctr.datasets.detection

         
       
    - + diff --git a/v0.2.0/_modules/doctr/datasets/doc_artefacts.html b/v0.2.0/_modules/doctr/datasets/doc_artefacts.html index 3313ae4660..94c32aaa0f 100644 --- a/v0.2.0/_modules/doctr/datasets/doc_artefacts.html +++ b/v0.2.0/_modules/doctr/datasets/doc_artefacts.html @@ -13,7 +13,7 @@ - + doctr.datasets.doc_artefacts - docTR documentation @@ -414,7 +414,7 @@

    Source code for doctr.datasets.doc_artefacts

       
    - + diff --git a/v0.2.0/_modules/doctr/datasets/funsd.html b/v0.2.0/_modules/doctr/datasets/funsd.html index f536b9282c..f08612f9fa 100644 --- a/v0.2.0/_modules/doctr/datasets/funsd.html +++ b/v0.2.0/_modules/doctr/datasets/funsd.html @@ -13,7 +13,7 @@ - + doctr.datasets.funsd - docTR documentation @@ -225,15 +225,42 @@

    Source code for doctr.datasets.funsd

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    -import tensorflow as tf
    +from typing import Any, Dict, List, Tuple, Union
     
    -from .core import VisionDataset
    +import numpy as np
    +from tqdm import tqdm
    +
    +from .datasets import VisionDataset
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['FUNSD']
    +__all__ = ["FUNSD"]
     
     
     
    -[docs] +[docs] class FUNSD(VisionDataset): """FUNSD dataset from `"FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents" <https://arxiv.org/pdf/1905.13538.pdf>`_. - Example:: - >>> from doctr.datasets import FUNSD - >>> train_set = FUNSD(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/funsd-grid.png&src=0 + :align: center + + >>> from doctr.datasets import FUNSD + >>> train_set = FUNSD(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - URL = 'https://guillaumejaume.github.io/FUNSD/dataset.zip' - SHA256 = 'c31735649e4f441bcbb4fd0f379574f7520b42286e80b01d80b445649d54761f' - FILE_NAME = 'funsd.zip' + URL = "https://guillaumejaume.github.io/FUNSD/dataset.zip" + SHA256 = "c31735649e4f441bcbb4fd0f379574f7520b42286e80b01d80b445649d54761f" + FILE_NAME = "funsd.zip" def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[tf.Tensor], tf.Tensor]] = None, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + super().__init__( + self.URL, + self.FILE_NAME, + self.SHA256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - super().__init__(self.URL, self.FILE_NAME, self.SHA256, True, **kwargs) self.train = train - self.sample_transforms = (lambda x: x) if sample_transforms is None else sample_transforms + np_dtype = np.float32 # Use the subset - subfolder = os.path.join('dataset', 'training_data' if train else 'testing_data') + subfolder = os.path.join("dataset", "training_data" if train else "testing_data") # # List images - self.root = os.path.join(self._root, subfolder, 'images') - self.data: List[Tuple[str, Dict[str, Any]]] = [] - for img_path in os.listdir(self.root): + tmp_root = os.path.join(self.root, subfolder, "images") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking FUNSD", total=len(os.listdir(tmp_root))): + # File existence check + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem - with open(os.path.join(self._root, subfolder, 'annotations', f"{stem}.json"), 'rb') as f: + with open(os.path.join(self.root, subfolder, "annotations", f"{stem}.json"), "rb") as f: data = json.load(f) - _targets = [(word['text'], word['box']) for block in data['form'] - for word in block['words'] if len(word['text']) > 0] - + _targets = [ + (word["text"], word["box"]) + for block in data["form"] + for word in block["words"] + if len(word["text"]) > 0 + ] text_targets, box_targets = zip(*_targets) - - self.data.append((img_path, dict(boxes=np.asarray(box_targets, dtype=np.int), labels=text_targets))) + if use_polygons: + # xmin, ymin, xmax, ymax -> (x, y) coordinates of top left, top right, bottom right, bottom left corners + box_targets = [ # type: ignore[assignment] + [ + [box[0], box[1]], + [box[2], box[1]], + [box[2], box[3]], + [box[0], box[3]], + ] + for box in box_targets + ] + + if recognition_task: + crops = crop_bboxes_from_image( + img_path=os.path.join(tmp_root, img_path), geoms=np.asarray(box_targets, dtype=np_dtype) + ) + for crop, label in zip(crops, list(text_targets)): + # filter labels with unknown characters + if not any(char in label for char in ["☑", "☐", "\uf703", "\uf702"]): + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, np.asarray(box_targets, dtype=np_dtype))) + else: + self.data.append(( + img_path, + dict(boxes=np.asarray(box_targets, dtype=np_dtype), labels=list(text_targets)), + )) + + self.root = tmp_root def extra_repr(self) -> str: - return f"train={self.train}" - - def __getitem__(self, index: int) -> Tuple[tf.Tensor, Dict[str, Any]]: - img_name, target = self.data[index] - # Read image - img = tf.io.read_file(os.path.join(self.root, img_name)) - img = tf.image.decode_jpeg(img, channels=3) - img = self.sample_transforms(img) - - return img, target - - @staticmethod - def collate_fn(samples: List[Tuple[tf.Tensor, Dict[str, Any]]]) -> Tuple[tf.Tensor, List[Dict[str, Any]]]: - - images, targets = zip(*samples) - images = tf.stack(images, axis=0) - - return images, list(targets)
    + return f"train={self.train}"
    @@ -388,8 +453,8 @@

    Source code for doctr.datasets.funsd

           
         
       
    - - + + diff --git a/v0.2.0/_modules/doctr/datasets/generator/tensorflow.html b/v0.2.0/_modules/doctr/datasets/generator/tensorflow.html index 9f562582d9..a3e619f720 100644 --- a/v0.2.0/_modules/doctr/datasets/generator/tensorflow.html +++ b/v0.2.0/_modules/doctr/datasets/generator/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.datasets.generator.tensorflow - docTR documentation @@ -395,7 +395,7 @@

    Source code for doctr.datasets.generator.tensorflow

    - + diff --git a/v0.2.0/_modules/doctr/datasets/ic03.html b/v0.2.0/_modules/doctr/datasets/ic03.html index 3d221d07de..60e54a8a4b 100644 --- a/v0.2.0/_modules/doctr/datasets/ic03.html +++ b/v0.2.0/_modules/doctr/datasets/ic03.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic03 - docTR documentation @@ -468,7 +468,7 @@

    Source code for doctr.datasets.ic03

         
       
    - + diff --git a/v0.2.0/_modules/doctr/datasets/ic13.html b/v0.2.0/_modules/doctr/datasets/ic13.html index 8137e08e9f..219c98dcd1 100644 --- a/v0.2.0/_modules/doctr/datasets/ic13.html +++ b/v0.2.0/_modules/doctr/datasets/ic13.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic13 - docTR documentation @@ -440,7 +440,7 @@

    Source code for doctr.datasets.ic13

         
       
    - + diff --git a/v0.2.0/_modules/doctr/datasets/iiit5k.html b/v0.2.0/_modules/doctr/datasets/iiit5k.html index 1fc8ecfb27..b49c80fe18 100644 --- a/v0.2.0/_modules/doctr/datasets/iiit5k.html +++ b/v0.2.0/_modules/doctr/datasets/iiit5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiit5k - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.datasets.iiit5k

         
       
    - + diff --git a/v0.2.0/_modules/doctr/datasets/iiithws.html b/v0.2.0/_modules/doctr/datasets/iiithws.html index 07f5b13685..f7220afbc7 100644 --- a/v0.2.0/_modules/doctr/datasets/iiithws.html +++ b/v0.2.0/_modules/doctr/datasets/iiithws.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiithws - docTR documentation @@ -407,7 +407,7 @@

    Source code for doctr.datasets.iiithws

         
       
    - + diff --git a/v0.2.0/_modules/doctr/datasets/imgur5k.html b/v0.2.0/_modules/doctr/datasets/imgur5k.html index 68d433ca62..51c6545db4 100644 --- a/v0.2.0/_modules/doctr/datasets/imgur5k.html +++ b/v0.2.0/_modules/doctr/datasets/imgur5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.imgur5k - docTR documentation @@ -488,7 +488,7 @@

    Source code for doctr.datasets.imgur5k

         
       
    - + diff --git a/v0.2.0/_modules/doctr/datasets/loader.html b/v0.2.0/_modules/doctr/datasets/loader.html index 5108e3b731..ed80350ef0 100644 --- a/v0.2.0/_modules/doctr/datasets/loader.html +++ b/v0.2.0/_modules/doctr/datasets/loader.html @@ -13,7 +13,7 @@ - + doctr.datasets.loader - docTR documentation @@ -225,15 +225,42 @@

    Source code for doctr.datasets.loader

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import math
    -import tensorflow as tf
    -import numpy as np
    -from typing import List, Tuple, Dict, Any, Optional
    +from typing import Callable, Optional
     
    -from .multithreading import multithread_exec
    +import numpy as np
    +import tensorflow as tf
     
     __all__ = ["DataLoader"]
     
    @@ -288,12 +314,13 @@ 

    Source code for doctr.datasets.loader

         """Collate multiple elements into batches
     
         Args:
    +    ----
             samples: list of N tuples containing M elements
     
         Returns:
    +    -------
             Tuple of M sequences contianing N elements each
         """
    -
         batch_data = zip(*samples)
     
         tf_data = tuple(tf.stack(elt, axis=0) for elt in batch_data)
    @@ -302,23 +329,23 @@ 

    Source code for doctr.datasets.loader

     
     
     
    -[docs] +[docs] class DataLoader: """Implements a dataset wrapper for fast data loading - Example:: - >>> from doctr.datasets import FUNSD, 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) + >>> 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) Args: + ---- dataset: the dataset shuffle: whether the samples should be shuffled before passing it to the iterator batch_size: number of elements in each batch drop_last: if `True`, drops the last batch if it isn't full - workers: number of workers to use for data loading + collate_fn: function to merge samples into a batch """ def __init__( @@ -327,17 +354,22 @@

    Source code for doctr.datasets.loader

             shuffle: bool = True,
             batch_size: int = 1,
             drop_last: bool = False,
    -        workers: Optional[int] = None,
    +        collate_fn: Optional[Callable] = None,
         ) -> None:
             self.dataset = dataset
             self.shuffle = shuffle
             self.batch_size = batch_size
             nb = len(self.dataset) / batch_size
             self.num_batches = math.floor(nb) if drop_last else math.ceil(nb)
    -        self.collate_fn = self.dataset.collate_fn if hasattr(self.dataset, 'collate_fn') else default_collate
    -        self.workers = workers
    +        if collate_fn is None:
    +            self.collate_fn = self.dataset.collate_fn if hasattr(self.dataset, "collate_fn") else default_collate
    +        else:
    +            self.collate_fn = collate_fn
             self.reset()
     
    +    def __len__(self) -> int:
    +        return self.num_batches
    +
         def reset(self) -> None:
             # Updates indices after each epoch
             self._num_yielded = 0
    @@ -353,9 +385,9 @@ 

    Source code for doctr.datasets.loader

             if self._num_yielded < self.num_batches:
                 # Get next indices
                 idx = self._num_yielded * self.batch_size
    -            indices = self.indices[idx: min(len(self.dataset), idx + self.batch_size)]
    +            indices = self.indices[idx : min(len(self.dataset), idx + self.batch_size)]
     
    -            samples = multithread_exec(self.dataset.__getitem__, indices, threads=self.workers)
    +            samples = list(map(self.dataset.__getitem__, indices))
     
                 batch_data = self.collate_fn(samples)
     
    @@ -396,8 +428,8 @@ 

    Source code for doctr.datasets.loader

           
         
       
    -
    - +
    + diff --git a/v0.2.0/_modules/doctr/datasets/mjsynth.html b/v0.2.0/_modules/doctr/datasets/mjsynth.html index 77bb01d523..df34e49cf9 100644 --- a/v0.2.0/_modules/doctr/datasets/mjsynth.html +++ b/v0.2.0/_modules/doctr/datasets/mjsynth.html @@ -13,7 +13,7 @@ - + doctr.datasets.mjsynth - docTR documentation @@ -438,7 +438,7 @@

    Source code for doctr.datasets.mjsynth

         
       
    - + diff --git a/v0.2.0/_modules/doctr/datasets/ocr.html b/v0.2.0/_modules/doctr/datasets/ocr.html index 5832933ea5..ce1ed8b0d4 100644 --- a/v0.2.0/_modules/doctr/datasets/ocr.html +++ b/v0.2.0/_modules/doctr/datasets/ocr.html @@ -13,7 +13,7 @@ - + doctr.datasets.ocr - docTR documentation @@ -403,7 +403,7 @@

    Source code for doctr.datasets.ocr

         
       
    - + diff --git a/v0.2.0/_modules/doctr/datasets/recognition.html b/v0.2.0/_modules/doctr/datasets/recognition.html index 512c70c308..1754789364 100644 --- a/v0.2.0/_modules/doctr/datasets/recognition.html +++ b/v0.2.0/_modules/doctr/datasets/recognition.html @@ -13,7 +13,7 @@ - + doctr.datasets.recognition - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.datasets.recognition

         
       
    - + diff --git a/v0.2.0/_modules/doctr/datasets/sroie.html b/v0.2.0/_modules/doctr/datasets/sroie.html index 97f29ccdda..04cf10bda2 100644 --- a/v0.2.0/_modules/doctr/datasets/sroie.html +++ b/v0.2.0/_modules/doctr/datasets/sroie.html @@ -13,7 +13,7 @@ - + doctr.datasets.sroie - docTR documentation @@ -225,15 +225,42 @@

    Source code for doctr.datasets.sroie

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import csv
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    -import tensorflow as tf
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
    -from doctr.documents.reader import read_img
    -from .core import VisionDataset
    +from .datasets import VisionDataset
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['SROIE']
    +__all__ = ["SROIE"]
     
     
     
    -[docs] +[docs] class SROIE(VisionDataset): """SROIE dataset from `"ICDAR2019 Competition on Scanned Receipt OCR and Information Extraction" <https://arxiv.org/pdf/2103.10213.pdf>`_. - Example:: - >>> from doctr.datasets import SROIE - >>> train_set = SROIE(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/sroie-grid.png&src=0 + :align: center + + >>> from doctr.datasets import SROIE + >>> train_set = SROIE(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - TRAIN = ('https://github.com/mindee/doctr/releases/download/v0.1.1/sroie2019_train_task1.zip', - 'd4fa9e60abb03500d83299c845b9c87fd9c9430d1aeac96b83c5d0bb0ab27f6f') - TEST = ('https://github.com/mindee/doctr/releases/download/v0.1.1/sroie2019_test.zip', - '41b3c746a20226fddc80d86d4b2a903d43b5be4f521dd1bbe759dbf8844745e2') + TRAIN = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/sroie2019_train_task1.zip&src=0", + "d4fa9e60abb03500d83299c845b9c87fd9c9430d1aeac96b83c5d0bb0ab27f6f", + "sroie2019_train_task1.zip", + ) + TEST = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/sroie2019_test.zip&src=0", + "41b3c746a20226fddc80d86d4b2a903d43b5be4f521dd1bbe759dbf8844745e2", + "sroie2019_test.zip", + ) def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[tf.Tensor], tf.Tensor]] = None, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + url, sha256, name = self.TRAIN if train else self.TEST + super().__init__( + url, + name, + sha256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - url, sha256 = self.TRAIN if train else self.TEST - super().__init__(url, None, sha256, True, **kwargs) - self.sample_transforms = (lambda x: x) if sample_transforms is None else sample_transforms self.train = train - # # List images - self.root = os.path.join(self._root, 'images') - self.data: List[Tuple[str, Dict[str, Any]]] = [] - for img_path in os.listdir(self.root): - stem = Path(img_path).stem - _targets = [] - with open(os.path.join(self._root, 'annotations', f"{stem}.txt"), encoding='latin') as f: - for row in csv.reader(f, delimiter=','): - # Safeguard for blank lines - if len(row) > 0: - # Label may contain commas - label = ",".join(row[8:]) - # Reduce 8 coords to 4 - p1_x, p1_y, p2_x, p2_y, p3_x, p3_y, p4_x, p4_y = map(int, row[:8]) - left, right = min(p1_x, p2_x, p3_x, p4_x), max(p1_x, p2_x, p3_x, p4_x) - top, bot = min(p1_y, p2_y, p3_y, p4_y), max(p1_y, p2_y, p3_y, p4_y) - if len(label) > 0: - _targets.append((label, [left, top, right, bot])) - - text_targets, box_targets = zip(*_targets) - - self.data.append((img_path, dict(boxes=np.asarray(box_targets, dtype=np.float32), labels=text_targets))) + tmp_root = os.path.join(self.root, "images") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] + np_dtype = np.float32 - def extra_repr(self) -> str: - return f"train={self.train}" + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking SROIE", total=len(os.listdir(tmp_root))): + # File existence check + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") - def __getitem__(self, index: int) -> Tuple[tf.Tensor, Dict[str, Any]]: - img_name, target = self.data[index] - # Read image - img = tf.io.read_file(os.path.join(self.root, img_name)) - img = tf.image.decode_jpeg(img, channels=3) - img = self.sample_transforms(img) - - return img, target - - @staticmethod - def collate_fn(samples: List[Tuple[tf.Tensor, Dict[str, Any]]]) -> Tuple[tf.Tensor, List[Dict[str, Any]]]: - - images, targets = zip(*samples) - images = tf.stack(images, axis=0) + stem = Path(img_path).stem + with open(os.path.join(self.root, "annotations", f"{stem}.txt"), encoding="latin") as f: + _rows = [row for row in list(csv.reader(f, delimiter=",")) if len(row) > 0] + + labels = [",".join(row[8:]) for row in _rows] + # reorder coordinates (8 -> (4,2) -> + # (x, y) coordinates of top left, top right, bottom right, bottom left corners) and filter empty lines + coords: np.ndarray = np.stack( + [np.array(list(map(int, row[:8])), dtype=np_dtype).reshape((4, 2)) for row in _rows], axis=0 + ) + + if not use_polygons: + # xmin, ymin, xmax, ymax + coords = np.concatenate((coords.min(axis=1), coords.max(axis=1)), axis=1) + + if recognition_task: + crops = crop_bboxes_from_image(img_path=os.path.join(tmp_root, img_path), geoms=coords) + for crop, label in zip(crops, labels): + if crop.shape[0] > 0 and crop.shape[1] > 0 and len(label) > 0: + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, coords)) + else: + self.data.append((img_path, dict(boxes=coords, labels=labels))) + + self.root = tmp_root - return images, list(targets)
    + def extra_repr(self) -> str: + return f"train={self.train}"
    @@ -396,8 +444,8 @@

    Source code for doctr.datasets.sroie

           
         
       
    - - + + diff --git a/v0.2.0/_modules/doctr/datasets/svhn.html b/v0.2.0/_modules/doctr/datasets/svhn.html index 48e4e4d210..60e02b1b3b 100644 --- a/v0.2.0/_modules/doctr/datasets/svhn.html +++ b/v0.2.0/_modules/doctr/datasets/svhn.html @@ -13,7 +13,7 @@ - + doctr.datasets.svhn - docTR documentation @@ -473,7 +473,7 @@

    Source code for doctr.datasets.svhn

         
       
    - + diff --git a/v0.2.0/_modules/doctr/datasets/svt.html b/v0.2.0/_modules/doctr/datasets/svt.html index 4144dc6b9b..a997fcbb50 100644 --- a/v0.2.0/_modules/doctr/datasets/svt.html +++ b/v0.2.0/_modules/doctr/datasets/svt.html @@ -13,7 +13,7 @@ - + doctr.datasets.svt - docTR documentation @@ -459,7 +459,7 @@

    Source code for doctr.datasets.svt

         
       
    - + diff --git a/v0.2.0/_modules/doctr/datasets/synthtext.html b/v0.2.0/_modules/doctr/datasets/synthtext.html index 3b9de506a7..c776e1d673 100644 --- a/v0.2.0/_modules/doctr/datasets/synthtext.html +++ b/v0.2.0/_modules/doctr/datasets/synthtext.html @@ -13,7 +13,7 @@ - + doctr.datasets.synthtext - docTR documentation @@ -470,7 +470,7 @@

    Source code for doctr.datasets.synthtext

         
       
    - + diff --git a/v0.2.0/_modules/doctr/datasets/utils.html b/v0.2.0/_modules/doctr/datasets/utils.html index aedf276e89..bde9304597 100644 --- a/v0.2.0/_modules/doctr/datasets/utils.html +++ b/v0.2.0/_modules/doctr/datasets/utils.html @@ -13,7 +13,7 @@ - + doctr.datasets.utils - docTR documentation @@ -225,15 +225,42 @@

    Source code for doctr.datasets.utils

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import string
     import unicodedata
    +from collections.abc import Sequence
    +from functools import partial
    +from pathlib import Path
    +from typing import Any, Dict, List, Optional, Tuple, TypeVar, Union
    +from typing import Sequence as SequenceType
    +
     import numpy as np
    -from typing import List, Optional, Any
    +from PIL import Image
    +
    +from doctr.io.image import get_img_shape
    +from doctr.utils.geometry import convert_to_relative_coords, extract_crops, extract_rcrops
     
     from .vocabs import VOCABS
     
    -__all__ = ['translate', 'encode_sequence', 'decode_sequence', 'encode_sequences']
    +__all__ = ["translate", "encode_string", "decode_sequence", "encode_sequences", "pre_transform_multiclass"]
    +
    +ImageTensor = TypeVar("ImageTensor")
     
     
     def translate(
         input_string: str,
         vocab_name: str,
    -    unknown_char: str = '■',
    +    unknown_char: str = "■",
     ) -> str:
         """Translate a string input in a given vocabulary
     
         Args:
    +    ----
             input_string: input string to translate
             vocab_name: vocabulary to use (french, latin, ...)
             unknown_char: unknown character for non-translatable characters
     
         Returns:
    -        A string translated in a given vocab"""
    -
    +    -------
    +        A string translated in a given vocab
    +    """
         if VOCABS.get(vocab_name) is None:
             raise KeyError("output vocabulary must be in vocabs dictionnary")
     
    -    translated = ''
    +    translated = ""
         for char in input_string:
             if char not in VOCABS[vocab_name]:
                 # we need to translate char into a vocab char
    @@ -310,85 +350,177 @@ 

    Source code for doctr.datasets.utils

                     # remove whitespaces
                     continue
                 # normalize character if it is not in vocab
    -            char = unicodedata.normalize('NFD', char).encode('ascii', 'ignore').decode('ascii')
    -            if char == '' or char not in VOCABS[vocab_name]:
    +            char = unicodedata.normalize("NFD", char).encode("ascii", "ignore").decode("ascii")
    +            if char == "" or char not in VOCABS[vocab_name]:
                     # if normalization fails or char still not in vocab, return unknown character)
                     char = unknown_char
             translated += char
         return translated
     
     
    -def encode_sequence(
    +def encode_string(
         input_string: str,
         vocab: str,
    -) -> List[str]:
    +) -> List[int]:
         """Given a predefined mapping, encode the string to a sequence of numbers
     
         Args:
    +    ----
             input_string: string to encode
             vocab: vocabulary (string), the encoding is given by the indexing of the character sequence
     
         Returns:
    -        A list encoding the input_string"""
    -
    -    return list(map(vocab.index, input_string))
    +    -------
    +        A list encoding the input_string
    +    """
    +    try:
    +        return list(map(vocab.index, input_string))
    +    except ValueError:
    +        raise ValueError(
    +            f"some characters cannot be found in 'vocab'. \
    +                         Please check the input string {input_string} and the vocabulary {vocab}"
    +        )
     
     
     def decode_sequence(
    -    input_array: np.array,
    +    input_seq: Union[np.ndarray, SequenceType[int]],
         mapping: str,
     ) -> str:
         """Given a predefined mapping, decode the sequence of numbers to a string
     
         Args:
    -        input_array: array to decode
    +    ----
    +        input_seq: array to decode
             mapping: vocabulary (string), the encoding is given by the indexing of the character sequence
     
         Returns:
    -        A string, decoded from input_array"""
    -
    -    if not input_array.dtype == np.int_ or input_array.max() >= len(mapping):
    +    -------
    +        A string, decoded from input_seq
    +    """
    +    if not isinstance(input_seq, (Sequence, np.ndarray)):
    +        raise TypeError("Invalid sequence type")
    +    if isinstance(input_seq, np.ndarray) and (input_seq.dtype != np.int_ or input_seq.max() >= len(mapping)):
             raise AssertionError("Input must be an array of int, with max less than mapping size")
    -    decoded = ''.join(mapping[idx] for idx in input_array)
    -    return decoded
    +
    +    return "".join(map(mapping.__getitem__, input_seq))
     
     
     
    -[docs] +[docs] def encode_sequences( sequences: List[str], vocab: str, target_size: Optional[int] = None, eos: int = -1, - **kwargs: Any, + sos: Optional[int] = None, + pad: Optional[int] = None, + dynamic_seq_length: bool = False, ) -> np.ndarray: """Encode character sequences using a given vocab as mapping Args: + ---- sequences: the list of character sequences of size N vocab: the ordered vocab to use for encoding target_size: maximum length of the encoded data eos: encoding of End Of String + sos: optional encoding of Start Of String + pad: optional encoding for padding. In case of padding, all sequences are followed by 1 EOS then PAD + dynamic_seq_length: if `target_size` is specified, uses it as upper bound and enables dynamic sequence size Returns: + ------- the padded encoded data as a tensor """ - if 0 <= eos < len(vocab): raise ValueError("argument 'eos' needs to be outside of vocab possible indices") - if not isinstance(target_size, int): - target_size = max(len(w) for w in sequences) + if not isinstance(target_size, int) or dynamic_seq_length: + # Maximum string length + EOS + max_length = max(len(w) for w in sequences) + 1 + if isinstance(sos, int): + max_length += 1 + if isinstance(pad, int): + max_length += 1 + target_size = max_length if not isinstance(target_size, int) else min(max_length, target_size) # Pad all sequences - encoded_data = np.full([len(sequences), target_size], eos, dtype=np.int32) - - for idx, seq in enumerate(sequences): - encoded_seq = encode_sequence(seq, vocab) - encoded_data[idx, :min(len(encoded_seq), target_size)] = encoded_seq[:min(len(encoded_seq), target_size)] + if isinstance(pad, int): # pad with padding symbol + if 0 <= pad < len(vocab): + raise ValueError("argument 'pad' needs to be outside of vocab possible indices") + # In that case, add EOS at the end of the word before padding + default_symbol = pad + else: # pad with eos symbol + default_symbol = eos + encoded_data: np.ndarray = np.full([len(sequences), target_size], default_symbol, dtype=np.int32) + + # Encode the strings + for idx, seq in enumerate(map(partial(encode_string, vocab=vocab), sequences)): + if isinstance(pad, int): # add eos at the end of the sequence + seq.append(eos) + encoded_data[idx, : min(len(seq), target_size)] = seq[: min(len(seq), target_size)] + + if isinstance(sos, int): # place sos symbol at the beginning of each sequence + if 0 <= sos < len(vocab): + raise ValueError("argument 'sos' needs to be outside of vocab possible indices") + encoded_data = np.roll(encoded_data, 1) + encoded_data[:, 0] = sos return encoded_data
    + + +def convert_target_to_relative( + img: ImageTensor, target: Union[np.ndarray, Dict[str, Any]] +) -> Tuple[ImageTensor, Union[Dict[str, Any], np.ndarray]]: + if isinstance(target, np.ndarray): + target = convert_to_relative_coords(target, get_img_shape(img)) + else: + target["boxes"] = convert_to_relative_coords(target["boxes"], get_img_shape(img)) + return img, target + + +def crop_bboxes_from_image(img_path: Union[str, Path], geoms: np.ndarray) -> List[np.ndarray]: + """Crop a set of bounding boxes from an image + + Args: + ---- + img_path: path to the image + geoms: a array of polygons of shape (N, 4, 2) or of straight boxes of shape (N, 4) + + Returns: + ------- + a list of cropped images + """ + with Image.open(img_path) as pil_img: + img: np.ndarray = np.array(pil_img.convert("RGB")) + # Polygon + if geoms.ndim == 3 and geoms.shape[1:] == (4, 2): + return extract_rcrops(img, geoms.astype(dtype=int)) + if geoms.ndim == 2 and geoms.shape[1] == 4: + return extract_crops(img, geoms.astype(dtype=int)) + raise ValueError("Invalid geometry format") + + +def pre_transform_multiclass(img, target: Tuple[np.ndarray, List]) -> Tuple[np.ndarray, Dict[str, List]]: + """Converts multiclass target to relative coordinates. + + Args: + ---- + img: Image + target: tuple of target polygons and their classes names + + Returns: + ------- + Image and dictionary of boxes, with class names as keys + """ + boxes = convert_to_relative_coords(target[0], get_img_shape(img)) + boxes_classes = target[1] + boxes_dict: Dict = {k: [] for k in sorted(set(boxes_classes))} + for k, poly in zip(boxes_classes, boxes): + boxes_dict[k].append(poly) + boxes_dict = {k: np.stack(v, axis=0) for k, v in boxes_dict.items()} + return img, boxes_dict
    @@ -421,8 +553,8 @@

    Source code for doctr.datasets.utils

           
         
       
    - - + + diff --git a/v0.2.0/_modules/doctr/datasets/wildreceipt.html b/v0.2.0/_modules/doctr/datasets/wildreceipt.html index c543ee7cac..12c6aebd14 100644 --- a/v0.2.0/_modules/doctr/datasets/wildreceipt.html +++ b/v0.2.0/_modules/doctr/datasets/wildreceipt.html @@ -13,7 +13,7 @@ - + doctr.datasets.wildreceipt - docTR documentation @@ -454,7 +454,7 @@

    Source code for doctr.datasets.wildreceipt

         
       
    - + diff --git a/v0.2.0/_modules/doctr/documents/elements.html b/v0.2.0/_modules/doctr/documents/elements.html deleted file mode 100644 index df3a989d4a..0000000000 --- a/v0.2.0/_modules/doctr/documents/elements.html +++ /dev/null @@ -1,550 +0,0 @@ - - - - - - - - - - - - doctr.documents.elements - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.elements

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import matplotlib.pyplot as plt
    -from typing import Tuple, Dict, List, Any, Optional
    -
    -from doctr.utils.geometry import resolve_enclosing_bbox
    -from doctr.utils.visualization import visualize_page
    -from doctr.utils.common_types import BoundingBox
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['Element', 'Word', 'Artefact', 'Line', 'Block', 'Page', 'Document']
    -
    -
    -class Element(NestedObject):
    -    """Implements an abstract document element with exporting and text rendering capabilities"""
    -
    -    _exported_keys: List[str] = []
    -
    -    def __init__(self, **kwargs: Any) -> None:
    -        self._children_names: List[str] = []
    -        for k, v in kwargs.items():
    -            setattr(self, k, v)
    -            self._children_names.append(k)
    -
    -    def export(self) -> Dict[str, Any]:
    -        """Exports the object into a nested dict format"""
    -
    -        export_dict = {k: getattr(self, k) for k in self._exported_keys}
    -        for children_name in self._children_names:
    -            export_dict[children_name] = [c.export() for c in getattr(self, children_name)]
    -
    -        return export_dict
    -
    -    def render(self) -> str:
    -        raise NotImplementedError
    -
    -
    -
    -[docs] -class Word(Element): - """Implements a word element - - Args: - value: the text string of the word - confidence: the confidence associated with the text prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size - """ - - _exported_keys: List[str] = ["value", "confidence", "geometry"] - - def __init__(self, value: str, confidence: float, geometry: BoundingBox) -> None: - super().__init__() - self.value = value - self.confidence = confidence - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return self.value - - def extra_repr(self) -> str: - return f"value='{self.value}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Artefact(Element): - """Implements a non-textual element - - Args: - artefact_type: the type of artefact - confidence: the confidence of the type prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. - """ - - _exported_keys: List[str] = ["geometry", "type", "confidence"] - - def __init__(self, artefact_type: str, confidence: float, geometry: BoundingBox) -> None: - super().__init__() - self.geometry = geometry - self.type = artefact_type - self.confidence = confidence - - def render(self) -> str: - """Renders the full text of the element""" - return f"[{self.type.upper()}]" - - def extra_repr(self) -> str: - return f"type='{self.type}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Line(Element): - """Implements a line element as a collection of words - - Args: - words: list of word elements - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all words in it. - """ - - _exported_keys: List[str] = ["geometry"] - words: List[Word] = [] - - def __init__( - self, - words: List[Word], - geometry: Optional[BoundingBox] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - geometry = resolve_enclosing_bbox([w.geometry for w in words]) - - super().__init__(words=words) - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return " ".join(w.render() for w in self.words)
    - - - -
    -[docs] -class Block(Element): - """Implements a block element as a collection of lines and artefacts - - Args: - lines: list of line elements - artefacts: list of artefacts - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all lines and artefacts in it. - """ - - _exported_keys: List[str] = ["geometry"] - lines: List[Line] = [] - artefacts: List[Artefact] = [] - - def __init__( - self, - lines: List[Line] = [], - artefacts: List[Artefact] = [], - geometry: Optional[BoundingBox] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - line_boxes = [word.geometry for line in lines for word in line.words] - artefact_boxes = [artefact.geometry for artefact in artefacts] - geometry = resolve_enclosing_bbox(line_boxes + artefact_boxes) - super().__init__(lines=lines, artefacts=artefacts) - self.geometry = geometry - - def render(self, line_break: str = '\n') -> str: - """Renders the full text of the element""" - return line_break.join(line.render() for line in self.lines)
    - - - -
    -[docs] -class Page(Element): - """Implements a page element as a collection of blocks - - Args: - blocks: list of block elements - page_idx: the index of the page in the input raw document - dimensions: the page size in pixels in format (width, height) - orientation: a dictionary with the value of the rotation angle in degress and confidence of the prediction - language: a dictionary with the language value and confidence of the prediction - """ - - _exported_keys: List[str] = ["page_idx", "dimensions", "orientation", "language"] - blocks: List[Block] = [] - - def __init__( - self, - blocks: List[Block], - page_idx: int, - dimensions: Tuple[int, int], - orientation: Optional[Dict[str, Any]] = None, - language: Optional[Dict[str, Any]] = None, - ) -> None: - super().__init__(blocks=blocks) - self.page_idx = page_idx - self.dimensions = dimensions - self.orientation = orientation if isinstance(orientation, dict) else dict(value=None, confidence=None) - self.language = language if isinstance(language, dict) else dict(value=None, confidence=None) - - def render(self, block_break: str = '\n\n') -> str: - """Renders the full text of the element""" - return block_break.join(b.render() for b in self.blocks) - - def extra_repr(self) -> str: - return f"dimensions={self.dimensions}" - - def show(self, page: np.ndarray, interactive: bool = True, **kwargs) -> None: - visualize_page(self.export(), page, interactive=interactive) - plt.show(**kwargs)
    - - - -
    -[docs] -class Document(Element): - """Implements a document element as a collection of pages - - Args: - pages: list of page elements - """ - - pages: List[Page] = [] - - def __init__( - self, - pages: List[Page], - ) -> None: - super().__init__(pages=pages) - - def render(self, page_break: str = '\n\n\n\n') -> str: - """Renders the full text of the element""" - return page_break.join(p.render() for p in self.pages) - - def show(self, pages: List[np.ndarray], **kwargs) -> None: - """Plot the results""" - for img, result in zip(pages, self.pages): - result.show(img, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.0/_modules/doctr/documents/reader.html b/v0.2.0/_modules/doctr/documents/reader.html deleted file mode 100644 index 43865531a4..0000000000 --- a/v0.2.0/_modules/doctr/documents/reader.html +++ /dev/null @@ -1,606 +0,0 @@ - - - - - - - - - - - - doctr.documents.reader - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.reader

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import cv2
    -from pathlib import Path
    -import fitz
    -from weasyprint import HTML
    -from typing import List, Tuple, Optional, Any, Union, Sequence
    -
    -__all__ = ['read_pdf', 'read_img', 'read_html', 'DocumentFile', 'PDF']
    -
    -
    -AbstractPath = Union[str, Path]
    -AbstractFile = Union[AbstractPath, bytes]
    -Bbox = Tuple[float, float, float, float]
    -
    -
    -
    -[docs] -def read_img( - file: AbstractFile, - output_size: Optional[Tuple[int, int]] = None, - rgb_output: bool = True, -) -> np.ndarray: - """Read an image file into numpy format - - Example:: - >>> from doctr.documents import read_img - >>> page = read_img("path/to/your/doc.jpg") - - Args: - file: the path to the image file - output_size: the expected output size of each page in format H x W - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - Returns: - the page decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)): - if not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - img = cv2.imread(str(file), cv2.IMREAD_COLOR) - elif isinstance(file, bytes): - file = np.frombuffer(file, np.uint8) - img = cv2.imdecode(file, cv2.IMREAD_COLOR) - else: - raise TypeError("unsupported object type for argument 'file'") - - # Validity check - if img is None: - raise ValueError("unable to read file.") - # Resizing - if isinstance(output_size, tuple): - img = cv2.resize(img, output_size[::-1], interpolation=cv2.INTER_LINEAR) - # Switch the channel order - if rgb_output: - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - return img
    - - - -
    -[docs] -def read_pdf(file: AbstractFile, **kwargs: Any) -> fitz.Document: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_pdf - >>> doc = read_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)) and not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - - fitz_args = {} - - if isinstance(file, (str, Path)): - fitz_args['filename'] = file - elif isinstance(file, bytes): - fitz_args['stream'] = file - else: - raise TypeError("unsupported object type for argument 'file'") - - # Read pages with fitz and convert them to numpy ndarrays - return fitz.open(**fitz_args, filetype="pdf", **kwargs)
    - - - -def convert_page_to_numpy( - page: fitz.fitz.Page, - output_size: Optional[Tuple[int, int]] = None, - rgb_output: bool = True, - default_scales: Tuple[float, float] = (2, 2), -) -> np.ndarray: - """Convert a fitz page to a numpy-formatted image - - Args: - page: the page of a file read with PyMuPDF - output_size: the expected output size of each page in format H x W. Default goes to 840 x 595 for A4 pdf, - if you want to increase the resolution while preserving the original A4 aspect ratio can pass (1024, 726) - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - default_scales: spatial scaling to be applied when output_size is not specified where (1, 1) - corresponds to 72 dpi rendering. - - Returns: - the rendered image in numpy format - """ - - # If no output size is specified, keep the origin one - if output_size is not None: - scales = (output_size[1] / page.MediaBox[2], output_size[0] / page.MediaBox[3]) - else: - # Default 72 DPI (scales of (1, 1)) is unnecessarily low - scales = default_scales - - transform_matrix = fitz.Matrix(*scales) - - # Generate the pixel map using the transformation matrix - pixmap = page.getPixmap(matrix=transform_matrix) - # Decode it into a numpy - img = np.frombuffer(pixmap.samples, dtype=np.uint8).reshape(pixmap.height, pixmap.width, 3) - - # Switch the channel order - if rgb_output: - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - - return img - - -
    -[docs] -def read_html(url: str, **kwargs: Any) -> bytes: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_html - >>> doc = read_html("https://www.yoursite.com") - - Args: - url: URL of the target web page - Returns: - decoded PDF file as a bytes stream - """ - - return HTML(url, **kwargs).write_pdf()
    - - - -
    -[docs] -class PDF: - """PDF document template - - Args: - doc: input PDF document - """ - def __init__(self, doc: fitz.Document) -> None: - self.doc = doc - -
    -[docs] - def as_images(self, **kwargs) -> List[np.ndarray]: - """Convert all document pages to images - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images() - - Args: - kwargs: keyword arguments of `convert_page_to_numpy` - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - return [convert_page_to_numpy(page, **kwargs) for page in self.doc]
    - - - def get_page_words(self, idx, **kwargs) -> List[Tuple[Bbox, str]]: - """Get the annotations for all words of a given page""" - - # xmin, ymin, xmax, ymax, value, block_idx, line_idx, word_idx - return [(info[:4], info[4]) for info in self.doc[idx].getTextWords(**kwargs)] - -
    -[docs] - def get_words(self, **kwargs) -> List[List[Tuple[Bbox, str]]]: - """Get the annotations for all words in the document - - Example:: - >>> from doctr.documents import DocumentFile - >>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words() - - Args: - kwargs: keyword arguments of `fitz.Page.getTextWords` - Returns: - the list of pages annotations, represented as a list of tuple (bounding box, value) - """ - return [self.get_page_words(idx, **kwargs) for idx in range(len(self.doc))]
    - - - def get_page_artefacts(self, idx) -> List[Tuple[float, float, float, float]]: - return [tuple(self.doc[idx].getImageBbox(artefact)) for artefact in self.doc[idx].get_images(full=True)] - -
    -[docs] - def get_artefacts(self) -> List[List[Tuple[float, float, float, float]]]: - """Get the artefacts for the entire document - - Example:: - >>> from doctr.documents import DocumentFile - >>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts() - - Returns: - the list of pages artefacts, represented as a list of bounding boxes - """ - - return [self.get_page_artefacts(idx) for idx in range(len(self.doc))]
    -
    - - - -
    -[docs] -class DocumentFile: - """Read a document from multiple extensions""" - -
    -[docs] - @classmethod - def from_pdf(cls, file: AbstractFile, **kwargs) -> PDF: - """Read a PDF file - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file or a binary stream - Returns: - a PDF document - """ - - doc = read_pdf(file, **kwargs) - - return PDF(doc)
    - - -
    -[docs] - @classmethod - def from_url(cls, url: str, **kwargs) -> PDF: - """Interpret a web page as a PDF document - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_url("https://www.yoursite.com") - - Args: - url: the URL of the target web page - Returns: - a PDF document - """ - pdf_stream = read_html(url) - return cls.from_pdf(pdf_stream, **kwargs)
    - - -
    -[docs] - @classmethod - def from_images(cls, files: Union[Sequence[AbstractFile], AbstractFile], **kwargs) -> List[np.ndarray]: - """Read an image file (or a collection of image files) and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"]) - - Args: - files: the path to the image file or a binary stream, or a collection of those - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - if isinstance(files, (str, Path, bytes)): - files = [files] - - return [read_img(file, **kwargs) for file in files]
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.0/_modules/doctr/io/elements.html b/v0.2.0/_modules/doctr/io/elements.html index 753a47455c..e049d6ce30 100644 --- a/v0.2.0/_modules/doctr/io/elements.html +++ b/v0.2.0/_modules/doctr/io/elements.html @@ -13,7 +13,7 @@ - + doctr.io.elements - docTR documentation @@ -1008,7 +1008,7 @@

    Source code for doctr.io.elements

         
       
    - + diff --git a/v0.2.0/_modules/doctr/io/html.html b/v0.2.0/_modules/doctr/io/html.html index 7ad5b97031..be73631500 100644 --- a/v0.2.0/_modules/doctr/io/html.html +++ b/v0.2.0/_modules/doctr/io/html.html @@ -13,7 +13,7 @@ - + doctr.io.html - docTR documentation @@ -360,7 +360,7 @@

    Source code for doctr.io.html

         
       
    - + diff --git a/v0.2.0/_modules/doctr/io/image/base.html b/v0.2.0/_modules/doctr/io/image/base.html index 336b4bff0e..a50c95d595 100644 --- a/v0.2.0/_modules/doctr/io/image/base.html +++ b/v0.2.0/_modules/doctr/io/image/base.html @@ -13,7 +13,7 @@ - + doctr.io.image.base - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.io.image.base

         
       
    - + diff --git a/v0.2.0/_modules/doctr/io/image/tensorflow.html b/v0.2.0/_modules/doctr/io/image/tensorflow.html index f1846820a3..3b9e731756 100644 --- a/v0.2.0/_modules/doctr/io/image/tensorflow.html +++ b/v0.2.0/_modules/doctr/io/image/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.io.image.tensorflow - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.io.image.tensorflow

         
       
    - + diff --git a/v0.2.0/_modules/doctr/io/pdf.html b/v0.2.0/_modules/doctr/io/pdf.html index e3abf6960b..e5b94811c3 100644 --- a/v0.2.0/_modules/doctr/io/pdf.html +++ b/v0.2.0/_modules/doctr/io/pdf.html @@ -13,7 +13,7 @@ - + doctr.io.pdf - docTR documentation @@ -377,7 +377,7 @@

    Source code for doctr.io.pdf

         
       
    - + diff --git a/v0.2.0/_modules/doctr/io/reader.html b/v0.2.0/_modules/doctr/io/reader.html index c1ddc26edd..d36e5bb553 100644 --- a/v0.2.0/_modules/doctr/io/reader.html +++ b/v0.2.0/_modules/doctr/io/reader.html @@ -13,7 +13,7 @@ - + doctr.io.reader - docTR documentation @@ -426,7 +426,7 @@

    Source code for doctr.io.reader

         
       
    - + diff --git a/v0.2.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html b/v0.2.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html index 9f074805c1..61a010d548 100644 --- a/v0.2.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html +++ b/v0.2.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.magc_resnet.tensorflow - docTR documentation @@ -531,7 +531,7 @@

    Source code for doctr.models.classification.magc_resnet.tensorflow

    - + diff --git a/v0.2.0/_modules/doctr/models/classification/mobilenet/tensorflow.html b/v0.2.0/_modules/doctr/models/classification/mobilenet/tensorflow.html index 6a63851276..7c448394ad 100644 --- a/v0.2.0/_modules/doctr/models/classification/mobilenet/tensorflow.html +++ b/v0.2.0/_modules/doctr/models/classification/mobilenet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.mobilenet.tensorflow - docTR documentation @@ -793,7 +793,7 @@

    Source code for doctr.models.classification.mobilenet.tensorflow

    - + diff --git a/v0.2.0/_modules/doctr/models/classification/resnet/tensorflow.html b/v0.2.0/_modules/doctr/models/classification/resnet/tensorflow.html index 095d377f31..aed4343741 100644 --- a/v0.2.0/_modules/doctr/models/classification/resnet/tensorflow.html +++ b/v0.2.0/_modules/doctr/models/classification/resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.resnet.tensorflow - docTR documentation @@ -749,7 +749,7 @@

    Source code for doctr.models.classification.resnet.tensorflow

    - + diff --git a/v0.2.0/_modules/doctr/models/classification/textnet/tensorflow.html b/v0.2.0/_modules/doctr/models/classification/textnet/tensorflow.html index ad254ebbfb..c5567d7d67 100644 --- a/v0.2.0/_modules/doctr/models/classification/textnet/tensorflow.html +++ b/v0.2.0/_modules/doctr/models/classification/textnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.textnet.tensorflow - docTR documentation @@ -611,7 +611,7 @@

    Source code for doctr.models.classification.textnet.tensorflow

    - + diff --git a/v0.2.0/_modules/doctr/models/classification/vgg/tensorflow.html b/v0.2.0/_modules/doctr/models/classification/vgg/tensorflow.html index 01ae452624..788111ae87 100644 --- a/v0.2.0/_modules/doctr/models/classification/vgg/tensorflow.html +++ b/v0.2.0/_modules/doctr/models/classification/vgg/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vgg.tensorflow - docTR documentation @@ -451,7 +451,7 @@

    Source code for doctr.models.classification.vgg.tensorflow

    - + diff --git a/v0.2.0/_modules/doctr/models/classification/vit/tensorflow.html b/v0.2.0/_modules/doctr/models/classification/vit/tensorflow.html index 1333cf6045..971ba5abe9 100644 --- a/v0.2.0/_modules/doctr/models/classification/vit/tensorflow.html +++ b/v0.2.0/_modules/doctr/models/classification/vit/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vit.tensorflow - docTR documentation @@ -533,7 +533,7 @@

    Source code for doctr.models.classification.vit.tensorflow

    - + diff --git a/v0.2.0/_modules/doctr/models/classification/zoo.html b/v0.2.0/_modules/doctr/models/classification/zoo.html index f7796a7522..3eb2a3ec4e 100644 --- a/v0.2.0/_modules/doctr/models/classification/zoo.html +++ b/v0.2.0/_modules/doctr/models/classification/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.classification.zoo - docTR documentation @@ -447,7 +447,7 @@

    Source code for doctr.models.classification.zoo

    <
    - + diff --git a/v0.2.0/_modules/doctr/models/detection/differentiable_binarization.html b/v0.2.0/_modules/doctr/models/detection/differentiable_binarization.html deleted file mode 100644 index aef0023c40..0000000000 --- a/v0.2.0/_modules/doctr/models/detection/differentiable_binarization.html +++ /dev/null @@ -1,876 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.differentiable_binarization - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.differentiable_binarization

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -import cv2
    -from copy import deepcopy
    -import numpy as np
    -from shapely.geometry import Polygon
    -import pyclipper
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers
    -from typing import Union, List, Tuple, Optional, Any, Dict
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..utils import IntermediateLayerGetter, load_pretrained_params, conv_sequence
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['DBPostProcessor', 'DBNet', 'db_resnet50']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'db_resnet50': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'backbone': 'ResNet50',
    -        'fpn_layers': ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"],
    -        'fpn_channels': 128,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'DBPostProcessor',
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.1/db_resnet50-98ba765d.zip',
    -    },
    -}
    -
    -
    -class DBPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for DBNet adapted from the implementation of `xuannianz
    -    <https://github.com/xuannianz/DifferentiableBinarization>`_.
    -
    -    Args:
    -        unclip ratio: ratio used to unshrink polygons
    -        min_size_box: minimal length (pix) to keep a box
    -        max_candidates: maximum boxes to consider in a single page
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        unclip_ratio: Union[float, int] = 1.5,
    -        max_candidates: int = 1000,
    -        box_thresh: float = 0.1,
    -        bin_thresh: float = 0.3,
    -    ) -> None:
    -
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -        self.unclip_ratio = unclip_ratio
    -        self.max_candidates = max_candidates
    -
    -    def polygon_to_box(
    -        self,
    -        points: np.ndarray,
    -    ) -> Optional[Tuple[int, int, int, int]]:
    -        """Expand a polygon (points) by a factor unclip_ratio, and returns a 4-points box
    -
    -        Args:
    -            points: The first parameter.
    -
    -        Returns:
    -            a box in absolute coordinates (x, y, w, h)
    -        """
    -        poly = Polygon(points)
    -        distance = poly.area * self.unclip_ratio / poly.length  # compute distance to expand polygon
    -        offset = pyclipper.PyclipperOffset()
    -        offset.AddPath(points, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        _points = offset.Execute(distance)
    -        # Take biggest stack of points
    -        idx = 0
    -        if len(_points) > 1:
    -            max_size = 0
    -            for _idx, p in enumerate(_points):
    -                if len(p) > max_size:
    -                    idx = _idx
    -                    max_size = len(p)
    -            # We ensure that _points can be correctly casted to a ndarray
    -            _points = [_points[idx]]
    -        expanded_points = np.asarray(_points)  # expand polygon
    -        if len(expanded_points) < 1:
    -            return None
    -        x, y, w, h = cv2.boundingRect(expanded_points)  # compute a 4-points box from expanded polygon
    -        return x, y, w, h
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map
    -
    -        Args:
    -            pred: Pred map from differentiable binarization output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        # get contours from connected components on the bitmap
    -        contours, _ = cv2.findContours(bitmap.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    -        for contour in contours[:self.max_candidates]:
    -            # Check whether smallest enclosing bounding box is not too small
    -            if np.any(contour[:, 0].max(axis=0) - contour[:, 0].min(axis=0) < min_size_box):
    -                continue
    -            epsilon = 0.01 * cv2.arcLength(contour, True)
    -            approx = cv2.approxPolyDP(contour, epsilon, True)  # approximate contour by a polygon
    -            points = approx.reshape((-1, 2))  # get polygon points
    -            if points.shape[0] < 4:  # remove polygons with 3 points or less
    -                continue
    -            score = self.box_score(pred, points.reshape(-1, 2))
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            _box = self.polygon_to_box(points)
    -
    -            if _box is None or _box[2] < min_size_box or _box[3] < min_size_box:  # remove to small boxes
    -                continue
    -            x, y, w, h = _box
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -class FeaturePyramidNetwork(layers.Layer, NestedObject):
    -    """Feature Pyramid Network as described in `"Feature Pyramid Networks for Object Detection"
    -    <https://arxiv.org/pdf/1612.03144.pdf>`_.
    -
    -    Args:
    -        channels: number of channel to output
    -    """
    -
    -    def __init__(
    -        self,
    -        channels: int,
    -    ) -> None:
    -        super().__init__()
    -        self.channels = channels
    -        self.upsample = layers.UpSampling2D(size=(2, 2), interpolation='nearest')
    -        self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer='he_normal') for _ in range(4)]
    -        self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2 ** idx) for idx in range(4)]
    -
    -    @staticmethod
    -    def build_upsampling(
    -        channels: int,
    -        dilation_factor: int = 1,
    -    ) -> layers.Layer:
    -        """Module which performs a 3x3 convolution followed by up-sampling
    -
    -        Args:
    -            channels: number of output channels
    -            dilation_factor (int): dilation factor to scale the convolution output before concatenation
    -
    -        Returns:
    -            a keras.layers.Layer object, wrapping these operations in a sequential module
    -
    -        """
    -
    -        _layers = conv_sequence(channels, 'relu', True, kernel_size=3)
    -
    -        if dilation_factor > 1:
    -            _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation='nearest'))
    -
    -        module = keras.Sequential(_layers)
    -
    -        return module
    -
    -    def extra_repr(self) -> str:
    -        return f"channels={self.channels}"
    -
    -    def call(
    -        self,
    -        x: List[tf.Tensor],
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # Channel mapping
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.inner_blocks, x)]
    -        # Upsample & sum
    -        for idx in range(len(results) - 1, -1):
    -            results[idx] += self.upsample(results[idx + 1])
    -        # Conv & upsample
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.layer_blocks, results)]
    -
    -        return layers.concatenate(results)
    -
    -
    -class DBNet(DetectionModel, NestedObject):
    -    """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization"
    -    <https://arxiv.org/pdf/1911.08947.pdf>`_.
    -
    -    Args:
    -        feature extractor: the backbone serving as feature extractor
    -        fpn_channels: number of channels each extracted feature maps is mapped to
    -    """
    -
    -    _children_names = ['feat_extractor', 'fpn', 'probability_head', 'threshold_head']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: IntermediateLayerGetter,
    -        fpn_channels: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(cfg=cfg)
    -
    -        self.shrink_ratio = 0.4
    -        self.thresh_min = 0.3
    -        self.thresh_max = 0.7
    -        self.min_size_box = 3
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.fpn = FeaturePyramidNetwork(channels=fpn_channels)
    -        # Initialize kernels
    -        _inputs = [layers.Input(shape=in_shape[1:]) for in_shape in self.feat_extractor.output_shape]
    -        output_shape = tuple(self.fpn(_inputs).shape)
    -
    -        self.probability_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -        self.threshold_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -
    -        self.postprocessor = DBPostProcessor()
    -
    -    @staticmethod
    -    def compute_distance(
    -        xs: np.array,
    -        ys: np.array,
    -        a: np.array,
    -        b: np.array,
    -        eps: float = 1e-7,
    -    ) -> float:
    -        """Compute the distance for each point of the map (xs, ys) to the (a, b) segment
    -
    -        Args:
    -            xs : map of x coordinates (height, width)
    -            ys : map of y coordinates (height, width)
    -            a: first point defining the [ab] segment
    -            b: second point defining the [ab] segment
    -
    -        Returns:
    -            The computed distance
    -
    -        """
    -        square_dist_1 = np.square(xs - a[0]) + np.square(ys - a[1])
    -        square_dist_2 = np.square(xs - b[0]) + np.square(ys - b[1])
    -        square_dist = np.square(a[0] - b[0]) + np.square(a[1] - b[1])
    -        cosin = (square_dist - square_dist_1 - square_dist_2) / (2 * np.sqrt(square_dist_1 * square_dist_2) + eps)
    -        square_sin = 1 - np.square(cosin)
    -        square_sin = np.nan_to_num(square_sin)
    -        result = np.sqrt(square_dist_1 * square_dist_2 * square_sin / square_dist)
    -        result[cosin < 0] = np.sqrt(np.fmin(square_dist_1, square_dist_2))[cosin < 0]
    -        return result
    -
    -    def draw_thresh_map(
    -        self,
    -        polygon: np.array,
    -        canvas: np.array,
    -        mask: np.array,
    -    ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    -        """Draw a polygon treshold map on a canvas, as described in the DB paper
    -
    -        Args:
    -            polygon : array of coord., to draw the boundary of the polygon
    -            canvas : threshold map to fill with polygons
    -            mask : mask for training on threshold polygons
    -        """
    -        if polygon.ndim != 2 or polygon.shape[1] != 2:
    -            raise AttributeError("polygon should be a 2 dimensional array of coords")
    -
    -        # Augment polygon by shrink_ratio
    -        polygon_shape = Polygon(polygon)
    -        distance = polygon_shape.area * (1 - np.power(self.shrink_ratio, 2)) / polygon_shape.length
    -        subject = [tuple(coor) for coor in polygon]  # Get coord as list of tuples
    -        padding = pyclipper.PyclipperOffset()
    -        padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        padded_polygon = np.array(padding.Execute(distance)[0])
    -
    -        # Fill the mask with 1 on the new padded polygon
    -        cv2.fillPoly(mask, [padded_polygon.astype(np.int32)], 1.0)
    -
    -        # Get min/max to recover polygon after distance computation
    -        xmin = padded_polygon[:, 0].min()
    -        xmax = padded_polygon[:, 0].max()
    -        ymin = padded_polygon[:, 1].min()
    -        ymax = padded_polygon[:, 1].max()
    -        width = xmax - xmin + 1
    -        height = ymax - ymin + 1
    -        # Get absolute polygon for distance computation
    -        polygon[:, 0] = polygon[:, 0] - xmin
    -        polygon[:, 1] = polygon[:, 1] - ymin
    -        # Get absolute padded polygon
    -        xs = np.broadcast_to(np.linspace(0, width - 1, num=width).reshape(1, width), (height, width))
    -        ys = np.broadcast_to(np.linspace(0, height - 1, num=height).reshape(height, 1), (height, width))
    -
    -        # Compute distance map to fill the padded polygon
    -        distance_map = np.zeros((polygon.shape[0], height, width), dtype=np.float32)
    -        for i in range(polygon.shape[0]):
    -            j = (i + 1) % polygon.shape[0]
    -            absolute_distance = self.compute_distance(xs, ys, polygon[i], polygon[j])
    -            distance_map[i] = np.clip(absolute_distance / distance, 0, 1)
    -        distance_map = np.min(distance_map, axis=0)
    -
    -        # Clip the padded polygon inside the canvas
    -        xmin_valid = min(max(0, xmin), canvas.shape[1] - 1)
    -        xmax_valid = min(max(0, xmax), canvas.shape[1] - 1)
    -        ymin_valid = min(max(0, ymin), canvas.shape[0] - 1)
    -        ymax_valid = min(max(0, ymax), canvas.shape[0] - 1)
    -
    -        # Fill the canvas with the distances computed inside the valid padded polygon
    -        canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1] = np.fmax(
    -            1 - distance_map[
    -                ymin_valid - ymin:ymax_valid - ymin + 1,
    -                xmin_valid - xmin:xmax_valid - xmin + 1
    -            ],
    -            canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1]
    -        )
    -
    -        return polygon, canvas, mask
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor, tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.uint8)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -        thresh_target = np.zeros(output_shape, dtype=np.uint8)
    -        thresh_mask = np.ones(output_shape, dtype=np.uint8)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            polys = np.stack([
    -                abs_boxes[:, [0, 1]],
    -                abs_boxes[:, [0, 3]],
    -                abs_boxes[:, [2, 3]],
    -                abs_boxes[:, [2, 1]],
    -            ], axis=1)
    -
    -            for box, box_size, poly, is_ambiguous in zip(abs_boxes, boxes_size, polys, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -
    -                # Negative shrink for gt, as described in paper
    -                polygon = Polygon(poly)
    -                distance = polygon.area * (1 - np.power(self.shrink_ratio, 2)) / polygon.length
    -                subject = [tuple(coor) for coor in poly]
    -                padding = pyclipper.PyclipperOffset()
    -                padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -                shrinked = padding.Execute(-distance)
    -
    -                # Draw polygon on gt if it is valid
    -                if len(shrinked) == 0:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                shrinked = np.array(shrinked[0]).reshape(-1, 2)
    -                if shrinked.shape[0] <= 2 or not Polygon(shrinked).is_valid:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                cv2.fillPoly(seg_target[idx], [shrinked.astype(np.int32)], 1)
    -
    -                # Draw on both thresh map and thresh mask
    -                poly, thresh_target[idx], thresh_mask[idx] = self.draw_thresh_map(poly, thresh_target[idx],
    -                                                                                  thresh_mask[idx])
    -
    -        thresh_target = thresh_target.astype(np.float32) * (self.thresh_max - self.thresh_min) + self.thresh_min
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -        thresh_target = tf.convert_to_tensor(thresh_target, dtype=tf.float32)
    -        thresh_mask = tf.convert_to_tensor(thresh_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask, thresh_target, thresh_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        thresh_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts, masks, thresh_gts, thresh_masks from a list of boxes
    -        and a list of masks for each image. From there it computes the loss with the model output
    -
    -        Args:
    -            out_map: output feature map of the model of shape (N, H, W, C)
    -            thresh_map: threshold map of shape (N, H, W, C)
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -
    -        prob_map = tf.math.sigmoid(tf.squeeze(out_map, axis=[-1]))
    -        thresh_map = tf.math.sigmoid(tf.squeeze(thresh_map, axis=[-1]))
    -
    -        seg_target, seg_mask, thresh_target, thresh_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute balanced BCE loss for proba_map
    -        bce_scale = 5.
    -        bce_loss = tf.keras.losses.binary_crossentropy(seg_target[..., None], out_map, from_logits=True)[seg_mask]
    -
    -        neg_target = 1 - seg_target[seg_mask]
    -        positive_count = tf.math.reduce_sum(seg_target[seg_mask])
    -        negative_count = tf.math.reduce_min([tf.math.reduce_sum(neg_target), 3. * positive_count])
    -        negative_loss = bce_loss * neg_target
    -        negative_loss, _ = tf.nn.top_k(negative_loss, tf.cast(negative_count, tf.int32))
    -        sum_losses = tf.math.reduce_sum(bce_loss * seg_target[seg_mask]) + tf.math.reduce_sum(negative_loss)
    -        balanced_bce_loss = sum_losses / (positive_count + negative_count + 1e-6)
    -
    -        # Compute dice loss for approxbin_map
    -        bin_map = 1 / (1 + tf.exp(-50. * (prob_map[seg_mask] - thresh_map[seg_mask])))
    -
    -        bce_min = tf.math.reduce_min(bce_loss)
    -        weights = (bce_loss - bce_min) / (tf.math.reduce_max(bce_loss) - bce_min) + 1.
    -        inter = tf.math.reduce_sum(bin_map * seg_target[seg_mask] * weights)
    -        union = tf.math.reduce_sum(bin_map) + tf.math.reduce_sum(seg_target[seg_mask]) + 1e-8
    -        dice_loss = 1 - 2.0 * inter / union
    -
    -        # Compute l1 loss for thresh_map
    -        l1_scale = 10.
    -        if tf.reduce_any(thresh_mask):
    -            l1_loss = tf.math.reduce_mean(tf.math.abs(thresh_map[thresh_mask] - thresh_target[thresh_mask]))
    -        else:
    -            l1_loss = tf.constant(0.)
    -
    -        return l1_scale * l1_loss + bce_scale * balanced_bce_loss + dice_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        feat_maps = self.feat_extractor(x, **kwargs)
    -        feat_concat = self.fpn(feat_maps, **kwargs)
    -        logits = self.probability_head(feat_concat, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            thresh_map = self.threshold_head(feat_concat, **kwargs)
    -            loss = self.compute_loss(logits, thresh_map, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _db_resnet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> DBNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['fpn_channels'] = kwargs.get('fpn_channels', _cfg['fpn_channels'])
    -
    -    # Feature extractor
    -    resnet = tf.keras.applications.__dict__[_cfg['backbone']](
    -        include_top=False,
    -        weights=None,
    -        input_shape=_cfg['input_shape'],
    -        pooling=None,
    -    )
    -
    -    feat_extractor = IntermediateLayerGetter(
    -        resnet,
    -        _cfg['fpn_layers'],
    -    )
    -
    -    kwargs['fpn_channels'] = _cfg['fpn_channels']
    -
    -    # Build the model
    -    model = DBNet(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def db_resnet50(pretrained: bool = False, **kwargs: Any) -> DBNet: - """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" - <https://arxiv.org/pdf/1911.08947.pdf>`_, using a ResNet-50 backbone. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _db_resnet('db_resnet50', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html b/v0.2.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html index 4325d0b74a..66cef8663d 100644 --- a/v0.2.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html +++ b/v0.2.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.differentiable_binarization.tensorflow - docTR documentation @@ -759,7 +759,7 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo

    - + diff --git a/v0.2.0/_modules/doctr/models/detection/fast/tensorflow.html b/v0.2.0/_modules/doctr/models/detection/fast/tensorflow.html index 5b84d2dea1..65e1a77af8 100644 --- a/v0.2.0/_modules/doctr/models/detection/fast/tensorflow.html +++ b/v0.2.0/_modules/doctr/models/detection/fast/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.fast.tensorflow - docTR documentation @@ -769,7 +769,7 @@

    Source code for doctr.models.detection.fast.tensorflow

    - + diff --git a/v0.2.0/_modules/doctr/models/detection/linknet.html b/v0.2.0/_modules/doctr/models/detection/linknet.html deleted file mode 100644 index 42db111bb3..0000000000 --- a/v0.2.0/_modules/doctr/models/detection/linknet.html +++ /dev/null @@ -1,637 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.linknet - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.linknet

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -import numpy as np
    -import cv2
    -from tensorflow.keras import layers, Sequential
    -from typing import Dict, Any, Tuple, Optional, List
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..backbones import ResnetStage
    -from ..utils import conv_sequence, load_pretrained_params
    -from ...utils.repr import NestedObject
    -
    -__all__ = ['LinkNet', 'linknet', 'LinkNetPostProcessor']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'linknet': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'out_chan': 1,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'LinkNetPostProcessor',
    -        'url': None,
    -    },
    -}
    -
    -
    -class LinkNetPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for LinkNet model.
    -
    -    Args:
    -        min_size_box: minimal length (pix) to keep a box
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        min_size_box: int = 3,
    -        bin_thresh: float = 0.15,
    -        box_thresh: float = 0.1,
    -    ) -> None:
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map: find connected components then filter boxes
    -
    -        Args:
    -            pred: Pred map from differentiable linknet output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        label_num, labelimage = cv2.connectedComponents(bitmap.astype(np.uint8), connectivity=4)
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        for label in range(1, label_num + 1):
    -            points = np.array(np.where(labelimage == label)[::-1]).T
    -            if points.shape[0] < 4:  # remove polygons with 3 points or less
    -                continue
    -            score = self.box_score(pred, points.reshape(-1, 2))
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            x, y, w, h = cv2.boundingRect(points)
    -            if min(w, h) < min_size_box:  # filter too small boxes
    -                continue
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -def decoder_block(in_chan: int, out_chan: int) -> Sequential:
    -    """Creates a LinkNet decoder block"""
    -
    -    return Sequential([
    -        *conv_sequence(in_chan // 4, 'relu', True, kernel_size=1),
    -        layers.Conv2DTranspose(
    -            filters=in_chan // 4,
    -            kernel_size=3,
    -            strides=2,
    -            padding="same",
    -            use_bias=False,
    -            kernel_initializer='he_normal'
    -        ),
    -        layers.BatchNormalization(),
    -        layers.Activation('relu'),
    -        *conv_sequence(out_chan, 'relu', True, kernel_size=1),
    -    ])
    -
    -
    -class LinkNetFPN(layers.Layer, NestedObject):
    -    """LinkNet Encoder-Decoder module
    -
    -    """
    -
    -    def __init__(
    -        self,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.encoder_1 = ResnetStage(num_blocks=2, output_channels=64, downsample=True)
    -        self.encoder_2 = ResnetStage(num_blocks=2, output_channels=128, downsample=True)
    -        self.encoder_3 = ResnetStage(num_blocks=2, output_channels=256, downsample=True)
    -        self.encoder_4 = ResnetStage(num_blocks=2, output_channels=512, downsample=True)
    -        self.decoder_1 = decoder_block(in_chan=64, out_chan=64)
    -        self.decoder_2 = decoder_block(in_chan=128, out_chan=64)
    -        self.decoder_3 = decoder_block(in_chan=256, out_chan=128)
    -        self.decoder_4 = decoder_block(in_chan=512, out_chan=256)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor
    -    ) -> tf.Tensor:
    -        x_1 = self.encoder_1(x)
    -        x_2 = self.encoder_2(x_1)
    -        x_3 = self.encoder_3(x_2)
    -        x_4 = self.encoder_4(x_3)
    -        y_4 = self.decoder_4(x_4)
    -        y_3 = self.decoder_3(y_4 + x_3)
    -        y_2 = self.decoder_2(y_3 + x_2)
    -        y_1 = self.decoder_1(y_2 + x_1)
    -        return y_1
    -
    -
    -class LinkNet(DetectionModel, NestedObject):
    -    """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation"
    -    <https://arxiv.org/pdf/1707.03718.pdf>`_.
    -
    -    Args:
    -        out_chan: number of channels for the output
    -    """
    -
    -    def __init__(
    -        self,
    -        out_chan: int = 1,
    -        input_shape: Tuple[int, int, int] = (512, 512, 3),
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(cfg=cfg)
    -
    -        self.stem = Sequential([
    -            *conv_sequence(64, 'relu', True, strides=2, kernel_size=7, input_shape=input_shape),
    -            layers.MaxPool2D(pool_size=(3, 3), strides=2, padding='same'),
    -        ])
    -
    -        self.fpn = LinkNetFPN()
    -
    -        self.classifier = Sequential([
    -            layers.Conv2DTranspose(
    -                filters=32,
    -                kernel_size=3,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -            layers.BatchNormalization(),
    -            layers.Activation('relu'),
    -            *conv_sequence(32, 'relu', True, strides=1, kernel_size=3),
    -            layers.Conv2DTranspose(
    -                filters=out_chan,
    -                kernel_size=2,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -        ])
    -
    -        self.min_size_box = 3
    -
    -        self.postprocessor = LinkNetPostProcessor()
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.bool)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            for box, box_size, is_ambiguous in zip(abs_boxes, boxes_size, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Fill polygon with 1
    -                seg_target[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = True
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts and masks from a list of boxes and a list of masks for each image
    -        Then, it computes the loss function with proba_map, gts and masks
    -
    -        Args:
    -            out_map: output feature map of the model of shape N x H x W x 1
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -        seg_target, seg_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute BCE loss
    -        return tf.math.reduce_mean(tf.keras.losses.binary_crossentropy(
    -            seg_target[seg_mask],
    -            tf.squeeze(out_map, axis=[-1])[seg_mask],
    -            from_logits=True
    -        ))
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        logits = self.stem(x)
    -        logits = self.fpn(logits)
    -        logits = self.classifier(logits)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            loss = self.compute_loss(logits, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _linknet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> LinkNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['out_chan'] = kwargs.get('out_chan', _cfg['out_chan'])
    -
    -    kwargs['out_chan'] = _cfg['out_chan']
    -    kwargs['input_shape'] = _cfg['input_shape']
    -    # Build the model
    -    model = LinkNet(cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def linknet(pretrained: bool = False, **kwargs: Any) -> LinkNet: - """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" - <https://arxiv.org/pdf/1707.03718.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import linknet - >>> model = linknet(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _linknet('linknet', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.0/_modules/doctr/models/detection/linknet/tensorflow.html b/v0.2.0/_modules/doctr/models/detection/linknet/tensorflow.html index dbb58e37cf..ce995f99d4 100644 --- a/v0.2.0/_modules/doctr/models/detection/linknet/tensorflow.html +++ b/v0.2.0/_modules/doctr/models/detection/linknet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.linknet.tensorflow - docTR documentation @@ -716,7 +716,7 @@

    Source code for doctr.models.detection.linknet.tensorflow

    - + diff --git a/v0.2.0/_modules/doctr/models/detection/zoo.html b/v0.2.0/_modules/doctr/models/detection/zoo.html index 55630ebacb..3651c4e2d3 100644 --- a/v0.2.0/_modules/doctr/models/detection/zoo.html +++ b/v0.2.0/_modules/doctr/models/detection/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.detection.zoo - docTR documentation @@ -225,15 +225,42 @@

    Source code for doctr.models.detection.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -from typing import Dict, Any
    -from .core import DetectionPredictor, DetectionPreProcessor
    -from .. import detection
    +from typing import Any, List
    +
    +from doctr.file_utils import is_tf_available, is_torch_available
     
    +from .. import detection
    +from ..detection.fast import reparameterize
    +from ..preprocessor import PreProcessor
    +from .predictor import DetectionPredictor
     
     __all__ = ["detection_predictor"]
     
    -ARCHS = ['db_resnet50', 'linknet']
    +ARCHS: List[str]
    +
     
    +if is_tf_available():
    +    ARCHS = [
    +        "db_resnet50",
    +        "db_mobilenet_v3_large",
    +        "linknet_resnet18",
    +        "linknet_resnet34",
    +        "linknet_resnet50",
    +        "fast_tiny",
    +        "fast_small",
    +        "fast_base",
    +    ]
    +elif is_torch_available():
    +    ARCHS = [
    +        "db_resnet34",
    +        "db_resnet50",
    +        "db_mobilenet_v3_large",
    +        "linknet_resnet18",
    +        "linknet_resnet34",
    +        "linknet_resnet50",
    +        "fast_tiny",
    +        "fast_small",
    +        "fast_base",
    +    ]
     
    -def _predictor(arch: str, pretrained: bool, **kwargs: Any) -> DetectionPredictor:
     
    -    if arch not in ARCHS:
    -        raise ValueError(f"unknown architecture '{arch}'")
    +def _predictor(arch: Any, pretrained: bool, assume_straight_pages: bool = True, **kwargs: Any) -> DetectionPredictor:
    +    if isinstance(arch, str):
    +        if arch not in ARCHS:
    +            raise ValueError(f"unknown architecture '{arch}'")
     
    -    # Detection
    -    _model = detection.__dict__[arch](pretrained=pretrained)
    -    kwargs['mean'] = kwargs.get('mean', _model.cfg['mean'])
    -    kwargs['std'] = kwargs.get('std', _model.cfg['std'])
    +        _model = detection.__dict__[arch](
    +            pretrained=pretrained,
    +            pretrained_backbone=kwargs.get("pretrained_backbone", True),
    +            assume_straight_pages=assume_straight_pages,
    +        )
    +        # Reparameterize FAST models by default to lower inference latency and memory usage
    +        if isinstance(_model, detection.FAST):
    +            _model = reparameterize(_model)
    +    else:
    +        if not isinstance(arch, (detection.DBNet, detection.LinkNet, detection.FAST)):
    +            raise ValueError(f"unknown architecture: {type(arch)}")
    +
    +        _model = arch
    +        _model.assume_straight_pages = assume_straight_pages
    +        _model.postprocessor.assume_straight_pages = assume_straight_pages
    +
    +    kwargs.pop("pretrained_backbone", None)
    +
    +    kwargs["mean"] = kwargs.get("mean", _model.cfg["mean"])
    +    kwargs["std"] = kwargs.get("std", _model.cfg["std"])
    +    kwargs["batch_size"] = kwargs.get("batch_size", 2)
         predictor = DetectionPredictor(
    -        DetectionPreProcessor(output_size=_model.cfg['input_shape'][:2], **kwargs),
    -        _model
    +        PreProcessor(_model.cfg["input_shape"][:-1] if is_tf_available() else _model.cfg["input_shape"][1:], **kwargs),
    +        _model,
         )
         return predictor
     
     
     
    -[docs] -def detection_predictor(arch: str = 'db_resnet50', pretrained: bool = False, **kwargs: Any) -> DetectionPredictor: +[docs] +def detection_predictor( + arch: Any = "fast_base", + pretrained: bool = False, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + batch_size: int = 2, + **kwargs: Any, +) -> DetectionPredictor: """Text detection architecture. - Example:: - >>> import numpy as np - >>> from doctr.models import detection_predictor - >>> model = detection_predictor(pretrained=True) - >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) - >>> out = model([input_page]) + >>> import numpy as np + >>> from doctr.models import detection_predictor + >>> model = detection_predictor(arch='db_resnet50', pretrained=True) + >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) Args: - arch: name of the architecture to use ('db_resnet50') + ---- + arch: name of the architecture or model itself to use (e.g. 'db_resnet50') pretrained: If True, returns a model pre-trained on our text detection dataset + assume_straight_pages: If True, fit straight boxes to the page + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right + batch_size: number of samples the model processes in parallel + **kwargs: optional keyword arguments passed to the architecture Returns: + ------- Detection predictor """ - - return _predictor(arch, pretrained, **kwargs)
    + return _predictor( + arch=arch, + pretrained=pretrained, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + batch_size=batch_size, + **kwargs, + )
    @@ -354,8 +449,8 @@

    Source code for doctr.models.detection.zoo

           
         
       
    - - + + diff --git a/v0.2.0/_modules/doctr/models/export.html b/v0.2.0/_modules/doctr/models/export.html deleted file mode 100644 index f25a81aa21..0000000000 --- a/v0.2.0/_modules/doctr/models/export.html +++ /dev/null @@ -1,411 +0,0 @@ - - - - - - - - - - - - doctr.models.export - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.export

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import logging
    -import numpy as np
    -import tensorflow as tf
    -from tensorflow.keras import Model
    -from typing import Tuple
    -
    -logging.getLogger("tensorflow").setLevel(logging.DEBUG)
    -
    -
    -__all__ = ['convert_to_tflite', 'convert_to_fp16', 'quantize_model']
    -
    -
    -
    -[docs] -def convert_to_tflite(tf_model: Model) -> bytes: - """Converts a model to TFLite format - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_tflite, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_tflite(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - return converter.convert()
    - - - -
    -[docs] -def convert_to_fp16(tf_model: Model) -> bytes: - """Converts a model to half precision - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_fp16, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_fp16(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the serialized FP16 model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - converter.target_spec.supported_types = [tf.float16] - return converter.convert()
    - - - -
    -[docs] -def quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) -> bytes: - """Quantize a Tensorflow model - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import quantize_model, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = quantize_model(model, (224, 224, 3)) - - Args: - tf_model: a keras model - input_shape: shape of the expected input tensor (excluding batch dimension) with channel last order - - Returns: - bytes: the serialized quantized model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - - # Float fallback for operators that do not have an integer implementation - def representative_dataset(): - for _ in range(100): - data = np.random.rand(1, *input_shape) - yield [data.astype(np.float32)] - - converter.representative_dataset = representative_dataset - converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] - converter.inference_input_type = tf.int8 - converter.inference_output_type = tf.int8 - - return converter.convert()
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.0/_modules/doctr/models/factory/hub.html b/v0.2.0/_modules/doctr/models/factory/hub.html index 8274a809f5..756b2c7a17 100644 --- a/v0.2.0/_modules/doctr/models/factory/hub.html +++ b/v0.2.0/_modules/doctr/models/factory/hub.html @@ -13,7 +13,7 @@ - + doctr.models.factory.hub - docTR documentation @@ -568,7 +568,7 @@

    Source code for doctr.models.factory.hub

         
       
    - + diff --git a/v0.2.0/_modules/doctr/models/recognition/crnn.html b/v0.2.0/_modules/doctr/models/recognition/crnn.html deleted file mode 100644 index db8bbc2c27..0000000000 --- a/v0.2.0/_modules/doctr/models/recognition/crnn.html +++ /dev/null @@ -1,579 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.crnn - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.crnn

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import layers
    -from tensorflow.keras.models import Sequential
    -from typing import Tuple, Dict, Any, Optional, List
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel, RecognitionPostProcessor
    -
    -__all__ = ['CRNN', 'crnn_vgg16_bn', 'crnn_resnet31', 'CTCPostProcessor']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'crnn_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/crnn_vgg16_bn-748c855f.zip',
    -    },
    -    'crnn_resnet31': {
    -        'mean': (0.694, 0.695, 0.693),
    -        'std': (0.299, 0.296, 0.301),
    -        'backbone': 'resnet31', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.1/crnn_resnet31-69ab71db.zip',
    -    },
    -}
    -
    -
    -class CTCPostProcessor(RecognitionPostProcessor):
    -    """
    -    Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def ctc_decoder(
    -        self,
    -        logits: tf.Tensor
    -    ) -> tf.Tensor:
    -        """
    -        Decode logits with CTC decoder from keras backend
    -
    -        Args:
    -            logits: raw output of the model, shape BATCH_SIZE X SEQ_LEN X NUM_CLASSES + 1
    -
    -        Returns:
    -            decoded logits, shape BATCH_SIZE X SEQ_LEN
    -        """
    -        # computing prediction with ctc decoder
    -        _prediction = tf.nn.ctc_greedy_decoder(
    -            tf.nn.softmax(tf.transpose(logits, perm=[1, 0, 2])),
    -            tf.fill(logits.shape[0], logits.shape[1]),
    -            merge_repeated=True
    -        )[0][0]
    -        prediction = tf.sparse.to_dense(_prediction, default_value=len(self.vocab))
    -
    -        return prediction
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor
    -    ) -> List[str]:
    -        """
    -        Performs decoding of raw output with CTC and decoding of CTC predictions
    -        with label_to_idx mapping dictionnary
    -
    -        Args:
    -            logits: raw output of the model, shape BATCH_SIZE X SEQ_LEN X NUM_CLASSES + 1
    -
    -        Returns:
    -            A list of decoded words of length BATCH_SIZE
    -
    -        """
    -        # decode ctc for ctc models
    -        predictions = self.ctc_decoder(logits)
    -
    -        _decoded_strings_pred = tf.strings.reduce_join(
    -            inputs=tf.nn.embedding_lookup(self._embedding, predictions),
    -            axis=-1
    -        )
    -        _decoded_strings_pred = tf.strings.split(_decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        words_list = [word.decode() for word in list(decoded_strings_pred.numpy())]
    -
    -        if self.ignore_case:
    -            words_list = [word.lower() for word in words_list]
    -
    -        if self.ignore_accents:
    -            raise NotImplementedError
    -
    -        return words_list
    -
    -
    -class CRNN(RecognitionModel):
    -    """Implements a CRNN architecture as described in `"An End-to-End Trainable Neural Network for Image-based
    -    Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of units in the LSTM layers
    -        cfg: configuration dictionary
    -    """
    -    def __init__(
    -        self,
    -        feature_extractor: tf.keras.Model,
    -        vocab: str,
    -        rnn_units: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(vocab=vocab, cfg=cfg)
    -        self.feat_extractor = feature_extractor
    -
    -        # Initialize kernels
    -        h, w, c = self.feat_extractor.output_shape[1:]
    -        self.max_length = w
    -
    -        self.decoder = Sequential(
    -            [
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Dense(units=len(vocab) + 1)
    -            ]
    -        )
    -        self.decoder.build(input_shape=(None, w, h * c))
    -
    -        self.postprocessor = CTCPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        target: List[str],
    -    ) -> tf.Tensor:
    -        """Compute CTC loss for the model.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        gt, seq_len = self.compute_target(target)
    -        batch_len = model_output.shape[0]
    -        input_length = model_output.shape[1] * tf.ones(shape=(batch_len))
    -        ctc_loss = tf.nn.ctc_loss(
    -            gt, model_output, seq_len, input_length, logits_time_major=False, blank_index=len(self.vocab)
    -        )
    -        return ctc_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, tf.Tensor]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        # B x H x W x C --> B x W x H x C
    -        transposed_feat = tf.transpose(features, perm=[0, 2, 1, 3])
    -        w, h, c = transposed_feat.get_shape().as_list()[1:]
    -        # B x W x H x C --> B x W x H * C
    -        features_seq = tf.reshape(transposed_feat, shape=(-1, w, h * c))
    -        decoded_features = self.decoder(features_seq, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = decoded_features
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(decoded_features)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(decoded_features, target)
    -
    -        return out
    -
    -
    -def _crnn(arch: str, pretrained: bool, input_shape: Optional[Tuple[int, int, int]] = None, **kwargs: Any) -> CRNN:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[_cfg['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -
    -    # Build the model
    -    model = CRNN(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a VGG-16 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_vgg16_bn - >>> model = crnn_vgg16_bn(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_vgg16_bn', pretrained, **kwargs)
    - - - -def crnn_resnet31(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a resnet31 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_resnet31 - >>> model = crnn_resnet31(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_resnet31', pretrained, **kwargs) -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.0/_modules/doctr/models/recognition/crnn/tensorflow.html b/v0.2.0/_modules/doctr/models/recognition/crnn/tensorflow.html index e50c245923..bc64da9a1b 100644 --- a/v0.2.0/_modules/doctr/models/recognition/crnn/tensorflow.html +++ b/v0.2.0/_modules/doctr/models/recognition/crnn/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.crnn.tensorflow - docTR documentation @@ -658,7 +658,7 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    - + diff --git a/v0.2.0/_modules/doctr/models/recognition/master/tensorflow.html b/v0.2.0/_modules/doctr/models/recognition/master/tensorflow.html index 152ebb7e59..aa6aa69325 100644 --- a/v0.2.0/_modules/doctr/models/recognition/master/tensorflow.html +++ b/v0.2.0/_modules/doctr/models/recognition/master/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.master.tensorflow - docTR documentation @@ -655,7 +655,7 @@

    Source code for doctr.models.recognition.master.tensorflow

    - + diff --git a/v0.2.0/_modules/doctr/models/recognition/parseq/tensorflow.html b/v0.2.0/_modules/doctr/models/recognition/parseq/tensorflow.html index 0819737dfc..b181acef53 100644 --- a/v0.2.0/_modules/doctr/models/recognition/parseq/tensorflow.html +++ b/v0.2.0/_modules/doctr/models/recognition/parseq/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.parseq.tensorflow - docTR documentation @@ -845,7 +845,7 @@

    Source code for doctr.models.recognition.parseq.tensorflow

    - + diff --git a/v0.2.0/_modules/doctr/models/recognition/sar.html b/v0.2.0/_modules/doctr/models/recognition/sar.html deleted file mode 100644 index 7b3a3e74b1..0000000000 --- a/v0.2.0/_modules/doctr/models/recognition/sar.html +++ /dev/null @@ -1,709 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.sar - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.sar

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import Sequential, layers
    -from typing import Tuple, Dict, List, Any, Optional
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel
    -from .core import RecognitionPostProcessor
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['SAR', 'SARPostProcessor', 'sar_vgg16_bn', 'sar_resnet31']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'sar_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1-models/sar_vgg16bn-0d7e2c26.zip',
    -    },
    -    'sar_resnet31': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'resnet31', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/sar_resnet31-ea202587.zip',
    -    },
    -}
    -
    -
    -class AttentionModule(layers.Layer, NestedObject):
    -    """Implements attention module of the SAR model
    -
    -    Args:
    -        attention_units: number of hidden attention units
    -
    -    """
    -    def __init__(
    -        self,
    -        attention_units: int
    -    ) -> None:
    -
    -        super().__init__()
    -        self.hidden_state_projector = layers.Conv2D(
    -            attention_units, 1, strides=1, use_bias=False, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.features_projector = layers.Conv2D(
    -            attention_units, 3, strides=1, use_bias=True, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.attention_projector = layers.Conv2D(
    -            1, 1, strides=1, use_bias=False, padding="same", kernel_initializer='he_normal',
    -        )
    -        self.flatten = layers.Flatten()
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        hidden_state: tf.Tensor,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        [H, W] = features.get_shape().as_list()[1:3]
    -        # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units)
    -        hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs)
    -        # shape (N, H, W, vgg_units) -> (N, H, W, attention_units)
    -        features_projection = self.features_projector(features, **kwargs)
    -        projection = tf.math.tanh(hidden_state_projection + features_projection)
    -        # shape (N, H, W, attention_units) -> (N, H, W, 1)
    -        attention = self.attention_projector(projection, **kwargs)
    -        # shape (N, H, W, 1) -> (N, H * W)
    -        attention = self.flatten(attention)
    -        attention = tf.nn.softmax(attention)
    -        # shape (N, H * W) -> (N, H, W, 1)
    -        attention_map = tf.reshape(attention, [-1, H, W, 1])
    -        glimpse = tf.math.multiply(features, attention_map)
    -        # shape (N, H * W) -> (N, 1)
    -        glimpse = tf.reduce_sum(glimpse, axis=[1, 2])
    -        return glimpse
    -
    -
    -class SARDecoder(layers.Layer, NestedObject):
    -    """Implements decoder module of the SAR model
    -
    -    Args:
    -        rnn_units: number of hidden units in recurrent cells
    -        max_length: maximum length of a sequence
    -        vocab_size: number of classes in the model alphabet
    -        embedding_units: number of hidden embedding units
    -        attention_units: number of hidden attention units
    -        num_decoder_layers: number of LSTM layers to stack
    -
    -    """
    -    def __init__(
    -        self,
    -        rnn_units: int,
    -        max_length: int,
    -        vocab_size: int,
    -        embedding_units: int,
    -        attention_units: int,
    -        num_decoder_layers: int = 2,
    -        input_shape: Optional[List[Tuple[Optional[int]]]] = None,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.vocab_size = vocab_size
    -        self.lstm_decoder = layers.StackedRNNCells(
    -            [layers.LSTMCell(rnn_units, dtype=tf.float32, implementation=1) for _ in range(num_decoder_layers)]
    -        )
    -        self.embed = layers.Dense(embedding_units, use_bias=False, input_shape=(None, self.vocab_size + 1))
    -        self.attention_module = AttentionModule(attention_units)
    -        self.output_dense = layers.Dense(vocab_size + 1, use_bias=True, input_shape=(None, 2 * rnn_units))
    -        self.max_length = max_length
    -
    -        # Initialize kernels
    -        if input_shape is not None:
    -            self.attention_module.call(layers.Input(input_shape[0][1:]), layers.Input((1, 1, rnn_units)))
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        holistic: tf.Tensor,
    -        gt: Optional[tf.Tensor] = None,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # initialize states (each of shape (N, rnn_units))
    -        states = self.lstm_decoder.get_initial_state(
    -            inputs=None, batch_size=features.shape[0], dtype=tf.float32
    -        )
    -        # run first step of lstm
    -        # holistic: shape (N, rnn_units)
    -        _, states = self.lstm_decoder(holistic, states, **kwargs)
    -        # Initialize with the index of virtual START symbol (placed after <eos>)
    -        symbol = tf.fill(features.shape[0], self.vocab_size + 1)
    -        logits_list = []
    -        if kwargs.get('training') and gt is None:
    -            raise ValueError('Need to provide labels during training for teacher forcing')
    -        for t in range(self.max_length + 1):  # keep 1 step for <eos>
    -            # one-hot symbol with depth vocab_size + 1
    -            # embeded_symbol: shape (N, embedding_units)
    -            embeded_symbol = self.embed(tf.one_hot(symbol, depth=self.vocab_size + 1), **kwargs)
    -            logits, states = self.lstm_decoder(embeded_symbol, states, **kwargs)
    -            glimpse = self.attention_module(
    -                features, tf.expand_dims(tf.expand_dims(logits, axis=1), axis=1), **kwargs,
    -            )
    -            # logits: shape (N, rnn_units), glimpse: shape (N, 1)
    -            logits = tf.concat([logits, glimpse], axis=-1)
    -            # shape (N, rnn_units + 1) -> (N, vocab_size + 1)
    -            logits = self.output_dense(logits, **kwargs)
    -            # update symbol with predicted logits for t+1 step
    -            if kwargs.get('training'):
    -                symbol = gt[:, t]
    -            else:
    -                symbol = tf.argmax(logits, axis=-1)
    -            logits_list.append(logits)
    -        outputs = tf.stack(logits_list, axis=1)  # shape (N, max_length + 1, vocab_size + 1)
    -
    -        return outputs
    -
    -
    -class SAR(RecognitionModel):
    -    """Implements a SAR architecture as described in `"Show, Attend and Read:A Simple and Strong Baseline for
    -    Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of hidden units in both encoder and decoder LSTM
    -        embedding_units: number of embedding units
    -        attention_units: number of hidden units in attention module
    -        max_length: maximum word length handled by the model
    -        num_decoders: number of LSTM to stack in decoder layer
    -
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'encoder', 'decoder']
    -
    -    def __init__(
    -        self,
    -        feature_extractor,
    -        vocab: str,
    -        rnn_units: int = 512,
    -        embedding_units: int = 512,
    -        attention_units: int = 512,
    -        max_length: int = 30,
    -        num_decoders: int = 2,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(vocab=vocab, cfg=cfg)
    -
    -        self.max_length = max_length + 1  # Add 1 timestep for EOS after the longest word
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.encoder = Sequential(
    -            [
    -                layers.LSTM(units=rnn_units, return_sequences=True),
    -                layers.LSTM(units=rnn_units, return_sequences=False)
    -            ]
    -        )
    -        # Initialize the kernels (watch out for reduce_max)
    -        self.encoder.build(input_shape=(None,) + self.feat_extractor.output_shape[2:])
    -
    -        self.decoder = SARDecoder(
    -            rnn_units, max_length, len(vocab), embedding_units, attention_units, num_decoders,
    -            input_shape=[self.feat_extractor.output_shape, self.encoder.output_shape]
    -        )
    -
    -        self.postprocessor = SARPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        gt: tf.Tensor,
    -        seq_len: tf.Tensor,
    -    ) -> tf.Tensor:
    -        """Compute categorical cross-entropy loss for the model.
    -        Sequences are masked after the EOS character.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        # Input length : number of timesteps
    -        input_len = tf.shape(model_output)[1]
    -        # Add one for additional <eos> token
    -        seq_len = seq_len + 1
    -        # One-hot gt labels
    -        oh_gt = tf.one_hot(gt, depth=model_output.shape[2])
    -        # Compute loss
    -        cce = tf.nn.softmax_cross_entropy_with_logits(oh_gt, model_output)
    -        # Compute mask
    -        mask_values = tf.zeros_like(cce)
    -        mask_2d = tf.sequence_mask(seq_len, input_len)
    -        masked_loss = tf.where(mask_2d, cce, mask_values)
    -        ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32))
    -        return tf.expand_dims(ce_loss, axis=1)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, tf.Tensor]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        pooled_features = tf.reduce_max(features, axis=1)  # vertical max pooling
    -        encoded = self.encoder(pooled_features, **kwargs)
    -        if target is not None:
    -            gt, seq_len = self.compute_target(target)
    -        decoded_features = self.decoder(features, encoded, gt=None if target is None else gt, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = decoded_features
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(decoded_features)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(decoded_features, gt, seq_len)
    -
    -        return out
    -
    -
    -class SARPostProcessor(RecognitionPostProcessor):
    -    """Post processor for SAR architectures
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor,
    -    ) -> List[str]:
    -        # compute pred with argmax for attention models
    -        pred = tf.math.argmax(logits, axis=2)
    -
    -        # decode raw output of the model with tf_label_to_idx
    -        pred = tf.cast(pred, dtype='int32')
    -        decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(self._embedding, pred), axis=-1)
    -        decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        words_list = [word.decode() for word in list(decoded_strings_pred.numpy())]
    -
    -        if self.ignore_case:
    -            words_list = [word.lower() for word in words_list]
    -
    -        if self.ignore_accents:
    -            raise NotImplementedError
    -
    -        return words_list
    -
    -
    -def _sar(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> SAR:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -    _cfg['embedding_units'] = kwargs.get('embedding_units', _cfg['rnn_units'])
    -    _cfg['attention_units'] = kwargs.get('attention_units', _cfg['rnn_units'])
    -    _cfg['max_length'] = kwargs.get('max_length', _cfg['max_length'])
    -    _cfg['num_decoders'] = kwargs.get('num_decoders', _cfg['num_decoders'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[default_cfgs[arch]['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -    kwargs['embedding_units'] = _cfg['embedding_units']
    -    kwargs['attention_units'] = _cfg['attention_units']
    -    kwargs['max_length'] = _cfg['max_length']
    -    kwargs['num_decoders'] = _cfg['num_decoders']
    -
    -    # Build the model
    -    model = SAR(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, default_cfgs[arch]['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a VGG16 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import sar_vgg16_bn - >>> model = sar_vgg16_bn(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_vgg16_bn', pretrained, **kwargs)
    - - - -
    -[docs] -def sar_resnet31(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a resnet-31 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example: - >>> import tensorflow as tf - >>> from doctr.models import sar_resnet31 - >>> model = sar_resnet31(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_resnet31', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.0/_modules/doctr/models/recognition/sar/tensorflow.html b/v0.2.0/_modules/doctr/models/recognition/sar/tensorflow.html index 010bc2bc54..4a591e6451 100644 --- a/v0.2.0/_modules/doctr/models/recognition/sar/tensorflow.html +++ b/v0.2.0/_modules/doctr/models/recognition/sar/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.sar.tensorflow - docTR documentation @@ -757,7 +757,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    - + diff --git a/v0.2.0/_modules/doctr/models/recognition/vitstr/tensorflow.html b/v0.2.0/_modules/doctr/models/recognition/vitstr/tensorflow.html index 6e101893bf..c594d40a56 100644 --- a/v0.2.0/_modules/doctr/models/recognition/vitstr/tensorflow.html +++ b/v0.2.0/_modules/doctr/models/recognition/vitstr/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.vitstr.tensorflow - docTR documentation @@ -621,7 +621,7 @@

    Source code for doctr.models.recognition.vitstr.tensorflow

    - + diff --git a/v0.2.0/_modules/doctr/models/recognition/zoo.html b/v0.2.0/_modules/doctr/models/recognition/zoo.html index a4d43d1801..f664304019 100644 --- a/v0.2.0/_modules/doctr/models/recognition/zoo.html +++ b/v0.2.0/_modules/doctr/models/recognition/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.zoo - docTR documentation @@ -225,15 +225,42 @@

    Source code for doctr.models.recognition.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -from typing import Dict, Any
    -from .core import RecognitionPredictor, RecognitionPreProcessor
    -from .. import recognition
    +from typing import Any, List
     
    +from doctr.file_utils import is_tf_available
    +from doctr.models.preprocessor import PreProcessor
    +
    +from .. import recognition
    +from .predictor import RecognitionPredictor
     
     __all__ = ["recognition_predictor"]
     
    -ARCHS = ['crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31']
     
    +ARCHS: List[str] = [
    +    "crnn_vgg16_bn",
    +    "crnn_mobilenet_v3_small",
    +    "crnn_mobilenet_v3_large",
    +    "sar_resnet31",
    +    "master",
    +    "vitstr_small",
    +    "vitstr_base",
    +    "parseq",
    +]
     
    -def _predictor(arch: str, pretrained: bool, **kwargs: Any) -> RecognitionPredictor:
     
    -    if arch not in ARCHS:
    -        raise ValueError(f"unknown architecture '{arch}'")
    +def _predictor(arch: Any, pretrained: bool, **kwargs: Any) -> RecognitionPredictor:
    +    if isinstance(arch, str):
    +        if arch not in ARCHS:
    +            raise ValueError(f"unknown architecture '{arch}'")
     
    -    _model = recognition.__dict__[arch](pretrained=pretrained)
    -    kwargs['mean'] = kwargs.get('mean', _model.cfg['mean'])
    -    kwargs['std'] = kwargs.get('std', _model.cfg['std'])
    -    predictor = RecognitionPredictor(
    -        RecognitionPreProcessor(output_size=_model.cfg['input_shape'][:2], **kwargs),
    -        _model
    -    )
    +        _model = recognition.__dict__[arch](
    +            pretrained=pretrained, pretrained_backbone=kwargs.get("pretrained_backbone", True)
    +        )
    +    else:
    +        if not isinstance(
    +            arch, (recognition.CRNN, recognition.SAR, recognition.MASTER, recognition.ViTSTR, recognition.PARSeq)
    +        ):
    +            raise ValueError(f"unknown architecture: {type(arch)}")
    +        _model = arch
    +
    +    kwargs.pop("pretrained_backbone", None)
    +
    +    kwargs["mean"] = kwargs.get("mean", _model.cfg["mean"])
    +    kwargs["std"] = kwargs.get("std", _model.cfg["std"])
    +    kwargs["batch_size"] = kwargs.get("batch_size", 128)
    +    input_shape = _model.cfg["input_shape"][:2] if is_tf_available() else _model.cfg["input_shape"][-2:]
    +    predictor = RecognitionPredictor(PreProcessor(input_shape, preserve_aspect_ratio=True, **kwargs), _model)
     
         return predictor
     
     
     
    -[docs] -def recognition_predictor(arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) -> RecognitionPredictor: +[docs] +def recognition_predictor( + arch: Any = "crnn_vgg16_bn", + pretrained: bool = False, + symmetric_pad: bool = False, + batch_size: int = 128, + **kwargs: Any, +) -> RecognitionPredictor: """Text recognition architecture. Example:: @@ -313,14 +369,18 @@

    Source code for doctr.models.recognition.zoo

            >>> out = model([input_page])
     
         Args:
    -        arch: name of the architecture to use ('crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31')
    +    ----
    +        arch: name of the architecture or model itself to use (e.g. 'crnn_vgg16_bn')
             pretrained: If True, returns a model pre-trained on our text recognition dataset
    +        symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right
    +        batch_size: number of samples the model processes in parallel
    +        **kwargs: optional parameters to be passed to the architecture
     
         Returns:
    +    -------
             Recognition predictor
         """
    -
    -    return _predictor(arch, pretrained, **kwargs)
    + return _predictor(arch=arch, pretrained=pretrained, symmetric_pad=symmetric_pad, batch_size=batch_size, **kwargs)
    @@ -354,8 +414,8 @@

    Source code for doctr.models.recognition.zoo

       
    -
    - +
    + diff --git a/v0.2.0/_modules/doctr/models/zoo.html b/v0.2.0/_modules/doctr/models/zoo.html index dec6857019..d459671648 100644 --- a/v0.2.0/_modules/doctr/models/zoo.html +++ b/v0.2.0/_modules/doctr/models/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.zoo - docTR documentation @@ -225,15 +225,42 @@

    Source code for doctr.models.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     from typing import Any
    -from .core import OCRPredictor
    +
     from .detection.zoo import detection_predictor
    +from .kie_predictor import KIEPredictor
    +from .predictor import OCRPredictor
     from .recognition.zoo import recognition_predictor
     
    +__all__ = ["ocr_predictor", "kie_predictor"]
     
    -__all__ = ["ocr_predictor"]
    -
    -
    -def _predictor(det_arch: str, reco_arch: str, pretrained: bool, det_bs=2, reco_bs=128) -> OCRPredictor:
     
    +def _predictor(
    +    det_arch: Any,
    +    reco_arch: Any,
    +    pretrained: bool,
    +    pretrained_backbone: bool = True,
    +    assume_straight_pages: bool = True,
    +    preserve_aspect_ratio: bool = True,
    +    symmetric_pad: bool = True,
    +    det_bs: int = 2,
    +    reco_bs: int = 128,
    +    detect_orientation: bool = False,
    +    straighten_pages: bool = False,
    +    detect_language: bool = False,
    +    **kwargs,
    +) -> OCRPredictor:
         # Detection
    -    det_predictor = detection_predictor(det_arch, pretrained=pretrained, batch_size=det_bs)
    +    det_predictor = detection_predictor(
    +        det_arch,
    +        pretrained=pretrained,
    +        pretrained_backbone=pretrained_backbone,
    +        batch_size=det_bs,
    +        assume_straight_pages=assume_straight_pages,
    +        preserve_aspect_ratio=preserve_aspect_ratio,
    +        symmetric_pad=symmetric_pad,
    +    )
     
         # Recognition
    -    reco_predictor = recognition_predictor(reco_arch, pretrained=pretrained, batch_size=reco_bs)
    +    reco_predictor = recognition_predictor(
    +        reco_arch,
    +        pretrained=pretrained,
    +        pretrained_backbone=pretrained_backbone,
    +        batch_size=reco_bs,
    +    )
     
    -    return OCRPredictor(det_predictor, reco_predictor)
    +    return OCRPredictor(
    +        det_predictor,
    +        reco_predictor,
    +        assume_straight_pages=assume_straight_pages,
    +        preserve_aspect_ratio=preserve_aspect_ratio,
    +        symmetric_pad=symmetric_pad,
    +        detect_orientation=detect_orientation,
    +        straighten_pages=straighten_pages,
    +        detect_language=detect_language,
    +        **kwargs,
    +    )
     
     
     
    -[docs] +[docs] def ocr_predictor( - det_arch: str = 'db_resnet50', - reco_arch: str = 'crnn_vgg16_bn', + det_arch: Any = "fast_base", + reco_arch: Any = "crnn_vgg16_bn", pretrained: bool = False, - **kwargs: Any + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + export_as_straight_boxes: bool = False, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs: Any, ) -> OCRPredictor: """End-to-end OCR architecture using one model for localization, and another for text recognition. - Example:: - >>> import numpy as np - >>> from doctr.models import ocr_predictor - >>> model = ocr_predictor(pretrained=True) - >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) - >>> out = model([input_page]) + >>> 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(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) Args: - arch: name of the architecture to use ('db_sar_vgg', 'db_sar_resnet', 'db_crnn_vgg', 'db_crnn_resnet') + ---- + det_arch: name of the detection architecture or the model itself to use + (e.g. 'db_resnet50', 'db_mobilenet_v3_large') + reco_arch: name of the recognition architecture or the model itself to use + (e.g. 'crnn_vgg16_bn', 'sar_resnet31') pretrained: If True, returns a model pre-trained on our OCR dataset + pretrained_backbone: If True, returns a model with a pretrained backbone + assume_straight_pages: if True, speeds up the inference by assuming you only pass straight pages + without rotated textual elements. + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it. + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right. + export_as_straight_boxes: when assume_straight_pages is set to False, export final predictions + (potentially rotated) as straight bounding boxes. + detect_orientation: if True, the estimated general page orientation will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + straighten_pages: if True, estimates the page general orientation + based on the segmentation map median line orientation. + Then, rotates page before passing it again to the deep learning detection module. + Doing so will improve performances for documents with page-uniform rotations. + detect_language: if True, the language prediction will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + kwargs: keyword args of `OCRPredictor` Returns: + ------- OCR predictor """ + return _predictor( + det_arch, + reco_arch, + pretrained, + pretrained_backbone=pretrained_backbone, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + export_as_straight_boxes=export_as_straight_boxes, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + )
    + + - return _predictor(det_arch, reco_arch, pretrained, **kwargs)
    +def _kie_predictor( + det_arch: Any, + reco_arch: Any, + pretrained: bool, + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + det_bs: int = 2, + reco_bs: int = 128, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs, +) -> KIEPredictor: + # Detection + det_predictor = detection_predictor( + det_arch, + pretrained=pretrained, + pretrained_backbone=pretrained_backbone, + batch_size=det_bs, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + ) + + # Recognition + reco_predictor = recognition_predictor( + reco_arch, + pretrained=pretrained, + pretrained_backbone=pretrained_backbone, + batch_size=reco_bs, + ) + + return KIEPredictor( + det_predictor, + reco_predictor, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + ) + + +
    +[docs] +def kie_predictor( + det_arch: Any = "fast_base", + reco_arch: Any = "crnn_vgg16_bn", + pretrained: bool = False, + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + export_as_straight_boxes: bool = False, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs: Any, +) -> KIEPredictor: + """End-to-end KIE architecture using one model for localization, and another for text recognition. + + >>> 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(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) + + Args: + ---- + det_arch: name of the detection architecture or the model itself to use + (e.g. 'db_resnet50', 'db_mobilenet_v3_large') + reco_arch: name of the recognition architecture or the model itself to use + (e.g. 'crnn_vgg16_bn', 'sar_resnet31') + pretrained: If True, returns a model pre-trained on our OCR dataset + pretrained_backbone: If True, returns a model with a pretrained backbone + assume_straight_pages: if True, speeds up the inference by assuming you only pass straight pages + without rotated textual elements. + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it. + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right. + export_as_straight_boxes: when assume_straight_pages is set to False, export final predictions + (potentially rotated) as straight bounding boxes. + detect_orientation: if True, the estimated general page orientation will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + straighten_pages: if True, estimates the page general orientation + based on the segmentation map median line orientation. + Then, rotates page before passing it again to the deep learning detection module. + Doing so will improve performances for documents with page-uniform rotations. + detect_language: if True, the language prediction will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + kwargs: keyword args of `OCRPredictor` + + Returns: + ------- + KIE predictor + """ + return _kie_predictor( + det_arch, + reco_arch, + pretrained, + pretrained_backbone=pretrained_backbone, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + export_as_straight_boxes=export_as_straight_boxes, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + )

    @@ -353,8 +575,8 @@

    Source code for doctr.models.zoo

           
         
       
    - - + + diff --git a/v0.2.0/_modules/doctr/transforms/modules.html b/v0.2.0/_modules/doctr/transforms/modules.html deleted file mode 100644 index 214233e166..0000000000 --- a/v0.2.0/_modules/doctr/transforms/modules.html +++ /dev/null @@ -1,716 +0,0 @@ - - - - - - - - - - - - doctr.transforms.modules - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.transforms.modules

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import random
    -import tensorflow as tf
    -from typing import List, Any, Tuple, Callable
    -
    -from doctr.utils.repr import NestedObject
    -from . import functional as F
    -
    -
    -__all__ = ['Compose', 'Resize', 'Normalize', 'LambdaTransformation', 'ToGray', 'ColorInversion',
    -           'RandomBrightness', 'RandomContrast', 'RandomSaturation', 'RandomHue', 'RandomGamma', 'RandomJpegQuality',
    -           'OneOf', 'RandomApply']
    -
    -
    -
    -[docs] -class Compose(NestedObject): - """Implements a wrapper that will apply transformations sequentially - - Example:: - >>> from doctr.transforms import Compose, Resize - >>> import tensorflow as tf - >>> transfos = Compose([Resize((32, 32))]) - >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformation modules - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, x: Any) -> Any: - for t in self.transforms: - x = t(x) - - return x
    - - - -
    -[docs] -class Resize(NestedObject): - """Resizes a tensor to a target size - - Example:: - >>> from doctr.transforms import Resize - >>> import tensorflow as tf - >>> transfo = Resize((32, 32)) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - output_size: expected output size - method: interpolation method - preserve_aspect_ratio: if `True`, preserve aspect ratio and pad the rest with zeros - """ - def __init__( - self, - output_size: Tuple[int, int], - method: str = 'bilinear', - preserve_aspect_ratio: bool = False, - ) -> None: - self.output_size = output_size - self.method = method - self.preserve_aspect_ratio = preserve_aspect_ratio - - def extra_repr(self) -> str: - return f"output_size={self.output_size}, method='{self.method}'" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img = tf.image.resize(img, self.output_size, self.method, self.preserve_aspect_ratio) - if self.preserve_aspect_ratio: - img = tf.image.pad_to_bounding_box(img, 0, 0, *self.output_size) - return img
    - - - -
    -[docs] -class Normalize(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - mean: average value per channel - std: standard deviation per channel - """ - def __init__(self, mean: Tuple[float, float, float], std: Tuple[float, float, float]) -> None: - self.mean = tf.constant(mean, dtype=tf.float32) - self.std = tf.constant(std, dtype=tf.float32) - - def extra_repr(self) -> str: - return f"mean={self.mean.numpy().tolist()}, std={self.std.numpy().tolist()}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img -= self.mean - img /= self.std - return img
    - - - -
    -[docs] -class LambdaTransformation(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import LambdaTransformation - >>> import tensorflow as tf - >>> transfo = LambdaTransformation(lambda x: x/ 255.) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - fn: the function to be applied to the input tensor - """ - def __init__(self, fn: Callable[[tf.Tensor], tf.Tensor]) -> None: - self.fn = fn - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return self.fn(img)
    - - - -
    -[docs] -class ToGray(NestedObject): - """Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ToGray() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - """ - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.rgb_to_grayscale(img)
    - - - -
    -[docs] -class ColorInversion(NestedObject): - """Applies the following tranformation to a tensor (image or batch of images): - convert to grayscale, colorize (shift 0-values randomly), and then invert colors - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ColorInversion(min_val=0.6) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_val: range [min_val, 1] to colorize RGB pixels - """ - def __init__(self, min_val: float = 0.6) -> None: - self.min_val = min_val - - def extra_repr(self) -> str: - return f"min_val={self.min_val}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return F.invert_colors(img, self.min_val)
    - - - -
    -[docs] -class RandomBrightness(NestedObject): - """Randomly adjust brightness of a tensor (batch of images or image) by adding a delta - to all pixels - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Brightness() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - p: probability to apply transformation - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_brightness(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomContrast(NestedObject): - """Randomly adjust contrast of a tensor (batch of images or image) by adjusting - each pixel: (img - mean) * contrast_factor + mean. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Contrast() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1) - """ - def __init__(self, delta: float = .3) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_contrast(img, lower=1 - self.delta, upper=1 / (1 - self.delta))
    - - - -
    -[docs] -class RandomSaturation(NestedObject): - """Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and - increasing saturation by a factor. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Saturation() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1) - """ - def __init__(self, delta: float = .5) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_saturation(img, lower=1 - self.delta, upper=1 / (1 - self.delta))
    - - - -
    -[docs] -class RandomHue(NestedObject): - """Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Hue() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_hue(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomGamma(NestedObject): - """randomly performs gamma correction for a tensor (batch of images or image) - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Gamma() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_gamma: non-negative real number, lower bound for gamma param - max_gamma: non-negative real number, upper bound for gamma - min_gain: lower bound for constant multiplier - max_gain: upper bound for constant multiplier - """ - def __init__( - self, - min_gamma: float = 0.5, - max_gamma: float = 1.5, - min_gain: float = 0.8, - max_gain: float = 1.2, - ) -> None: - self.min_gamma = min_gamma - self.max_gamma = max_gamma - self.min_gain = min_gain - self.max_gain = max_gain - - def extra_repr(self) -> str: - return f"""gamma_range=({self.min_gamma}, {self.max_gamma}), - gain_range=({self.min_gain}, {self.max_gain})""" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - gamma = random.uniform(self.min_gamma, self.max_gamma) - gain = random.uniform(self.min_gain, self.max_gain) - return tf.image.adjust_gamma(img, gamma=gamma, gain=gain)
    - - - -
    -[docs] -class RandomJpegQuality(NestedObject): - """Randomly adjust jpeg quality of a 3 dimensional RGB image - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = JpegQuality() - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - min_quality: int between [0, 100] - max_quality: int between [0, 100] - """ - def __init__(self, min_quality: int = 60, max_quality: int = 100) -> None: - self.min_quality = min_quality - self.max_quality = max_quality - - def extra_repr(self) -> str: - return f"min_quality={self.min_quality}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_jpeg_quality( - img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality - )
    - - - -
    -[docs] -class OneOf(NestedObject): - """Randomly apply one of the input transformations - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = OneOf([JpegQuality(), Gamma()]) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformations, one only will be picked - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - # Pick transformation - transfo = self.transforms[int(random.random() * len(self.transforms))] - # Apply - return transfo(img)
    - - - -
    -[docs] -class RandomApply(NestedObject): - """Apply with a probability p the input transformation - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = RandomApply(Gamma(), p=.5) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transform: transformation to apply - p: probability to apply - """ - def __init__(self, transform: NestedObject, p: float = .5) -> None: - self.transform = transform - self.p = p - - def extra_repr(self) -> str: - return f"transform={self.transform}, p={self.p}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - if random.random() < self.p: - return self.transform(img) - return img
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.0/_modules/doctr/transforms/modules/base.html b/v0.2.0/_modules/doctr/transforms/modules/base.html index 96ebd680b7..4596df3848 100644 --- a/v0.2.0/_modules/doctr/transforms/modules/base.html +++ b/v0.2.0/_modules/doctr/transforms/modules/base.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.base - docTR documentation @@ -643,7 +643,7 @@

    Source code for doctr.transforms.modules.base

    - + diff --git a/v0.2.0/_modules/doctr/transforms/modules/tensorflow.html b/v0.2.0/_modules/doctr/transforms/modules/tensorflow.html index 0e18bcc922..acbbe96225 100644 --- a/v0.2.0/_modules/doctr/transforms/modules/tensorflow.html +++ b/v0.2.0/_modules/doctr/transforms/modules/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.tensorflow - docTR documentation @@ -956,7 +956,7 @@

    Source code for doctr.transforms.modules.tensorflow

    - + diff --git a/v0.2.0/_modules/doctr/utils/metrics.html b/v0.2.0/_modules/doctr/utils/metrics.html index afd16328c6..8a37d5949a 100644 --- a/v0.2.0/_modules/doctr/utils/metrics.html +++ b/v0.2.0/_modules/doctr/utils/metrics.html @@ -13,7 +13,7 @@ - + doctr.utils.metrics - docTR documentation @@ -225,15 +225,42 @@

    Source code for doctr.utils.metrics

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
    +
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +from typing import Dict, List, Optional, Tuple
     
     import numpy as np
    -from rapidfuzz.string_metric import levenshtein
    -from typing import List, Tuple
    +from anyascii import anyascii
     from scipy.optimize import linear_sum_assignment
    +from shapely.geometry import Polygon
     
    -__all__ = ['ExactMatch', 'box_iou', 'assign_pairs', 'LocalizationConfusion', 'OCRMetric']
    +__all__ = [
    +    "TextMatch",
    +    "box_iou",
    +    "polygon_iou",
    +    "nms",
    +    "LocalizationConfusion",
    +    "OCRMetric",
    +    "DetectionMetric",
    +]
     
     
    -
    -[docs] -class ExactMatch: - """Implements exact match metric (word-level accuracy) for recognition task. +def string_match(word1: str, word2: str) -> Tuple[bool, bool, bool, bool]: + """Performs string comparison with multiple levels of tolerance - The aggregated metric is computed as follows: + Args: + ---- + word1: a string + word2: another string - .. math:: - \\forall X, Y \\in \\mathcal{W}^N, - ExactMatch(X, Y) = \\frac{1}{N} \\sum\\limits_{i=1}^N f_{Y_i}(X_i) + Returns: + ------- + a tuple with booleans specifying respectively whether the raw strings, their lower-case counterparts, their + anyascii counterparts and their lower-case anyascii counterparts match + """ + raw_match = word1 == word2 + caseless_match = word1.lower() == word2.lower() + anyascii_match = anyascii(word1) == anyascii(word2) - with the indicator function :math:`f_{a}` defined as: + # Warning: the order is important here otherwise the pair ("EUR", "€") cannot be matched + unicase_match = anyascii(word1).lower() == anyascii(word2).lower() - .. math:: - \\forall a, x \\in \\mathcal{W}, - f_a(x) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } x = a \\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{W}` is the set of all possible character sequences, - :math:`N` is a strictly positive integer. + return raw_match, caseless_match, anyascii_match, unicase_match - Example:: - >>> from doctr.utils import ExactMatch - >>> metric = ExactMatch() - >>> metric.update(['Hello', 'world'], ['hello', 'world']) - >>> metric.summary() - Args: - ignore_case: if true, ignore letter case when computing metric - ignore_accents: if true, ignore accents errors when computing metrics""" +
    +[docs] +class TextMatch: + r"""Implements text match metric (word-level accuracy) for recognition task. - def __init__( - self, - ignore_case: bool = False, - ignore_accents: bool = False, - ) -> None: + The raw aggregated metric is computed as follows: - self.matches = 0 - self.total = 0 - self.ignore_case = ignore_case - self.ignore_accents = ignore_accents + .. math:: + \forall X, Y \in \mathcal{W}^N, + TextMatch(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N f_{Y_i}(X_i) - @staticmethod - def remove_accent(input_string: str) -> str: - """Removes all accents (¨^çéè...) from input_string + with the indicator function :math:`f_{a}` defined as: - Args: - input_string: character sequence with accents + .. math:: + \forall a, x \in \mathcal{W}, + f_a(x) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } x = a \\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{W}` is the set of all possible character sequences, + :math:`N` is a strictly positive integer. - Returns: - character sequence without accents""" + >>> from doctr.utils import TextMatch + >>> metric = TextMatch() + >>> metric.update(['Hello', 'world'], ['hello', 'world']) + >>> metric.summary() + """ - raise NotImplementedError + def __init__(self) -> None: + self.reset() +
    +[docs] def update( self, gt: List[str], @@ -348,53 +386,66 @@

    Source code for doctr.utils.metrics

             """Update the state of the metric with new predictions
     
             Args:
    +        ----
                 gt: list of groung-truth character sequences
    -            pred: list of predicted character sequences"""
    -
    +            pred: list of predicted character sequences
    +        """
             if len(gt) != len(pred):
                 raise AssertionError("prediction size does not match with ground-truth labels size")
     
    -        for pred_word, gt_word in zip(pred, gt):
    -            if self.ignore_accents:
    -                gt_word = self.remove_accent(gt_word)
    -                pred_word = self.remove_accent(pred_word)
    -
    -            if self.ignore_case:
    -                gt_word = gt_word.lower()
    -                pred_word = pred_word.lower()
    +        for gt_word, pred_word in zip(gt, pred):
    +            _raw, _caseless, _anyascii, _unicase = string_match(gt_word, pred_word)
    +            self.raw += int(_raw)
    +            self.caseless += int(_caseless)
    +            self.anyascii += int(_anyascii)
    +            self.unicase += int(_unicase)
     
    -            if pred_word == gt_word:
    -                self.matches += 1
    +        self.total += len(gt)
    - self.total += len(gt) - def summary(self) -> float: - """Computes the aggregated evaluation +
    +[docs] + def summary(self) -> Dict[str, float]: + """Computes the aggregated metrics - Returns: - metric result""" + Returns + ------- + a dictionary with the exact match score for the raw data, its lower-case counterpart, its anyascii + counterpart and its lower-case anyascii counterpart + """ if self.total == 0: raise AssertionError("you need to update the metric before getting the summary") - return self.matches / self.total + + return dict( + raw=self.raw / self.total, + caseless=self.caseless / self.total, + anyascii=self.anyascii / self.total, + unicase=self.unicase / self.total, + )
    + def reset(self) -> None: - self.matches = 0 + self.raw = 0 + self.caseless = 0 + self.anyascii = 0 + self.unicase = 0 self.total = 0
    def box_iou(boxes_1: np.ndarray, boxes_2: np.ndarray) -> np.ndarray: - """Compute the IoU between two sets of bounding boxes + """Computes the IoU between two sets of bounding boxes Args: + ---- boxes_1: bounding boxes of shape (N, 4) in format (xmin, ymin, xmax, ymax) boxes_2: bounding boxes of shape (M, 4) in format (xmin, ymin, xmax, ymax) Returns: + ------- the IoU matrix of shape (N, M) """ - - iou_mat = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32) + iou_mat: np.ndarray = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32) if boxes_1.shape[0] > 0 and boxes_2.shape[0] > 0: l1, t1, r1, b1 = np.split(boxes_1, 4, axis=1) @@ -405,169 +456,244 @@

    Source code for doctr.utils.metrics

             right = np.minimum(r1, r2.T)
             bot = np.minimum(b1, b2.T)
     
    -        intersection = np.clip(right - left, 0, np.Inf) * np.clip(bot - top, 0, np.Inf)
    +        intersection = np.clip(right - left, 0, np.inf) * np.clip(bot - top, 0, np.inf)
             union = (r1 - l1) * (b1 - t1) + ((r2 - l2) * (b2 - t2)).T - intersection
             iou_mat = intersection / union
     
         return iou_mat
     
     
    -def assign_pairs(score_mat: np.ndarray, score_threshold: float = 0.5) -> Tuple[np.ndarray, np.ndarray]:
    -    """Assigns candidates by maximizing the score of all pairs
    +def polygon_iou(polys_1: np.ndarray, polys_2: np.ndarray) -> np.ndarray:
    +    """Computes the IoU between two sets of rotated bounding boxes
     
         Args:
    -        score_mat: score matrix
    -        score_threshold: minimum score to validate an assignment
    +    ----
    +        polys_1: rotated bounding boxes of shape (N, 4, 2)
    +        polys_2: rotated bounding boxes of shape (M, 4, 2)
    +        mask_shape: spatial shape of the intermediate masks
    +        use_broadcasting: if set to True, leverage broadcasting speedup by consuming more memory
    +
         Returns:
    -        a tuple of two lists: the list of assigned row candidates indices, and the list of their column counterparts
    +    -------
    +        the IoU matrix of shape (N, M)
         """
    +    if polys_1.ndim != 3 or polys_2.ndim != 3:
    +        raise AssertionError("expects boxes to be in format (N, 4, 2)")
    +
    +    iou_mat = np.zeros((polys_1.shape[0], polys_2.shape[0]), dtype=np.float32)
    +
    +    shapely_polys_1 = [Polygon(poly) for poly in polys_1]
    +    shapely_polys_2 = [Polygon(poly) for poly in polys_2]
    +
    +    for i, poly1 in enumerate(shapely_polys_1):
    +        for j, poly2 in enumerate(shapely_polys_2):
    +            intersection_area = poly1.intersection(poly2).area
    +            union_area = poly1.area + poly2.area - intersection_area
    +            iou_mat[i, j] = intersection_area / union_area
     
    -    row_ind, col_ind = linear_sum_assignment(-score_mat)
    -    is_kept = score_mat[row_ind, col_ind] >= score_threshold
    -    return row_ind[is_kept], col_ind[is_kept]
    +    return iou_mat
    +
    +
    +def nms(boxes: np.ndarray, thresh: float = 0.5) -> List[int]:
    +    """Perform non-max suppression, borrowed from <https://github.com/rbgirshick/fast-rcnn>`_.
    +
    +    Args:
    +    ----
    +        boxes: np array of straight boxes: (*, 5), (xmin, ymin, xmax, ymax, score)
    +        thresh: iou threshold to perform box suppression.
    +
    +    Returns:
    +    -------
    +        A list of box indexes to keep
    +    """
    +    x1 = boxes[:, 0]
    +    y1 = boxes[:, 1]
    +    x2 = boxes[:, 2]
    +    y2 = boxes[:, 3]
    +    scores = boxes[:, 4]
    +
    +    areas = (x2 - x1) * (y2 - y1)
    +    order = scores.argsort()[::-1]
    +
    +    keep = []
    +    while order.size > 0:
    +        i = order[0]
    +        keep.append(i)
    +        xx1 = np.maximum(x1[i], x1[order[1:]])
    +        yy1 = np.maximum(y1[i], y1[order[1:]])
    +        xx2 = np.minimum(x2[i], x2[order[1:]])
    +        yy2 = np.minimum(y2[i], y2[order[1:]])
    +
    +        w = np.maximum(0.0, xx2 - xx1)
    +        h = np.maximum(0.0, yy2 - yy1)
    +        inter = w * h
    +        ovr = inter / (areas[i] + areas[order[1:]] - inter)
    +
    +        inds = np.where(ovr <= thresh)[0]
    +        order = order[inds + 1]
    +    return keep
     
     
     
    -[docs] +[docs] class LocalizationConfusion: - """Implements common confusion metrics and mean IoU for localization evaluation. + r"""Implements common confusion metrics and mean IoU for localization evaluation. The aggregated metrics are computed as follows: .. math:: - \\forall Y \\in \\mathcal{B}^N, \\forall X \\in \\mathcal{B}^M, \\\\ - Recall(X, Y) = \\frac{1}{N} \\sum\\limits_{i=1}^N g_{X}(Y_i) \\\\ - Precision(X, Y) = \\frac{1}{M} \\sum\\limits_{i=1}^N g_{X}(Y_i) \\\\ - meanIoU(X, Y) = \\frac{1}{M} \\sum\\limits_{i=1}^M \\max\\limits_{j \\in [1, N]} IoU(X_i, Y_j) + \forall Y \in \mathcal{B}^N, \forall X \in \mathcal{B}^M, \\ + Recall(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N g_{X}(Y_i) \\ + Precision(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M g_{X}(Y_i) \\ + meanIoU(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(X_i, Y_j) with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and :math:`y`, and the function :math:`g_{X}` defined as: .. math:: - \\forall y \\in \\mathcal{B}, - g_X(y) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } y\\mbox{ has been assigned to any }(X_i)_i\\mbox{ with an }IoU \\geq 0.5 \\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{B}` is the set of possible bounding boxes, + \forall y \in \mathcal{B}, + g_X(y) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } y\mbox{ has been assigned to any }(X_i)_i\mbox{ with an }IoU \geq 0.5 \\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. - Example:: - >>> import numpy as np - >>> from doctr.utils import LocalizationConfusion - >>> metric = LocalizationConfusion(iou_thresh=0.5) - >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]])) - >>> metric.summary() + >>> import numpy as np + >>> from doctr.utils import LocalizationConfusion + >>> metric = LocalizationConfusion(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]])) + >>> metric.summary() Args: + ---- iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format """ - def __init__(self, iou_thresh: float = 0.5) -> None: - + def __init__( + self, + iou_thresh: float = 0.5, + use_polygons: bool = False, + ) -> None: self.iou_thresh = iou_thresh - self.num_gts = 0 - self.num_preds = 0 - self.num_matches = 0 - self.tot_iou = 0. + self.use_polygons = use_polygons + self.reset() +
    +[docs] def update(self, gts: np.ndarray, preds: np.ndarray) -> None: + """Updates the metric + Args: + ---- + gts: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones + preds: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones + """ if preds.shape[0] > 0: # Compute IoU - iou_mat = box_iou(gts, preds) - self.tot_iou += float(iou_mat.max(axis=1).sum()) + if self.use_polygons: + iou_mat = polygon_iou(gts, preds) + else: + iou_mat = box_iou(gts, preds) + self.tot_iou += float(iou_mat.max(axis=0).sum()) + # Assign pairs - gt_indices, _ = assign_pairs(iou_mat, self.iou_thresh) - self.num_matches += len(gt_indices) + gt_indices, pred_indices = linear_sum_assignment(-iou_mat) + self.matches += int((iou_mat[gt_indices, pred_indices] >= self.iou_thresh).sum()) # Update counts self.num_gts += gts.shape[0] - self.num_preds += preds.shape[0] + self.num_preds += preds.shape[0]
    - def summary(self) -> Tuple[float, float, float]: +
    +[docs] + def summary(self) -> Tuple[Optional[float], Optional[float], Optional[float]]: + """Computes the aggregated metrics + + Returns + ------- + a tuple with the recall, precision and meanIoU scores + """ # Recall - recall = self.num_matches / self.num_gts + recall = self.matches / self.num_gts if self.num_gts > 0 else None # Precision - precision = self.num_matches / self.num_preds + precision = self.matches / self.num_preds if self.num_preds > 0 else None # mean IoU - mean_iou = self.tot_iou / self.num_preds + mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None + + return recall, precision, mean_iou
    - return recall, precision, mean_iou def reset(self) -> None: self.num_gts = 0 self.num_preds = 0 - self.num_matches = 0 - self.tot_iou = 0.
    + self.matches = 0 + self.tot_iou = 0.0
    -[docs] +[docs] class OCRMetric: - """Implements end-to-end OCR metric. + r"""Implements an end-to-end OCR metric. The aggregated metrics are computed as follows: .. math:: - \\forall (B, L) \\in \\mathcal{B}^N \\times \\mathcal{L}^N, - \\forall (\\hat{B}, \\hat{L}) \\in \\mathcal{B}^M \\times \\mathcal{L}^M, \\\\ - Recall(B, \\hat{B}, L, \\hat{L}) = \\frac{1}{N} \\sum\\limits_{i=1}^N h_{B,L}(\\hat{B}_i, \\hat{L}_i) \\\\ - Precision(B, \\hat{B}, L, \\hat{L}) = \\frac{1}{M} \\sum\\limits_{i=1}^N h_{B,L}(\\hat{B}_i, \\hat{L}_i) \\\\ - meanIoU(B, \\hat{B}) = \\frac{1}{M} \\sum\\limits_{i=1}^M \\max\\limits_{j \\in [1, N]} IoU(\\hat{B}_i, B_j) + \forall (B, L) \in \mathcal{B}^N \times \mathcal{L}^N, + \forall (\hat{B}, \hat{L}) \in \mathcal{B}^M \times \mathcal{L}^M, \\ + Recall(B, \hat{B}, L, \hat{L}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ + Precision(B, \hat{B}, L, \hat{L}) = \frac{1}{M} \sum\limits_{i=1}^M h_{B,L}(\hat{B}_i, \hat{L}_i) \\ + meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j) with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and :math:`y`, and the function :math:`h_{B, L}` defined as: .. math:: - \\forall (b, l) \\in \\mathcal{B} \\times \\mathcal{L}, - h_{B,L}(b, l) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } b\\mbox{ has been assigned to a given }B_j\\mbox{ with an } \\\\ - & IoU \\geq 0.5 \\mbox{ and that for this assignment, } l = L_j\\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{B}` is the set of possible bounding boxes, - :math:`\\mathcal{L}` is the set of possible character sequences, + \forall (b, l) \in \mathcal{B} \times \mathcal{L}, + h_{B,L}(b, l) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ + & IoU \geq 0.5 \mbox{ and that for this assignment, } l = L_j\\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, + :math:`\mathcal{L}` is the set of possible character sequences, :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. - Example:: - >>> import numpy as np - >>> from doctr.utils import OCRMetric - >>> metric = OCRMetric(iou_thresh=0.5) - >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), - ['hello'], ['hello', 'world']) - >>> metric.summary() + >>> import numpy as np + >>> from doctr.utils import OCRMetric + >>> metric = OCRMetric(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), + >>> ['hello'], ['hello', 'world']) + >>> metric.summary() Args: + ---- iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match - max_dist: maximum Levenshtein distance between 2 sequence to consider a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format """ def __init__( self, iou_thresh: float = 0.5, - max_dist: int = 0 + use_polygons: bool = False, ) -> None: - self.iou_thresh = iou_thresh - self.max_dist = max_dist - self.num_gts = 0 - self.num_preds = 0 - self.num_det_matches = 0 - self.num_reco_matches = 0 - self.tot_iou = 0. - self.tot_dist = 0 + self.use_polygons = use_polygons + self.reset() +
    +[docs] def update( self, gt_boxes: np.ndarray, @@ -575,52 +701,207 @@

    Source code for doctr.utils.metrics

             gt_labels: List[str],
             pred_labels: List[str],
         ) -> None:
    +        """Updates the metric
    +
    +        Args:
    +        ----
    +            gt_boxes: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones
    +            pred_boxes: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones
    +            gt_labels: a list of N string labels
    +            pred_labels: a list of M string labels
    +        """
    +        if gt_boxes.shape[0] != len(gt_labels) or pred_boxes.shape[0] != len(pred_labels):
    +            raise AssertionError(
    +                "there should be the same number of boxes and string both for the ground truth and the predictions"
    +            )
     
             # Compute IoU
    -        iou_mat = box_iou(gt_boxes, pred_boxes)
    -        if iou_mat.shape[1] == 0:
    -            self.tot_iou = 0
    -        else:
    -            self.tot_iou += float(iou_mat.max(axis=1).sum())
    -
    -        # Assign pairs
    -        gt_indices, preds_indices = assign_pairs(iou_mat, self.iou_thresh)
    -
    -        # Compare sequences
    -        for gt_idx, pred_idx in zip(gt_indices, preds_indices):
    -            dist = levenshtein(gt_labels[gt_idx], pred_labels[pred_idx])
    -            self.tot_dist += dist
    -            if dist <= self.max_dist:
    -                self.num_reco_matches += 1
    +        if pred_boxes.shape[0] > 0:
    +            if self.use_polygons:
    +                iou_mat = polygon_iou(gt_boxes, pred_boxes)
    +            else:
    +                iou_mat = box_iou(gt_boxes, pred_boxes)
    +
    +            self.tot_iou += float(iou_mat.max(axis=0).sum())
    +
    +            # Assign pairs
    +            gt_indices, pred_indices = linear_sum_assignment(-iou_mat)
    +            is_kept = iou_mat[gt_indices, pred_indices] >= self.iou_thresh
    +            # String comparison
    +            for gt_idx, pred_idx in zip(gt_indices[is_kept], pred_indices[is_kept]):
    +                _raw, _caseless, _anyascii, _unicase = string_match(gt_labels[gt_idx], pred_labels[pred_idx])
    +                self.raw_matches += int(_raw)
    +                self.caseless_matches += int(_caseless)
    +                self.anyascii_matches += int(_anyascii)
    +                self.unicase_matches += int(_unicase)
    +
    +        self.num_gts += gt_boxes.shape[0]
    +        self.num_preds += pred_boxes.shape[0]
    + + +
    +[docs] + def summary(self) -> Tuple[Dict[str, Optional[float]], Dict[str, Optional[float]], Optional[float]]: + """Computes the aggregated metrics + + Returns + ------- + a tuple with the recall & precision for each string comparison and the mean IoU + """ + # Recall + recall = dict( + raw=self.raw_matches / self.num_gts if self.num_gts > 0 else None, + caseless=self.caseless_matches / self.num_gts if self.num_gts > 0 else None, + anyascii=self.anyascii_matches / self.num_gts if self.num_gts > 0 else None, + unicase=self.unicase_matches / self.num_gts if self.num_gts > 0 else None, + ) + + # Precision + precision = dict( + raw=self.raw_matches / self.num_preds if self.num_preds > 0 else None, + caseless=self.caseless_matches / self.num_preds if self.num_preds > 0 else None, + anyascii=self.anyascii_matches / self.num_preds if self.num_preds > 0 else None, + unicase=self.unicase_matches / self.num_preds if self.num_preds > 0 else None, + ) + + # mean IoU (overall detected boxes) + mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None + + return recall, precision, mean_iou
    + + + def reset(self) -> None: + self.num_gts = 0 + self.num_preds = 0 + self.tot_iou = 0.0 + self.raw_matches = 0 + self.caseless_matches = 0 + self.anyascii_matches = 0 + self.unicase_matches = 0
    + + + +
    +[docs] +class DetectionMetric: + r"""Implements an object detection metric. + + The aggregated metrics are computed as follows: + + .. math:: + \forall (B, C) \in \mathcal{B}^N \times \mathcal{C}^N, + \forall (\hat{B}, \hat{C}) \in \mathcal{B}^M \times \mathcal{C}^M, \\ + Recall(B, \hat{B}, C, \hat{C}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,C}(\hat{B}_i, \hat{C}_i) \\ + Precision(B, \hat{B}, C, \hat{C}) = \frac{1}{M} \sum\limits_{i=1}^M h_{B,C}(\hat{B}_i, \hat{C}_i) \\ + meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j) + + with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and + :math:`y`, and the function :math:`h_{B, C}` defined as: + + .. math:: + \forall (b, c) \in \mathcal{B} \times \mathcal{C}, + h_{B,C}(b, c) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ + & IoU \geq 0.5 \mbox{ and that for this assignment, } c = C_j\\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, + :math:`\mathcal{C}` is the set of possible class indices, + :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. + + >>> import numpy as np + >>> from doctr.utils import DetectionMetric + >>> metric = DetectionMetric(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), + >>> np.zeros(1, dtype=np.int64), np.array([0, 1], dtype=np.int64)) + >>> metric.summary() + + Args: + ---- + iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format + """ + + def __init__( + self, + iou_thresh: float = 0.5, + use_polygons: bool = False, + ) -> None: + self.iou_thresh = iou_thresh + self.use_polygons = use_polygons + self.reset() + +
    +[docs] + def update( + self, + gt_boxes: np.ndarray, + pred_boxes: np.ndarray, + gt_labels: np.ndarray, + pred_labels: np.ndarray, + ) -> None: + """Updates the metric + + Args: + ---- + gt_boxes: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones + pred_boxes: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones + gt_labels: an array of class indices of shape (N,) + pred_labels: an array of class indices of shape (M,) + """ + if gt_boxes.shape[0] != gt_labels.shape[0] or pred_boxes.shape[0] != pred_labels.shape[0]: + raise AssertionError( + "there should be the same number of boxes and string both for the ground truth and the predictions" + ) + + # Compute IoU + if pred_boxes.shape[0] > 0: + if self.use_polygons: + iou_mat = polygon_iou(gt_boxes, pred_boxes) + else: + iou_mat = box_iou(gt_boxes, pred_boxes) + + self.tot_iou += float(iou_mat.max(axis=0).sum()) + + # Assign pairs + gt_indices, pred_indices = linear_sum_assignment(-iou_mat) + is_kept = iou_mat[gt_indices, pred_indices] >= self.iou_thresh + # Category comparison + self.num_matches += int((gt_labels[gt_indices[is_kept]] == pred_labels[pred_indices[is_kept]]).sum()) - # Update counts - self.num_det_matches = len(gt_indices) self.num_gts += gt_boxes.shape[0] - self.num_preds += pred_boxes.shape[0] + self.num_preds += pred_boxes.shape[0]
    + - def summary(self) -> Tuple[float, float, float, float]: +
    +[docs] + def summary(self) -> Tuple[Optional[float], Optional[float], Optional[float]]: + """Computes the aggregated metrics + Returns + ------- + a tuple with the recall & precision for each class prediction and the mean IoU + """ # Recall - recall = self.num_reco_matches / self.num_gts + recall = self.num_matches / self.num_gts if self.num_gts > 0 else None # Precision - precision = self.num_reco_matches / self.num_preds + precision = self.num_matches / self.num_preds if self.num_preds > 0 else None # mean IoU (overall detected boxes) - mean_iou = self.tot_iou / self.num_preds + mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None - # mean distance (overall detection-matching boxes) - mean_distance = self.tot_dist / self.num_det_matches + return recall, precision, mean_iou
    - return recall, precision, mean_iou, mean_distance def reset(self) -> None: self.num_gts = 0 self.num_preds = 0 - self.num_det_matches = 0 - self.num_reco_matches = 0 - self.tot_iou = 0. - self.tot_dist = 0
    + self.tot_iou = 0.0 + self.num_matches = 0
    @@ -654,8 +935,8 @@

    Source code for doctr.utils.metrics

           
         
       
    -
    - + + diff --git a/v0.2.0/_modules/doctr/utils/visualization.html b/v0.2.0/_modules/doctr/utils/visualization.html index 3e5bc073f8..c818be6d7b 100644 --- a/v0.2.0/_modules/doctr/utils/visualization.html +++ b/v0.2.0/_modules/doctr/utils/visualization.html @@ -13,7 +13,7 @@ - + doctr.utils.visualization - docTR documentation @@ -225,15 +225,42 @@

    Source code for doctr.utils.visualization

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
    +import colorsys
    +from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple, Union
     
    -import matplotlib.pyplot as plt
    -from matplotlib.figure import Figure
    +import cv2
     import matplotlib.patches as patches
    -import mplcursors
    +import matplotlib.pyplot as plt
     import numpy as np
    -from typing import Tuple, List, Dict, Any
    +from matplotlib.figure import Figure
     
    -from .common_types import BoundingBox
    +from .common_types import BoundingBox, Polygon4P
     
    -__all__ = ['visualize_page']
    +__all__ = ["visualize_page", "visualize_kie_page", "draw_boxes"]
     
     
    -def create_rect_patch(
    +def rect_patch(
         geometry: BoundingBox,
    -    label: str,
         page_dimensions: Tuple[int, int],
    -    color: Tuple[int, int, int],
    +    label: Optional[str] = None,
    +    color: Tuple[float, float, float] = (0, 0, 0),
         alpha: float = 0.3,
         linewidth: int = 2,
         fill: bool = True,
    -) -> patches.Patch:
    -    """Create a matplotlib patch (rectangle) bounding the element
    +    preserve_aspect_ratio: bool = False,
    +) -> patches.Rectangle:
    +    """Create a matplotlib rectangular patch for the element
     
         Args:
    +    ----
             geometry: bounding box of the element
    +        page_dimensions: dimensions of the Page in format (height, width)
             label: label to display when hovered
    -        page_dimensions: dimensions of the Page
             color: color to draw box
             alpha: opacity parameter to fill the boxes, 0 = transparent
             linewidth: line width
    +        fill: whether the patch should be filled
    +        preserve_aspect_ratio: pass True if you passed True to the predictor
     
         Returns:
    +    -------
             a rectangular Patch
         """
    -    h, w = page_dimensions
    +    if len(geometry) != 2 or any(not isinstance(elt, tuple) or len(elt) != 2 for elt in geometry):
    +        raise ValueError("invalid geometry format")
    +
    +    # Unpack
    +    height, width = page_dimensions
         (xmin, ymin), (xmax, ymax) = geometry
    -    xmin, xmax = xmin * w, xmax * w
    -    ymin, ymax = ymin * h, ymax * h
    -    rect = patches.Rectangle(
    +    # Switch to absolute coords
    +    if preserve_aspect_ratio:
    +        width = height = max(height, width)
    +    xmin, w = xmin * width, (xmax - xmin) * width
    +    ymin, h = ymin * height, (ymax - ymin) * height
    +
    +    return patches.Rectangle(
             (xmin, ymin),
    -        xmax - xmin,
    -        ymax - ymin,
    +        w,
    +        h,
    +        fill=fill,
    +        linewidth=linewidth,
    +        edgecolor=(*color, alpha),
    +        facecolor=(*color, alpha),
    +        label=label,
    +    )
    +
    +
    +def polygon_patch(
    +    geometry: np.ndarray,
    +    page_dimensions: Tuple[int, int],
    +    label: Optional[str] = None,
    +    color: Tuple[float, float, float] = (0, 0, 0),
    +    alpha: float = 0.3,
    +    linewidth: int = 2,
    +    fill: bool = True,
    +    preserve_aspect_ratio: bool = False,
    +) -> patches.Polygon:
    +    """Create a matplotlib polygon patch for the element
    +
    +    Args:
    +    ----
    +        geometry: bounding box of the element
    +        page_dimensions: dimensions of the Page in format (height, width)
    +        label: label to display when hovered
    +        color: color to draw box
    +        alpha: opacity parameter to fill the boxes, 0 = transparent
    +        linewidth: line width
    +        fill: whether the patch should be filled
    +        preserve_aspect_ratio: pass True if you passed True to the predictor
    +
    +    Returns:
    +    -------
    +        a polygon Patch
    +    """
    +    if not geometry.shape == (4, 2):
    +        raise ValueError("invalid geometry format")
    +
    +    # Unpack
    +    height, width = page_dimensions
    +    geometry[:, 0] = geometry[:, 0] * (max(width, height) if preserve_aspect_ratio else width)
    +    geometry[:, 1] = geometry[:, 1] * (max(width, height) if preserve_aspect_ratio else height)
    +
    +    return patches.Polygon(
    +        geometry,
             fill=fill,
             linewidth=linewidth,
             edgecolor=(*color, alpha),
             facecolor=(*color, alpha),
    -        label=label
    +        label=label,
         )
    -    return rect
    +
    +
    +def create_obj_patch(
    +    geometry: Union[BoundingBox, Polygon4P, np.ndarray],
    +    page_dimensions: Tuple[int, int],
    +    **kwargs: Any,
    +) -> patches.Patch:
    +    """Create a matplotlib patch for the element
    +
    +    Args:
    +    ----
    +        geometry: bounding box (straight or rotated) of the element
    +        page_dimensions: dimensions of the page in format (height, width)
    +        **kwargs: keyword arguments for the patch
    +
    +    Returns:
    +    -------
    +        a matplotlib Patch
    +    """
    +    if isinstance(geometry, tuple):
    +        if len(geometry) == 2:  # straight word BB (2 pts)
    +            return rect_patch(geometry, page_dimensions, **kwargs)
    +        elif len(geometry) == 4:  # rotated word BB (4 pts)
    +            return polygon_patch(np.asarray(geometry), page_dimensions, **kwargs)
    +    elif isinstance(geometry, np.ndarray) and geometry.shape == (4, 2):  # rotated line
    +        return polygon_patch(geometry, page_dimensions, **kwargs)
    +    raise ValueError("invalid geometry format")
    +
    +
    +def get_colors(num_colors: int) -> List[Tuple[float, float, float]]:
    +    """Generate num_colors color for matplotlib
    +
    +    Args:
    +    ----
    +        num_colors: number of colors to generate
    +
    +    Returns:
    +    -------
    +        colors: list of generated colors
    +    """
    +    colors = []
    +    for i in np.arange(0.0, 360.0, 360.0 / num_colors):
    +        hue = i / 360.0
    +        lightness = (50 + np.random.rand() * 10) / 100.0
    +        saturation = (90 + np.random.rand() * 10) / 100.0
    +        colors.append(colorsys.hls_to_rgb(hue, lightness, saturation))
    +    return colors
     
     
     
    -[docs] +[docs] def visualize_page( page: Dict[str, Any], image: np.ndarray, words_only: bool = True, + display_artefacts: bool = True, scale: float = 10, interactive: bool = True, add_labels: bool = True, @@ -338,22 +472,30 @@

    Source code for doctr.utils.visualization

     ) -> Figure:
         """Visualize a full page with predicted blocks, lines and words
     
    -    Example::
    -        >>> import numpy as np
    -        >>> import matplotlib.pyplot as plt
    -        >>> from doctr.utils.visualization import visualize_page
    -        >>> from doctr.models import ocr_db_crnn
    -        >>> model = ocr_db_crnn(pretrained=True)
    -        >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    -        >>> out = model([[input_page]])
    -        >>> visualize_page(out[0].pages[0].export(), input_page)
    -        >>> plt.show()
    +    >>> import numpy as np
    +    >>> import matplotlib.pyplot as plt
    +    >>> from doctr.utils.visualization import visualize_page
    +    >>> from doctr.models import ocr_db_crnn
    +    >>> model = ocr_db_crnn(pretrained=True)
    +    >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    +    >>> out = model([[input_page]])
    +    >>> visualize_page(out[0].pages[0].export(), input_page)
    +    >>> plt.show()
     
         Args:
    +    ----
             page: the exported Page of a Document
             image: np array of the page, needs to have the same shape than page['dimensions']
             words_only: whether only words should be displayed
    +        display_artefacts: whether artefacts should be displayed
             scale: figsize of the largest windows side
    +        interactive: whether the plot should be interactive
    +        add_labels: for static plot, adds text labels on top of bounding box
    +        **kwargs: keyword arguments for the polygon patch
    +
    +    Returns:
    +    -------
    +        the matplotlib figure
         """
         # Get proper scale and aspect ratio
         h, w = image.shape[:2]
    @@ -362,58 +504,189 @@ 

    Source code for doctr.utils.visualization

         # Display the image
         ax.imshow(image)
         # hide both axis
    -    ax.axis('off')
    +    ax.axis("off")
     
         if interactive:
             artists: List[patches.Patch] = []  # instantiate an empty list of patches (to be drawn on the page)
     
    -    for block in page['blocks']:
    +    for block in page["blocks"]:
             if not words_only:
    -            rect = create_rect_patch(block['geometry'], 'block', page['dimensions'], (0, 1, 0), linewidth=1, **kwargs)
    +            rect = create_obj_patch(
    +                block["geometry"], page["dimensions"], label="block", color=(0, 1, 0), linewidth=1, **kwargs
    +            )
                 # add patch on figure
                 ax.add_patch(rect)
                 if interactive:
                     # add patch to cursor's artists
                     artists.append(rect)
     
    -        for line in block['lines']:
    +        for line in block["lines"]:
                 if not words_only:
    -                rect = create_rect_patch(line['geometry'], 'line', page['dimensions'], (1, 0, 0), linewidth=1, **kwargs)
    +                rect = create_obj_patch(
    +                    line["geometry"], page["dimensions"], label="line", color=(1, 0, 0), linewidth=1, **kwargs
    +                )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
     
    -            for word in line['words']:
    -                rect = create_rect_patch(word['geometry'], f"{word['value']} (confidence: {word['confidence']:.2%})",
    -                                         page['dimensions'], (0, 0, 1), **kwargs)
    +            for word in line["words"]:
    +                rect = create_obj_patch(
    +                    word["geometry"],
    +                    page["dimensions"],
    +                    label=f"{word['value']} (confidence: {word['confidence']:.2%})",
    +                    color=(0, 0, 1),
    +                    **kwargs,
    +                )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
                     elif add_labels:
    -                    ax.text(
    -                        int(page['dimensions'][1] * word['geometry'][0][0]),
    -                        int(page['dimensions'][0] * word['geometry'][0][1]),
    -                        word['value'],
    -                        size=10,
    -                        alpha=0.5,
    -                        color=(0, 0, 1),
    -                    )
    +                    if len(word["geometry"]) == 5:
    +                        text_loc = (
    +                            int(page["dimensions"][1] * (word["geometry"][0] - word["geometry"][2] / 2)),
    +                            int(page["dimensions"][0] * (word["geometry"][1] - word["geometry"][3] / 2)),
    +                        )
    +                    else:
    +                        text_loc = (
    +                            int(page["dimensions"][1] * word["geometry"][0][0]),
    +                            int(page["dimensions"][0] * word["geometry"][0][1]),
    +                        )
     
    -        if not words_only:
    -            for artefact in block['artefacts']:
    -                rect = create_rect_patch(artefact['geometry'], 'artefact', page['dimensions'], (0.5, 0.5, 0.5),
    -                                         linewidth=1, **kwargs)
    +                    if len(word["geometry"]) == 2:
    +                        # We draw only if boxes are in straight format
    +                        ax.text(
    +                            *text_loc,
    +                            word["value"],
    +                            size=10,
    +                            alpha=0.5,
    +                            color=(0, 0, 1),
    +                        )
    +
    +        if display_artefacts:
    +            for artefact in block["artefacts"]:
    +                rect = create_obj_patch(
    +                    artefact["geometry"],
    +                    page["dimensions"],
    +                    label="artefact",
    +                    color=(0.5, 0.5, 0.5),
    +                    linewidth=1,
    +                    **kwargs,
    +                )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
     
         if interactive:
    +        import mplcursors
    +
             # Create mlp Cursor to hover patches in artists
             mplcursors.Cursor(artists, hover=2).connect("add", lambda sel: sel.annotation.set_text(sel.artist.get_label()))
    -    fig.tight_layout()
    +    fig.tight_layout(pad=0.0)
     
         return fig
    + + +def visualize_kie_page( + page: Dict[str, Any], + image: np.ndarray, + words_only: bool = False, + display_artefacts: bool = True, + scale: float = 10, + interactive: bool = True, + add_labels: bool = True, + **kwargs: Any, +) -> Figure: + """Visualize a full page with predicted blocks, lines and words + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from doctr.utils.visualization import visualize_page + >>> from doctr.models import ocr_db_crnn + >>> model = ocr_db_crnn(pretrained=True) + >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) + >>> out = model([[input_page]]) + >>> visualize_kie_page(out[0].pages[0].export(), input_page) + >>> plt.show() + + Args: + ---- + page: the exported Page of a Document + image: np array of the page, needs to have the same shape than page['dimensions'] + words_only: whether only words should be displayed + display_artefacts: whether artefacts should be displayed + scale: figsize of the largest windows side + interactive: whether the plot should be interactive + add_labels: for static plot, adds text labels on top of bounding box + **kwargs: keyword arguments for the polygon patch + + Returns: + ------- + the matplotlib figure + """ + # Get proper scale and aspect ratio + h, w = image.shape[:2] + size = (scale * w / h, scale) if h > w else (scale, h / w * scale) + fig, ax = plt.subplots(figsize=size) + # Display the image + ax.imshow(image) + # hide both axis + ax.axis("off") + + if interactive: + artists: List[patches.Patch] = [] # instantiate an empty list of patches (to be drawn on the page) + + colors = {k: color for color, k in zip(get_colors(len(page["predictions"])), page["predictions"])} + for key, value in page["predictions"].items(): + for prediction in value: + if not words_only: + rect = create_obj_patch( + prediction["geometry"], + page["dimensions"], + label=f"{key} \n {prediction['value']} (confidence: {prediction['confidence']:.2%}", + color=colors[key], + linewidth=1, + **kwargs, + ) + # add patch on figure + ax.add_patch(rect) + if interactive: + # add patch to cursor's artists + artists.append(rect) + + if interactive: + import mplcursors + + # Create mlp Cursor to hover patches in artists + mplcursors.Cursor(artists, hover=2).connect("add", lambda sel: sel.annotation.set_text(sel.artist.get_label())) + fig.tight_layout(pad=0.0) + + return fig + + +def draw_boxes(boxes: np.ndarray, image: np.ndarray, color: Optional[Tuple[int, int, int]] = None, **kwargs) -> None: + """Draw an array of relative straight boxes on an image + + Args: + ---- + boxes: array of relative boxes, of shape (*, 4) + image: np array, float32 or uint8 + color: color to use for bounding box edges + **kwargs: keyword arguments from `matplotlib.pyplot.plot` + """ + h, w = image.shape[:2] + # Convert boxes to absolute coords + _boxes = deepcopy(boxes) + _boxes[:, [0, 2]] *= w + _boxes[:, [1, 3]] *= h + _boxes = _boxes.astype(np.int32) + for box in _boxes.tolist(): + xmin, ymin, xmax, ymax = box + image = cv2.rectangle( + image, (xmin, ymin), (xmax, ymax), color=color if isinstance(color, tuple) else (0, 0, 255), thickness=2 + ) + plt.imshow(image) + plt.plot(**kwargs)
    @@ -446,8 +719,8 @@

    Source code for doctr.utils.visualization

           
         
       
    - - + + diff --git a/v0.2.0/_modules/index.html b/v0.2.0/_modules/index.html index dc72311281..5793c44f20 100644 --- a/v0.2.0/_modules/index.html +++ b/v0.2.0/_modules/index.html @@ -13,7 +13,7 @@ - + Overview: module code - docTR documentation @@ -225,15 +225,42 @@ - - + + diff --git a/v0.2.0/_sources/datasets.rst.txt b/v0.2.0/_sources/datasets.rst.txt deleted file mode 100644 index d2080bc034..0000000000 --- a/v0.2.0/_sources/datasets.rst.txt +++ /dev/null @@ -1,68 +0,0 @@ -doctr.datasets -============== - -.. currentmodule:: doctr.datasets - -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. - - -.. _datasets: - -Available Datasets ------------------- -The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL. - -.. autoclass:: doctr.datasets.core.VisionDataset - - -Here are all datasets that are available through DocTR: - -.. autoclass:: FUNSD -.. autoclass:: SROIE -.. autoclass:: CORD -..autoclass:: OCRDataset - - -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. - -.. autoclass:: doctr.datasets.loader.DataLoader - - -.. _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 - * - digits - - 10 - - 0123456789 - * - ascii_letters - - 52 - - abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ - * - punctuation - - 32 - - !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ - * - currency - - 5 - - £€¥¢฿ - * - latin - - 96 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~° - * - french - - 154 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿ - -.. autofunction:: encode_sequences diff --git a/v0.2.0/_sources/documents.rst.txt b/v0.2.0/_sources/documents.rst.txt deleted file mode 100644 index e2fa11b344..0000000000 --- a/v0.2.0/_sources/documents.rst.txt +++ /dev/null @@ -1,83 +0,0 @@ -doctr.documents -=============== - - -.. currentmodule:: doctr.documents - -The documents module enables users to easily access content from documents and export analysis -results to structured formats. - - -Document structure ------------------- - -Structural organization of the documents. - -Word -^^^^ -A Word is an uninterrupted sequence of characters. - -.. autoclass:: Word - -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 - -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 - - -Document -^^^^^^^^ - -A Document is a collection of Pages. - -.. autoclass:: Document - - -File reading ------------- - -High-performance file reading and conversion to processable structured data. - -.. autofunction:: read_pdf - -.. autofunction:: read_img - -.. autofunction:: read_html - - -.. autoclass:: DocumentFile - - .. automethod:: from_pdf - - .. automethod:: from_url - - .. automethod:: from_images - -.. autoclass:: PDF - - .. automethod:: as_images - - .. automethod:: get_words - - .. automethod:: get_artefacts diff --git a/v0.2.0/_sources/getting_started/installing.rst.txt b/v0.2.0/_sources/getting_started/installing.rst.txt index e764e734a7..39e79aa3dd 100644 --- a/v0.2.0/_sources/getting_started/installing.rst.txt +++ b/v0.2.0/_sources/getting_started/installing.rst.txt @@ -3,7 +3,7 @@ Installation ************ -This library requires `Python `_ 3.9 or higher. +This library requires `Python `_ 3.10 or higher. Prerequisites diff --git a/v0.2.0/_sources/index.rst.txt b/v0.2.0/_sources/index.rst.txt index a7d5ef909e..53251db142 100644 --- a/v0.2.0/_sources/index.rst.txt +++ b/v0.2.0/_sources/index.rst.txt @@ -1,75 +1,122 @@ -DocTR: Document Text Recognition -================================ +******************************** +docTR: Document Text Recognition +******************************** + +State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 & PyTorch + +.. image:: https://github.com/mindee/doctr/releases/download/v0.2.0/ocr.png + :align: center -State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 DocTR provides an easy and powerful way to extract valuable information from your documents: -* |:receipt:| **for automation**: seemlessly process documents for Natural Language Understanding tasks: we provide OCR predictors to parse textual information (localize and identify each word) 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. -This is the documentation of our repository `doctr `_. +Main Features +------------- -Features --------- - -* |:robot:| Robust 2-stages (detection + recognition) OCR predictors fully trained +* |:robot:| Robust 2-stage (detection + recognition) OCR predictors with pretrained parameters * |:zap:| User-friendly, 3 lines of code to load a document and extract text with a predictor -* |:rocket:| State-of-the-art performances on public document datasets, comparable with GoogleVision/AWS Textract -* |:zap:| Predictors optimized to be very fast on both CPU & GPU -* |:bird:| Light package, small dependencies -* |:tools:| Daily maintained -* |:factory:| Easily integrable - +* |: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) -|:scientist:| Build & train your predictor -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* |:construction_worker:| Compose your own end-to-end OCR predictor: mix and match detection & recognition predictors (all-pretrained) -* |:construction_worker:| Fine-tune or train from scratch any detection or recognition model to specialize on your data +.. toctree:: + :maxdepth: 2 + :caption: Getting started + :hidden: + + getting_started/installing + 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" `_ + + +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" `_. -|:toolbox:| Implemented models -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Detection models -"""""""""""""""" - * DB (Differentiable Binarization), `"Real-time Scene Text Detection with Differentiable Binarization" `_. - * LinkNet, `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" `_. +.. toctree:: + :maxdepth: 2 + :caption: Using docTR + :hidden: -Recognition models -"""""""""""""""""" - * SAR (Show, Attend and Read), `"Show, Attend and Read:A Simple and Strong Baseline for Irregular Text Recognition" `_. - * CRNN (Convolutional Recurrent Neural Network), `"An End-to-End Trainable Neural Network for Image-based Sequence Recognition and Its Application to Scene Text Recognition" `_. + 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 -|:receipt:| Integrated datasets -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - * FUNSD from `"FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents" `_. - * CORD from `"CORD: A Consolidated Receipt Dataset forPost-OCR Parsing" `_. +.. toctree:: + :maxdepth: 2 + :caption: Community + :hidden: + community/resources -Getting Started ---------------- .. toctree:: :maxdepth: 2 + :caption: Package Reference + :hidden: - installing + modules/contrib + modules/datasets + modules/io + modules/models + modules/transforms + modules/utils -Contents --------- - .. toctree:: - :maxdepth: 1 + :maxdepth: 2 + :caption: Contributing + :hidden: - datasets - documents - models - transforms - utils + contributing/code_of_conduct + contributing/contributing -.. automodule:: doctr - :members: +.. toctree:: + :maxdepth: 2 + :caption: Notes + :hidden: + + changelog diff --git a/v0.2.0/_sources/installing.rst.txt b/v0.2.0/_sources/installing.rst.txt deleted file mode 100644 index ee7de4dbc0..0000000000 --- a/v0.2.0/_sources/installing.rst.txt +++ /dev/null @@ -1,26 +0,0 @@ - -************ -Installation -************ - -This library requires Python 3.6 or newer. - -Via Python Package -================== - -Install the last stable release of the package using pip: - -.. code:: bash - - pip install python-doctr - - -Via Git -======= - -Install the library in developper mode: - -.. code:: bash - - git clone https://github.com/mindee/doctr.git - pip install -e doctr/. diff --git a/v0.2.0/_sources/models.rst.txt b/v0.2.0/_sources/models.rst.txt deleted file mode 100644 index 410e9604f7..0000000000 --- a/v0.2.0/_sources/models.rst.txt +++ /dev/null @@ -1,209 +0,0 @@ -doctr.models -============ - -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. - -.. currentmodule:: doctr.models - -For a given task, DocTR provides a Predictor, which is composed of 3 components: - -* PreProcessor: a module in charge of making inputs directly usable by the TensorFlow model. -* Model: a deep learning model, implemented with TensorFlow backend. -* PostProcessor: making model outputs structured and reusable. - - -Text Detection --------------- -Localizing text elements in images - -+---------------------------------------------------+----------------------------+----------------------------+---------+ -| | FUNSD | CORD | | -+==================+=================+==============+============+===============+============+===============+=========+ -| **Architecture** | **Input shape** | **# params** | **Recall** | **Precision** | **Recall** | **Precision** | **FPS** | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ -| db_resnet50 | (1024, 1024, 3) | | 0.733 | 0.817 | 0.745 | 0.875 | 2.1 | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ - -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 combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for detection -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for detection is the following: - -1. resize each input image to the target size (bilinear interpolation by default) with potential deformation. -2. batch images together -3. normalize the batch using the training data statistics - - -Detection models -^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - -.. autofunction:: doctr.models.detection.db_resnet50 -.. autofunction:: doctr.models.detection.linknet - - -Post-processing detections -^^^^^^^^^^^^^^^^^^^^^^^^^^ -The purpose of this block is to turn the model output (binary segmentation map for instance), into a set of bounding boxes. - - -Detection predictors -^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information. - -.. autofunction:: doctr.models.detection.detection_predictor - - -Text Recognition ----------------- -Identifying strings in images - -.. list-table:: Text recognition model zoo - :widths: 20 20 15 10 10 10 - :header-rows: 1 - - * - Architecture - - Input shape - - # params - - FUNSD - - CORD - - FPS - * - crnn_vgg16_bn - - (32, 128, 3) - - - - 0.860 - - 0.913 - - 12.8 - * - sar_vgg16_bn - - (32, 128, 3) - - - - 0.862 - - 0.917 - - 3.3 - * - sar_resnet31 - - (32, 128, 3) - - - - **0.863** - - **0.921** - - 2.7 - -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 metrics being used are available in :ref:`metrics`. - -All these recognition models are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for recognition -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for recognition is the following: - -1. resize each input image to the target size (bilinear interpolation by default) without deformation. -2. pad the image to the target size (with zeros by default) -3. batch images together -4. normalize the batch using the training data statistics - -Recognition models -^^^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - - -.. autofunction:: doctr.models.recognition.crnn_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_resnet31 - -Post-processing outputs -^^^^^^^^^^^^^^^^^^^^^^^ -The purpose of this block is to turn the model output (symbol classification for the sequence), into a set of strings. - -Recognition predictors -^^^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage. - -.. autofunction:: doctr.models.recognition.recognition_predictor - - -End-to-End OCR --------------- -Predictors that localize and identify text elements in images - -+--------------------------------------------------------------+--------------------------------------+--------------------------------------+ -| | FUNSD | CORD | -+=============================+=================+==============+============+===============+=========+============+===============+=========+ -| **Architecture** | **Input shape** | **# params** | **Recall** | **Precision** | **FPS** | **Recall** | **Precision** | **FPS** | -+-----------------------------+-----------------+--------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + crnn_vgg16_bn | (1024, 1024, 3) | | 0.629 | 0.701 | 0.85 | 0.664 | 0.780 | 1.6 | -+-----------------------------+-----------------+--------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_vgg16_bn | (1024, 1024, 3) | | 0.630 | 0.702 | 0.49 | 0.666 | 0.783 | 1.0 | -+-----------------------------+-----------------+--------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_resnet31 | (1024, 1024, 3) | | 0.640 | 0.713 | 0.27 | 0.672 | **0.789** | 0.83 | -+-----------------------------+-----------------+--------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision text detection | NA | | 0.595 | 0.625 | | 0.753 | 0.700 | | -+-----------------------------+-----------------+--------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision doc. text detection | NA | | 0.640 | 0.533 | | 0.689 | 0.611 | | -+-----------------------------+-----------------+--------------+------------+---------------+---------+------------+---------------+---------+ -| aws textract | NA | | **0.781** | **0.830** | | **0.875** | 0.660 | | -+-----------------------------+-----------------+--------------+------------+---------------+---------+------------+---------------+---------+ - -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`. - -All recognition models of predictors are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -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. - -.. autofunction:: doctr.models.zoo.ocr_predictor - - -Model export ------------- -Utility functions to make the most of document analysis models. - -.. currentmodule:: doctr.models.export - -Model compression -^^^^^^^^^^^^^^^^^ - -.. autofunction:: convert_to_tflite - -.. autofunction:: convert_to_fp16 - -.. autofunction:: quantize_model - -Using SavedModel -^^^^^^^^^^^^^^^^ - -Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -`SavedModel `_ format as follows: - - - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> _ = model(input_t, training=False) - >>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/') - -And loaded just as easily: - - - >>> import tensorflow as tf - >>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/') diff --git a/v0.2.0/_sources/transforms.rst.txt b/v0.2.0/_sources/transforms.rst.txt deleted file mode 100644 index 0230fe75f5..0000000000 --- a/v0.2.0/_sources/transforms.rst.txt +++ /dev/null @@ -1,32 +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: - -.. autoclass:: Resize -.. autoclass:: Normalize -.. autoclass:: LambdaTransformation -.. autoclass:: ToGray -.. autoclass:: ColorInversion -.. autoclass:: RandomBrightness -.. autoclass:: RandomContrast -.. autoclass:: RandomSaturation -.. autoclass:: RandomHue -.. autoclass:: RandomGamma -.. autoclass:: RandomJpegQuality - - -Composing transformations ---------------------------------------------- -It is common to require several transformations to be performed consecutively. - -.. autoclass:: Compose -.. autoclass:: OneOf -.. autoclass:: RandomApply diff --git a/v0.2.0/_sources/utils.rst.txt b/v0.2.0/_sources/utils.rst.txt deleted file mode 100644 index 1a02858378..0000000000 --- a/v0.2.0/_sources/utils.rst.txt +++ /dev/null @@ -1,30 +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 - - -.. _metrics: - -Task evaluation ---------------- -Implementations of task-specific metrics to easily assess your model performances. - -.. currentmodule:: doctr.utils.metrics - -.. autoclass:: ExactMatch - -.. autoclass:: LocalizationConfusion - -.. autoclass:: OCRMetric diff --git a/v0.2.0/_static/basic.css b/v0.2.0/_static/basic.css index f316efcb47..7ebbd6d07b 100644 --- a/v0.2.0/_static/basic.css +++ b/v0.2.0/_static/basic.css @@ -1,12 +1,5 @@ /* - * basic.css - * ~~~~~~~~~ - * * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ /* -- main layout ----------------------------------------------------------- */ @@ -115,15 +108,11 @@ img { /* -- search page ----------------------------------------------------------- */ ul.search { - margin: 10px 0 0 20px; - padding: 0; + margin-top: 10px; } ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; + padding: 5px 0; } ul.search li a { diff --git a/v0.2.0/_static/doctools.js b/v0.2.0/_static/doctools.js index 4d67807d17..0398ebb9f0 100644 --- a/v0.2.0/_static/doctools.js +++ b/v0.2.0/_static/doctools.js @@ -1,12 +1,5 @@ /* - * doctools.js - * ~~~~~~~~~~~ - * * Base JavaScript utilities for all Sphinx HTML documentation. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; diff --git a/v0.2.0/_static/documentation_options.js b/v0.2.0/_static/documentation_options.js index 40b838b240..4f656fdbea 100644 --- a/v0.2.0/_static/documentation_options.js +++ b/v0.2.0/_static/documentation_options.js @@ -1,5 +1,5 @@ const DOCUMENTATION_OPTIONS = { - VERSION: '0.1.2a0-git', + VERSION: '0.10.1a0-git', LANGUAGE: 'en', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/v0.2.0/_static/language_data.js b/v0.2.0/_static/language_data.js index 367b8ed81b..c7fe6c6faf 100644 --- a/v0.2.0/_static/language_data.js +++ b/v0.2.0/_static/language_data.js @@ -1,13 +1,6 @@ /* - * language_data.js - * ~~~~~~~~~~~~~~~~ - * * This script contains the language-specific data used by searchtools.js, * namely the list of stopwords, stemmer, scorer and splitter. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; diff --git a/v0.2.0/_static/searchtools.js b/v0.2.0/_static/searchtools.js index b08d58c9b9..2c774d17af 100644 --- a/v0.2.0/_static/searchtools.js +++ b/v0.2.0/_static/searchtools.js @@ -1,12 +1,5 @@ /* - * searchtools.js - * ~~~~~~~~~~~~~~~~ - * * Sphinx JavaScript utilities for the full-text search. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; @@ -20,7 +13,7 @@ if (typeof Scorer === "undefined") { // and returns the new score. /* score: result => { - const [docname, title, anchor, descr, score, filename] = result + const [docname, title, anchor, descr, score, filename, kind] = result return score }, */ @@ -47,6 +40,14 @@ if (typeof Scorer === "undefined") { }; } +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + const _removeChildren = (element) => { while (element && element.lastChild) element.removeChild(element.lastChild); }; @@ -64,9 +65,13 @@ const _displayItem = (item, searchTerms, highlightTerms) => { const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; const contentRoot = document.documentElement.dataset.content_root; - const [docName, title, anchor, descr, score, _filename] = item; + const [docName, title, anchor, descr, score, _filename, kind] = item; let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); let requestUrl; let linkUrl; if (docBuilder === "dirhtml") { @@ -115,8 +120,10 @@ const _finishSearch = (resultCount) => { "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." ); else - Search.status.innerText = _( - "Search finished, found ${resultCount} page(s) matching the search query." + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, ).replace('${resultCount}', resultCount); }; const _displayNextItem = ( @@ -138,7 +145,7 @@ const _displayNextItem = ( else _finishSearch(resultCount); }; // Helper function used by query() to order search results. -// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. // Order the results by score (in opposite order of appearance, since the // `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. const _orderResultsByScoreThenName = (a, b) => { @@ -248,6 +255,7 @@ const Search = { searchSummary.classList.add("search-summary"); searchSummary.innerText = ""; const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); searchList.classList.add("search"); const out = document.getElementById("search-results"); @@ -318,7 +326,7 @@ const Search = { const indexEntries = Search._index.indexentries; // Collect multiple result groups to be sorted separately and then ordered. - // Each is an array of [docname, title, anchor, descr, score, filename]. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. const normalResults = []; const nonMainIndexResults = []; @@ -337,6 +345,7 @@ const Search = { null, score + boost, filenames[file], + SearchResultKind.title, ]); } } @@ -354,6 +363,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.index, ]; if (isMain) { normalResults.push(result); @@ -475,6 +485,7 @@ const Search = { descr, score, filenames[match[0]], + SearchResultKind.object, ]); }; Object.keys(objects).forEach((prefix) => @@ -585,6 +596,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.text, ]); } return results; diff --git a/v0.2.0/changelog.html b/v0.2.0/changelog.html index ac81a6f231..fc45a50384 100644 --- a/v0.2.0/changelog.html +++ b/v0.2.0/changelog.html @@ -14,7 +14,7 @@ - + Changelog - docTR documentation @@ -446,7 +446,7 @@

    v0.1.0 (2021-03-05) - + diff --git a/v0.2.0/community/resources.html b/v0.2.0/community/resources.html index 2564037893..9a1988258c 100644 --- a/v0.2.0/community/resources.html +++ b/v0.2.0/community/resources.html @@ -14,7 +14,7 @@ - + Community resources - docTR documentation @@ -389,7 +389,7 @@

    Community resources - + diff --git a/v0.2.0/contributing/code_of_conduct.html b/v0.2.0/contributing/code_of_conduct.html index 5ea4a1f99d..03422dbb4d 100644 --- a/v0.2.0/contributing/code_of_conduct.html +++ b/v0.2.0/contributing/code_of_conduct.html @@ -14,7 +14,7 @@ - + Contributor Covenant Code of Conduct - docTR documentation @@ -504,7 +504,7 @@

    Attribution - + diff --git a/v0.2.0/contributing/contributing.html b/v0.2.0/contributing/contributing.html index e5a85682c6..05e2b3641b 100644 --- a/v0.2.0/contributing/contributing.html +++ b/v0.2.0/contributing/contributing.html @@ -14,7 +14,7 @@ - + Contributing to docTR - docTR documentation @@ -481,7 +481,7 @@

    Let’s connect - + diff --git a/v0.2.0/datasets.html b/v0.2.0/datasets.html deleted file mode 100644 index 766f224a12..0000000000 --- a/v0.2.0/datasets.html +++ /dev/null @@ -1,564 +0,0 @@ - - - - - - - - - - - - - doctr.datasets - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.datasets

    -

    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.

    -
    -

    Available Datasets

    -

    The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL.

    -
    -
    -class doctr.datasets.core.VisionDataset(url: str, file_name: str | None = None, file_hash: str | None = None, extract_archive: bool = False, download: bool = False, overwrite: bool = False)[source]
    -

    Implements an abstract dataset

    -
    -
    Parameters:
    -
      -
    • url – URL of the dataset

    • -
    • file_name – name of the file once downloaded

    • -
    • file_hash – expected SHA256 of the file

    • -
    • extract_archive – whether the downloaded file is an archive to be extracted

    • -
    • download – whether the dataset should be downloaded if not present on disk

    • -
    • overwrite – whether the archive should be re-extracted

    • -
    -
    -
    -
    - -

    Here are all datasets that are available through DocTR:

    -
    -
    -class doctr.datasets.FUNSD(train: bool = True, sample_transforms: Callable[[Tensor], Tensor] | None = None, **kwargs: Any)[source]
    -

    FUNSD dataset from “FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents”.

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD
    ->>> train_set = FUNSD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.SROIE(train: bool = True, sample_transforms: Callable[[Tensor], Tensor] | None = None, **kwargs: Any)[source]
    -

    SROIE dataset from “ICDAR2019 Competition on Scanned Receipt OCR and Information Extraction”.

    -
    -
    Example::
    >>> from doctr.datasets import SROIE
    ->>> train_set = SROIE(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.CORD(train: bool = True, sample_transforms: Callable[[Tensor], Tensor] | None = None, **kwargs: Any)[source]
    -

    CORD dataset from “CORD: A Consolidated Receipt Dataset forPost-OCR Parsing”.

    -
    -
    Example::
    >>> from doctr.datasets import CORD
    ->>> train_set = CORD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -

    ..autoclass:: OCRDataset

    -
    -
    -

    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.

    -
    -
    -class doctr.datasets.loader.DataLoader(dataset, shuffle: bool = True, batch_size: int = 1, drop_last: bool = False, workers: int | None = None)[source]
    -

    Implements a dataset wrapper for fast data loading

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD, 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)
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • dataset – the dataset

    • -
    • shuffle – whether the samples should be shuffled before passing it to the iterator

    • -
    • batch_size – number of elements in each batch

    • -
    • drop_last – if True, drops the last batch if it isn’t full

    • -
    • workers – number of workers to use for data loading

    • -
    -
    -
    -
    - -
    -
    -

    Supported Vocabs

    -

    Since textual content has to be encoded properly for models to interpret them efficiently, DocTR supports multiple sets -of vocabs.

    -
    - - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    DocTR Vocabs

    Name

    size

    characters

    digits

    10

    0123456789

    ascii_letters

    52

    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

    punctuation

    32

    !”#$%&'()*+,-./:;<=>?@[\]^_`{|}~

    currency

    5

    £€¥¢฿

    latin

    96

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°

    french

    154

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿

    -
    -
    -
    -doctr.datasets.encode_sequences(sequences: List[str], vocab: str, target_size: int | None = None, eos: int = -1, **kwargs: Any) ndarray[source]
    -

    Encode character sequences using a given vocab as mapping

    -
    -
    Parameters:
    -
      -
    • sequences – the list of character sequences of size N

    • -
    • vocab – the ordered vocab to use for encoding

    • -
    • target_size – maximum length of the encoded data

    • -
    • eos – encoding of End Of String

    • -
    -
    -
    Returns:
    -

    the padded encoded data as a tensor

    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.0/documents.html b/v0.2.0/documents.html deleted file mode 100644 index a7450d8048..0000000000 --- a/v0.2.0/documents.html +++ /dev/null @@ -1,736 +0,0 @@ - - - - - - - - - - - - - doctr.documents - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.documents

    -

    The documents module enables users to easily access content from documents and export analysis -results to structured formats.

    -
    -

    Document structure

    -

    Structural organization of the documents.

    -
    -

    Word

    -

    A Word is an uninterrupted sequence of characters.

    -
    -
    -class doctr.documents.Word(value: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]])[source]
    -

    Implements a word element

    -
    -
    Parameters:
    -
      -
    • value – the text string of the word

    • -
    • confidence – the confidence associated with the text prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to

    • -
    • size (the page's)

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Line(words: List[Word], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | None = None)[source]
    -

    Implements a line element as a collection of words

    -
    -
    Parameters:
    -
      -
    • words – list of word elements

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all words in it.

    • -
    -
    -
    -
    - -
    -
    -

    Artefact

    -

    An Artefact is a non-textual element (e.g. QR code, picture, chart, signature, logo, etc.).

    -
    -
    -class doctr.documents.Artefact(artefact_type: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]])[source]
    -

    Implements a non-textual element

    -
    -
    Parameters:
    -
      -
    • artefact_type – the type of artefact

    • -
    • confidence – the confidence of the type prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size.

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Block(lines: List[Line] = [], artefacts: List[Artefact] = [], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | None = None)[source]
    -

    Implements a block element as a collection of lines and artefacts

    -
    -
    Parameters:
    -
      -
    • lines – list of line elements

    • -
    • artefacts – list of artefacts

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all lines and artefacts in it.

    • -
    -
    -
    -
    - -
    -
    -

    Page

    -

    A Page is a collection of Blocks that were on the same physical page.

    -
    -
    -class doctr.documents.Page(blocks: List[Block], page_idx: int, dimensions: Tuple[int, int], orientation: Dict[str, Any] | None = None, language: Dict[str, Any] | None = None)[source]
    -

    Implements a page element as a collection of blocks

    -
    -
    Parameters:
    -
      -
    • blocks – list of block elements

    • -
    • page_idx – the index of the page in the input raw document

    • -
    • dimensions – the page size in pixels in format (width, height)

    • -
    • orientation – a dictionary with the value of the rotation angle in degress and confidence of the prediction

    • -
    • language – a dictionary with the language value and confidence of the prediction

    • -
    -
    -
    -
    - -
    -
    -

    Document

    -

    A Document is a collection of Pages.

    -
    -
    -class doctr.documents.Document(pages: List[Page])[source]
    -

    Implements a document element as a collection of pages

    -
    -
    Parameters:
    -

    pages – list of page elements

    -
    -
    -
    - -
    -
    -
    -

    File reading

    -

    High-performance file reading and conversion to processable structured data.

    -
    -
    -doctr.documents.read_pdf(file: str | Path | bytes, **kwargs: Any) Document[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_pdf
    ->>> doc = read_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_img(file: str | Path | bytes, output_size: Tuple[int, int] | None = None, rgb_output: bool = True) ndarray[source]
    -

    Read an image file into numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_img
    ->>> page = read_img("path/to/your/doc.jpg")
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • file – the path to the image file

    • -
    • output_size – the expected output size of each page in format H x W

    • -
    • rgb_output – whether the output ndarray channel order should be RGB instead of BGR.

    • -
    -
    -
    Returns:
    -

    the page decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_html(url: str, **kwargs: Any) bytes[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_html
    ->>> doc = read_html("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – URL of the target web page

    -
    -
    Returns:
    -

    decoded PDF file as a bytes stream

    -
    -
    -
    - -
    -
    -class doctr.documents.DocumentFile[source]
    -

    Read a document from multiple extensions

    -
    -
    -classmethod from_pdf(file: str | Path | bytes, **kwargs) PDF[source]
    -

    Read a PDF file

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file or a binary stream

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_url(url: str, **kwargs) PDF[source]
    -

    Interpret a web page as a PDF document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_url("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – the URL of the target web page

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_images(files: Sequence[str | Path | bytes] | str | Path | bytes, **kwargs) List[ndarray][source]
    -

    Read an image file (or a collection of image files) and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"])
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    files – the path to the image file or a binary stream, or a collection of those

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    - -
    -
    -class doctr.documents.PDF(doc: Document)[source]
    -

    PDF document template

    -
    -
    Parameters:
    -

    doc – input PDF document

    -
    -
    -
    -
    -as_images(**kwargs) List[ndarray][source]
    -

    Convert all document pages to images

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of convert_page_to_numpy

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -get_words(**kwargs) List[List[Tuple[Tuple[float, float, float, float], str]]][source]
    -

    Get the annotations for all words in the document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of fitz.Page.getTextWords

    -
    -
    Returns:
    -

    the list of pages annotations, represented as a list of tuple (bounding box, value)

    -
    -
    -
    - -
    -
    -get_artefacts() List[List[Tuple[float, float, float, float]]][source]
    -

    Get the artefacts for the entire document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts()
    -
    -
    -
    -
    -
    -
    Returns:
    -

    the list of pages artefacts, represented as a list of bounding boxes

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.0/genindex.html b/v0.2.0/genindex.html index 7be65c62d4..21520455b4 100644 --- a/v0.2.0/genindex.html +++ b/v0.2.0/genindex.html @@ -13,7 +13,7 @@ - Index - docTR documentation + Index - docTR documentation @@ -224,15 +224,42 @@

    - -
    -

    Q

    -
    @@ -478,31 +559,53 @@

    Q

    R

    @@ -512,13 +615,33 @@

    R

    S

    @@ -528,8 +651,36 @@

    S

    T

    + +
    +
    + +
    +

    U

    + +
    @@ -538,11 +689,19 @@

    T

    V

    @@ -552,7 +711,13 @@

    V

    W

    +
    @@ -590,8 +755,8 @@

    W

    - - + + diff --git a/v0.2.0/getting_started/installing.html b/v0.2.0/getting_started/installing.html index a488e9a030..af3b58193e 100644 --- a/v0.2.0/getting_started/installing.html +++ b/v0.2.0/getting_started/installing.html @@ -14,7 +14,7 @@ - + Installation - docTR documentation @@ -305,7 +305,7 @@

    Installation

    -

    This library requires Python 3.9 or higher.

    +

    This library requires Python 3.10 or higher.

    Prerequisites

    Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so:

    @@ -435,7 +435,7 @@

    Via Git - + diff --git a/v0.2.0/index.html b/v0.2.0/index.html index 19218e24cf..3a06afc6d9 100644 --- a/v0.2.0/index.html +++ b/v0.2.0/index.html @@ -12,9 +12,9 @@ gtag('js', new Date()); gtag('config', 'G-40DVRMX8T4'); - + - + docTR documentation @@ -226,15 +226,42 @@
    -

    DocTR: Document Text Recognition

    -

    State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2

    +

    docTR: Document Text Recognition

    +

    State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 & PyTorch

    +https://github.com/mindee/doctr/releases/download/v0.2.0/ocr.png

    DocTR provides an easy and powerful way to extract valuable information from your documents:

      -
    • 🧾 for automation: seemlessly process documents for Natural Language Understanding tasks: we provide OCR predictors to parse textual information (localize and identify each word) from your documents.

    • +
    • 🧾 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.

    • 👩‍🔬 for research: quickly compare your own architectures speed & performances with state-of-art models on public datasets.

    -

    This is the documentation of our repository doctr.

    -
    -

    Features

    +
    +

    Main Features

      -
    • 🤖 Robust 2-stages (detection + recognition) OCR predictors fully trained

    • +
    • 🤖 Robust 2-stage (detection + recognition) OCR predictors with pretrained parameters

    • ⚡ User-friendly, 3 lines of code to load a document and extract text with a predictor

    • -
    • 🚀 State-of-the-art performances on public document datasets, comparable with GoogleVision/AWS Textract

    • -
    • ⚡ Predictors optimized to be very fast on both CPU & GPU

    • -
    • 🐦 Light package, small dependencies

    • -
    • 🛠️ Daily maintained

    • -
    • 🏭 Easily integrable

    • +
    • 🚀 State-of-the-art performance on public document datasets, comparable with GoogleVision/AWS Textract

    • +
    • ⚡ Optimized for inference speed on both CPU & GPU

    • +
    • 🐦 Light package, minimal dependencies

    • +
    • 🛠️ Actively maintained by Mindee

    • +
    • 🏭 Easy integration (available templates for browser demo & API deployment)

    -
    -

    🧑‍🔬 Build & train your predictor

    +
    +
    +
    +

    Model zoo

    +
    +

    Text detection models

      -
    • 👷 Compose your own end-to-end OCR predictor: mix and match detection & recognition predictors (all-pretrained)

    • -
    • 👷 Fine-tune or train from scratch any detection or recognition model to specialize on your data

    • -
    -
    -
    -

    🧰 Implemented models

    -
    -

    Detection models

    -
    -
    -
    -

    Recognition models

    -
    -
    -
    -

    🧾 Integrated datasets

    -
    -
    -
    -
    -
    -

    Getting Started

    -
    -
    -

    Contents

    -
    +
    +
    +
    +
    +
    +
    +
    +
    @@ -364,7 +381,7 @@

    Contents
    - + diff --git a/v0.2.0/modules/io.html b/v0.2.0/modules/io.html index 01eadaa4b8..24c41954be 100644 --- a/v0.2.0/modules/io.html +++ b/v0.2.0/modules/io.html @@ -14,7 +14,7 @@ - + doctr.io - docTR documentation @@ -760,7 +760,7 @@

    Returns: - + diff --git a/v0.2.0/modules/models.html b/v0.2.0/modules/models.html index c465cc0586..91b8810a6a 100644 --- a/v0.2.0/modules/models.html +++ b/v0.2.0/modules/models.html @@ -14,7 +14,7 @@ - + doctr.models - docTR documentation @@ -1612,7 +1612,7 @@

    Args: - + diff --git a/v0.2.0/modules/transforms.html b/v0.2.0/modules/transforms.html index 30f7a2631a..c5ead3f3ce 100644 --- a/v0.2.0/modules/transforms.html +++ b/v0.2.0/modules/transforms.html @@ -14,7 +14,7 @@ - + doctr.transforms - docTR documentation @@ -835,7 +835,7 @@

    Args:< - + diff --git a/v0.2.0/modules/utils.html b/v0.2.0/modules/utils.html index 888a32c321..b7f6fc570b 100644 --- a/v0.2.0/modules/utils.html +++ b/v0.2.0/modules/utils.html @@ -14,7 +14,7 @@ - + doctr.utils - docTR documentation @@ -715,7 +715,7 @@

    Args: - + diff --git a/v0.2.0/notebooks.html b/v0.2.0/notebooks.html index f97771aebb..d36539f59e 100644 --- a/v0.2.0/notebooks.html +++ b/v0.2.0/notebooks.html @@ -14,7 +14,7 @@ - + docTR Notebooks - docTR documentation @@ -391,7 +391,7 @@

    docTR Notebooks - + diff --git a/v0.2.0/objects.inv b/v0.2.0/objects.inv index 35f61a1448678d4a8ec597fd8a1b1615e49cdb91..c1700f291b7b6bc7252a625eb1acde3a63b67eca 100644 GIT binary patch delta 4089 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
    !Fy{@A0)1;>IcChm zqx&{mmkGn2HT|Mm_bS;1G_0jo>*U~ZUYEh{vp<&uz3$=HZ@0dc?!V*KKh*l|v<9Wk zCZa1ASeVi)WV$(bIGN37C;m^TC#Q38at2P$f9}0NX$ZO*}b7;;+Gh8CBMxni8Yv0%)BD$W^a%8%FJ4DnE#cVL#qhe$e8S5kYpv4f;RfBz|V0 zi-h9x$IouZ9fZTKwLw5J?4*TZ{IA5of*ZJ#2CkU_W_ueIYwm`qh;lzg1%ssgm>`A0 z_tu${4Ug-A)Kj!LdAR3~yxMPCEKcaZ>g7HA4bzME4(VwJNfYgG8Hax4hgdD!xGd7L r_@_(eNnwD)Km5^D6|@=#GOJMYHfO@z|~lJD$!s?sCA9 zIAg++sEE|W4zlPEbWx!DEW7EVU(k=~7vvMlhaMy)QID)fqRu&&m%O~Z=^1osU4C>4;l%@Cgr-Z~HmM&t4QYh841XxZBW9AkFVc_;y)k@& zWsy5#L!M}OODm=*!udkFaibn5dBSo^usah&J<;%5i-tvlQ_=n==Xj4odo1g(ZWrng zZ(|xhP2?=grxjI?2|8|igkEe>nXfP&z{|3J%w-US>$hI3gjZ z`8=VRq)(}?Eyj}Zb4GJ63Y7GSbH3$tyU$Kvf13i|$Ase<8XpRja`FjpRr3^q9EbFf z5;Y28o~GO5;c)tYdb>;KiWtxHX2ld^C@(|ghtqG-nB&wZN*6TDCpy7%l(Id|6H(F9 znC91Va%K5y?luOfqcoz)n=Ff8 zW^&)=XOHQR)a3U%eakS1!$QBpR;y`+fc=%}MVvTXquuZ(7 zq09_w97Kff3%9-*p>cP~|kv=j9)>^M(V zCvZI;}?|?eZgHUZDBt0~ft6X+E;m&+b zgD&m7Pf(ajx$J+BUsv)s9}vM}g#A z+mRVP>g`{NgB9+o-5L~s zU}U3mTnt`dW-JNq2$$>yRv$FyR{w>n14oqz{&AES%0O(BTqJH9O1H#3)!E#WC>;(o zIICW{mL*#7tl$&ORD@Nx5D-&iHj6aenDX71U`+c! zJgi>$qYm9BEKEZ~`=NoYcExpzZYhp`DHlvt1HbN+0Su#Dlv>F_EpE!>OLiMCZ&v;8t3If249J6B!I;Vkw~T5ql!4MneOA-ys2i3bmpb zI^syD?_N!Xga-1xqHbh^O5>oZ>q@Vhi3*I$4;%>!4ut#nO#y-u^dP`jgey{}mv4r` zr&ccS;%hvt@KUPG04v=DtmC9-=X@}6U^v20+956f(jM4;iKF0q8I2?jUTBdwiKo|Y zEdUiADl(MhCYi|&{PEv!zkmJm=ik9wj}_4&PHDWO&(ixO)Bog(9(QGCqZ1_w8k8zQ Q3)BAu*DWdVKafYWB3~0+c>n+a diff --git a/v0.2.0/py-modindex.html b/v0.2.0/py-modindex.html deleted file mode 100644 index c1569be607..0000000000 --- a/v0.2.0/py-modindex.html +++ /dev/null @@ -1,330 +0,0 @@ - - - - - - - - - - - Python Module Index - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    - -
    -

    Python Module Index

    - -
    - - - - - - - - - - - -
     
    d
    - doctr -
    - -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.2.0/search.html b/v0.2.0/search.html index d4ae2e99e8..d050f5eac7 100644 --- a/v0.2.0/search.html +++ b/v0.2.0/search.html @@ -14,7 +14,7 @@ - + Search - docTR documentation @@ -226,15 +226,42 @@ - - + + diff --git a/v0.2.0/searchindex.js b/v0.2.0/searchindex.js index a97cd6ba72..6f154115ab 100644 --- a/v0.2.0/searchindex.js +++ b/v0.2.0/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles": {"Artefact": [[1, "artefact"]], "Available Datasets": [[0, "available-datasets"]], "Block": [[1, "block"]], "Composing transformations": [[5, "composing-transformations"]], "Contents": [[2, "contents"]], "Data Loading": [[0, "data-loading"]], "Detection models": [[2, "detection-models"], [4, "detection-models"]], "Detection predictors": [[4, "detection-predictors"]], "DocTR Vocabs": [[0, "id1"]], "DocTR: Document Text Recognition": [[2, null]], "Document": [[1, "document"]], "Document structure": [[1, "document-structure"]], "End-to-End OCR": [[4, "end-to-end-ocr"]], "Features": [[2, "features"]], "File reading": [[1, "file-reading"]], "Getting Started": [[2, "getting-started"]], "Installation": [[3, null]], "Line": [[1, "line"]], "Model compression": [[4, "model-compression"]], "Model export": [[4, "model-export"]], "Page": [[1, "page"]], "Post-processing detections": [[4, "post-processing-detections"]], "Post-processing outputs": [[4, "post-processing-outputs"]], "Pre-processing for detection": [[4, "pre-processing-for-detection"]], "Pre-processing for recognition": [[4, "pre-processing-for-recognition"]], "Recognition models": [[2, "recognition-models"], [4, "recognition-models"]], "Recognition predictors": [[4, "recognition-predictors"]], "Supported Vocabs": [[0, "supported-vocabs"]], "Supported transformations": [[5, "supported-transformations"]], "Task evaluation": [[6, "task-evaluation"]], "Text Detection": [[4, "text-detection"]], "Text Recognition": [[4, "text-recognition"]], "Text recognition model zoo": [[4, "id2"]], "Two-stage approaches": [[4, "two-stage-approaches"]], "Using SavedModel": [[4, "using-savedmodel"]], "Via Git": [[3, "via-git"]], "Via Python Package": [[3, "via-python-package"]], "Visualization": [[6, "visualization"]], "Word": [[1, "word"]], "doctr.datasets": [[0, null]], "doctr.documents": [[1, null]], "doctr.models": [[4, null]], "doctr.transforms": [[5, null]], "doctr.utils": [[6, null]], "\ud83e\uddd1\u200d\ud83d\udd2c Build & train your predictor": [[2, "scientist-build-train-your-predictor"]], "\ud83e\uddf0 Implemented models": [[2, "toolbox-implemented-models"]], "\ud83e\uddfe Integrated datasets": [[2, "receipt-integrated-datasets"]]}, "docnames": ["datasets", "documents", "index", "installing", "models", "transforms", "utils"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["datasets.rst", "documents.rst", "index.rst", "installing.rst", "models.rst", "transforms.rst", "utils.rst"], "indexentries": {"artefact (class in doctr.documents)": [[1, "doctr.documents.Artefact", false]], "as_images() (doctr.documents.pdf method)": [[1, "doctr.documents.PDF.as_images", false]], "block (class in doctr.documents)": [[1, "doctr.documents.Block", false]], "colorinversion (class in doctr.transforms)": [[5, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[5, "doctr.transforms.Compose", false]], "convert_to_fp16() (in module doctr.models.export)": [[4, "doctr.models.export.convert_to_fp16", false]], "convert_to_tflite() (in module doctr.models.export)": [[4, "doctr.models.export.convert_to_tflite", false]], "cord (class in doctr.datasets)": [[0, "doctr.datasets.CORD", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[4, "doctr.models.recognition.crnn_vgg16_bn", false]], "dataloader (class in doctr.datasets.loader)": [[0, "doctr.datasets.loader.DataLoader", false]], "db_resnet50() (in module doctr.models.detection)": [[4, "doctr.models.detection.db_resnet50", false]], "detection_predictor() (in module doctr.models.detection)": [[4, "doctr.models.detection.detection_predictor", false]], "doctr": [[2, "module-doctr", false]], "document (class in doctr.documents)": [[1, "doctr.documents.Document", false]], "documentfile (class in doctr.documents)": [[1, "doctr.documents.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[0, "doctr.datasets.encode_sequences", false]], "exactmatch (class in doctr.utils.metrics)": [[6, "doctr.utils.metrics.ExactMatch", false]], "from_images() (doctr.documents.documentfile class method)": [[1, "doctr.documents.DocumentFile.from_images", false]], "from_pdf() (doctr.documents.documentfile class method)": [[1, "doctr.documents.DocumentFile.from_pdf", false]], "from_url() (doctr.documents.documentfile class method)": [[1, "doctr.documents.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[0, "doctr.datasets.FUNSD", false]], "get_artefacts() (doctr.documents.pdf method)": [[1, "doctr.documents.PDF.get_artefacts", false]], "get_words() (doctr.documents.pdf method)": [[1, "doctr.documents.PDF.get_words", false]], "lambdatransformation (class in doctr.transforms)": [[5, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.documents)": [[1, "doctr.documents.Line", false]], "linknet() (in module doctr.models.detection)": [[4, "doctr.models.detection.linknet", false]], "localizationconfusion (class in doctr.utils.metrics)": [[6, "doctr.utils.metrics.LocalizationConfusion", false]], "module": [[2, "module-doctr", false]], "normalize (class in doctr.transforms)": [[5, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models.zoo)": [[4, "doctr.models.zoo.ocr_predictor", false]], "ocrmetric (class in doctr.utils.metrics)": [[6, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[5, "doctr.transforms.OneOf", false]], "page (class in doctr.documents)": [[1, "doctr.documents.Page", false]], "pdf (class in doctr.documents)": [[1, "doctr.documents.PDF", false]], "quantize_model() (in module doctr.models.export)": [[4, "doctr.models.export.quantize_model", false]], "randomapply (class in doctr.transforms)": [[5, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[5, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[5, "doctr.transforms.RandomContrast", false]], "randomgamma (class in doctr.transforms)": [[5, "doctr.transforms.RandomGamma", false]], "randomhue (class in doctr.transforms)": [[5, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[5, "doctr.transforms.RandomJpegQuality", false]], "randomsaturation (class in doctr.transforms)": [[5, "doctr.transforms.RandomSaturation", false]], "read_html() (in module doctr.documents)": [[1, "doctr.documents.read_html", false]], "read_img() (in module doctr.documents)": [[1, "doctr.documents.read_img", false]], "read_pdf() (in module doctr.documents)": [[1, "doctr.documents.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[4, "doctr.models.recognition.recognition_predictor", false]], "resize (class in doctr.transforms)": [[5, "doctr.transforms.Resize", false]], "sar_resnet31() (in module doctr.models.recognition)": [[4, "doctr.models.recognition.sar_resnet31", false]], "sar_vgg16_bn() (in module doctr.models.recognition)": [[4, "doctr.models.recognition.sar_vgg16_bn", false]], "sroie (class in doctr.datasets)": [[0, "doctr.datasets.SROIE", false]], "togray (class in doctr.transforms)": [[5, "doctr.transforms.ToGray", false]], "visiondataset (class in doctr.datasets.core)": [[0, "doctr.datasets.core.VisionDataset", false]], "visualize_page() (in module doctr.utils.visualization)": [[6, "doctr.utils.visualization.visualize_page", false]], "word (class in doctr.documents)": [[1, "doctr.documents.Word", false]]}, "objects": {"": [[2, 0, 0, "-", "doctr"]], "doctr.datasets": [[0, 1, 1, "", "CORD"], [0, 1, 1, "", "FUNSD"], [0, 1, 1, "", "SROIE"], [0, 2, 1, "", "encode_sequences"]], "doctr.datasets.core": [[0, 1, 1, "", "VisionDataset"]], "doctr.datasets.loader": [[0, 1, 1, "", "DataLoader"]], "doctr.documents": [[1, 1, 1, "", "Artefact"], [1, 1, 1, "", "Block"], [1, 1, 1, "", "Document"], [1, 1, 1, "", "DocumentFile"], [1, 1, 1, "", "Line"], [1, 1, 1, "", "PDF"], [1, 1, 1, "", "Page"], [1, 1, 1, "", "Word"], [1, 2, 1, "", "read_html"], [1, 2, 1, "", "read_img"], [1, 2, 1, "", "read_pdf"]], "doctr.documents.DocumentFile": [[1, 3, 1, "", "from_images"], [1, 3, 1, "", "from_pdf"], [1, 3, 1, "", "from_url"]], "doctr.documents.PDF": [[1, 3, 1, "", "as_images"], [1, 3, 1, "", "get_artefacts"], [1, 3, 1, "", "get_words"]], "doctr.models.detection": [[4, 2, 1, "", "db_resnet50"], [4, 2, 1, "", "detection_predictor"], [4, 2, 1, "", "linknet"]], "doctr.models.export": [[4, 2, 1, "", "convert_to_fp16"], [4, 2, 1, "", "convert_to_tflite"], [4, 2, 1, "", "quantize_model"]], "doctr.models.recognition": [[4, 2, 1, "", "crnn_vgg16_bn"], [4, 2, 1, "", "recognition_predictor"], [4, 2, 1, "", "sar_resnet31"], [4, 2, 1, "", "sar_vgg16_bn"]], "doctr.models.zoo": [[4, 2, 1, "", "ocr_predictor"]], "doctr.transforms": [[5, 1, 1, "", "ColorInversion"], [5, 1, 1, "", "Compose"], [5, 1, 1, "", "LambdaTransformation"], [5, 1, 1, "", "Normalize"], [5, 1, 1, "", "OneOf"], [5, 1, 1, "", "RandomApply"], [5, 1, 1, "", "RandomBrightness"], [5, 1, 1, "", "RandomContrast"], [5, 1, 1, "", "RandomGamma"], [5, 1, 1, "", "RandomHue"], [5, 1, 1, "", "RandomJpegQuality"], [5, 1, 1, "", "RandomSaturation"], [5, 1, 1, "", "Resize"], [5, 1, 1, "", "ToGray"]], "doctr.utils.metrics": [[6, 1, 1, "", "ExactMatch"], [6, 1, 1, "", "LocalizationConfusion"], [6, 1, 1, "", "OCRMetric"]], "doctr.utils.visualization": [[6, 2, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "function", "Python function"], "3": ["py", "method", "Python method"]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:function", "3": "py:method"}, "terms": {"": [1, 6], "0": [0, 4, 5, 6], "00": [], "01": [], "0123456789": 0, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 0, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": [], "02562": [], "03": [], "035": [], "0361328125": [], "04": [], "05": [], "06": [], "06640625": [], "07": [], "08": [], "09": [], "0966796875": [], "1": [0, 4, 5, 6], "10": [0, 6], "100": [4, 5, 6], "1000": 4, "101": [], "1024": 4, "104": [], "106": [], "108": [], "1095": [], "11": [], "110": 6, "1107": [], "114": [], "115": [], "1156": [], "116": [], "118": [], "11800h": [], "11th": [], "12": 4, "120": [], "123": [], "126": [], "1268": [], "128": 4, "13": [], "130": [], "13068": [], "131": [], "1337891": [], "1357421875": [], "1396484375": [], "14": [], "1420": [], "14470v1": [], "149": [], "15": [], "150": 6, "154": 0, "1552": [], "16": 4, "1630859375": [], "1684": [], "16x16": [], "17": [], "1778": [], "1782": [], "18": [], "185546875": [], "1900": [], "1910": [], "19342": [], "19370": [], "195": [], "19598": [], "199": 4, "1999": [], "2": [2, 4, 5, 6], "20": [], "200": 6, "2000": [], "2003": [], "2012": [], "2013": [], "2015": [], "2019": [], "2023": [], "207901": [], "21": [], "2103": [], "2186": [], "21888": [], "22": [], "224": [4, 5], "225": 5, "22672": [], "229": 5, "23": [], "233": [], "236": [], "24": [], "246": [], "249": [], "25": [], "2504": [], "255": [4, 5, 6], "256": 4, "257": [], "26": [], "26032": [], "264": [], "27": 4, "2700": [], "2710": [], "2749": [], "28": [], "287": [], "29": [], "296": [], "299": [], "2d": [], "3": [1, 2, 3, 4, 5, 6], "30": [], "300": [], "3000": [], "301": [], "30595": 4, "30ghz": [], "31": 4, "32": [0, 4, 5], "3232421875": [], "33": [], "33402": [], "33608": [], "34": [], "340": [], "3456": [], "3515625": [], "36": [], "360": [], "37": [], "38": [], "39": [], "4": [], "40": [], "406": 5, "41": [], "42": [], "43": [], "44": [], "45": [], "456": 5, "46": [], "47": [], "472": [], "48": [], "485": 5, "49": 4, "49377": [], "5": [0, 5, 6], "50": 4, "51": [], "51171875": [], "512": [], "52": 0, "529": [], "53": [], "533": 4, "54": [], "540": [], "5478515625": [], "55": [], "56": [], "57": [], "58": [], "580": [], "5810546875": [], "583": [], "59": [], "595": 4, "597": [], "5k": [], "5m": [], "6": [3, 4, 5], "60": 5, "600": [4, 6], "61": [], "611": 4, "62": [], "625": 4, "626": [], "629": 4, "63": [], "630": 4, "64": [4, 5], "640": 4, "641": [], "647": [], "65": [], "66": [], "660": 4, "664": 4, "666": 4, "67": [], "672": 4, "68": [], "689": 4, "69": [], "693": [], "694": [], "695": [], "6m": [], "7": 4, "70": 6, "700": 4, "701": 4, "702": 4, "707470": [], "71": [], "7100000": [], "713": 4, "7141797": [], "7149": [], "72": [], "72dpi": [], "73": [], "73257": [], "733": 4, "74": [], "745": 4, "75": [], "753": 4, "7581382": [], "76": [], "77": [], "772": [], "772875": [], "78": [], "780": 4, "781": 4, "783": 4, "785": [], "789": 4, "79": [], "793533": [], "796": [], "798": [], "7m": [], "8": [4, 5], "80": [], "800": [4, 6], "81": [], "817": 4, "82": [], "8275l": 4, "83": 4, "830": 4, "84": [], "849": [], "85": 4, "8564453125": [], "857": [], "85875": [], "86": [], "860": 4, "8603515625": [], "862": 4, "863": 4, "87": [], "8707": [], "875": 4, "88": [], "89": [], "9": [], "90": [], "90k": [], "90kdict32px": [], "91": [], "913": 4, "914085328578949": [], "917": 4, "92": [], "921": 4, "93": [], "94": [], "95": 6, "9578408598899841": [], "96": 0, "97": [], "98": [], "99": [], "9949972033500671": [], "A": [0, 1, 2, 4], "And": 4, "As": [], "Be": [], "Being": [], "By": [], "For": 4, "If": [1, 4], "In": 4, "It": 5, "Its": [2, 4], "No": [], "Of": 0, "Or": [], "The": [0, 1, 4, 6], "Then": 4, "To": [], "_": [0, 4], "__call__": [], "_build": [], "_i": 6, "ab": [], "abc": [], "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 0, "abdef": [], "abl": [], "about": 4, "abov": 4, "abstract": 0, "abstractdataset": [], "abus": [], "accent": 6, "accept": [], "access": [0, 1, 2], "account": [], "accur": [], "accuraci": 6, "achiev": [], "act": [], "action": [], "activ": [], "ad": 5, "adapt": [], "add": 5, "add_hook": [], "add_label": 6, "addit": [], "addition": 4, "address": 1, "adjust": 5, "advanc": [], "advantag": [], "advis": [], "aesthet": [], "affect": [], "after": [], "ag": [], "again": [], "aggreg": [0, 6], "aggress": [], "align": 1, "all": [0, 1, 2, 4, 5, 6], "allow": [], "along": [], "alreadi": [], "also": [], "alwai": [], "an": [0, 1, 2, 4, 6], "analysi": [1, 4], "ancient_greek": [], "andrej": [], "angl": 1, "ani": [0, 1, 2, 4, 6], "annot": 1, "anot": [], "anoth": [0, 4], "answer": [], "anyascii": [], "anyon": 2, "anyth": [], "api": [], "apolog": [], "apologi": [], "app": [], "appear": [], "appli": [0, 5], "applic": [2, 4], "appoint": [], "appreci": [], "appropri": [], "ar": [0, 1, 4, 5, 6], "arab": [], "arabic_diacrit": [], "arabic_lett": [], "arabic_punctu": [], "arbitrarili": [], "arch": 4, "architectur": [2, 4], "archiv": 0, "area": [], "argument": [0, 1], "around": 4, "arrai": 6, "art": 2, "artefact": [], "artefact_typ": 1, "articl": [], "artifici": [], "arxiv": [], "as_imag": 1, "asarrai": 6, "ascii_lett": 0, "aspect": 5, "assess": 6, "assign": 6, "associ": 1, "assum": [], "assume_straight_pag": [], "astyp": [4, 6], "attack": [], "attend": [2, 4], "attent": [], "autoclass": 0, "autom": 2, "automat": [], "autoregress": [], "avail": [4, 5], "averag": [4, 5], "avoid": [], "aw": [2, 4], "awar": [], "azur": [], "b": 6, "b_j": 6, "back": [], "backbon": 4, "backend": 4, "background": [], "bangla": [], "bar": [], "bar_cod": [], "baranovskij": [], "base": [2, 4], "baselin": [2, 4], "batch": [0, 4, 5], "batch_siz": 0, "bblanchon": [], "bbox": [], "becaus": [], "been": [4, 6], "befor": 0, "begin": 6, "behavior": [], "being": [4, 6], "belong": [], "benchmark": [], "best": [], "better": [], "between": [5, 6], "bgr": 1, "bilinear": [4, 5], "bin_thresh": [], "binar": [2, 4], "binari": [1, 4], "bit": [], "block": [4, 6], "block_1_1": [], "blur": [], "bmvc": [], "bn": [], "bodi": [], "bool": [0, 1, 4, 5, 6], "boolean": [], "both": [2, 4, 5], "bottom": [], "bound": [1, 4, 5, 6], "box": [1, 4, 6], "box_thresh": [], "bright": 5, "browser": [], "build": [], "built": [], "byte": [1, 4], "c": [], "c5": 4, "c_j": [], "cach": [], "cache_sampl": [], "call": [], "callabl": [0, 5], "can": [0, 4], "capabl": 4, "case": 6, "cf": 4, "cfg": [], "challeng": [], "challenge2_test_task12_imag": [], "challenge2_test_task1_gt": [], "challenge2_training_task12_imag": [], "challenge2_training_task1_gt": [], "chang": [], "channel": [1, 4, 5], "channel_prior": [], "channelshuffl": [], "charact": [0, 1, 2, 4, 6], "charactergener": [], "characterist": [], "charg": 4, "charset": [], "chart": 1, "check": [], "checkpoint": [], "chip": [], "christian": [], "ci": [], "clarifi": [], "clariti": [], "class": [0, 1, 5, 6], "class_nam": [], "classif": 4, "classmethod": 1, "clear": [], "clone": 3, "close": [], "co": [], "code": [1, 2], "codecov": [], "colab": [], "collate_fn": [], "collect": 1, "color": 5, "colorinvers": 5, "column": 1, "com": [1, 3], "combin": 4, "command": [], "comment": [], "commit": [], "common": [5, 6], "commun": [], "compar": 2, "comparison": [], "competit": 0, "compil": [], "complaint": [], "complementari": 6, "complet": [], "compon": 4, "compos": [0, 2, 4], "comprehens": [], "comput": [4, 6], "conf_threshold": [], "confid": 1, "config": [], "configur": [], "confus": 6, "consecut": [4, 5], "consequ": [], "consid": [1, 6], "consist": [], "consolid": [0, 2], "constant": 5, "construct": [], "contact": [], "contain": [], "content": [0, 1], "context": [], "contib": [], "continu": [], "contrast": 5, "contrast_factor": 5, "contrib": [], "contribut": [], "contributor": [], "conv_sequ": 4, "convers": 1, "convert": [1, 4, 5], "convert_page_to_numpi": 1, "convert_to_fp16": 4, "convert_to_tflit": 4, "convolut": 2, "cool": [], "coordin": 1, "cord": [0, 2, 4], "core": [0, 6], "corner": [], "correct": 5, "correspond": 4, "could": [], "counterpart": [], "cover": [], "coverag": [], "cpu": [2, 4], "creat": [], "crnn": [2, 4], "crnn_mobilenet_v3_larg": [], "crnn_mobilenet_v3_smal": [], "crnn_resnet31": 4, "crnn_vgg16_bn": 4, "crop": 4, "crop_orient": [], "crop_orientation_predictor": [], "crop_param": [], "cuda": [], "currenc": 0, "current": [], "custom": [], "custom_crop_orientation_model": [], "custom_page_orientation_model": [], "customhook": [], "cvit": [], "czczup": [], "czech": [], "d": [], "daili": 2, "danish": [], "data": [1, 2, 4, 5], "dataload": 0, "dataset": 4, "dataset_info": [], "date": [], "db": 2, "db_crnn_resnet": 4, "db_crnn_vgg": 4, "db_mobilenet_v3_larg": [], "db_resnet34": [], "db_resnet50": 4, "db_sar_resnet": 4, "db_sar_vgg": 4, "dbnet": 4, "deal": [], "decis": [], "decod": 1, "decode_img_as_tensor": [], "dedic": [], "deem": [], "deep": 4, "def": [], "default": [1, 4], "defer": 0, "defin": 6, "deform": 4, "degre": [], "degress": 1, "delet": [], "delimit": [], "delta": 5, "demo": [], "demonstr": [], "depend": 2, "deploi": [], "deploy": [], "derogatori": [], "describ": 4, "descript": [], "design": 5, "desir": [], "det_arch": 4, "det_b": [], "det_model": [], "det_param": [], "det_predictor": [], "detail": [], "detect": [], "detect_languag": [], "detect_orient": [], "detection_predictor": 4, "detection_task": [], "detectiondataset": [], "detectionmetr": [], "detectionpredictor": 4, "detector": [], "deterior": [], "determin": [], "dev": [], "develop": [], "developp": 3, "deviat": 5, "devic": [], "dict": [1, 6], "dictionari": 1, "differ": [], "differenti": [2, 4], "digit": 0, "dimens": [1, 4, 6], "dimension": 5, "direct": [], "directli": 4, "directori": [], "disabl": [], "disable_crop_orient": [], "disable_page_orient": [], "disclaim": 4, "discuss": [], "disk": 0, "disparag": [], "displai": 6, "display_artefact": [], "distanc": 6, "distribut": 5, "div": [], "divers": [], "divid": [], "do": [], "doc": [1, 4], "docartefact": [], "docstr": [], "doctr": 3, "doctr_cache_dir": [], "doctr_multiprocessing_dis": [], "document": [0, 4, 6], "documentbuild": [], "documentfil": 1, "doesn": [], "don": [], "done": [], "download": 0, "downsiz": [], "draw": 5, "drop": 0, "drop_last": 0, "dtype": 4, "dual": [], "dummi": [], "dummy_img": [], "dummy_input": [], "dure": [], "dutch": [], "dynam": [], "dynamic_seq_length": [], "e": [1, 3], "each": [0, 1, 2, 4, 5], "eas": [], "easi": [2, 6], "easier": 4, "easili": [1, 2, 4, 6], "econom": [], "edit": [], "educ": [], "effect": [], "effici": [0, 2, 4], "either": 4, "element": [0, 1, 4], "els": [], "email": [], "empathi": [], "en": [], "enabl": 1, "enclos": 1, "encod": [0, 2, 4], "encode_sequ": 0, "encount": [], "encrypt": [], "end": [0, 2, 6], "english": [], "enough": 4, "ensur": [], "entir": 1, "entri": [], "environ": [], "eo": 0, "equiv": [], "error": 6, "estim": [], "etc": 1, "ethnic": [], "evalu": [0, 4], "event": [], "everyon": [], "everyth": [], "exact": 6, "exactmatch": 6, "exampl": [0, 1, 4, 5, 6], "exchang": [], "exclud": 4, "execut": [], "exist": [], "expand": [], "expect": [0, 1, 4, 5], "experi": 4, "explan": 4, "explicit": [], "exploit": [2, 4], "export": [1, 6], "export_as_straight_box": [], "export_as_xml": [], "export_model_to_onnx": [], "express": 5, "extens": 1, "extern": [], "extract": [0, 2], "extract_arch": 0, "extractor": 4, "f_": 6, "f_a": 6, "factor": 5, "fair": [], "fairli": [], "fals": [0, 4, 5, 6], "faq": [], "fascan": [], "fast": [0, 2], "fast_bas": [], "fast_smal": [], "fast_tini": [], "faster": [], "fasterrcnn_mobilenet_v3_large_fpn": [], "favorit": [], "featur": [4, 6], "feed": 4, "feedback": [], "feel": [], "felix92": [], "few": [], "figsiz": 6, "figur": 6, "file": 0, "file_hash": 0, "file_nam": 0, "final": [], "find": [], "fine": 2, "finnish": [], "first": [], "firsthand": [], "fit": [], "fitz": 1, "flag": [], "flip": [], "float": [1, 5, 6], "float32": 4, "fn": 5, "focu": [], "focus": [], "folder": 4, "follow": [4, 5, 6], "font": [], "font_famili": [], "foral": 6, "forc": [], "forg": [], "form": [0, 2], "format": [1, 4], "forpost": [0, 2], "forum": [], "found": [], "fp": 4, "fp16": 4, "frac": 6, "frame": 4, "framework": 0, "free": [], "french": [0, 4], "friendli": 2, "from": [0, 1, 2, 4, 5, 6], "from_hub": [], "from_imag": 1, "from_pdf": 1, "from_url": 1, "full": [0, 4, 6], "fulli": 2, "function": [4, 5, 6], "funsd": [0, 2, 4], "further": [], "futur": [], "g": 1, "g_": 6, "g_x": 6, "gallagh": [], "gamma": 5, "gaussian": 5, "gaussianblur": [], "gaussiannois": [], "gen": [], "gender": [], "gener": [], "generic_cyrillic_lett": [], "geometri": 1, "geq": 6, "german": [], "get": 1, "get_artefact": 1, "get_word": 1, "gettextword": 1, "git": 2, "github": 3, "give": [], "given": [0, 4, 6], "global": [], "go": [], "good": [], "googl": [], "googlevis": 2, "gpu": 2, "gracefulli": [], "graph": 1, "grayscal": 5, "ground": 6, "groung": [], "group": [], "gt": [], "gt_box": [], "gt_label": [], "guid": [], "guidanc": [], "gvision": 4, "h": 1, "h_": 6, "ha": [0, 6], "half": 4, "handl": 0, "handwrit": [], "handwritten": [], "harass": [], "hardwar": [], "harm": [], "hat": 6, "have": [0, 4, 6], "head": [], "healthi": [], "hebrew": [], "height": 1, "hello": 6, "help": [], "here": [0, 5], "hf": [], "hf_hub_download": [], "high": 1, "higher": [], "hindi": [], "hindi_digit": [], "hocr": [], "hook": [], "horizont": 1, "hous": [], "how": [], "howev": [], "hsv": 5, "html": [], "http": [1, 3], "hub": [], "hue": 5, "huggingfac": [], "hw": [], "i": [0, 1, 2, 4, 5, 6], "i7": [], "ibrahimov": [], "ic03": [], "ic13": [], "icdar": [], "icdar2019": 0, "id": [], "ident": [], "identifi": [2, 4], "ignor": 6, "ignore_acc": 6, "ignore_cas": 6, "iiit": [], "iiit5k": [], "iiithw": [], "imag": [0, 1, 2, 4, 5, 6], "imagenet": [], "imageri": [], "images_90k_norm": [], "img": [0, 5], "img_cont": [], "img_fold": [], "img_path": [], "img_transform": [], "imgur5k": [], "imgur5k_annot": [], "imlist": [], "impact": [], "implement": [0, 1, 4, 5, 6], "import": [0, 1, 4, 5, 6], "improv": [], "inappropri": [], "incid": [], "includ": 4, "inclus": [], "increas": 5, "independ": [], "index": 1, "indic": 6, "individu": [], "infer": 5, "inform": [0, 2, 4], "inherit": [0, 4], "input": [1, 4, 5], "input_crop": [], "input_pag": [4, 6], "input_shap": 4, "input_t": 4, "input_tensor": 4, "inspir": 5, "instal": 2, "instanc": 4, "instanti": 4, "instead": 1, "insult": [], "int": [0, 1, 4, 5, 6], "int64": [], "integ": 6, "integr": [], "intel": [], "interact": 6, "interfac": [], "interoper": [], "interpol": [4, 5], "interpret": [0, 1], "intersect": 6, "invert": 5, "investig": [], "invis": [], "involv": 4, "io": [], "iou": 6, "iou_thresh": 6, "iou_threshold": [], "irregular": [2, 4], "isn": 0, "issu": [], "italian": [], "iter": 0, "its": [0, 1], "itself": [], "j": 6, "jame": [], "job": [], "join": [], "jpeg": 5, "jpegqual": 5, "jpg": 1, "json": [], "json_output": [], "jump": [], "just": 4, "kei": [], "kera": 4, "kernel": [], "kernel_s": 4, "kernel_shap": [], "keywoard": [], "keyword": [0, 1], "kie": [], "kie_predictor": [], "kiepredictor": [], "kind": [], "know": [], "kwarg": [0, 1, 4, 6], "l": 6, "l_j": 6, "label": [], "label_fil": [], "label_fold": [], "label_path": [], "labels_path": [], "ladder": [], "lambda": 5, "lambdatransform": 5, "lang": [], "languag": [1, 2], "larg": [], "largest": 6, "last": [0, 3, 4], "latenc": [], "later": [], "latest": [], "latin": 0, "layer": [], "layout": [], "lead": [], "leader": [], "learn": 4, "least": [], "left": 6, "legacy_french": [], "length": 0, "less": [], "let": 4, "letter": 6, "level": [4, 6], "levenshtein": 6, "leverag": [], "lf": [], "librari": 3, "light": 2, "lightweight": [], "like": [], "limits_": 6, "line": [2, 6], "line_1_1": [], "link": [], "linknet": [2, 4], "linknet_resnet18": [], "linknet_resnet34": [], "linknet_resnet50": [], "list": [0, 1, 5], "ll": 6, "load": [2, 4], "load_state_dict": [], "load_weight": [], "loader": 0, "loc_pr": [], "local": [2, 4, 6], "localis": [], "localizationconfus": 6, "locat": [], "login": [], "login_to_hub": [], "logo": 1, "love": [], "lower": 5, "m": 6, "m1": [], "macbook": [], "machin": [], "made": 2, "magc_resnet31": [], "mai": [], "mail": [], "main": [], "maintain": 2, "mainten": [], "make": [4, 6], "mani": [], "manipul": [], "map": [0, 4], "map_loc": [], "master": [], "match": [2, 6], "mathcal": 6, "matplotlib": 6, "max": 6, "max_angl": [], "max_area": [], "max_char": [], "max_delta": 5, "max_dist": 6, "max_gain": 5, "max_gamma": 5, "max_qual": 5, "max_ratio": [], "maximum": [0, 6], "maxval": [4, 5], "mbox": 6, "mean": [5, 6], "meaniou": 6, "meant": 1, "measur": 4, "media": [], "median": [], "meet": [], "member": [], "memori": [], "mention": [], "merg": [], "messag": [], "meta": [], "metadata": [], "metal": [], "method": 5, "metric": [4, 6], "middl": [], "might": 4, "min": [], "min_area": [], "min_char": [], "min_gain": 5, "min_gamma": 5, "min_qual": 5, "min_ratio": [], "min_val": 5, "minde": 3, "minim": [], "minimalist": [], "minimum": 6, "minval": 5, "miss": [], "mistak": [], "mix": 2, "mixed_float16": [], "mixed_precis": [], "mjsynth": [], "mnt": [], "mobilenet": [], "mobilenet_v3_larg": [], "mobilenet_v3_large_r": [], "mobilenet_v3_smal": [], "mobilenet_v3_small_crop_orient": [], "mobilenet_v3_small_page_orient": [], "mobilenet_v3_small_r": [], "mobilenetv3": [], "modal": [], "mode": 3, "model": [0, 6], "model_nam": [], "model_path": [], "moder": [], "modif": [], "modifi": [], "modul": [1, 4, 5, 6], "more": [], "moscardi": [], "most": 4, "mozilla": [], "multi": [], "multilingu": [], "multipl": [0, 1, 5], "multipli": 5, "multiprocess": [], "my": [], "my_awesome_model": [], "my_hook": [], "n": [0, 6], "na": 4, "name": [0, 4], "nation": [], "natur": 2, "ndarrai": [0, 1, 6], "necessari": [], "need": 6, "neg": 5, "nest": [], "nestedobject": 5, "netraj": [], "network": [2, 4], "neural": [2, 4], "new": [], "newer": 3, "next": 0, "nois": [], "noisi": [0, 2], "non": [1, 5, 6], "none": [0, 1], "normal": [4, 5], "norwegian": [], "note": [], "now": [], "np": [4, 6], "num_output_channel": [], "num_sampl": [], "number": [0, 5, 6], "numpi": [1, 4, 6], "o": [], "obb": [], "obj_detect": [], "object": 0, "objectness_scor": [], "oblig": [], "obtain": [], "occupi": [], "ocr": [0, 2, 6], "ocr_carea": [], "ocr_db_crnn": 6, "ocr_lin": [], "ocr_pag": [], "ocr_par": [], "ocr_predictor": 4, "ocrdataset": 0, "ocrmetr": 6, "ocrpredictor": 4, "ocrx_word": [], "offens": [], "offici": [], "offlin": [], "offset": 5, "onc": [0, 4], "one": [0, 4, 5], "oneof": 5, "ones": [], "onli": [5, 6], "onlin": [], "onnx": [], "onnxruntim": [], "onnxtr": [], "opac": [], "opacity_rang": [], "open": [], "opinion": [], "optic": [2, 4], "optim": 2, "option": [], "order": [0, 1, 4], "org": [], "organ": 1, "orient": 1, "orientationpredictor": [], "other": [], "otherwis": 6, "our": [2, 4], "out": [4, 5, 6], "outpout": [], "output": [1, 5], "output_s": [1, 5], "outsid": [], "over": 6, "overal": [], "overlai": [], "overview": [], "overwrit": 0, "overwritten": [], "own": 2, "p": 5, "packag": [2, 6], "pad": [0, 4, 5], "page": [4, 6], "page1": 1, "page2": 1, "page_1": [], "page_idx": 1, "page_orientation_predictor": [], "page_param": [], "pair": 6, "paper": [], "par_1_1": [], "paragraph": [], "paragraph_break": [], "parallel": [], "param": [4, 5], "paramet": [0, 1, 4, 5, 6], "pars": [0, 2], "parseq": [], "part": 5, "parti": [], "partial": [], "particip": [], "pass": [0, 4], "password": [], "patch": [], "path": [1, 4], "path_to_checkpoint": [], "path_to_custom_model": [], "path_to_pt": [], "patil": [], "pattern": [], "pdf": 1, "pdfpage": [], "peopl": [], "per": [4, 5], "perform": [1, 2, 4, 5, 6], "period": [], "permiss": [], "permut": [], "persian_lett": [], "person": [], "phase": [], "photo": [], "physic": 1, "pick": 5, "pictur": 1, "pip": 3, "pipelin": [], "pixel": [1, 5], "platinum": 4, "pleas": [], "plot": [], "plt": 6, "plug": [], "plugin": [], "png": 1, "point": [], "polici": [], "polish": [], "polit": [], "polygon": [], "pool": [], "portugues": [], "posit": 6, "possibl": 6, "post": [], "postprocessor": 4, "potenti": 4, "power": 2, "ppageno": [], "pre": [], "precis": [4, 6], "pred": [], "pred_box": [], "pred_label": [], "predefin": 0, "predict": [1, 6], "predictor": [], "prefer": 0, "preinstal": [], "preprocessor": 4, "prerequisit": [], "present": 0, "preserv": 5, "preserve_aspect_ratio": 5, "pretrain": [2, 4, 6], "pretrained_backbon": [], "print": [], "prior": [], "privaci": [], "privat": [], "probabl": 5, "problem": [], "procedur": 5, "process": [1, 2], "processor": [], "produc": 4, "product": [], "profession": [], "project": [], "promptli": [], "proper": [], "properli": 0, "properti": 4, "provid": [2, 4], "public": 2, "publicli": [], "publish": [], "pull": [], "punctuat": 0, "pure": [], "purpos": 4, "push_to_hf_hub": [], "py": [], "pypdfium2": [], "pyplot": 6, "python": 2, "python3": [], "pytorch": [], "q": [], "qr": 1, "qr_code": [], "qualiti": 5, "quantiz": 4, "quantize_model": 4, "question": [], "quickli": 2, "quicktour": [], "r": [], "race": [], "ramdisk": [], "rand": [4, 6], "random": [4, 5, 6], "randomappli": 5, "randombright": 5, "randomcontrast": 5, "randomcrop": [], "randomgamma": 5, "randomhorizontalflip": [], "randomhu": 5, "randomjpegqu": 5, "randomli": 5, "randomres": [], "randomrot": [], "randomsatur": 5, "randomshadow": [], "rang": 5, "rassi": [], "ratio": 5, "raw": 1, "re": 0, "read": [2, 4], "read_html": 1, "read_img": 1, "read_img_as_numpi": [], "read_img_as_tensor": [], "read_pdf": 1, "readi": [], "real": [2, 4, 5], "realli": [], "reason": [], "rebuild": [], "rebuilt": [], "recal": [4, 6], "receipt": [0, 2], "reco_arch": 4, "reco_b": [], "reco_model": [], "reco_param": [], "reco_predictor": [], "recogn": [], "recognit": 6, "recognition_predictor": 4, "recognition_task": [], "recognitiondataset": [], "recognitionpredictor": 4, "rectangular": [], "recurr": 2, "reduc": 5, "refer": [], "regardless": [], "region": [], "regroup": 6, "regular": [], "reject": [], "rel": 1, "relat": [], "releas": 3, "relev": [], "religion": [], "relu": 4, "remov": [], "render": [], "repo": [], "repo_id": [], "report": [], "repositori": 2, "repres": [1, 4], "represent": [2, 4], "request": [], "requir": [3, 5], "research": 2, "residu": [], "resiz": [4, 5], "resnet": 4, "resnet18": [], "resnet31": [], "resnet34": [], "resnet50": [], "resolv": 1, "resolve_block": [], "resolve_lin": [], "resourc": [], "respect": [], "rest": [5, 6], "restrict": [], "result": 1, "return": [0, 1, 4], "reusabl": 4, "review": [], "rgb": [1, 5], "rgb_mode": [], "rgb_output": 1, "right": [4, 6], "roboflow": [], "robust": 2, "root": [], "rotat": 1, "run": [], "same": [1, 6], "sampl": 0, "sample_transform": 0, "sanjin": [], "sar": [2, 4], "sar_resnet31": 4, "sar_vgg16_bn": 4, "satur": 5, "save": [0, 4], "saved_model": 4, "scale": 6, "scale_rang": [], "scan": [0, 2], "scene": [2, 4], "scheme": 4, "score": [], "scratch": 2, "script": [], "seamless": 2, "seamlessli": [], "search": [], "searchabl": [], "sec": [], "second": 4, "section": [], "secur": [], "see": [], "seemlessli": 2, "seen": 4, "segment": [2, 4], "self": [], "semant": [2, 4], "send": [], "sens": 6, "sensit": [], "separ": 4, "sequenc": [0, 1, 2, 4, 6], "sequenti": [4, 5], "seri": [], "serial": 4, "serialized_model": 4, "seriou": [], "set": [0, 4, 6], "set_global_polici": [], "sever": [1, 5], "sex": [], "sexual": [], "sha256": 0, "shade": [], "shape": [1, 4, 5, 6], "share": [], "shift": 5, "shm": [], "should": [0, 1, 6], "show": [2, 4, 6], "showcas": [], "shuffl": 0, "side": 6, "signatur": 1, "signific": 0, "simpl": [2, 4], "simpler": [], "sinc": 0, "singl": [], "single_img_doc": [], "size": [0, 1, 4, 5], "skew": [], "slack": [], "slightli": [], "small": 2, "smallest": 1, "snapshot_download": [], "snippet": [], "so": [], "social": [], "socio": [], "some": [], "someth": [], "somewher": [], "sort": [], "sourc": [0, 1, 4, 5, 6], "space": [], "span": [], "spanish": [], "spatial": 1, "special": 2, "specif": [0, 6], "specifi": 1, "speed": [2, 4], "sphinx": [], "sroie": 0, "stabl": 3, "stackoverflow": [], "stage": 2, "standalon": [], "standard": 5, "start": [], "state": 2, "static": [], "statist": 4, "statu": [], "std": 5, "step": [], "still": [], "str": [0, 1, 4, 5, 6], "straight": [], "straighten": [], "straighten_pag": [], "straigten_pag": [], "stream": 1, "street": [], "strict": [], "strictli": 6, "string": [0, 1, 4], "strive": [], "strong": [2, 4], "structur": 4, "subset": [0, 4], "suggest": [], "sum": 6, "summari": 6, "support": 4, "sustain": [], "svhn": [], "svt": [], "swedish": [], "symbol": 4, "symmetr": [], "symmetric_pad": [], "synthet": [], "synthtext": [], "system": [], "t": 0, "tabl": [], "take": [], "target": [0, 1, 4, 5], "target_s": 0, "task": [0, 2, 4], "task2": [], "team": [], "techminde": [], "templat": 1, "tensor": [0, 4, 5], "tensorflow": [2, 4, 5], "tensorspec": [], "term": [], "test": [], "test_set": [], "text": 1, "text_output": [], "textmatch": [], "textnet": [], "textnet_bas": [], "textnet_smal": [], "textnet_tini": [], "textract": [2, 4], "textstylebrush": [], "textual": [0, 1, 2], "tf": [4, 5], "tf_model": 4, "tflite": 4, "than": 6, "thank": [], "thei": [], "them": 0, "thi": [2, 3, 4, 6], "thing": [], "third": [], "those": [1, 4], "threaten": [], "threshold": [], "through": [0, 5], "tilman": [], "time": [0, 2, 4, 6], "tini": [], "titl": 1, "tm": [], "tmp": [], "togeth": [1, 4], "tograi": 5, "tool": [], "top": [], "topic": [], "torch": [], "torchvis": 5, "total": [], "toward": [], "train": [0, 4, 5], "train_it": 0, "train_load": 0, "train_pytorch": [], "train_set": 0, "train_tensorflow": [], "trainabl": [2, 4], "tranform": 5, "transcrib": [], "transfer": [], "transfo": 5, "transform": [0, 2], "translat": [], "troll": [], "true": [0, 1, 4, 5, 6], "truth": 6, "tune": 2, "tupl": [1, 4, 5], "turn": 4, "two": 1, "txt": [], "type": [1, 4], "typic": [], "u": [], "ucsd": [], "udac": [], "uint8": [4, 6], "ukrainian": [], "unaccept": [], "underli": 0, "underneath": 1, "understand": [0, 2], "uniform": [4, 5], "uniformli": [], "uninterrupt": 1, "union": 6, "unit": [], "unittest": [], "unlock": [], "unoffici": [], "unprofession": [], "unsolicit": [], "unsupervis": [], "unwelcom": [], "up": 4, "updat": 6, "upgrad": [], "upper": 5, "uppercas": [], "url": [0, 1], "us": [0, 3, 6], "usabl": 4, "usag": 4, "use_polygon": [], "useabl": [], "user": [1, 2], "utf": [], "util": [2, 4], "v1": [], "v3": [], "valid": [], "valu": [1, 5], "valuabl": 2, "variabl": [], "varieti": [], "veri": 2, "verifi": 0, "verma": [], "version": 4, "vgg": 4, "vgg16": 4, "vgg16_bn_r": [], "via": 2, "video": [], "vietnames": [], "view": [], "viewpoint": [], "violat": [], "visibl": [], "vision": [], "visiondataset": 0, "visiontransform": [], "visual": [], "visualize_pag": 6, "vit_": [], "vit_b": [], "vitstr": [], "vitstr_bas": [], "vitstr_smal": [], "viz": [], "vocab": 4, "vocabulari": [], "w": [1, 6], "w3": [], "wa": [], "wai": [0, 2, 4], "want": [], "warm": 4, "warmup": [], "wasn": [], "we": [1, 2, 4, 5], "weasyprint": [], "web": 1, "websit": [], "welcom": [], "well": [], "were": 1, "what": [], "when": 6, "whenev": [], "where": [1, 6], "whether": [0, 1, 6], "which": 4, "whichev": [], "while": [], "why": [], "width": 1, "wiki": [], "wildreceipt": [], "window": 6, "wish": [], "within": [], "without": 4, "wonder": [], "word": [2, 4, 6], "word_1_1": [], "word_1_2": [], "word_1_3": [], "wordgener": [], "words_onli": 6, "work": [], "worker": 0, "workflow": [], "worklow": [], "world": 6, "worth": [], "wrap": [], "wrapper": [0, 5], "write": [], "written": 1, "www": 1, "x": [1, 5, 6], "x12larg": 4, "x_ascend": [], "x_descend": [], "x_i": 6, "x_size": [], "x_wconf": [], "xeon": 4, "xhtml": [], "xmax": 1, "xmin": 1, "xml": [], "xml_bytes_str": [], "xml_element": [], "xml_output": [], "xmln": [], "y": 6, "y_i": 6, "y_j": 6, "yet": [], "ymax": 1, "ymin": 1, "yolov8": [], "you": 4, "your": [0, 1, 4, 6], "yoursit": 1, "yugesh": [], "zero": [4, 5], "zoo": [], "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 0, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": [], "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": [], "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": [], "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": [], "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": [], "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": [], "\u00e4\u00f6\u00e4\u00f6": [], "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": [], "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": [], "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": [], "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": [], "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": [], "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": [], "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": [], "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": [], "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": [], "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": [], "\u067e\u0686\u06a2\u06a4\u06af": [], "\u0905": [], "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": [], "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": [], "\u0950": [], "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": [], "\u09bd": [], "\u09ce": [], "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": []}, "titles": ["doctr.datasets", "doctr.documents", "DocTR: Document Text Recognition", "Installation", "doctr.models", "doctr.transforms", "doctr.utils"], "titleterms": {"": [], "0": [], "01": [], "02": [], "03": [], "04": [], "05": [], "07": [], "08": [], "09": [], "1": [], "10": [], "11": [], "12": [], "18": [], "2": [], "2021": [], "2022": [], "2023": [], "2024": [], "21": [], "22": [], "27": [], "28": [], "29": [], "3": [], "31": [], "4": [], "5": [], "6": [], "7": [], "8": [], "9": [], "advanc": [], "approach": 4, "architectur": [], "arg": [], "artefact": 1, "artefactdetect": [], "attribut": [], "avail": 0, "aw": [], "ban": [], "block": 1, "bug": [], "build": 2, "changelog": [], "choos": [], "classif": [], "code": [], "codebas": [], "commit": [], "commun": [], "compos": 5, "compress": 4, "conda": [], "conduct": [], "connect": [], "content": 2, "continu": [], "contrib": [], "contribut": [], "contributor": [], "convent": [], "correct": [], "coven": [], "custom": [], "data": 0, "dataload": [], "dataset": [0, 2], "detect": [2, 4], "develop": [], "do": [], "doctr": [0, 1, 2, 4, 5, 6], "document": [1, 2], "end": 4, "enforc": [], "evalu": 6, "export": 4, "factori": [], "featur": 2, "feedback": [], "file": 1, "from": [], "gener": [], "get": 2, "git": 3, "guidelin": [], "half": [], "hub": [], "huggingfac": [], "i": [], "implement": 2, "infer": [], "instal": 3, "integr": 2, "io": [], "lambda": [], "let": [], "line": 1, "linux": [], "load": 0, "loader": [], "main": [], "mode": [], "model": [2, 4], "modifi": [], "modul": [], "name": [], "notebook": [], "object": [], "ocr": 4, "onli": [], "onnx": [], "optim": [], "option": [], "orient": [], "our": [], "output": 4, "own": [], "packag": 3, "page": 1, "perman": [], "pipelin": [], "pledg": [], "post": 4, "pre": 4, "precis": [], "predictor": [2, 4], "prepar": [], "prerequisit": [], "pretrain": [], "process": 4, "push": [], "python": 3, "qualiti": [], "question": [], "read": 1, "readi": [], "recognit": [2, 4], "report": [], "request": [], "resourc": [], "respons": [], "return": [], "right": [], "savedmodel": 4, "scope": [], "share": [], "should": [], "stage": 4, "standard": [], "start": 2, "structur": 1, "style": [], "support": [0, 5], "synthet": [], "task": 6, "temporari": [], "test": [], "text": [2, 4], "train": 2, "transform": 5, "two": 4, "unit": [], "us": 4, "util": 6, "v0": [], "verif": [], "via": 3, "visual": 6, "vocab": 0, "warn": [], "what": [], "word": 1, "your": 2, "zoo": 4}}) \ No newline at end of file +Search.setIndex({"alltitles": {"1. Correction": [[2, "correction"]], "2. Warning": [[2, "warning"]], "3. Temporary Ban": [[2, "temporary-ban"]], "4. Permanent Ban": [[2, "permanent-ban"]], "AWS Lambda": [[14, null]], "Advanced options": [[19, "advanced-options"]], "Args:": [[7, "args"], [7, "id4"], [7, "id7"], [7, "id10"], [7, "id13"], [7, "id16"], [7, "id19"], [7, "id22"], [7, "id25"], [7, "id29"], [7, "id32"], [7, "id37"], [7, "id40"], [7, "id46"], [7, "id49"], [7, "id50"], [7, "id51"], [7, "id54"], [7, "id57"], [7, "id60"], [7, "id61"], [8, "args"], [8, "id2"], [8, "id3"], [8, "id4"], [8, "id5"], [8, "id6"], [8, "id7"], [8, "id10"], [8, "id12"], [8, "id14"], [8, "id16"], [8, "id20"], [8, "id24"], [8, "id28"], [9, "args"], [9, "id3"], [9, "id8"], [9, "id13"], [9, "id17"], [9, "id21"], [9, "id26"], [9, "id31"], [9, "id36"], [9, "id41"], [9, "id46"], [9, "id50"], [9, "id54"], [9, "id59"], [9, "id63"], [9, "id68"], [9, "id73"], [9, "id77"], [9, "id81"], [9, "id85"], [9, "id90"], [9, "id95"], [9, "id99"], [9, "id104"], [9, "id109"], [9, "id114"], [9, "id119"], [9, "id123"], [9, "id127"], [9, "id132"], [9, "id137"], [9, "id142"], [9, "id146"], [9, "id150"], [9, "id155"], [9, "id159"], [9, "id163"], [9, "id167"], [9, "id169"], [9, "id171"], [9, "id173"], [10, "args"], [10, "id1"], [10, "id2"], [10, "id3"], [10, "id4"], [10, "id5"], [10, "id6"], [10, "id7"], [10, "id8"], [10, "id9"], [10, "id10"], [10, "id11"], [10, "id12"], [10, "id13"], [10, "id14"], [10, "id15"], [10, "id16"], [10, "id17"], [10, "id18"], [10, "id19"], [11, "args"], [11, "id3"], [11, "id4"], [11, "id5"], [11, "id6"], [11, "id7"], [11, "id8"], [11, "id9"]], "Artefact": [[8, "artefact"]], "ArtefactDetection": [[16, "artefactdetection"]], "Attribution": [[2, "attribution"]], "Available Datasets": [[17, "available-datasets"]], "Available architectures": [[19, "available-architectures"], [19, "id1"], [19, "id2"]], "Available contribution modules": [[16, "available-contribution-modules"]], "Block": [[8, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[17, null]], "Choosing the right model": [[19, null]], "Classification": [[15, "classification"]], "Code quality": [[3, "code-quality"]], "Code style verification": [[3, "code-style-verification"]], "Codebase structure": [[3, "codebase-structure"]], "Commits": [[3, "commits"]], "Community resources": [[1, null]], "Composing transformations": [[10, "composing-transformations"]], "Continuous Integration": [[3, "continuous-integration"]], "Contributing to docTR": [[3, null]], "Contributor Covenant Code of Conduct": [[2, null]], "Custom dataset loader": [[7, "custom-dataset-loader"]], "Custom orientation classification models": [[13, "custom-orientation-classification-models"]], "Data Loading": [[17, "data-loading"]], "Dataloader": [[7, "dataloader"]], "Detection": [[15, "detection"], [17, "detection"]], "Detection predictors": [[19, "detection-predictors"]], "Developer mode installation": [[3, "developer-mode-installation"]], "Developing docTR": [[3, "developing-doctr"]], "Document": [[8, "document"]], "Document structure": [[8, "document-structure"]], "End-to-End OCR": [[19, "end-to-end-ocr"]], "Enforcement": [[2, "enforcement"]], "Enforcement Guidelines": [[2, "enforcement-guidelines"]], "Enforcement Responsibilities": [[2, "enforcement-responsibilities"]], "Export to ONNX": [[18, "export-to-onnx"]], "Feature requests & bug report": [[3, "feature-requests-bug-report"]], "Feedback": [[3, "feedback"]], "File reading": [[8, "file-reading"]], "Half-precision": [[18, "half-precision"]], "Installation": [[4, null]], "Integrate contributions into your pipeline": [[16, null]], "Let\u2019s connect": [[3, "let-s-connect"]], "Line": [[8, "line"]], "Loading from Huggingface Hub": [[15, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[13, "loading-your-custom-trained-model"]], "Loading your custom trained orientation classification model": [[13, "loading-your-custom-trained-orientation-classification-model"]], "Main Features": [[5, "main-features"]], "Model optimization": [[18, "model-optimization"]], "Model zoo": [[5, "model-zoo"]], "Modifying the documentation": [[3, "modifying-the-documentation"]], "Naming conventions": [[15, "naming-conventions"]], "OCR": [[17, "ocr"]], "Object Detection": [[17, "object-detection"]], "Our Pledge": [[2, "our-pledge"]], "Our Standards": [[2, "our-standards"]], "Page": [[8, "page"]], "Preparing your model for inference": [[18, null]], "Prerequisites": [[4, "prerequisites"]], "Pretrained community models": [[15, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[15, "pushing-to-the-huggingface-hub"]], "Questions": [[3, "questions"]], "Recognition": [[15, "recognition"], [17, "recognition"]], "Recognition predictors": [[19, "recognition-predictors"]], "Returns:": [[7, "returns"], [8, "returns"], [8, "id11"], [8, "id13"], [8, "id15"], [8, "id19"], [8, "id23"], [8, "id27"], [8, "id31"], [9, "returns"], [9, "id6"], [9, "id11"], [9, "id16"], [9, "id20"], [9, "id24"], [9, "id29"], [9, "id34"], [9, "id39"], [9, "id44"], [9, "id49"], [9, "id53"], [9, "id57"], [9, "id62"], [9, "id66"], [9, "id71"], [9, "id76"], [9, "id80"], [9, "id84"], [9, "id88"], [9, "id93"], [9, "id98"], [9, "id102"], [9, "id107"], [9, "id112"], [9, "id117"], [9, "id122"], [9, "id126"], [9, "id130"], [9, "id135"], [9, "id140"], [9, "id145"], [9, "id149"], [9, "id153"], [9, "id158"], [9, "id162"], [9, "id166"], [9, "id168"], [9, "id170"], [9, "id172"], [11, "returns"]], "Scope": [[2, "scope"]], "Share your model with the community": [[15, null]], "Supported Vocabs": [[7, "supported-vocabs"]], "Supported contribution modules": [[6, "supported-contribution-modules"]], "Supported datasets": [[5, "supported-datasets"]], "Supported transformations": [[10, "supported-transformations"]], "Synthetic dataset generator": [[7, "synthetic-dataset-generator"], [17, "synthetic-dataset-generator"]], "Task evaluation": [[11, "task-evaluation"]], "Text Detection": [[19, "text-detection"]], "Text Recognition": [[19, "text-recognition"]], "Text detection models": [[5, "text-detection-models"]], "Text recognition models": [[5, "text-recognition-models"]], "Train your own model": [[13, null]], "Two-stage approaches": [[19, "two-stage-approaches"]], "Unit tests": [[3, "unit-tests"]], "Use your own datasets": [[17, "use-your-own-datasets"]], "Using your ONNX exported model": [[18, "using-your-onnx-exported-model"]], "Via Conda (Only for Linux)": [[4, "via-conda-only-for-linux"]], "Via Git": [[4, "via-git"]], "Via Python Package": [[4, "via-python-package"]], "Visualization": [[11, "visualization"]], "What should I do with the output?": [[19, "what-should-i-do-with-the-output"]], "Word": [[8, "word"]], "docTR Notebooks": [[12, null]], "docTR Vocabs": [[7, "id62"]], "docTR: Document Text Recognition": [[5, null]], "doctr.contrib": [[6, null]], "doctr.datasets": [[7, null], [7, "datasets"]], "doctr.io": [[8, null]], "doctr.models": [[9, null]], "doctr.models.classification": [[9, "doctr-models-classification"]], "doctr.models.detection": [[9, "doctr-models-detection"]], "doctr.models.factory": [[9, "doctr-models-factory"]], "doctr.models.recognition": [[9, "doctr-models-recognition"]], "doctr.models.zoo": [[9, "doctr-models-zoo"]], "doctr.transforms": [[10, null]], "doctr.utils": [[11, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.10.0 (2024-10-21)": [[0, "v0-10-0-2024-10-21"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]], "v0.7.0 (2023-09-09)": [[0, "v0-7-0-2023-09-09"]], "v0.8.0 (2024-02-28)": [[0, "v0-8-0-2024-02-28"]], "v0.8.1 (2024-03-04)": [[0, "v0-8-1-2024-03-04"]], "v0.9.0 (2024-08-08)": [[0, "v0-9-0-2024-08-08"]]}, "docnames": ["changelog", "community/resources", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/contrib", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_contrib_modules", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "community/resources.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/contrib.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_contrib_modules.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[8, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[8, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[10, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[7, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[10, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[10, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[7, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[7, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[9, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[7, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[7, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[8, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[8, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[7, "doctr.datasets.encode_sequences", false]], "fast_base() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_base", false]], "fast_small() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_small", false]], "fast_tiny() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_tiny", false]], "from_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[7, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[10, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[10, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[7, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[7, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[7, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[7, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[7, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[9, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[10, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[8, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet34() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[7, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_crop_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_crop_orientation", false]], "mobilenet_v3_small_page_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_page_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[10, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[9, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[7, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[10, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[8, "doctr.io.Page", false]], "page_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.page_orientation_predictor", false]], "parseq() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[10, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[10, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[10, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[10, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[10, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[10, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[10, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[10, "doctr.transforms.RandomJpegQuality", false]], "randomresize (class in doctr.transforms)": [[10, "doctr.transforms.RandomResize", false]], "randomrotate (class in doctr.transforms)": [[10, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[10, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[10, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[8, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[8, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[8, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[7, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[10, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[8, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[8, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[7, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[7, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[7, "doctr.datasets.SVT", false]], "synthtext (class in doctr.datasets)": [[7, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.TextMatch", false]], "textnet_base() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_base", false]], "textnet_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_small", false]], "textnet_tiny() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_tiny", false]], "togray (class in doctr.transforms)": [[10, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[11, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_small", false]], "wildreceipt (class in doctr.datasets)": [[7, "doctr.datasets.WILDRECEIPT", false]], "word (class in doctr.io)": [[8, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[7, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[7, 0, 1, "", "CORD"], [7, 0, 1, "", "CharacterGenerator"], [7, 0, 1, "", "DetectionDataset"], [7, 0, 1, "", "DocArtefacts"], [7, 0, 1, "", "FUNSD"], [7, 0, 1, "", "IC03"], [7, 0, 1, "", "IC13"], [7, 0, 1, "", "IIIT5K"], [7, 0, 1, "", "IIITHWS"], [7, 0, 1, "", "IMGUR5K"], [7, 0, 1, "", "MJSynth"], [7, 0, 1, "", "OCRDataset"], [7, 0, 1, "", "RecognitionDataset"], [7, 0, 1, "", "SROIE"], [7, 0, 1, "", "SVHN"], [7, 0, 1, "", "SVT"], [7, 0, 1, "", "SynthText"], [7, 0, 1, "", "WILDRECEIPT"], [7, 0, 1, "", "WordGenerator"], [7, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[7, 0, 1, "", "DataLoader"]], "doctr.io": [[8, 0, 1, "", "Artefact"], [8, 0, 1, "", "Block"], [8, 0, 1, "", "Document"], [8, 0, 1, "", "DocumentFile"], [8, 0, 1, "", "Line"], [8, 0, 1, "", "Page"], [8, 0, 1, "", "Word"], [8, 1, 1, "", "decode_img_as_tensor"], [8, 1, 1, "", "read_html"], [8, 1, 1, "", "read_img_as_numpy"], [8, 1, 1, "", "read_img_as_tensor"], [8, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[8, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[8, 2, 1, "", "from_images"], [8, 2, 1, "", "from_pdf"], [8, 2, 1, "", "from_url"]], "doctr.io.Page": [[8, 2, 1, "", "show"]], "doctr.models": [[9, 1, 1, "", "kie_predictor"], [9, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[9, 1, 1, "", "crop_orientation_predictor"], [9, 1, 1, "", "magc_resnet31"], [9, 1, 1, "", "mobilenet_v3_large"], [9, 1, 1, "", "mobilenet_v3_large_r"], [9, 1, 1, "", "mobilenet_v3_small"], [9, 1, 1, "", "mobilenet_v3_small_crop_orientation"], [9, 1, 1, "", "mobilenet_v3_small_page_orientation"], [9, 1, 1, "", "mobilenet_v3_small_r"], [9, 1, 1, "", "page_orientation_predictor"], [9, 1, 1, "", "resnet18"], [9, 1, 1, "", "resnet31"], [9, 1, 1, "", "resnet34"], [9, 1, 1, "", "resnet50"], [9, 1, 1, "", "textnet_base"], [9, 1, 1, "", "textnet_small"], [9, 1, 1, "", "textnet_tiny"], [9, 1, 1, "", "vgg16_bn_r"], [9, 1, 1, "", "vit_b"], [9, 1, 1, "", "vit_s"]], "doctr.models.detection": [[9, 1, 1, "", "db_mobilenet_v3_large"], [9, 1, 1, "", "db_resnet50"], [9, 1, 1, "", "detection_predictor"], [9, 1, 1, "", "fast_base"], [9, 1, 1, "", "fast_small"], [9, 1, 1, "", "fast_tiny"], [9, 1, 1, "", "linknet_resnet18"], [9, 1, 1, "", "linknet_resnet34"], [9, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[9, 1, 1, "", "from_hub"], [9, 1, 1, "", "login_to_hub"], [9, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[9, 1, 1, "", "crnn_mobilenet_v3_large"], [9, 1, 1, "", "crnn_mobilenet_v3_small"], [9, 1, 1, "", "crnn_vgg16_bn"], [9, 1, 1, "", "master"], [9, 1, 1, "", "parseq"], [9, 1, 1, "", "recognition_predictor"], [9, 1, 1, "", "sar_resnet31"], [9, 1, 1, "", "vitstr_base"], [9, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[10, 0, 1, "", "ChannelShuffle"], [10, 0, 1, "", "ColorInversion"], [10, 0, 1, "", "Compose"], [10, 0, 1, "", "GaussianBlur"], [10, 0, 1, "", "GaussianNoise"], [10, 0, 1, "", "LambdaTransformation"], [10, 0, 1, "", "Normalize"], [10, 0, 1, "", "OneOf"], [10, 0, 1, "", "RandomApply"], [10, 0, 1, "", "RandomBrightness"], [10, 0, 1, "", "RandomContrast"], [10, 0, 1, "", "RandomCrop"], [10, 0, 1, "", "RandomGamma"], [10, 0, 1, "", "RandomHorizontalFlip"], [10, 0, 1, "", "RandomHue"], [10, 0, 1, "", "RandomJpegQuality"], [10, 0, 1, "", "RandomResize"], [10, 0, 1, "", "RandomRotate"], [10, 0, 1, "", "RandomSaturation"], [10, 0, 1, "", "RandomShadow"], [10, 0, 1, "", "Resize"], [10, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[11, 0, 1, "", "DetectionMetric"], [11, 0, 1, "", "LocalizationConfusion"], [11, 0, 1, "", "OCRMetric"], [11, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.visualization": [[11, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [2, 8, 9, 11, 15, 18], "0": [2, 4, 7, 10, 11, 13, 16, 17, 19], "00": 19, "01": 19, "0123456789": 7, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "02562": 9, "03": 19, "035": 19, "0361328125": 19, "04": 19, "05": 19, "06": 19, "06640625": 19, "07": 19, "08": [10, 19], "09": 19, "0966796875": 19, "1": [7, 8, 9, 10, 11, 13, 17, 19], "10": [4, 7, 11, 19], "100": [7, 10, 11, 17, 19], "1000": 19, "101": 7, "1024": [9, 13, 19], "104": 7, "106": 7, "108": 7, "1095": 17, "11": 19, "110": 11, "1107": 17, "114": 7, "115": 7, "1156": 17, "116": 7, "118": 7, "11800h": 19, "11th": 19, "12": 19, "120": 7, "123": 7, "126": 7, "1268": 17, "128": [9, 13, 18, 19], "13": 19, "130": 7, "13068": 17, "131": 7, "1337891": 17, "1357421875": 19, "1396484375": 19, "14": 19, "1420": 19, "14470v1": 7, "149": 17, "15": 19, "150": [11, 19], "1552": 19, "16": [9, 18, 19], "1630859375": 19, "1684": 19, "16x16": 9, "17": 19, "1778": 19, "1782": 19, "18": [9, 19], "185546875": 19, "1900": 19, "1910": 9, "19342": 17, "19370": 17, "195": 7, "19598": 17, "199": 19, "1999": 19, "2": [4, 5, 7, 8, 9, 10, 16, 19], "20": 19, "200": 11, "2000": 17, "2003": [5, 7], "2012": 7, "2013": [5, 7], "2015": 7, "2019": 5, "2023": 1, "207901": 17, "21": 19, "2103": 7, "2186": 17, "21888": 17, "22": 19, "224": [9, 10], "225": 10, "22672": 17, "229": [10, 17], "23": 19, "233": 17, "236": 7, "24": 19, "246": 17, "249": 17, "25": 19, "2504": 19, "255": [8, 9, 10, 11, 19], "256": 9, "257": 17, "26": 19, "26032": 17, "264": 13, "27": 19, "2700": 17, "2710": 19, "2749": 13, "28": 19, "287": 13, "29": 19, "296": 13, "299": 13, "2d": 19, "3": [4, 5, 8, 9, 10, 11, 18, 19], "30": 19, "300": 17, "3000": 17, "301": 13, "30595": 19, "30ghz": 19, "31": 9, "32": [7, 9, 10, 13, 17, 18, 19], "3232421875": 19, "33": [10, 19], "33402": 17, "33608": 17, "34": [9, 19], "340": 19, "3456": 19, "3515625": 19, "36": 19, "360": 17, "37": [7, 19], "38": 19, "39": 19, "4": [9, 10, 11, 19], "40": 19, "406": 10, "41": 19, "42": 19, "43": 19, "44": 19, "45": 19, "456": 10, "46": 19, "47": 19, "472": 17, "48": [7, 19], "485": 10, "49": 19, "49377": 17, "5": [7, 10, 11, 16, 19], "50": [9, 17, 19], "51": 19, "51171875": 19, "512": 9, "52": [7, 19], "529": 19, "53": 19, "54": 19, "540": 19, "5478515625": 19, "55": 19, "56": 19, "57": 19, "58": [7, 19], "580": 19, "5810546875": 19, "583": 19, "59": 19, "597": 19, "5k": [5, 7], "5m": 19, "6": [10, 19], "60": 10, "600": [9, 11, 19], "61": 19, "62": 19, "626": 17, "63": 19, "64": [9, 10, 19], "641": 19, "647": 17, "65": 19, "66": 19, "67": 19, "68": 19, "69": 19, "693": 13, "694": 13, "695": 13, "6m": 19, "7": 19, "70": [7, 11, 19], "707470": 17, "71": [7, 19], "7100000": 17, "7141797": 17, "7149": 17, "72": 19, "72dpi": 8, "73": 19, "73257": 17, "74": 19, "75": [10, 19], "7581382": 17, "76": 19, "77": 19, "772": 13, "772875": 17, "78": 19, "785": 13, "79": 19, "793533": 17, "796": 17, "798": 13, "7m": 19, "8": [9, 10, 19], "80": 19, "800": [9, 11, 17, 19], "81": 19, "82": 19, "83": 19, "84": 19, "849": 17, "85": 19, "8564453125": 19, "857": 19, "85875": 17, "86": 19, "8603515625": 19, "87": 19, "8707": 17, "88": 19, "89": 19, "9": [10, 19], "90": 19, "90k": 7, "90kdict32px": 7, "91": 19, "914085328578949": 19, "92": 19, "93": 19, "94": [7, 19], "95": [11, 19], "9578408598899841": 19, "96": 19, "97": 19, "98": 19, "99": 19, "9949972033500671": 19, "A": [2, 3, 5, 7, 8, 9, 12, 18], "As": 3, "Be": 19, "Being": 2, "By": 14, "For": [2, 3, 4, 13, 19], "If": [3, 8, 9, 13, 19], "In": [3, 7, 17], "It": [10, 15, 16, 18], "Its": [5, 9], "No": [2, 19], "Of": 7, "Or": [16, 18], "The": [2, 3, 7, 8, 11, 14, 16, 17, 18, 19], "Then": 9, "To": [3, 4, 14, 15, 16, 18, 19], "_": [2, 7, 9], "__call__": 19, "_build": 3, "_i": 11, "ab": 7, "abc": 18, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "abdef": [7, 17], "abl": [17, 19], "about": [2, 17, 19], "abov": 19, "abstract": 1, "abstractdataset": 7, "abus": 2, "accept": 2, "access": [5, 8, 17, 19], "account": [2, 15], "accur": 19, "accuraci": 11, "achiev": 18, "act": 2, "action": 2, "activ": 5, "ad": [3, 9, 10], "adapt": 2, "add": [10, 11, 15, 19], "add_hook": 19, "add_label": 11, "addit": [3, 4, 8, 16, 19], "addition": [3, 19], "address": [2, 8], "adjust": 10, "advanc": 2, "advantag": 18, "advis": 3, "aesthet": [5, 7], "affect": 2, "after": [15, 19], "ag": 2, "again": 9, "aggreg": [11, 17], "aggress": 2, "align": [2, 8, 10], "all": [2, 3, 6, 7, 8, 10, 11, 16, 17, 19], "allow": [2, 18], "along": 19, "alreadi": [3, 18], "also": [2, 9, 15, 16, 17, 19], "alwai": 17, "an": [2, 3, 5, 7, 8, 9, 11, 16, 18, 19], "analysi": [8, 16], "ancient_greek": 7, "andrej": 1, "angl": [8, 10], "ani": [2, 7, 8, 9, 10, 11, 18, 19], "annot": 7, "anot": 17, "anoth": [9, 13, 17], "answer": 2, "anyascii": 11, "anyon": 5, "anyth": 16, "api": [3, 5], "apolog": 2, "apologi": 2, "app": 3, "appear": 2, "appli": [2, 7, 10], "applic": [5, 9], "appoint": 2, "appreci": 15, "appropri": [2, 3, 19], "ar": [2, 3, 4, 6, 7, 8, 10, 11, 12, 16, 17, 19], "arab": 7, "arabic_diacrit": 7, "arabic_lett": 7, "arabic_punctu": 7, "arbitrarili": [5, 9], "arch": [9, 15], "architectur": [5, 9, 15, 16], "area": 19, "argument": [7, 8, 9, 11, 13, 19], "around": 2, "arrai": [8, 10, 11], "art": [5, 16], "artefact": [11, 16, 19], "artefact_typ": 8, "articl": 1, "artifici": [5, 7], "arxiv": [7, 9], "asarrai": 11, "ascii_lett": 7, "aspect": [5, 9, 10, 19], "assess": 11, "assign": 11, "associ": 8, "assum": 9, "assume_straight_pag": [9, 13, 19], "astyp": [9, 11, 19], "attack": 2, "attend": [5, 9], "attent": [2, 9], "autom": 5, "automat": 19, "autoregress": [5, 9], "avail": [2, 5, 6, 10], "averag": [10, 19], "avoid": [2, 4], "aw": [5, 19], "awar": 19, "azur": 19, "b": [9, 11, 19], "b_j": 11, "back": 3, "backbon": 9, "backend": 19, "background": 17, "bangla": 7, "bar": 16, "bar_cod": 17, "baranovskij": 1, "base": [5, 9, 16], "baselin": [5, 9, 19], "batch": [7, 9, 10, 16, 17, 19], "batch_siz": [7, 9, 13, 16, 17, 18], "bblanchon": 4, "bbox": 19, "becaus": 14, "been": [3, 11, 17, 19], "befor": [7, 9, 10, 19], "begin": 11, "behavior": [2, 19], "being": [11, 19], "belong": 19, "benchmark": 19, "best": [1, 2], "better": [12, 19], "between": [10, 11, 19], "bgr": 8, "bilinear": 10, "bin_thresh": 19, "binar": [5, 9, 19], "binari": [8, 18, 19], "bit": 18, "block": [11, 19], "block_1_1": 19, "blur": 10, "bmvc": 7, "bn": 15, "bodi": [2, 19], "bool": [7, 8, 9, 10, 11], "boolean": [9, 19], "both": [5, 7, 10, 17, 19], "bottom": [9, 19], "bound": [7, 8, 9, 10, 11, 16, 17, 19], "box": [7, 8, 9, 10, 11, 16, 17, 19], "box_thresh": 19, "bright": 10, "browser": [3, 5], "build": [3, 4, 18], "built": 3, "byte": [8, 19], "c": [4, 8, 11], "c_j": 11, "cach": [3, 7, 14], "cache_sampl": 7, "call": 18, "callabl": [7, 10], "can": [3, 4, 13, 14, 15, 16, 17, 19], "capabl": [3, 12, 19], "case": [7, 11], "cf": 19, "cfg": 19, "challeng": 7, "challenge2_test_task12_imag": 7, "challenge2_test_task1_gt": 7, "challenge2_training_task12_imag": 7, "challenge2_training_task1_gt": 7, "chang": [14, 19], "channel": [2, 3, 8, 10], "channel_prior": 4, "channelshuffl": 10, "charact": [5, 7, 8, 11, 17, 19], "charactergener": [7, 17], "characterist": 2, "charg": 19, "charset": 19, "chart": 8, "check": [3, 15, 19], "checkpoint": 9, "chip": 4, "christian": 1, "ci": 3, "clarifi": 2, "clariti": 2, "class": [2, 7, 8, 10, 11, 19], "class_nam": 13, "classif": [17, 19], "classmethod": 8, "clear": 3, "clone": 4, "close": 3, "co": 15, "code": [5, 8, 16], "codecov": 3, "colab": 12, "collate_fn": 7, "collect": [8, 16], "color": 10, "colorinvers": 10, "column": 8, "com": [2, 4, 8, 9, 15], "combin": 19, "command": [3, 16], "comment": 2, "commit": 2, "common": [2, 10, 11, 18], "commun": 2, "compar": 5, "comparison": [11, 19], "competit": 7, "compil": [12, 19], "complaint": 2, "complementari": 11, "complet": 3, "compon": 19, "compos": [7, 19], "comprehens": 19, "comput": [7, 11, 18, 19], "conf_threshold": 16, "confid": [8, 19], "config": [4, 9], "configur": 9, "confus": 11, "consecut": [10, 19], "consequ": 2, "consid": [2, 3, 7, 8, 11, 19], "consist": 19, "consolid": [5, 7], "constant": 10, "construct": 2, "contact": 2, "contain": [1, 6, 7, 12, 17, 19], "content": [7, 8, 19], "context": 9, "contib": 4, "continu": 2, "contrast": 10, "contrast_factor": 10, "contrib": [4, 16], "contribut": 2, "contributor": 3, "convers": 8, "convert": [8, 10], "convolut": 9, "cool": 1, "coordin": [8, 19], "cord": [5, 7, 17, 19], "core": [11, 19], "corner": 19, "correct": 10, "correspond": [4, 8, 10, 19], "could": [2, 16], "counterpart": 11, "cover": 3, "coverag": 3, "cpu": [5, 13, 18], "creat": [1, 15], "crnn": [5, 9, 15], "crnn_mobilenet_v3_larg": [9, 15, 19], "crnn_mobilenet_v3_smal": [9, 18, 19], "crnn_vgg16_bn": [9, 13, 15, 19], "crop": [8, 9, 10, 13, 17, 19], "crop_orient": [8, 19], "crop_orientation_predictor": [9, 13], "crop_param": 13, "cuda": 18, "currenc": 7, "current": [3, 13, 19], "custom": [15, 16, 18, 19], "custom_crop_orientation_model": 13, "custom_page_orientation_model": 13, "customhook": 19, "cvit": 5, "czczup": 9, "czech": 7, "d": [7, 17], "danish": 7, "data": [5, 7, 8, 10, 11, 13, 15], "dataload": 17, "dataset": [9, 13, 19], "dataset_info": 7, "date": [13, 19], "db": 15, "db_mobilenet_v3_larg": [9, 15, 19], "db_resnet34": 19, "db_resnet50": [9, 13, 15, 19], "dbnet": [5, 9], "deal": [12, 19], "decis": 2, "decod": 8, "decode_img_as_tensor": 8, "dedic": 18, "deem": 2, "deep": [9, 19], "def": 19, "default": [4, 8, 13, 14, 19], "defer": 17, "defin": [11, 18], "degre": [8, 10, 19], "degress": 8, "delet": 3, "delimit": 19, "delta": 10, "demo": [3, 5], "demonstr": 2, "depend": [3, 4, 5, 19], "deploi": 3, "deploy": 5, "derogatori": 2, "describ": 9, "descript": 12, "design": 10, "desir": 8, "det_arch": [9, 13, 15, 18], "det_b": 19, "det_model": [13, 15, 18], "det_param": 13, "det_predictor": [13, 19], "detail": [13, 19], "detect": [1, 7, 8, 11, 12, 13, 16], "detect_languag": 9, "detect_orient": [9, 13, 19], "detection_predictor": [9, 19], "detection_task": [7, 17], "detectiondataset": [7, 17], "detectionmetr": 11, "detectionpredictor": [9, 13], "detector": [5, 9, 16], "deterior": 9, "determin": 2, "dev": [3, 14], "develop": 4, "deviat": 10, "devic": 18, "dict": [8, 11, 19], "dictionari": [8, 11], "differ": 2, "differenti": [5, 9], "digit": [5, 7, 17], "dimens": [8, 11, 19], "dimension": 10, "direct": 7, "directli": [15, 19], "directori": [3, 14], "disabl": [2, 14, 19], "disable_crop_orient": 19, "disable_page_orient": 19, "disclaim": 19, "discuss": 3, "disparag": 2, "displai": [8, 11], "display_artefact": 11, "distribut": 10, "div": 19, "divers": 2, "divid": 8, "do": [3, 4, 9], "doc": [3, 8, 16, 18, 19], "docartefact": [7, 17], "docstr": 3, "doctr": [1, 4, 13, 14, 15, 16, 17, 18, 19], "doctr_cache_dir": 14, "doctr_multiprocessing_dis": 14, "document": [1, 7, 9, 11, 12, 13, 16, 17, 18, 19], "documentbuild": 19, "documentfil": [8, 13, 15, 16, 18], "doesn": 18, "don": [13, 19], "done": 10, "download": [7, 17], "downsiz": 9, "draw": 10, "drop": 7, "drop_last": 7, "dtype": [8, 9, 10, 11, 18], "dual": [5, 7], "dummi": 15, "dummy_img": 19, "dummy_input": 18, "dure": 2, "dutch": 7, "dynam": [7, 16], "dynamic_seq_length": 7, "e": [2, 3, 4, 8, 9], "each": [5, 7, 8, 9, 10, 11, 17, 19], "eas": 3, "easi": [5, 11, 15, 18], "easili": [8, 11, 13, 15, 17, 19], "econom": 2, "edit": 2, "educ": 2, "effect": 19, "effici": [3, 5, 7, 9], "either": [11, 19], "element": [7, 8, 9, 19], "els": [3, 16], "email": 2, "empathi": 2, "en": 19, "enabl": [7, 8], "enclos": 8, "encod": [5, 7, 8, 9, 19], "encode_sequ": 7, "encount": 3, "encrypt": 8, "end": [5, 7, 9, 11], "english": [7, 17], "enough": [3, 19], "ensur": 3, "entri": 7, "environ": [2, 14], "eo": 7, "equiv": 19, "estim": 9, "etc": [8, 16], "ethnic": 2, "evalu": [17, 19], "event": 2, "everyon": 2, "everyth": [3, 19], "exact": [11, 19], "exampl": [2, 3, 5, 7, 9, 15, 19], "exchang": 18, "execut": 19, "exist": 15, "expand": 10, "expect": [8, 10, 11], "experi": 2, "explan": [2, 19], "explicit": 2, "exploit": [5, 9], "export": [8, 9, 11, 12, 16, 19], "export_as_straight_box": [9, 19], "export_as_xml": 19, "export_model_to_onnx": 18, "express": [2, 10], "extens": 8, "extern": [2, 17], "extract": [1, 5, 7], "extractor": 9, "f_": 11, "f_a": 11, "factor": 10, "fair": 2, "fairli": 2, "fals": [7, 8, 9, 10, 11, 13, 19], "faq": 2, "fascan": 15, "fast": [5, 7, 9], "fast_bas": [9, 19], "fast_smal": [9, 19], "fast_tini": [9, 19], "faster": [5, 9, 18], "fasterrcnn_mobilenet_v3_large_fpn": 9, "favorit": 19, "featur": [4, 9, 11, 12, 13, 16], "feedback": 2, "feel": [3, 15], "felix92": 15, "few": [18, 19], "figsiz": 11, "figur": [11, 16], "file": [3, 7], "final": 9, "find": [3, 17], "fine": 1, "finnish": 7, "first": [3, 7], "firsthand": 7, "fit": [9, 19], "flag": 19, "flip": 10, "float": [8, 10, 11, 18], "float32": [8, 9, 10, 18], "fn": 10, "focu": 15, "focus": [2, 7], "folder": 7, "follow": [2, 3, 4, 7, 10, 11, 13, 14, 15, 16, 19], "font": 7, "font_famili": 7, "foral": 11, "forc": 3, "forg": 4, "form": [5, 7, 19], "format": [8, 11, 13, 17, 18, 19], "forpost": [5, 7], "forum": 3, "found": 1, "fp16": 18, "frac": 11, "framework": [4, 15, 17, 19], "free": [2, 3, 15], "french": [7, 13, 15, 19], "friendli": 5, "from": [1, 2, 5, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19], "from_hub": [9, 15], "from_imag": [8, 15, 16, 18], "from_pdf": 8, "from_url": 8, "full": [7, 11, 19], "function": [7, 10, 11, 16], "funsd": [5, 7, 17, 19], "further": 17, "futur": 7, "g": [8, 9], "g_": 11, "g_x": 11, "gallagh": 1, "gamma": 10, "gaussian": 10, "gaussianblur": 10, "gaussiannois": 10, "gen": 19, "gender": 2, "gener": [3, 5, 8, 9], "generic_cyrillic_lett": 7, "geometri": [5, 8, 19], "geq": 11, "german": [7, 13, 15], "get": [18, 19], "git": 15, "github": [3, 4, 9, 15], "give": [2, 16], "given": [7, 8, 10, 11, 19], "global": 9, "go": 19, "good": 18, "googl": 3, "googlevis": 5, "gpu": [5, 16, 18], "gracefulli": 2, "graph": [5, 7, 8], "grayscal": 10, "ground": 11, "groung": 11, "group": [5, 19], "gt": 11, "gt_box": 11, "gt_label": 11, "guid": 3, "guidanc": 17, "gvision": 19, "h": [8, 9, 10], "h_": 11, "ha": [3, 7, 11, 17], "handl": [12, 17, 19], "handwrit": 7, "handwritten": 17, "harass": 2, "hardwar": 19, "harm": 2, "hat": 11, "have": [2, 3, 11, 13, 15, 17, 18, 19], "head": [9, 19], "healthi": 2, "hebrew": 7, "height": [8, 10], "hello": [11, 19], "help": 18, "here": [6, 10, 12, 16, 17, 19], "hf": 9, "hf_hub_download": 9, "high": 8, "higher": [4, 7, 19], "hindi": 7, "hindi_digit": 7, "hocr": 19, "hook": 19, "horizont": [8, 10, 19], "hous": 7, "how": [1, 3, 12, 13, 15, 17], "howev": 17, "hsv": 10, "html": [2, 3, 4, 8, 19], "http": [2, 4, 7, 8, 9, 15, 19], "hub": 9, "hue": 10, "huggingfac": 9, "hw": 7, "i": [2, 3, 7, 8, 9, 10, 11, 14, 15, 16, 17, 18], "i7": 19, "ibrahimov": 1, "ic03": [5, 7, 17], "ic13": [5, 7, 17], "icdar": [5, 7], "icdar2019": 7, "id": 19, "ident": 2, "identifi": 5, "iiit": [5, 7], "iiit5k": [7, 17], "iiithw": [5, 7, 17], "imag": [1, 5, 7, 8, 9, 10, 11, 15, 16, 17, 19], "imagenet": 9, "imageri": 2, "images_90k_norm": 7, "img": [7, 10, 17, 18], "img_cont": 8, "img_fold": [7, 17], "img_path": 8, "img_transform": 7, "imgur5k": [5, 7, 17], "imgur5k_annot": 7, "imlist": 7, "impact": 2, "implement": [7, 8, 9, 10, 11, 19], "import": [7, 8, 9, 10, 11, 13, 15, 16, 17, 18, 19], "improv": 9, "inappropri": 2, "incid": 2, "includ": [2, 7, 17, 18], "inclus": 2, "increas": 10, "independ": 10, "index": [3, 8], "indic": 11, "individu": 2, "infer": [5, 9, 10, 16, 19], "inform": [1, 2, 3, 5, 7, 17], "input": [3, 8, 9, 10, 18, 19], "input_crop": 9, "input_pag": [9, 11, 19], "input_shap": 18, "input_tensor": 9, "inspir": [2, 10], "instal": [15, 16, 18], "instanc": [2, 19], "instanti": [9, 19], "instead": [7, 8, 9], "insult": 2, "int": [7, 8, 9, 10], "int64": 11, "integ": 11, "integr": [1, 5, 15, 17], "intel": 19, "interact": [2, 8, 11], "interfac": [15, 18], "interoper": 18, "interpol": 10, "interpret": [7, 8], "intersect": 11, "invert": 10, "investig": 2, "invis": 2, "involv": [2, 19], "io": [13, 15, 16, 18], "iou": 11, "iou_thresh": 11, "iou_threshold": 16, "irregular": [5, 9, 17], "isn": 7, "issu": [2, 3, 15], "italian": 7, "iter": [7, 10, 17, 19], "its": [8, 9, 10, 11, 17, 19], "itself": [9, 15], "j": 11, "jame": 1, "job": 3, "join": 3, "jpeg": 10, "jpegqual": 10, "jpg": [7, 8, 15, 18], "json": [7, 17, 19], "json_output": 19, "jump": 3, "just": 2, "kei": [5, 7], "kera": [9, 18], "kernel": [5, 9, 10], "kernel_shap": 10, "keywoard": 9, "keyword": [7, 8, 9, 11], "kie": [9, 13], "kie_predictor": [9, 13], "kiepredictor": 9, "kind": 2, "know": [3, 18], "kwarg": [7, 8, 9, 11], "l": 11, "l_j": 11, "label": [7, 11, 16, 17], "label_fil": [7, 17], "label_fold": 7, "label_path": [7, 17], "labels_path": [7, 17], "ladder": 2, "lambda": 10, "lambdatransform": 10, "lang": 19, "languag": [2, 5, 7, 8, 9, 15, 19], "larg": [9, 15], "largest": 11, "last": [4, 7], "latenc": 9, "later": 3, "latest": 19, "latin": 7, "layer": 18, "layout": 19, "lead": 2, "leader": 2, "learn": [2, 5, 9, 18, 19], "least": 4, "left": [11, 19], "legacy_french": 7, "length": [7, 19], "less": [18, 19], "level": [2, 7, 11, 19], "leverag": 12, "lf": 15, "librari": [3, 4, 12, 13], "light": 5, "lightweight": 18, "like": 2, "limits_": 11, "line": [5, 9, 11, 19], "line_1_1": 19, "link": 13, "linknet": [5, 9], "linknet_resnet18": [9, 13, 18, 19], "linknet_resnet34": [9, 18, 19], "linknet_resnet50": [9, 19], "list": [7, 8, 10, 11, 15], "ll": 11, "load": [5, 7, 9, 16, 18], "load_state_dict": 13, "load_weight": 13, "loc_pr": 19, "local": [3, 5, 7, 9, 11, 17, 19], "localis": 7, "localizationconfus": 11, "locat": [3, 8, 19], "login": 9, "login_to_hub": [9, 15], "logo": [8, 16, 17], "love": 15, "lower": [10, 11, 19], "m": [3, 11, 19], "m1": 4, "macbook": 4, "machin": 18, "made": 5, "magc_resnet31": 9, "mai": [2, 3], "mail": 2, "main": 12, "maintain": 5, "mainten": 3, "make": [2, 3, 11, 13, 14, 15, 18, 19], "mani": [17, 19], "manipul": 19, "map": [7, 9], "map_loc": 13, "master": [5, 9, 19], "match": [11, 19], "mathcal": 11, "matplotlib": [8, 11], "max": [7, 10, 11], "max_angl": 10, "max_area": 10, "max_char": [7, 17], "max_delta": 10, "max_gain": 10, "max_gamma": 10, "max_qual": 10, "max_ratio": 10, "maximum": [7, 10], "maxval": [9, 10], "mbox": 11, "mean": [10, 11, 13], "meaniou": 11, "meant": [8, 18], "measur": 19, "media": 2, "median": 9, "meet": 13, "member": 2, "memori": [14, 18], "mention": 19, "merg": 7, "messag": 3, "meta": 19, "metadata": 18, "metal": 4, "method": [8, 10, 19], "metric": [11, 19], "middl": 19, "might": [18, 19], "min": 10, "min_area": 10, "min_char": [7, 17], "min_gain": 10, "min_gamma": 10, "min_qual": 10, "min_ratio": 10, "min_val": 10, "minde": [1, 2, 4, 5, 9], "minim": [3, 5], "minimalist": [5, 9], "minimum": [4, 7, 10, 11, 19], "minval": 10, "miss": 4, "mistak": 2, "mixed_float16": 18, "mixed_precis": 18, "mjsynth": [5, 7, 17], "mnt": 7, "mobilenet": [9, 15], "mobilenet_v3_larg": 9, "mobilenet_v3_large_r": 9, "mobilenet_v3_smal": [9, 13], "mobilenet_v3_small_crop_orient": [9, 13], "mobilenet_v3_small_page_orient": [9, 13], "mobilenet_v3_small_r": 9, "mobilenetv3": 9, "modal": [5, 7], "mode": 4, "model": [7, 11, 14, 16, 17], "model_nam": [9, 15, 18], "model_path": [16, 18], "moder": 2, "modif": 3, "modifi": [9, 14, 19], "modul": [4, 8, 9, 10, 11, 19], "more": [3, 17, 19], "moscardi": 1, "most": 19, "mozilla": 2, "multi": [5, 9], "multilingu": [7, 15], "multipl": [7, 8, 10, 19], "multipli": 10, "multiprocess": 14, "my": 9, "my_awesome_model": 15, "my_hook": 19, "n": [7, 11], "name": [7, 9, 18, 19], "nation": 2, "natur": [2, 5, 7], "ndarrai": [7, 8, 10, 11], "necessari": [4, 13, 14], "need": [3, 4, 7, 11, 13, 14, 15, 16, 19], "neg": 10, "nest": 19, "netraj": 1, "network": [5, 7, 9, 18], "neural": [5, 7, 9, 18], "new": [3, 11], "next": [7, 17], "nois": 10, "noisi": [5, 7], "non": [5, 7, 8, 9, 10, 11], "none": [7, 8, 9, 10, 11, 19], "normal": [9, 10], "norwegian": 7, "note": [0, 3, 7, 9, 13, 15, 16, 18], "now": 3, "np": [9, 10, 11, 19], "num_output_channel": 10, "num_sampl": [7, 17], "number": [7, 9, 10, 11, 19], "numpi": [8, 9, 11, 19], "o": 4, "obb": 16, "obj_detect": 15, "object": [7, 8, 11, 16, 19], "objectness_scor": [8, 19], "oblig": 2, "obtain": 19, "occupi": 18, "ocr": [1, 5, 7, 9, 11, 15], "ocr_carea": 19, "ocr_db_crnn": 11, "ocr_lin": 19, "ocr_pag": 19, "ocr_par": 19, "ocr_predictor": [9, 13, 15, 18, 19], "ocrdataset": [7, 17], "ocrmetr": 11, "ocrpredictor": [9, 13], "ocrx_word": 19, "offens": 2, "offici": [2, 9], "offlin": 2, "offset": 10, "onc": 19, "one": [3, 7, 9, 10, 13, 15, 19], "oneof": 10, "ones": [7, 11], "onli": [3, 9, 10, 11, 13, 15, 17, 18, 19], "onlin": 2, "onnx": 16, "onnxruntim": [16, 18], "onnxtr": 18, "opac": 10, "opacity_rang": 10, "open": [1, 2, 3, 15, 18], "opinion": 2, "optic": [5, 19], "optim": [5, 19], "option": [7, 9, 13], "order": [3, 7, 8, 10], "org": [2, 7, 9, 19], "organ": 8, "orient": [2, 8, 9, 12, 16, 19], "orientationpredictor": 9, "other": [2, 3], "otherwis": [2, 8, 11], "our": [1, 3, 9, 19], "out": [3, 9, 10, 11, 19], "outpout": 19, "output": [8, 10, 18], "output_s": [8, 10], "outsid": 14, "over": [7, 11, 19], "overal": [2, 9], "overlai": 8, "overview": 16, "overwrit": 13, "overwritten": 15, "own": 5, "p": [10, 19], "packag": [3, 5, 11, 14, 16, 17, 18], "pad": [7, 9, 10, 19], "page": [4, 7, 9, 11, 13, 19], "page1": 8, "page2": 8, "page_1": 19, "page_idx": [8, 19], "page_orientation_predictor": [9, 13], "page_param": 13, "pair": 11, "paper": 9, "par_1_1": 19, "paragraph": 19, "paragraph_break": 19, "parallel": 9, "param": [10, 19], "paramet": [5, 8, 9, 18], "pars": [5, 7], "parseq": [5, 9, 15, 18, 19], "part": [7, 10, 19], "parti": 4, "partial": 19, "particip": 2, "pass": [7, 8, 9, 13, 19], "password": 8, "patch": [9, 11], "path": [7, 8, 16, 17, 18], "path_to_checkpoint": 13, "path_to_custom_model": 18, "path_to_pt": 13, "patil": 1, "pattern": 2, "pdf": [8, 9, 12], "pdfpage": 8, "peopl": 2, "per": [10, 19], "perform": [5, 8, 9, 10, 11, 14, 18, 19], "period": 2, "permiss": 2, "permut": [5, 9], "persian_lett": 7, "person": [2, 17], "phase": 19, "photo": 17, "physic": [2, 8], "pick": 10, "pictur": 8, "pip": [3, 4, 16, 18], "pipelin": 19, "pixel": [8, 10, 19], "pleas": 3, "plot": 11, "plt": 11, "plug": 15, "plugin": 4, "png": 8, "point": 18, "polici": 14, "polish": 7, "polit": 2, "polygon": [7, 11, 19], "pool": 9, "portugues": 7, "posit": [2, 11], "possibl": [3, 11, 15, 19], "post": [2, 19], "postprocessor": 19, "potenti": 9, "power": 5, "ppageno": 19, "pre": [3, 9, 18], "precis": [11, 19], "pred": 11, "pred_box": 11, "pred_label": 11, "predefin": 17, "predict": [8, 9, 11, 19], "predictor": [5, 8, 9, 12, 13, 15, 18], "prefer": 17, "preinstal": 4, "preprocessor": [13, 19], "prerequisit": 15, "present": 12, "preserv": [9, 10, 19], "preserve_aspect_ratio": [8, 9, 10, 13, 19], "pretrain": [5, 9, 11, 13, 18, 19], "pretrained_backbon": [9, 13], "print": 19, "prior": 7, "privaci": 2, "privat": 2, "probabl": [1, 10], "problem": 3, "procedur": 10, "process": [3, 5, 8, 9, 13, 19], "processor": 19, "produc": [12, 19], "product": 18, "profession": 2, "project": [3, 17], "promptli": 2, "proper": 3, "properli": 7, "provid": [2, 3, 5, 15, 16, 17, 19], "public": [2, 5], "publicli": 19, "publish": 2, "pull": 15, "punctuat": 7, "pure": 7, "purpos": 3, "push_to_hf_hub": [9, 15], "py": 15, "pypdfium2": [4, 8], "pyplot": [8, 11], "python": [1, 3, 16], "python3": 15, "pytorch": [4, 5, 9, 10, 13, 15, 18, 19], "q": 3, "qr": [8, 16], "qr_code": 17, "qualiti": 10, "question": 2, "quickli": 5, "quicktour": 12, "r": 19, "race": 2, "ramdisk": 7, "rand": [9, 10, 11, 18, 19], "random": [9, 10, 11, 19], "randomappli": 10, "randombright": 10, "randomcontrast": 10, "randomcrop": 10, "randomgamma": 10, "randomhorizontalflip": 10, "randomhu": 10, "randomjpegqu": 10, "randomli": 10, "randomres": 10, "randomrot": 10, "randomsatur": 10, "randomshadow": 10, "rang": 10, "rassi": 15, "ratio": [9, 10, 19], "raw": [8, 11], "re": 18, "read": [5, 7, 9], "read_html": 8, "read_img_as_numpi": 8, "read_img_as_tensor": 8, "read_pdf": 8, "readi": 18, "real": [1, 5, 9, 10], "realli": 1, "reason": [2, 5, 7], "rebuild": 3, "rebuilt": 3, "recal": [11, 19], "receipt": [5, 7, 19], "reco_arch": [9, 13, 15, 18], "reco_b": 19, "reco_model": [13, 15, 18], "reco_param": 13, "reco_predictor": 13, "recogn": 19, "recognit": [7, 11, 12, 13], "recognition_predictor": [9, 19], "recognition_task": [7, 17], "recognitiondataset": [7, 17], "recognitionpredictor": [9, 13], "rectangular": 9, "reduc": [4, 10], "refer": [3, 4, 13, 15, 16, 17, 19], "regardless": 2, "region": 19, "regroup": 11, "regular": 17, "reject": 2, "rel": [8, 10, 11, 19], "relat": 8, "releas": [0, 4], "relev": 16, "religion": 2, "remov": 2, "render": [8, 19], "repo": 9, "repo_id": [9, 15], "report": 2, "repositori": [7, 9, 15], "repres": [2, 18, 19], "represent": [5, 9], "request": [2, 15], "requir": [4, 10, 18], "research": 5, "residu": 9, "resiz": [10, 19], "resnet": 9, "resnet18": [9, 15], "resnet31": 9, "resnet34": 9, "resnet50": [9, 15], "resolv": 8, "resolve_block": 19, "resolve_lin": 19, "resourc": 17, "respect": 2, "rest": [3, 10, 11], "restrict": 14, "result": [3, 7, 8, 12, 15, 18, 19], "return": 19, "reusabl": 19, "review": 2, "rgb": [8, 10], "rgb_mode": 8, "rgb_output": 8, "right": [2, 9, 11], "roboflow": 1, "robust": [5, 7], "root": 7, "rotat": [7, 8, 9, 10, 11, 12, 13, 17, 19], "run": [3, 4, 9], "same": [3, 8, 11, 17, 18, 19], "sampl": [7, 9, 17, 19], "sample_transform": 7, "sanjin": 1, "sar": [5, 9], "sar_resnet31": [9, 19], "satur": 10, "save": [9, 17], "scale": [8, 9, 10, 11], "scale_rang": 10, "scan": [5, 7], "scene": [5, 7, 9], "score": [8, 11], "script": [3, 17], "seamless": 5, "seamlessli": [5, 19], "search": [1, 9], "searchabl": 12, "sec": 19, "second": 19, "section": [1, 13, 15, 16, 18, 19], "secur": [2, 14], "see": [2, 3], "seen": 19, "segment": [5, 9, 19], "self": 19, "semant": [5, 9], "send": 19, "sens": 11, "sensit": 17, "separ": 19, "sequenc": [5, 7, 8, 9, 11, 19], "sequenti": [10, 19], "seri": 2, "seriou": 2, "set": [2, 4, 7, 9, 11, 14, 16, 19], "set_global_polici": 18, "sever": [8, 10, 19], "sex": 2, "sexual": 2, "shade": 10, "shape": [5, 8, 9, 10, 11, 19], "share": [14, 17], "shift": 10, "shm": 14, "should": [3, 7, 8, 10, 11], "show": [5, 8, 9, 11, 13, 15, 16], "showcas": [3, 12], "shuffl": [7, 10], "side": 11, "signatur": 8, "signific": 17, "simpl": [5, 9, 18], "simpler": 9, "sinc": [7, 17], "singl": [2, 3, 5, 7], "single_img_doc": 18, "size": [2, 7, 8, 10, 16, 19], "skew": 19, "slack": 3, "slightli": 9, "small": [3, 9, 19], "smallest": 8, "snapshot_download": 9, "snippet": 19, "so": [3, 4, 7, 9, 15, 17], "social": 2, "socio": 2, "some": [1, 4, 12, 15, 17], "someth": 3, "somewher": 3, "sort": 2, "sourc": [1, 7, 8, 9, 10, 11, 15], "space": [2, 19], "span": 19, "spanish": 7, "spatial": [5, 7, 8], "specif": [3, 4, 11, 13, 17, 19], "specifi": [2, 7, 8], "speed": [5, 9, 19], "sphinx": 3, "sroie": [5, 7, 17], "stabl": 4, "stackoverflow": 3, "stage": 5, "standalon": 12, "standard": 10, "start": 7, "state": [1, 5, 11, 16], "static": 11, "statist": 1, "statu": 2, "std": [10, 13], "step": 14, "still": 19, "str": [7, 8, 9, 10, 11], "straight": [7, 9, 17, 19], "straighten": 19, "straighten_pag": [9, 13, 19], "straigten_pag": 13, "stream": 8, "street": [5, 7], "strict": 4, "strictli": 11, "string": [7, 8, 11, 19], "strive": 4, "strong": [5, 9], "structur": [18, 19], "subset": [7, 19], "suggest": [3, 15], "sum": 11, "summari": 11, "support": [4, 13, 16, 18, 19], "sustain": 2, "svhn": [5, 7, 17], "svt": [7, 17], "swedish": 7, "symmetr": [9, 10, 19], "symmetric_pad": [9, 10, 19], "synthet": 5, "synthtext": [5, 7, 17], "system": 19, "t": [3, 7, 13, 18, 19], "tabl": [15, 16, 17], "take": [2, 7, 19], "target": [7, 8, 10, 11, 17], "target_s": 7, "task": [5, 7, 9, 15, 17, 19], "task2": 7, "team": 4, "techminde": 4, "templat": [3, 5], "tensor": [7, 8, 10, 19], "tensorflow": [4, 5, 8, 9, 10, 13, 15, 18, 19], "tensorspec": 18, "term": 2, "test": [7, 17], "test_set": 7, "text": [1, 7, 8, 9, 11, 17], "text_output": 19, "textmatch": 11, "textnet": 9, "textnet_bas": 9, "textnet_smal": 9, "textnet_tini": 9, "textract": [5, 19], "textstylebrush": [5, 7], "textual": [5, 7, 8, 9, 19], "tf": [4, 8, 9, 10, 15, 18], "than": [3, 11, 15], "thank": 3, "thei": [2, 11], "them": [7, 19], "thi": [1, 2, 3, 4, 6, 7, 10, 11, 13, 14, 15, 17, 18, 19], "thing": [18, 19], "third": 4, "those": [2, 8, 19], "threaten": 2, "threshold": 19, "through": [2, 10, 16, 17], "tilman": 15, "time": [1, 2, 5, 9, 11, 17], "tini": 9, "titl": [8, 19], "tm": 19, "tmp": 14, "togeth": [3, 8], "tograi": 10, "tool": [1, 17], "top": [11, 18, 19], "topic": 3, "torch": [4, 10, 13, 15, 18], "torchvis": 10, "total": 13, "toward": [2, 4], "train": [3, 7, 9, 10, 15, 16, 17, 18, 19], "train_it": [7, 17], "train_load": [7, 17], "train_pytorch": 15, "train_set": [7, 17], "train_tensorflow": 15, "trainabl": [5, 9], "tranform": 10, "transcrib": 19, "transfer": [5, 7], "transfo": 10, "transform": [5, 7, 9], "translat": 2, "troll": 2, "true": [7, 8, 9, 10, 11, 13, 14, 15, 17, 18, 19], "truth": 11, "tune": [1, 18], "tupl": [7, 8, 10, 11], "two": [8, 14], "txt": 7, "type": [8, 11, 15, 18, 19], "typic": 19, "u": [2, 3], "ucsd": 7, "udac": 3, "uint8": [8, 9, 11, 19], "ukrainian": 7, "unaccept": 2, "underli": [17, 19], "underneath": 8, "understand": [5, 7, 19], "uniform": [9, 10], "uniformli": 10, "uninterrupt": [8, 19], "union": 11, "unit": 1, "unittest": 3, "unlock": 8, "unoffici": 9, "unprofession": 2, "unsolicit": 2, "unsupervis": 5, "unwelcom": 2, "up": [9, 19], "updat": 11, "upgrad": 3, "upper": [7, 10], "uppercas": 17, "url": 8, "us": [2, 3, 4, 7, 9, 11, 12, 13, 14, 15, 16, 19], "usabl": 19, "usag": [14, 18], "use_polygon": [7, 11, 17], "useabl": 19, "user": [5, 8, 12], "utf": 19, "util": 18, "v1": 15, "v3": [9, 15, 19], "valid": 17, "valu": [3, 8, 10, 19], "valuabl": 5, "variabl": 14, "varieti": 7, "veri": 9, "verma": 1, "version": [2, 3, 4, 18, 19], "vgg": 9, "vgg16": 15, "vgg16_bn_r": 9, "via": 2, "video": 1, "vietnames": 7, "view": [5, 7], "viewpoint": 2, "violat": 2, "visibl": 2, "vision": [5, 7, 9], "visiondataset": 7, "visiontransform": 9, "visual": [4, 5, 16], "visualize_pag": 11, "vit_": 9, "vit_b": 9, "vitstr": [5, 9, 18], "vitstr_bas": [9, 19], "vitstr_smal": [9, 13, 18, 19], "viz": 4, "vocab": [13, 15, 17, 18, 19], "vocabulari": [7, 13, 15], "w": [8, 9, 10, 11], "w3": 19, "wa": 2, "wai": [2, 5, 17], "want": [3, 18, 19], "warmup": 19, "wasn": 3, "we": [1, 2, 3, 4, 5, 8, 10, 13, 15, 17, 18, 19], "weasyprint": 8, "web": [3, 8], "websit": 7, "welcom": 2, "well": [1, 2, 18], "were": [2, 8, 19], "what": [1, 2], "when": [2, 3, 9], "whenev": 3, "where": [3, 8, 10, 11], "whether": [3, 7, 8, 10, 11, 17, 19], "which": [2, 9, 14, 16, 17, 19], "whichev": 4, "while": [10, 19], "why": 2, "width": [8, 10], "wiki": 2, "wildreceipt": [5, 7, 17], "window": [9, 11], "wish": 3, "within": 2, "without": [2, 7, 9], "wonder": 3, "word": [5, 7, 9, 11, 19], "word_1_1": 19, "word_1_2": 19, "word_1_3": 19, "wordgener": [7, 17], "words_onli": 11, "work": [1, 13, 14, 19], "workflow": 3, "worklow": 3, "world": [11, 19], "worth": 9, "wrap": 19, "wrapper": [7, 10], "write": 14, "written": [2, 8], "www": [2, 8, 19], "x": [8, 10, 11], "x_ascend": 19, "x_descend": 19, "x_i": 11, "x_size": 19, "x_wconf": 19, "xhtml": 19, "xmax": 8, "xmin": 8, "xml": 19, "xml_bytes_str": 19, "xml_element": 19, "xml_output": 19, "xmln": 19, "y": 11, "y_i": 11, "y_j": 11, "yet": 16, "ymax": 8, "ymin": 8, "yolov8": 16, "you": [3, 4, 7, 8, 9, 13, 14, 15, 16, 17, 18, 19], "your": [3, 5, 8, 11, 19], "yoursit": 8, "yugesh": 1, "zero": [10, 11], "zoo": 13, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 7, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 7, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": 7, "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 7, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 7, "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 7, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 7, "\u00e4\u00f6\u00e4\u00f6": 7, "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 7, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": 7, "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": 7, "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": 7, "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": 7, "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": 7, "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0905": 7, "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": 7, "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": 7, "\u0950": 7, "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": 7, "\u09bd": 7, "\u09ce": 7, "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": 7}, "titles": ["Changelog", "Community resources", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.contrib", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Integrate contributions into your pipeline", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 3, "0": 0, "01": 0, "02": 0, "03": 0, "04": 0, "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 2], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 2], "2021": 0, "2022": 0, "2023": 0, "2024": 0, "21": 0, "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 2], "31": 0, "4": [0, 2], "5": 0, "6": 0, "7": 0, "8": 0, "9": 0, "advanc": 19, "approach": 19, "architectur": 19, "arg": [7, 8, 9, 10, 11], "artefact": 8, "artefactdetect": 16, "attribut": 2, "avail": [16, 17, 19], "aw": 14, "ban": 2, "block": 8, "bug": 3, "changelog": 0, "choos": [17, 19], "classif": [9, 13, 15], "code": [2, 3], "codebas": 3, "commit": 3, "commun": [1, 15], "compos": 10, "conda": 4, "conduct": 2, "connect": 3, "continu": 3, "contrib": 6, "contribut": [3, 6, 16], "contributor": 2, "convent": 15, "correct": 2, "coven": 2, "custom": [7, 13], "data": 17, "dataload": 7, "dataset": [5, 7, 17], "detect": [5, 9, 15, 17, 19], "develop": 3, "do": 19, "doctr": [3, 5, 6, 7, 8, 9, 10, 11, 12], "document": [3, 5, 8], "end": 19, "enforc": 2, "evalu": 11, "export": 18, "factori": 9, "featur": [3, 5], "feedback": 3, "file": 8, "from": 15, "gener": [7, 17], "git": 4, "guidelin": 2, "half": 18, "hub": 15, "huggingfac": 15, "i": 19, "infer": 18, "instal": [3, 4], "integr": [3, 16], "io": 8, "lambda": 14, "let": 3, "line": 8, "linux": 4, "load": [13, 15, 17], "loader": 7, "main": 5, "mode": 3, "model": [5, 9, 13, 15, 18, 19], "modifi": 3, "modul": [6, 16], "name": 15, "notebook": 12, "object": 17, "ocr": [17, 19], "onli": 4, "onnx": 18, "optim": 18, "option": 19, "orient": 13, "our": 2, "output": 19, "own": [13, 17], "packag": 4, "page": 8, "perman": 2, "pipelin": 16, "pledg": 2, "precis": 18, "predictor": 19, "prepar": 18, "prerequisit": 4, "pretrain": 15, "push": 15, "python": 4, "qualiti": 3, "question": 3, "read": 8, "readi": 17, "recognit": [5, 9, 15, 17, 19], "report": 3, "request": 3, "resourc": 1, "respons": 2, "return": [7, 8, 9, 11], "right": 19, "scope": 2, "share": 15, "should": 19, "stage": 19, "standard": 2, "structur": [3, 8], "style": 3, "support": [5, 6, 7, 10], "synthet": [7, 17], "task": 11, "temporari": 2, "test": 3, "text": [5, 19], "train": 13, "transform": 10, "two": 19, "unit": 3, "us": [17, 18], "util": 11, "v0": 0, "verif": 3, "via": 4, "visual": 11, "vocab": 7, "warn": 2, "what": 19, "word": 8, "your": [13, 15, 16, 17, 18], "zoo": [5, 9]}}) \ No newline at end of file diff --git a/v0.2.0/transforms.html b/v0.2.0/transforms.html deleted file mode 100644 index a79dd132cb..0000000000 --- a/v0.2.0/transforms.html +++ /dev/null @@ -1,678 +0,0 @@ - - - - - - - - - - - - - doctr.transforms - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    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:

    -
    -
    -class doctr.transforms.Resize(output_size: Tuple[int, int], method: str = 'bilinear', preserve_aspect_ratio: bool = False)[source]
    -

    Resizes a tensor to a target size

    -
    -
    Example::
    >>> from doctr.transforms import Resize
    ->>> import tensorflow as tf
    ->>> transfo = Resize((32, 32))
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • output_size – expected output size

    • -
    • method – interpolation method

    • -
    • preserve_aspect_ratio – if True, preserve aspect ratio and pad the rest with zeros

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.Normalize(mean: Tuple[float, float, float], std: Tuple[float, float, float])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • mean – average value per channel

    • -
    • std – standard deviation per channel

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.LambdaTransformation(fn: Callable[[Tensor], Tensor])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import LambdaTransformation
    ->>> import tensorflow as tf
    ->>> transfo = LambdaTransformation(lambda x: x/ 255.)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    fn – the function to be applied to the input tensor

    -
    -
    -
    - -
    -
    -class doctr.transforms.ToGray[source]
    -

    Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ToGray()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    - -
    -
    -class doctr.transforms.ColorInversion(min_val: float = 0.6)[source]
    -

    Applies the following tranformation to a tensor (image or batch of images): -convert to grayscale, colorize (shift 0-values randomly), and then invert colors

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ColorInversion(min_val=0.6)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    min_val – range [min_val, 1] to colorize RGB pixels

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomBrightness(max_delta: float = 0.3)[source]
    -

    Randomly adjust brightness of a tensor (batch of images or image) by adding a delta -to all pixels

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Brightness()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    • -
    • p – probability to apply transformation

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomContrast(delta: float = 0.3)[source]
    -

    Randomly adjust contrast of a tensor (batch of images or image) by adjusting -each pixel: (img - mean) * contrast_factor + mean.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Contrast()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomSaturation(delta: float = 0.5)[source]
    -

    Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and -increasing saturation by a factor.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Saturation()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomHue(max_delta: float = 0.3)[source]
    -

    Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Hue()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomGamma(min_gamma: float = 0.5, max_gamma: float = 1.5, min_gain: float = 0.8, max_gain: float = 1.2)[source]
    -

    randomly performs gamma correction for a tensor (batch of images or image)

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Gamma()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_gamma – non-negative real number, lower bound for gamma param

    • -
    • max_gamma – non-negative real number, upper bound for gamma

    • -
    • min_gain – lower bound for constant multiplier

    • -
    • max_gain – upper bound for constant multiplier

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomJpegQuality(min_quality: int = 60, max_quality: int = 100)[source]
    -

    Randomly adjust jpeg quality of a 3 dimensional RGB image

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = JpegQuality()
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_quality – int between [0, 100]

    • -
    • max_quality – int between [0, 100]

    • -
    -
    -
    -
    - -
    -
    -

    Composing transformations

    -

    It is common to require several transformations to be performed consecutively.

    -
    -
    -class doctr.transforms.Compose(transforms: List[NestedObject])[source]
    -

    Implements a wrapper that will apply transformations sequentially

    -
    -
    Example::
    >>> from doctr.transforms import Compose, Resize
    ->>> import tensorflow as tf
    ->>> transfos = Compose([Resize((32, 32))])
    ->>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformation modules

    -
    -
    -
    - -
    -
    -class doctr.transforms.OneOf(transforms: List[NestedObject])[source]
    -

    Randomly apply one of the input transformations

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = OneOf([JpegQuality(), Gamma()])
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformations, one only will be picked

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomApply(transform: NestedObject, p: float = 0.5)[source]
    -

    Apply with a probability p the input transformation

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = RandomApply(Gamma(), p=.5)
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • transform – transformation to apply

    • -
    • p – probability to apply

    • -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.0/using_doctr/custom_models_training.html b/v0.2.0/using_doctr/custom_models_training.html index df39d8d568..b714c1f971 100644 --- a/v0.2.0/using_doctr/custom_models_training.html +++ b/v0.2.0/using_doctr/custom_models_training.html @@ -14,7 +14,7 @@ - + Train your own model - docTR documentation @@ -619,7 +619,7 @@

    Loading your custom trained orientation classification model - + diff --git a/v0.2.0/using_doctr/running_on_aws.html b/v0.2.0/using_doctr/running_on_aws.html index 16ceaca7a1..808ea541cd 100644 --- a/v0.2.0/using_doctr/running_on_aws.html +++ b/v0.2.0/using_doctr/running_on_aws.html @@ -14,7 +14,7 @@ - + AWS Lambda - docTR documentation @@ -362,7 +362,7 @@

    AWS Lambda - + diff --git a/v0.2.0/using_doctr/sharing_models.html b/v0.2.0/using_doctr/sharing_models.html index d76b4017f4..c9e978400a 100644 --- a/v0.2.0/using_doctr/sharing_models.html +++ b/v0.2.0/using_doctr/sharing_models.html @@ -14,7 +14,7 @@ - + Share your model with the community - docTR documentation @@ -544,7 +544,7 @@

    Recognition - + diff --git a/v0.2.0/using_doctr/using_contrib_modules.html b/v0.2.0/using_doctr/using_contrib_modules.html index 50598dae5d..0c5fffdf9f 100644 --- a/v0.2.0/using_doctr/using_contrib_modules.html +++ b/v0.2.0/using_doctr/using_contrib_modules.html @@ -14,7 +14,7 @@ - + Integrate contributions into your pipeline - docTR documentation @@ -415,7 +415,7 @@

    ArtefactDetection - + diff --git a/v0.2.0/using_doctr/using_datasets.html b/v0.2.0/using_doctr/using_datasets.html index 460476dbbf..8a7d4f0a64 100644 --- a/v0.2.0/using_doctr/using_datasets.html +++ b/v0.2.0/using_doctr/using_datasets.html @@ -14,7 +14,7 @@ - + Choose a ready to use dataset - docTR documentation @@ -642,7 +642,7 @@

    Data Loading - + diff --git a/v0.2.0/using_doctr/using_model_export.html b/v0.2.0/using_doctr/using_model_export.html index 6124c00ebe..6790dd0642 100644 --- a/v0.2.0/using_doctr/using_model_export.html +++ b/v0.2.0/using_doctr/using_model_export.html @@ -14,7 +14,7 @@ - + Preparing your model for inference - docTR documentation @@ -467,7 +467,7 @@

    Using your ONNX exported model - + diff --git a/v0.2.0/using_doctr/using_models.html b/v0.2.0/using_doctr/using_models.html index 61f1f5ab7a..9ead8498e1 100644 --- a/v0.2.0/using_doctr/using_models.html +++ b/v0.2.0/using_doctr/using_models.html @@ -14,7 +14,7 @@ - + Choosing the right model - docTR documentation @@ -1253,7 +1253,7 @@

    Advanced options - + diff --git a/v0.2.0/utils.html b/v0.2.0/utils.html deleted file mode 100644 index baa1f29d3b..0000000000 --- a/v0.2.0/utils.html +++ /dev/null @@ -1,534 +0,0 @@ - - - - - - - - - - - - - doctr.utils - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.utils

    -

    This module regroups non-core features that are complementary to the rest of the package.

    -
    -

    Visualization

    -

    Easy-to-use functions to make sense of your model’s predictions.

    -
    -
    -doctr.utils.visualization.visualize_page(page: Dict[str, Any], image: ndarray, words_only: bool = True, scale: float = 10, interactive: bool = True, add_labels: bool = True, **kwargs: Any) Figure[source]
    -

    Visualize a full page with predicted blocks, lines and words

    -
    -
    Example::
    >>> import numpy as np
    ->>> import matplotlib.pyplot as plt
    ->>> from doctr.utils.visualization import visualize_page
    ->>> from doctr.models import ocr_db_crnn
    ->>> model = ocr_db_crnn(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([[input_page]])
    ->>> visualize_page(out[0].pages[0].export(), input_page)
    ->>> plt.show()
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • page – the exported Page of a Document

    • -
    • image – np array of the page, needs to have the same shape than page[‘dimensions’]

    • -
    • words_only – whether only words should be displayed

    • -
    • scale – figsize of the largest windows side

    • -
    -
    -
    -
    - -
    -
    -

    Task evaluation

    -

    Implementations of task-specific metrics to easily assess your model performances.

    -
    -
    -class doctr.utils.metrics.ExactMatch(ignore_case: bool = False, ignore_accents: bool = False)[source]
    -

    Implements exact match metric (word-level accuracy) for recognition task.

    -

    The aggregated metric is computed as follows:

    -
    -
    -\[\forall X, Y \in \mathcal{W}^N, -ExactMatch(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N f_{Y_i}(X_i)\]
    -
    -

    with the indicator function \(f_{a}\) defined as:

    -
    -
    -\[\begin{split}\forall a, x \in \mathcal{W}, -f_a(x) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } x = a \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{W}\) is the set of all possible character sequences, -\(N\) is a strictly positive integer.

    -
    -
    Example::
    >>> from doctr.utils import ExactMatch
    ->>> metric = ExactMatch()
    ->>> metric.update(['Hello', 'world'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • ignore_case – if true, ignore letter case when computing metric

    • -
    • ignore_accents – if true, ignore accents errors when computing metrics

    • -
    -
    -
    -
    - -
    -
    -class doctr.utils.metrics.LocalizationConfusion(iou_thresh: float = 0.5)[source]
    -

    Implements common confusion metrics and mean IoU for localization evaluation.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall Y \in \mathcal{B}^N, \forall X \in \mathcal{B}^M, \\ -Recall(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -Precision(X, Y) = \frac{1}{M} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -meanIoU(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(X_i, Y_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(g_{X}\) defined as:

    -
    -
    -\[\begin{split}\forall y \in \mathcal{B}, -g_X(y) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } y\mbox{ has been assigned to any }(X_i)_i\mbox{ with an }IoU \geq 0.5 \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import LocalizationConfusion
    ->>> metric = LocalizationConfusion(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]))
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    - -
    -
    -class doctr.utils.metrics.OCRMetric(iou_thresh: float = 0.5, max_dist: int = 0)[source]
    -

    Implements end-to-end OCR metric.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall (B, L) \in \mathcal{B}^N \times \mathcal{L}^N, -\forall (\hat{B}, \hat{L}) \in \mathcal{B}^M \times \mathcal{L}^M, \\ -Recall(B, \hat{B}, L, \hat{L}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -Precision(B, \hat{B}, L, \hat{L}) = \frac{1}{M} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(h_{B, L}\) defined as:

    -
    -
    -\[\begin{split}\forall (b, l) \in \mathcal{B} \times \mathcal{L}, -h_{B,L}(b, l) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ - & IoU \geq 0.5 \mbox{ and that for this assignment, } l = L_j\\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(\mathcal{L}\) is the set of possible character sequences, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import OCRMetric
    ->>> metric = OCRMetric(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]),
    -['hello'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    • -
    • max_dist – maximum Levenshtein distance between 2 sequence to consider a match

    • -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.2.1/_modules/doctr/datasets/cord.html b/v0.2.1/_modules/doctr/datasets/cord.html index a750524015..55b0584830 100644 --- a/v0.2.1/_modules/doctr/datasets/cord.html +++ b/v0.2.1/_modules/doctr/datasets/cord.html @@ -13,7 +13,7 @@ - + doctr.datasets.cord - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.cord

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    -import tensorflow as tf
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
    -from .core import VisionDataset
    +from .datasets import VisionDataset
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['CORD']
    +__all__ = ["CORD"]
     
     
     
    -[docs] +[docs] class CORD(VisionDataset): """CORD dataset from `"CORD: A Consolidated Receipt Dataset forPost-OCR Parsing" <https://openreview.net/pdf?id=SJl3z659UH>`_. - Example:: - >>> from doctr.datasets import CORD - >>> train_set = CORD(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/cord-grid.png&src=0 + :align: center + + >>> from doctr.datasets import CORD + >>> train_set = CORD(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - TRAIN = ('https://github.com/mindee/doctr/releases/download/v0.1.1/cord_train.zip', - '45f9dc77f126490f3e52d7cb4f70ef3c57e649ea86d19d862a2757c9c455d7f8') - TEST = ('https://github.com/mindee/doctr/releases/download/v0.1.1/cord_test.zip', - '8c895e3d6f7e1161c5b7245e3723ce15c04d84be89eaa6093949b75a66fb3c58') + TRAIN = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/cord_train.zip&src=0", + "45f9dc77f126490f3e52d7cb4f70ef3c57e649ea86d19d862a2757c9c455d7f8", + "cord_train.zip", + ) + + TEST = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/cord_test.zip&src=0", + "8c895e3d6f7e1161c5b7245e3723ce15c04d84be89eaa6093949b75a66fb3c58", + "cord_test.zip", + ) def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[tf.Tensor], tf.Tensor]] = None, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + url, sha256, name = self.TRAIN if train else self.TEST + super().__init__( + url, + name, + sha256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - url, sha256 = self.TRAIN if train else self.TEST - super().__init__(url, None, sha256, True, **kwargs) - - # # List images - self.root = os.path.join(self._root, 'image') - self.data: List[Tuple[str, Dict[str, Any]]] = [] + # List images + tmp_root = os.path.join(self.root, "image") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] self.train = train - self.sample_transforms = sample_transforms - for img_path in os.listdir(self.root): - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + np_dtype = np.float32 + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking CORD", total=len(os.listdir(tmp_root))): + # File existence check + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem _targets = [] - with open(os.path.join(self._root, 'json', f"{stem}.json"), 'rb') as f: + with open(os.path.join(self.root, "json", f"{stem}.json"), "rb") as f: label = json.load(f) for line in label["valid_line"]: for word in line["words"]: - x = word["quad"]["x1"], word["quad"]["x2"], word["quad"]["x3"], word["quad"]["x4"] - y = word["quad"]["y1"], word["quad"]["y2"], word["quad"]["y3"], word["quad"]["y4"] - # Reduce 8 coords to 4 - left, right = min(x), max(x) - top, bot = min(y), max(y) if len(word["text"]) > 0: - _targets.append((word["text"], [left, top, right, bot])) + x = word["quad"]["x1"], word["quad"]["x2"], word["quad"]["x3"], word["quad"]["x4"] + y = word["quad"]["y1"], word["quad"]["y2"], word["quad"]["y3"], word["quad"]["y4"] + box: Union[List[float], np.ndarray] + if use_polygons: + # (x, y) coordinates of top left, top right, bottom right, bottom left corners + box = np.array( + [ + [x[0], y[0]], + [x[1], y[1]], + [x[2], y[2]], + [x[3], y[3]], + ], + dtype=np_dtype, + ) + else: + # Reduce 8 coords to 4 -> xmin, ymin, xmax, ymax + box = [min(x), min(y), max(x), max(y)] + _targets.append((word["text"], box)) text_targets, box_targets = zip(*_targets) - self.data.append((img_path, dict(boxes=np.asarray(box_targets, dtype=np.int), labels=text_targets))) + if recognition_task: + crops = crop_bboxes_from_image( + img_path=os.path.join(tmp_root, img_path), geoms=np.asarray(box_targets, dtype=int).clip(min=0) + ) + for crop, label in zip(crops, list(text_targets)): + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, np.asarray(box_targets, dtype=int).clip(min=0))) + else: + self.data.append(( + img_path, + dict(boxes=np.asarray(box_targets, dtype=int).clip(min=0), labels=list(text_targets)), + )) + + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -384,8 +461,8 @@

    Source code for doctr.datasets.cord

           
         
       
    -
    - + + diff --git a/v0.2.1/_modules/doctr/datasets/core.html b/v0.2.1/_modules/doctr/datasets/core.html deleted file mode 100644 index b3dcc29ff9..0000000000 --- a/v0.2.1/_modules/doctr/datasets/core.html +++ /dev/null @@ -1,417 +0,0 @@ - - - - - - - - - - - - doctr.datasets.core - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.core

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from pathlib import Path
    -from zipfile import ZipFile
    -from typing import List, Any, Optional, Tuple
    -import tensorflow as tf
    -
    -from doctr.models.utils import download_from_url
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset:
    -
    -    data: List[Any] = []
    -
    -    def __len__(self):
    -        return len(self.data)
    -
    -    def __getitem__(
    -        self,
    -        index: int
    -    ) -> Tuple[tf.Tensor, Any]:
    -
    -        img_name, target = self.data[index]
    -        # Read image
    -        img = tf.io.read_file(os.path.join(self.root, img_name))
    -        img = tf.image.decode_jpeg(img, channels=3)
    -        if self.sample_transforms is not None:
    -            img = self.sample_transforms(img)
    -
    -        return img, target
    -
    -    def extra_repr(self) -> str:
    -        return ""
    -
    -    def __repr__(self) -> str:
    -        return f"{self.__class__.__name__}({self.extra_repr()})"
    -
    -    @staticmethod
    -    def collate_fn(samples: List[Tuple[tf.Tensor, Any]]) -> Tuple[tf.Tensor, List[Any]]:
    -
    -        images, targets = zip(*samples)
    -        images = tf.stack(images, axis=0)
    -
    -        return images, list(targets)
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset): - """Implements an abstract dataset - - Args: - url: URL of the dataset - file_name: name of the file once downloaded - file_hash: expected SHA256 of the file - extract_archive: whether the downloaded file is an archive to be extracted - download: whether the dataset should be downloaded if not present on disk - overwrite: whether the archive should be re-extracted - """ - - def __init__( - self, - url: str, - file_name: Optional[str] = None, - file_hash: Optional[str] = None, - extract_archive: bool = False, - download: bool = False, - overwrite: bool = False, - ) -> None: - - dataset_cache = os.path.join(os.path.expanduser('~'), '.cache', 'doctr', 'datasets') - - file_name = file_name if isinstance(file_name, str) else os.path.basename(url) - # Download the file if not present - archive_path = os.path.join(dataset_cache, file_name) - - if not os.path.exists(archive_path) and not download: - raise ValueError("the dataset needs to be downloaded first with download=True") - - archive_path = download_from_url(url, file_name, file_hash, cache_subdir='datasets') - - # Extract the archive - if extract_archive: - archive_path = Path(archive_path) - dataset_path = archive_path.parent.joinpath(archive_path.stem) - if not dataset_path.is_dir() or overwrite: - with ZipFile(archive_path, 'r') as f: - f.extractall(path=dataset_path) - - # List images - self._root = dataset_path if extract_archive else archive_path - self.data: List[Any] = []
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.1/_modules/doctr/datasets/detection.html b/v0.2.1/_modules/doctr/datasets/detection.html index 739563e466..718001e4cf 100644 --- a/v0.2.1/_modules/doctr/datasets/detection.html +++ b/v0.2.1/_modules/doctr/datasets/detection.html @@ -13,7 +13,7 @@ - + doctr.datasets.detection - docTR documentation @@ -430,7 +430,7 @@

    Source code for doctr.datasets.detection

         
       
    - + diff --git a/v0.2.1/_modules/doctr/datasets/doc_artefacts.html b/v0.2.1/_modules/doctr/datasets/doc_artefacts.html index 3313ae4660..94c32aaa0f 100644 --- a/v0.2.1/_modules/doctr/datasets/doc_artefacts.html +++ b/v0.2.1/_modules/doctr/datasets/doc_artefacts.html @@ -13,7 +13,7 @@ - + doctr.datasets.doc_artefacts - docTR documentation @@ -414,7 +414,7 @@

    Source code for doctr.datasets.doc_artefacts

       
    - + diff --git a/v0.2.1/_modules/doctr/datasets/funsd.html b/v0.2.1/_modules/doctr/datasets/funsd.html index 670e036f7f..f08612f9fa 100644 --- a/v0.2.1/_modules/doctr/datasets/funsd.html +++ b/v0.2.1/_modules/doctr/datasets/funsd.html @@ -13,7 +13,7 @@ - + doctr.datasets.funsd - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.funsd

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    -import tensorflow as tf
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
    -from .core import VisionDataset
    +from .datasets import VisionDataset
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['FUNSD']
    +__all__ = ["FUNSD"]
     
     
     
    -[docs] +[docs] class FUNSD(VisionDataset): """FUNSD dataset from `"FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents" <https://arxiv.org/pdf/1905.13538.pdf>`_. - Example:: - >>> from doctr.datasets import FUNSD - >>> train_set = FUNSD(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/funsd-grid.png&src=0 + :align: center + + >>> from doctr.datasets import FUNSD + >>> train_set = FUNSD(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - URL = 'https://guillaumejaume.github.io/FUNSD/dataset.zip' - SHA256 = 'c31735649e4f441bcbb4fd0f379574f7520b42286e80b01d80b445649d54761f' - FILE_NAME = 'funsd.zip' + URL = "https://guillaumejaume.github.io/FUNSD/dataset.zip" + SHA256 = "c31735649e4f441bcbb4fd0f379574f7520b42286e80b01d80b445649d54761f" + FILE_NAME = "funsd.zip" def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[tf.Tensor], tf.Tensor]] = None, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + super().__init__( + self.URL, + self.FILE_NAME, + self.SHA256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - super().__init__(self.URL, self.FILE_NAME, self.SHA256, True, **kwargs) self.train = train - self.sample_transforms = sample_transforms + np_dtype = np.float32 # Use the subset - subfolder = os.path.join('dataset', 'training_data' if train else 'testing_data') + subfolder = os.path.join("dataset", "training_data" if train else "testing_data") # # List images - self.root = os.path.join(self._root, subfolder, 'images') - self.data: List[Tuple[str, Dict[str, Any]]] = [] - for img_path in os.listdir(self.root): - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + tmp_root = os.path.join(self.root, subfolder, "images") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking FUNSD", total=len(os.listdir(tmp_root))): + # File existence check + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem - with open(os.path.join(self._root, subfolder, 'annotations', f"{stem}.json"), 'rb') as f: + with open(os.path.join(self.root, subfolder, "annotations", f"{stem}.json"), "rb") as f: data = json.load(f) - _targets = [(word['text'], word['box']) for block in data['form'] - for word in block['words'] if len(word['text']) > 0] - + _targets = [ + (word["text"], word["box"]) + for block in data["form"] + for word in block["words"] + if len(word["text"]) > 0 + ] text_targets, box_targets = zip(*_targets) + if use_polygons: + # xmin, ymin, xmax, ymax -> (x, y) coordinates of top left, top right, bottom right, bottom left corners + box_targets = [ # type: ignore[assignment] + [ + [box[0], box[1]], + [box[2], box[1]], + [box[2], box[3]], + [box[0], box[3]], + ] + for box in box_targets + ] + + if recognition_task: + crops = crop_bboxes_from_image( + img_path=os.path.join(tmp_root, img_path), geoms=np.asarray(box_targets, dtype=np_dtype) + ) + for crop, label in zip(crops, list(text_targets)): + # filter labels with unknown characters + if not any(char in label for char in ["☑", "☐", "\uf703", "\uf702"]): + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, np.asarray(box_targets, dtype=np_dtype))) + else: + self.data.append(( + img_path, + dict(boxes=np.asarray(box_targets, dtype=np_dtype), labels=list(text_targets)), + )) - self.data.append((img_path, dict(boxes=np.asarray(box_targets, dtype=np.int), labels=text_targets))) + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -378,8 +453,8 @@

    Source code for doctr.datasets.funsd

           
         
       
    -
    - + + diff --git a/v0.2.1/_modules/doctr/datasets/generator/tensorflow.html b/v0.2.1/_modules/doctr/datasets/generator/tensorflow.html index 9f562582d9..a3e619f720 100644 --- a/v0.2.1/_modules/doctr/datasets/generator/tensorflow.html +++ b/v0.2.1/_modules/doctr/datasets/generator/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.datasets.generator.tensorflow - docTR documentation @@ -395,7 +395,7 @@

    Source code for doctr.datasets.generator.tensorflow

    - + diff --git a/v0.2.1/_modules/doctr/datasets/ic03.html b/v0.2.1/_modules/doctr/datasets/ic03.html index 3d221d07de..60e54a8a4b 100644 --- a/v0.2.1/_modules/doctr/datasets/ic03.html +++ b/v0.2.1/_modules/doctr/datasets/ic03.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic03 - docTR documentation @@ -468,7 +468,7 @@

    Source code for doctr.datasets.ic03

         
       
    - + diff --git a/v0.2.1/_modules/doctr/datasets/ic13.html b/v0.2.1/_modules/doctr/datasets/ic13.html index 8137e08e9f..219c98dcd1 100644 --- a/v0.2.1/_modules/doctr/datasets/ic13.html +++ b/v0.2.1/_modules/doctr/datasets/ic13.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic13 - docTR documentation @@ -440,7 +440,7 @@

    Source code for doctr.datasets.ic13

         
       
    - + diff --git a/v0.2.1/_modules/doctr/datasets/iiit5k.html b/v0.2.1/_modules/doctr/datasets/iiit5k.html index 1fc8ecfb27..b49c80fe18 100644 --- a/v0.2.1/_modules/doctr/datasets/iiit5k.html +++ b/v0.2.1/_modules/doctr/datasets/iiit5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiit5k - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.datasets.iiit5k

         
       
    - + diff --git a/v0.2.1/_modules/doctr/datasets/iiithws.html b/v0.2.1/_modules/doctr/datasets/iiithws.html index 07f5b13685..f7220afbc7 100644 --- a/v0.2.1/_modules/doctr/datasets/iiithws.html +++ b/v0.2.1/_modules/doctr/datasets/iiithws.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiithws - docTR documentation @@ -407,7 +407,7 @@

    Source code for doctr.datasets.iiithws

         
       
    - + diff --git a/v0.2.1/_modules/doctr/datasets/imgur5k.html b/v0.2.1/_modules/doctr/datasets/imgur5k.html index 68d433ca62..51c6545db4 100644 --- a/v0.2.1/_modules/doctr/datasets/imgur5k.html +++ b/v0.2.1/_modules/doctr/datasets/imgur5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.imgur5k - docTR documentation @@ -488,7 +488,7 @@

    Source code for doctr.datasets.imgur5k

         
       
    - + diff --git a/v0.2.1/_modules/doctr/datasets/loader.html b/v0.2.1/_modules/doctr/datasets/loader.html index 9e0c1c25b4..ed80350ef0 100644 --- a/v0.2.1/_modules/doctr/datasets/loader.html +++ b/v0.2.1/_modules/doctr/datasets/loader.html @@ -13,7 +13,7 @@ - + doctr.datasets.loader - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.loader

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import math
    -import tensorflow as tf
    -import numpy as np
    -from typing import Dict, Any, Optional
    +from typing import Callable, Optional
     
    -from .multithreading import multithread_exec
    +import numpy as np
    +import tensorflow as tf
     
     __all__ = ["DataLoader"]
     
    @@ -293,12 +314,13 @@ 

    Source code for doctr.datasets.loader

         """Collate multiple elements into batches
     
         Args:
    +    ----
             samples: list of N tuples containing M elements
     
         Returns:
    +    -------
             Tuple of M sequences contianing N elements each
         """
    -
         batch_data = zip(*samples)
     
         tf_data = tuple(tf.stack(elt, axis=0) for elt in batch_data)
    @@ -307,23 +329,23 @@ 

    Source code for doctr.datasets.loader

     
     
     
    -[docs] +[docs] class DataLoader: """Implements a dataset wrapper for fast data loading - Example:: - >>> from doctr.datasets import FUNSD, 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) + >>> 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) Args: + ---- dataset: the dataset shuffle: whether the samples should be shuffled before passing it to the iterator batch_size: number of elements in each batch drop_last: if `True`, drops the last batch if it isn't full - workers: number of workers to use for data loading + collate_fn: function to merge samples into a batch """ def __init__( @@ -332,17 +354,22 @@

    Source code for doctr.datasets.loader

             shuffle: bool = True,
             batch_size: int = 1,
             drop_last: bool = False,
    -        workers: Optional[int] = None,
    +        collate_fn: Optional[Callable] = None,
         ) -> None:
             self.dataset = dataset
             self.shuffle = shuffle
             self.batch_size = batch_size
             nb = len(self.dataset) / batch_size
             self.num_batches = math.floor(nb) if drop_last else math.ceil(nb)
    -        self.collate_fn = self.dataset.collate_fn if hasattr(self.dataset, 'collate_fn') else default_collate
    -        self.workers = workers
    +        if collate_fn is None:
    +            self.collate_fn = self.dataset.collate_fn if hasattr(self.dataset, "collate_fn") else default_collate
    +        else:
    +            self.collate_fn = collate_fn
             self.reset()
     
    +    def __len__(self) -> int:
    +        return self.num_batches
    +
         def reset(self) -> None:
             # Updates indices after each epoch
             self._num_yielded = 0
    @@ -358,9 +385,9 @@ 

    Source code for doctr.datasets.loader

             if self._num_yielded < self.num_batches:
                 # Get next indices
                 idx = self._num_yielded * self.batch_size
    -            indices = self.indices[idx: min(len(self.dataset), idx + self.batch_size)]
    +            indices = self.indices[idx : min(len(self.dataset), idx + self.batch_size)]
     
    -            samples = multithread_exec(self.dataset.__getitem__, indices, threads=self.workers)
    +            samples = list(map(self.dataset.__getitem__, indices))
     
                 batch_data = self.collate_fn(samples)
     
    @@ -401,8 +428,8 @@ 

    Source code for doctr.datasets.loader

           
         
       
    -
    - +
    + diff --git a/v0.2.1/_modules/doctr/datasets/mjsynth.html b/v0.2.1/_modules/doctr/datasets/mjsynth.html index 77bb01d523..df34e49cf9 100644 --- a/v0.2.1/_modules/doctr/datasets/mjsynth.html +++ b/v0.2.1/_modules/doctr/datasets/mjsynth.html @@ -13,7 +13,7 @@ - + doctr.datasets.mjsynth - docTR documentation @@ -438,7 +438,7 @@

    Source code for doctr.datasets.mjsynth

         
       
    - + diff --git a/v0.2.1/_modules/doctr/datasets/ocr.html b/v0.2.1/_modules/doctr/datasets/ocr.html index 4ad72c3663..ce1ed8b0d4 100644 --- a/v0.2.1/_modules/doctr/datasets/ocr.html +++ b/v0.2.1/_modules/doctr/datasets/ocr.html @@ -13,7 +13,7 @@ - + doctr.datasets.ocr - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.ocr

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    -import tensorflow as tf
    +from typing import Any, Dict, List, Tuple
     
    -from .core import AbstractDataset
    +import numpy as np
     
    +from .datasets import AbstractDataset
     
    -__all__ = ['OCRDataset']
    +__all__ = ["OCRDataset"]
     
     
     
    -[docs] +[docs] class OCRDataset(AbstractDataset): """Implements an OCR dataset + >>> from doctr.datasets import OCRDataset + >>> train_set = OCRDataset(img_folder="/path/to/images", + >>> label_file="/path/to/labels.json") + >>> img, target = train_set[0] + Args: + ---- img_folder: local path to image folder (all jpg at the root) label_file: local path to the label file - sample_transforms: composable transformations that will be applied to each image - **kwargs: keyword arguments from `VisionDataset`. + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + **kwargs: keyword arguments from `AbstractDataset`. """ def __init__( self, img_folder: str, label_file: str, - sample_transforms: Optional[Callable[[tf.Tensor], tf.Tensor]] = None, + use_polygons: bool = False, **kwargs: Any, ) -> None: - - self.sample_transforms = sample_transforms - self.root = img_folder + super().__init__(img_folder, **kwargs) # List images self.data: List[Tuple[str, Dict[str, Any]]] = [] - with open(label_file, 'rb') as f: + np_dtype = np.float32 + with open(label_file, "rb") as f: data = json.load(f) - for file_dic in data: + for img_name, annotations in data.items(): # Get image path - img_name = Path(os.path.basename(file_dic["raw-archive-filepath"])).stem + '.jpg' + img_name = Path(img_name) + # File existence check if not os.path.exists(os.path.join(self.root, img_name)): raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_name)}") # handle empty images - if (len(file_dic["coordinates"]) == 0 or - (len(file_dic["coordinates"]) == 1 and file_dic["coordinates"][0] == "N/A")): - self.data.append((img_name, dict(boxes=np.zeros((0, 4), dtype=np.float32), labels=[]))) + if len(annotations["typed_words"]) == 0: + self.data.append((img_name, dict(boxes=np.zeros((0, 4), dtype=np_dtype), labels=[]))) continue - is_valid: List[bool] = [] - box_targets: List[List[float]] = [] - for box in file_dic["coordinates"]: - xs, ys = zip(*box) - box = [min(xs), min(ys), max(xs), max(ys)] - if box[0] < box[2] and box[1] < box[3]: - box_targets.append(box) - is_valid.append(True) - else: - is_valid.append(False) + # Unpack the straight boxes (xmin, ymin, xmax, ymax) + geoms = [list(map(float, obj["geometry"][:4])) for obj in annotations["typed_words"]] + if use_polygons: + # (x, y) coordinates of top left, top right, bottom right, bottom left corners + geoms = [ + [geom[:2], [geom[2], geom[1]], geom[2:], [geom[0], geom[3]]] # type: ignore[list-item] + for geom in geoms + ] + + text_targets = [obj["value"] for obj in annotations["typed_words"]] - text_targets = [word for word, _valid in zip(file_dic["string"], is_valid) if _valid] - self.data.append((img_name, dict(boxes=np.asarray(box_targets, dtype=np.float32), labels=text_targets)))
    + self.data.append((img_name, dict(boxes=np.asarray(geoms, dtype=np_dtype), labels=text_targets)))
    @@ -377,8 +402,8 @@

    Source code for doctr.datasets.ocr

           
         
       
    - - + + diff --git a/v0.2.1/_modules/doctr/datasets/recognition.html b/v0.2.1/_modules/doctr/datasets/recognition.html index 512c70c308..1754789364 100644 --- a/v0.2.1/_modules/doctr/datasets/recognition.html +++ b/v0.2.1/_modules/doctr/datasets/recognition.html @@ -13,7 +13,7 @@ - + doctr.datasets.recognition - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.datasets.recognition

         
       
    - + diff --git a/v0.2.1/_modules/doctr/datasets/sroie.html b/v0.2.1/_modules/doctr/datasets/sroie.html index 656697b3b9..04cf10bda2 100644 --- a/v0.2.1/_modules/doctr/datasets/sroie.html +++ b/v0.2.1/_modules/doctr/datasets/sroie.html @@ -13,7 +13,7 @@ - + doctr.datasets.sroie - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.sroie

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import csv
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    -import tensorflow as tf
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
    -from .core import VisionDataset
    +from .datasets import VisionDataset
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['SROIE']
    +__all__ = ["SROIE"]
     
     
     
    -[docs] +[docs] class SROIE(VisionDataset): """SROIE dataset from `"ICDAR2019 Competition on Scanned Receipt OCR and Information Extraction" <https://arxiv.org/pdf/2103.10213.pdf>`_. - Example:: - >>> from doctr.datasets import SROIE - >>> train_set = SROIE(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/sroie-grid.png&src=0 + :align: center + + >>> from doctr.datasets import SROIE + >>> train_set = SROIE(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - TRAIN = ('https://github.com/mindee/doctr/releases/download/v0.1.1/sroie2019_train_task1.zip', - 'd4fa9e60abb03500d83299c845b9c87fd9c9430d1aeac96b83c5d0bb0ab27f6f') - TEST = ('https://github.com/mindee/doctr/releases/download/v0.1.1/sroie2019_test.zip', - '41b3c746a20226fddc80d86d4b2a903d43b5be4f521dd1bbe759dbf8844745e2') + TRAIN = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/sroie2019_train_task1.zip&src=0", + "d4fa9e60abb03500d83299c845b9c87fd9c9430d1aeac96b83c5d0bb0ab27f6f", + "sroie2019_train_task1.zip", + ) + TEST = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/sroie2019_test.zip&src=0", + "41b3c746a20226fddc80d86d4b2a903d43b5be4f521dd1bbe759dbf8844745e2", + "sroie2019_test.zip", + ) def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[tf.Tensor], tf.Tensor]] = None, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + url, sha256, name = self.TRAIN if train else self.TEST + super().__init__( + url, + name, + sha256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - url, sha256 = self.TRAIN if train else self.TEST - super().__init__(url, None, sha256, True, **kwargs) - self.sample_transforms = sample_transforms self.train = train - # # List images - self.root = os.path.join(self._root, 'images') - self.data: List[Tuple[str, Dict[str, Any]]] = [] - for img_path in os.listdir(self.root): - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + tmp_root = os.path.join(self.root, "images") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] + np_dtype = np.float32 + + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking SROIE", total=len(os.listdir(tmp_root))): + # File existence check + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem - _targets = [] - with open(os.path.join(self._root, 'annotations', f"{stem}.txt"), encoding='latin') as f: - for row in csv.reader(f, delimiter=','): - # Safeguard for blank lines - if len(row) > 0: - # Label may contain commas - label = ",".join(row[8:]) - # Reduce 8 coords to 4 - p1_x, p1_y, p2_x, p2_y, p3_x, p3_y, p4_x, p4_y = map(int, row[:8]) - left, right = min(p1_x, p2_x, p3_x, p4_x), max(p1_x, p2_x, p3_x, p4_x) - top, bot = min(p1_y, p2_y, p3_y, p4_y), max(p1_y, p2_y, p3_y, p4_y) - if len(label) > 0: - _targets.append((label, [left, top, right, bot])) - - text_targets, box_targets = zip(*_targets) - - self.data.append((img_path, dict(boxes=np.asarray(box_targets, dtype=np.float32), labels=text_targets))) + with open(os.path.join(self.root, "annotations", f"{stem}.txt"), encoding="latin") as f: + _rows = [row for row in list(csv.reader(f, delimiter=",")) if len(row) > 0] + + labels = [",".join(row[8:]) for row in _rows] + # reorder coordinates (8 -> (4,2) -> + # (x, y) coordinates of top left, top right, bottom right, bottom left corners) and filter empty lines + coords: np.ndarray = np.stack( + [np.array(list(map(int, row[:8])), dtype=np_dtype).reshape((4, 2)) for row in _rows], axis=0 + ) + + if not use_polygons: + # xmin, ymin, xmax, ymax + coords = np.concatenate((coords.min(axis=1), coords.max(axis=1)), axis=1) + + if recognition_task: + crops = crop_bboxes_from_image(img_path=os.path.join(tmp_root, img_path), geoms=coords) + for crop, label in zip(crops, labels): + if crop.shape[0] > 0 and crop.shape[1] > 0 and len(label) > 0: + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, coords)) + else: + self.data.append((img_path, dict(boxes=coords, labels=labels))) + + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -385,8 +444,8 @@

    Source code for doctr.datasets.sroie

           
         
       
    -
    - + + diff --git a/v0.2.1/_modules/doctr/datasets/svhn.html b/v0.2.1/_modules/doctr/datasets/svhn.html index 48e4e4d210..60e02b1b3b 100644 --- a/v0.2.1/_modules/doctr/datasets/svhn.html +++ b/v0.2.1/_modules/doctr/datasets/svhn.html @@ -13,7 +13,7 @@ - + doctr.datasets.svhn - docTR documentation @@ -473,7 +473,7 @@

    Source code for doctr.datasets.svhn

         
       
    - + diff --git a/v0.2.1/_modules/doctr/datasets/svt.html b/v0.2.1/_modules/doctr/datasets/svt.html index 4144dc6b9b..a997fcbb50 100644 --- a/v0.2.1/_modules/doctr/datasets/svt.html +++ b/v0.2.1/_modules/doctr/datasets/svt.html @@ -13,7 +13,7 @@ - + doctr.datasets.svt - docTR documentation @@ -459,7 +459,7 @@

    Source code for doctr.datasets.svt

         
       
    - + diff --git a/v0.2.1/_modules/doctr/datasets/synthtext.html b/v0.2.1/_modules/doctr/datasets/synthtext.html index 3b9de506a7..c776e1d673 100644 --- a/v0.2.1/_modules/doctr/datasets/synthtext.html +++ b/v0.2.1/_modules/doctr/datasets/synthtext.html @@ -13,7 +13,7 @@ - + doctr.datasets.synthtext - docTR documentation @@ -470,7 +470,7 @@

    Source code for doctr.datasets.synthtext

         
       
    - + diff --git a/v0.2.1/_modules/doctr/datasets/utils.html b/v0.2.1/_modules/doctr/datasets/utils.html index aedf276e89..bde9304597 100644 --- a/v0.2.1/_modules/doctr/datasets/utils.html +++ b/v0.2.1/_modules/doctr/datasets/utils.html @@ -13,7 +13,7 @@ - + doctr.datasets.utils - docTR documentation @@ -225,15 +225,42 @@

    Source code for doctr.datasets.utils

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import string
     import unicodedata
    +from collections.abc import Sequence
    +from functools import partial
    +from pathlib import Path
    +from typing import Any, Dict, List, Optional, Tuple, TypeVar, Union
    +from typing import Sequence as SequenceType
    +
     import numpy as np
    -from typing import List, Optional, Any
    +from PIL import Image
    +
    +from doctr.io.image import get_img_shape
    +from doctr.utils.geometry import convert_to_relative_coords, extract_crops, extract_rcrops
     
     from .vocabs import VOCABS
     
    -__all__ = ['translate', 'encode_sequence', 'decode_sequence', 'encode_sequences']
    +__all__ = ["translate", "encode_string", "decode_sequence", "encode_sequences", "pre_transform_multiclass"]
    +
    +ImageTensor = TypeVar("ImageTensor")
     
     
     def translate(
         input_string: str,
         vocab_name: str,
    -    unknown_char: str = '■',
    +    unknown_char: str = "■",
     ) -> str:
         """Translate a string input in a given vocabulary
     
         Args:
    +    ----
             input_string: input string to translate
             vocab_name: vocabulary to use (french, latin, ...)
             unknown_char: unknown character for non-translatable characters
     
         Returns:
    -        A string translated in a given vocab"""
    -
    +    -------
    +        A string translated in a given vocab
    +    """
         if VOCABS.get(vocab_name) is None:
             raise KeyError("output vocabulary must be in vocabs dictionnary")
     
    -    translated = ''
    +    translated = ""
         for char in input_string:
             if char not in VOCABS[vocab_name]:
                 # we need to translate char into a vocab char
    @@ -310,85 +350,177 @@ 

    Source code for doctr.datasets.utils

                     # remove whitespaces
                     continue
                 # normalize character if it is not in vocab
    -            char = unicodedata.normalize('NFD', char).encode('ascii', 'ignore').decode('ascii')
    -            if char == '' or char not in VOCABS[vocab_name]:
    +            char = unicodedata.normalize("NFD", char).encode("ascii", "ignore").decode("ascii")
    +            if char == "" or char not in VOCABS[vocab_name]:
                     # if normalization fails or char still not in vocab, return unknown character)
                     char = unknown_char
             translated += char
         return translated
     
     
    -def encode_sequence(
    +def encode_string(
         input_string: str,
         vocab: str,
    -) -> List[str]:
    +) -> List[int]:
         """Given a predefined mapping, encode the string to a sequence of numbers
     
         Args:
    +    ----
             input_string: string to encode
             vocab: vocabulary (string), the encoding is given by the indexing of the character sequence
     
         Returns:
    -        A list encoding the input_string"""
    -
    -    return list(map(vocab.index, input_string))
    +    -------
    +        A list encoding the input_string
    +    """
    +    try:
    +        return list(map(vocab.index, input_string))
    +    except ValueError:
    +        raise ValueError(
    +            f"some characters cannot be found in 'vocab'. \
    +                         Please check the input string {input_string} and the vocabulary {vocab}"
    +        )
     
     
     def decode_sequence(
    -    input_array: np.array,
    +    input_seq: Union[np.ndarray, SequenceType[int]],
         mapping: str,
     ) -> str:
         """Given a predefined mapping, decode the sequence of numbers to a string
     
         Args:
    -        input_array: array to decode
    +    ----
    +        input_seq: array to decode
             mapping: vocabulary (string), the encoding is given by the indexing of the character sequence
     
         Returns:
    -        A string, decoded from input_array"""
    -
    -    if not input_array.dtype == np.int_ or input_array.max() >= len(mapping):
    +    -------
    +        A string, decoded from input_seq
    +    """
    +    if not isinstance(input_seq, (Sequence, np.ndarray)):
    +        raise TypeError("Invalid sequence type")
    +    if isinstance(input_seq, np.ndarray) and (input_seq.dtype != np.int_ or input_seq.max() >= len(mapping)):
             raise AssertionError("Input must be an array of int, with max less than mapping size")
    -    decoded = ''.join(mapping[idx] for idx in input_array)
    -    return decoded
    +
    +    return "".join(map(mapping.__getitem__, input_seq))
     
     
     
    -[docs] +[docs] def encode_sequences( sequences: List[str], vocab: str, target_size: Optional[int] = None, eos: int = -1, - **kwargs: Any, + sos: Optional[int] = None, + pad: Optional[int] = None, + dynamic_seq_length: bool = False, ) -> np.ndarray: """Encode character sequences using a given vocab as mapping Args: + ---- sequences: the list of character sequences of size N vocab: the ordered vocab to use for encoding target_size: maximum length of the encoded data eos: encoding of End Of String + sos: optional encoding of Start Of String + pad: optional encoding for padding. In case of padding, all sequences are followed by 1 EOS then PAD + dynamic_seq_length: if `target_size` is specified, uses it as upper bound and enables dynamic sequence size Returns: + ------- the padded encoded data as a tensor """ - if 0 <= eos < len(vocab): raise ValueError("argument 'eos' needs to be outside of vocab possible indices") - if not isinstance(target_size, int): - target_size = max(len(w) for w in sequences) + if not isinstance(target_size, int) or dynamic_seq_length: + # Maximum string length + EOS + max_length = max(len(w) for w in sequences) + 1 + if isinstance(sos, int): + max_length += 1 + if isinstance(pad, int): + max_length += 1 + target_size = max_length if not isinstance(target_size, int) else min(max_length, target_size) # Pad all sequences - encoded_data = np.full([len(sequences), target_size], eos, dtype=np.int32) - - for idx, seq in enumerate(sequences): - encoded_seq = encode_sequence(seq, vocab) - encoded_data[idx, :min(len(encoded_seq), target_size)] = encoded_seq[:min(len(encoded_seq), target_size)] + if isinstance(pad, int): # pad with padding symbol + if 0 <= pad < len(vocab): + raise ValueError("argument 'pad' needs to be outside of vocab possible indices") + # In that case, add EOS at the end of the word before padding + default_symbol = pad + else: # pad with eos symbol + default_symbol = eos + encoded_data: np.ndarray = np.full([len(sequences), target_size], default_symbol, dtype=np.int32) + + # Encode the strings + for idx, seq in enumerate(map(partial(encode_string, vocab=vocab), sequences)): + if isinstance(pad, int): # add eos at the end of the sequence + seq.append(eos) + encoded_data[idx, : min(len(seq), target_size)] = seq[: min(len(seq), target_size)] + + if isinstance(sos, int): # place sos symbol at the beginning of each sequence + if 0 <= sos < len(vocab): + raise ValueError("argument 'sos' needs to be outside of vocab possible indices") + encoded_data = np.roll(encoded_data, 1) + encoded_data[:, 0] = sos return encoded_data
    + + +def convert_target_to_relative( + img: ImageTensor, target: Union[np.ndarray, Dict[str, Any]] +) -> Tuple[ImageTensor, Union[Dict[str, Any], np.ndarray]]: + if isinstance(target, np.ndarray): + target = convert_to_relative_coords(target, get_img_shape(img)) + else: + target["boxes"] = convert_to_relative_coords(target["boxes"], get_img_shape(img)) + return img, target + + +def crop_bboxes_from_image(img_path: Union[str, Path], geoms: np.ndarray) -> List[np.ndarray]: + """Crop a set of bounding boxes from an image + + Args: + ---- + img_path: path to the image + geoms: a array of polygons of shape (N, 4, 2) or of straight boxes of shape (N, 4) + + Returns: + ------- + a list of cropped images + """ + with Image.open(img_path) as pil_img: + img: np.ndarray = np.array(pil_img.convert("RGB")) + # Polygon + if geoms.ndim == 3 and geoms.shape[1:] == (4, 2): + return extract_rcrops(img, geoms.astype(dtype=int)) + if geoms.ndim == 2 and geoms.shape[1] == 4: + return extract_crops(img, geoms.astype(dtype=int)) + raise ValueError("Invalid geometry format") + + +def pre_transform_multiclass(img, target: Tuple[np.ndarray, List]) -> Tuple[np.ndarray, Dict[str, List]]: + """Converts multiclass target to relative coordinates. + + Args: + ---- + img: Image + target: tuple of target polygons and their classes names + + Returns: + ------- + Image and dictionary of boxes, with class names as keys + """ + boxes = convert_to_relative_coords(target[0], get_img_shape(img)) + boxes_classes = target[1] + boxes_dict: Dict = {k: [] for k in sorted(set(boxes_classes))} + for k, poly in zip(boxes_classes, boxes): + boxes_dict[k].append(poly) + boxes_dict = {k: np.stack(v, axis=0) for k, v in boxes_dict.items()} + return img, boxes_dict
    @@ -421,8 +553,8 @@

    Source code for doctr.datasets.utils

           
         
       
    - - + + diff --git a/v0.2.1/_modules/doctr/datasets/wildreceipt.html b/v0.2.1/_modules/doctr/datasets/wildreceipt.html index c543ee7cac..12c6aebd14 100644 --- a/v0.2.1/_modules/doctr/datasets/wildreceipt.html +++ b/v0.2.1/_modules/doctr/datasets/wildreceipt.html @@ -13,7 +13,7 @@ - + doctr.datasets.wildreceipt - docTR documentation @@ -454,7 +454,7 @@

    Source code for doctr.datasets.wildreceipt

         
       
    - + diff --git a/v0.2.1/_modules/doctr/documents/elements.html b/v0.2.1/_modules/doctr/documents/elements.html deleted file mode 100644 index e4e7bb08a6..0000000000 --- a/v0.2.1/_modules/doctr/documents/elements.html +++ /dev/null @@ -1,571 +0,0 @@ - - - - - - - - - - - - doctr.documents.elements - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.elements

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import matplotlib.pyplot as plt
    -from typing import Tuple, Dict, List, Any, Optional
    -
    -from doctr.utils.geometry import resolve_enclosing_bbox
    -from doctr.utils.visualization import visualize_page
    -from doctr.utils.common_types import BoundingBox
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['Element', 'Word', 'Artefact', 'Line', 'Block', 'Page', 'Document']
    -
    -
    -class Element(NestedObject):
    -    """Implements an abstract document element with exporting and text rendering capabilities"""
    -
    -    _exported_keys: List[str] = []
    -
    -    def __init__(self, **kwargs: Any) -> None:
    -        self._children_names: List[str] = []
    -        for k, v in kwargs.items():
    -            setattr(self, k, v)
    -            self._children_names.append(k)
    -
    -    def export(self) -> Dict[str, Any]:
    -        """Exports the object into a nested dict format"""
    -
    -        export_dict = {k: getattr(self, k) for k in self._exported_keys}
    -        for children_name in self._children_names:
    -            export_dict[children_name] = [c.export() for c in getattr(self, children_name)]
    -
    -        return export_dict
    -
    -    def render(self) -> str:
    -        raise NotImplementedError
    -
    -
    -
    -[docs] -class Word(Element): - """Implements a word element - - Args: - value: the text string of the word - confidence: the confidence associated with the text prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size - """ - - _exported_keys: List[str] = ["value", "confidence", "geometry"] - - def __init__(self, value: str, confidence: float, geometry: BoundingBox) -> None: - super().__init__() - self.value = value - self.confidence = confidence - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return self.value - - def extra_repr(self) -> str: - return f"value='{self.value}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Artefact(Element): - """Implements a non-textual element - - Args: - artefact_type: the type of artefact - confidence: the confidence of the type prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. - """ - - _exported_keys: List[str] = ["geometry", "type", "confidence"] - - def __init__(self, artefact_type: str, confidence: float, geometry: BoundingBox) -> None: - super().__init__() - self.geometry = geometry - self.type = artefact_type - self.confidence = confidence - - def render(self) -> str: - """Renders the full text of the element""" - return f"[{self.type.upper()}]" - - def extra_repr(self) -> str: - return f"type='{self.type}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Line(Element): - """Implements a line element as a collection of words - - Args: - words: list of word elements - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all words in it. - """ - - _exported_keys: List[str] = ["geometry"] - words: List[Word] = [] - - def __init__( - self, - words: List[Word], - geometry: Optional[BoundingBox] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - geometry = resolve_enclosing_bbox([w.geometry for w in words]) - - super().__init__(words=words) - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return " ".join(w.render() for w in self.words)
    - - - -
    -[docs] -class Block(Element): - """Implements a block element as a collection of lines and artefacts - - Args: - lines: list of line elements - artefacts: list of artefacts - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all lines and artefacts in it. - """ - - _exported_keys: List[str] = ["geometry"] - lines: List[Line] = [] - artefacts: List[Artefact] = [] - - def __init__( - self, - lines: List[Line] = [], - artefacts: List[Artefact] = [], - geometry: Optional[BoundingBox] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - line_boxes = [word.geometry for line in lines for word in line.words] - artefact_boxes = [artefact.geometry for artefact in artefacts] - geometry = resolve_enclosing_bbox(line_boxes + artefact_boxes) - super().__init__(lines=lines, artefacts=artefacts) - self.geometry = geometry - - def render(self, line_break: str = '\n') -> str: - """Renders the full text of the element""" - return line_break.join(line.render() for line in self.lines)
    - - - -
    -[docs] -class Page(Element): - """Implements a page element as a collection of blocks - - Args: - blocks: list of block elements - page_idx: the index of the page in the input raw document - dimensions: the page size in pixels in format (width, height) - orientation: a dictionary with the value of the rotation angle in degress and confidence of the prediction - language: a dictionary with the language value and confidence of the prediction - """ - - _exported_keys: List[str] = ["page_idx", "dimensions", "orientation", "language"] - blocks: List[Block] = [] - - def __init__( - self, - blocks: List[Block], - page_idx: int, - dimensions: Tuple[int, int], - orientation: Optional[Dict[str, Any]] = None, - language: Optional[Dict[str, Any]] = None, - ) -> None: - super().__init__(blocks=blocks) - self.page_idx = page_idx - self.dimensions = dimensions - self.orientation = orientation if isinstance(orientation, dict) else dict(value=None, confidence=None) - self.language = language if isinstance(language, dict) else dict(value=None, confidence=None) - - def render(self, block_break: str = '\n\n') -> str: - """Renders the full text of the element""" - return block_break.join(b.render() for b in self.blocks) - - def extra_repr(self) -> str: - return f"dimensions={self.dimensions}" - -
    -[docs] - def show(self, page: np.ndarray, interactive: bool = True, **kwargs) -> None: - """Overlay the result on a given image - - Args: - page: image encoded as a numpy array in uint8 - interactive: whether the display should be interactive - """ - visualize_page(self.export(), page, interactive=interactive) - plt.show(**kwargs)
    -
    - - - -
    -[docs] -class Document(Element): - """Implements a document element as a collection of pages - - Args: - pages: list of page elements - """ - - pages: List[Page] = [] - - def __init__( - self, - pages: List[Page], - ) -> None: - super().__init__(pages=pages) - - def render(self, page_break: str = '\n\n\n\n') -> str: - """Renders the full text of the element""" - return page_break.join(p.render() for p in self.pages) - -
    -[docs] - def show(self, pages: List[np.ndarray], **kwargs) -> None: - """Overlay the result on a given image - - Args: - pages: list of images encoded as numpy arrays in uint8 - """ - for img, result in zip(pages, self.pages): - result.show(img, **kwargs)
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.1/_modules/doctr/documents/reader.html b/v0.2.1/_modules/doctr/documents/reader.html deleted file mode 100644 index 44de246dc8..0000000000 --- a/v0.2.1/_modules/doctr/documents/reader.html +++ /dev/null @@ -1,611 +0,0 @@ - - - - - - - - - - - - doctr.documents.reader - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.reader

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import cv2
    -from pathlib import Path
    -import fitz
    -from weasyprint import HTML
    -from typing import List, Tuple, Optional, Any, Union, Sequence
    -
    -__all__ = ['read_pdf', 'read_img', 'read_html', 'DocumentFile', 'PDF']
    -
    -
    -AbstractPath = Union[str, Path]
    -AbstractFile = Union[AbstractPath, bytes]
    -Bbox = Tuple[float, float, float, float]
    -
    -
    -
    -[docs] -def read_img( - file: AbstractFile, - output_size: Optional[Tuple[int, int]] = None, - rgb_output: bool = True, -) -> np.ndarray: - """Read an image file into numpy format - - Example:: - >>> from doctr.documents import read_img - >>> page = read_img("path/to/your/doc.jpg") - - Args: - file: the path to the image file - output_size: the expected output size of each page in format H x W - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - Returns: - the page decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)): - if not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - img = cv2.imread(str(file), cv2.IMREAD_COLOR) - elif isinstance(file, bytes): - file = np.frombuffer(file, np.uint8) - img = cv2.imdecode(file, cv2.IMREAD_COLOR) - else: - raise TypeError("unsupported object type for argument 'file'") - - # Validity check - if img is None: - raise ValueError("unable to read file.") - # Resizing - if isinstance(output_size, tuple): - img = cv2.resize(img, output_size[::-1], interpolation=cv2.INTER_LINEAR) - # Switch the channel order - if rgb_output: - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - return img
    - - - -
    -[docs] -def read_pdf(file: AbstractFile, **kwargs: Any) -> fitz.Document: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_pdf - >>> doc = read_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)) and not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - - fitz_args = {} - - if isinstance(file, (str, Path)): - fitz_args['filename'] = file - elif isinstance(file, bytes): - fitz_args['stream'] = file - else: - raise TypeError("unsupported object type for argument 'file'") - - # Read pages with fitz and convert them to numpy ndarrays - return fitz.open(**fitz_args, filetype="pdf", **kwargs)
    - - - -def convert_page_to_numpy( - page: fitz.fitz.Page, - output_size: Optional[Tuple[int, int]] = None, - bgr_output: bool = False, - default_scales: Tuple[float, float] = (2, 2), -) -> np.ndarray: - """Convert a fitz page to a numpy-formatted image - - Args: - page: the page of a file read with PyMuPDF - output_size: the expected output size of each page in format H x W. Default goes to 840 x 595 for A4 pdf, - if you want to increase the resolution while preserving the original A4 aspect ratio can pass (1024, 726) - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - default_scales: spatial scaling to be applied when output_size is not specified where (1, 1) - corresponds to 72 dpi rendering. - - Returns: - the rendered image in numpy format - """ - - # If no output size is specified, keep the origin one - if output_size is not None: - scales = (output_size[1] / page.MediaBox[2], output_size[0] / page.MediaBox[3]) - else: - # Default 72 DPI (scales of (1, 1)) is unnecessarily low - scales = default_scales - - transform_matrix = fitz.Matrix(*scales) - - # Generate the pixel map using the transformation matrix - pixmap = page.getPixmap(matrix=transform_matrix) - # Decode it into a numpy - img = np.frombuffer(pixmap.samples, dtype=np.uint8).reshape(pixmap.height, pixmap.width, 3) - - # Switch the channel order - if bgr_output: - img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) - - return img - - -
    -[docs] -def read_html(url: str, **kwargs: Any) -> bytes: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_html - >>> doc = read_html("https://www.yoursite.com") - - Args: - url: URL of the target web page - Returns: - decoded PDF file as a bytes stream - """ - - return HTML(url, **kwargs).write_pdf()
    - - - -
    -[docs] -class PDF: - """PDF document template - - Args: - doc: input PDF document - """ - def __init__(self, doc: fitz.Document) -> None: - self.doc = doc - -
    -[docs] - def as_images(self, **kwargs) -> List[np.ndarray]: - """Convert all document pages to images - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images() - - Args: - kwargs: keyword arguments of `convert_page_to_numpy` - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - return [convert_page_to_numpy(page, **kwargs) for page in self.doc]
    - - - def get_page_words(self, idx, **kwargs) -> List[Tuple[Bbox, str]]: - """Get the annotations for all words of a given page""" - - # xmin, ymin, xmax, ymax, value, block_idx, line_idx, word_idx - return [(info[:4], info[4]) for info in self.doc[idx].getTextWords(**kwargs)] - -
    -[docs] - def get_words(self, **kwargs) -> List[List[Tuple[Bbox, str]]]: - """Get the annotations for all words in the document - - Example:: - >>> from doctr.documents import DocumentFile - >>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words() - - Args: - kwargs: keyword arguments of `fitz.Page.getTextWords` - Returns: - the list of pages annotations, represented as a list of tuple (bounding box, value) - """ - return [self.get_page_words(idx, **kwargs) for idx in range(len(self.doc))]
    - - - def get_page_artefacts(self, idx) -> List[Tuple[float, float, float, float]]: - return [tuple(self.doc[idx].getImageBbox(artefact)) for artefact in self.doc[idx].get_images(full=True)] - -
    -[docs] - def get_artefacts(self) -> List[List[Tuple[float, float, float, float]]]: - """Get the artefacts for the entire document - - Example:: - >>> from doctr.documents import DocumentFile - >>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts() - - Returns: - the list of pages artefacts, represented as a list of bounding boxes - """ - - return [self.get_page_artefacts(idx) for idx in range(len(self.doc))]
    -
    - - - -
    -[docs] -class DocumentFile: - """Read a document from multiple extensions""" - -
    -[docs] - @classmethod - def from_pdf(cls, file: AbstractFile, **kwargs) -> PDF: - """Read a PDF file - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file or a binary stream - Returns: - a PDF document - """ - - doc = read_pdf(file, **kwargs) - - return PDF(doc)
    - - -
    -[docs] - @classmethod - def from_url(cls, url: str, **kwargs) -> PDF: - """Interpret a web page as a PDF document - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_url("https://www.yoursite.com") - - Args: - url: the URL of the target web page - Returns: - a PDF document - """ - pdf_stream = read_html(url) - return cls.from_pdf(pdf_stream, **kwargs)
    - - -
    -[docs] - @classmethod - def from_images(cls, files: Union[Sequence[AbstractFile], AbstractFile], **kwargs) -> List[np.ndarray]: - """Read an image file (or a collection of image files) and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"]) - - Args: - files: the path to the image file or a binary stream, or a collection of those - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - if isinstance(files, (str, Path, bytes)): - files = [files] - - return [read_img(file, **kwargs) for file in files]
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.1/_modules/doctr/io/elements.html b/v0.2.1/_modules/doctr/io/elements.html index 753a47455c..e049d6ce30 100644 --- a/v0.2.1/_modules/doctr/io/elements.html +++ b/v0.2.1/_modules/doctr/io/elements.html @@ -13,7 +13,7 @@ - + doctr.io.elements - docTR documentation @@ -1008,7 +1008,7 @@

    Source code for doctr.io.elements

         
       
    - + diff --git a/v0.2.1/_modules/doctr/io/html.html b/v0.2.1/_modules/doctr/io/html.html index 7ad5b97031..be73631500 100644 --- a/v0.2.1/_modules/doctr/io/html.html +++ b/v0.2.1/_modules/doctr/io/html.html @@ -13,7 +13,7 @@ - + doctr.io.html - docTR documentation @@ -360,7 +360,7 @@

    Source code for doctr.io.html

         
       
    - + diff --git a/v0.2.1/_modules/doctr/io/image/base.html b/v0.2.1/_modules/doctr/io/image/base.html index 336b4bff0e..a50c95d595 100644 --- a/v0.2.1/_modules/doctr/io/image/base.html +++ b/v0.2.1/_modules/doctr/io/image/base.html @@ -13,7 +13,7 @@ - + doctr.io.image.base - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.io.image.base

         
       
    - + diff --git a/v0.2.1/_modules/doctr/io/image/tensorflow.html b/v0.2.1/_modules/doctr/io/image/tensorflow.html index f1846820a3..3b9e731756 100644 --- a/v0.2.1/_modules/doctr/io/image/tensorflow.html +++ b/v0.2.1/_modules/doctr/io/image/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.io.image.tensorflow - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.io.image.tensorflow

         
       
    - + diff --git a/v0.2.1/_modules/doctr/io/pdf.html b/v0.2.1/_modules/doctr/io/pdf.html index e3abf6960b..e5b94811c3 100644 --- a/v0.2.1/_modules/doctr/io/pdf.html +++ b/v0.2.1/_modules/doctr/io/pdf.html @@ -13,7 +13,7 @@ - + doctr.io.pdf - docTR documentation @@ -377,7 +377,7 @@

    Source code for doctr.io.pdf

         
       
    - + diff --git a/v0.2.1/_modules/doctr/io/reader.html b/v0.2.1/_modules/doctr/io/reader.html index c1ddc26edd..d36e5bb553 100644 --- a/v0.2.1/_modules/doctr/io/reader.html +++ b/v0.2.1/_modules/doctr/io/reader.html @@ -13,7 +13,7 @@ - + doctr.io.reader - docTR documentation @@ -426,7 +426,7 @@

    Source code for doctr.io.reader

         
       
    - + diff --git a/v0.2.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html b/v0.2.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html index 9f074805c1..61a010d548 100644 --- a/v0.2.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html +++ b/v0.2.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.magc_resnet.tensorflow - docTR documentation @@ -531,7 +531,7 @@

    Source code for doctr.models.classification.magc_resnet.tensorflow

    - + diff --git a/v0.2.1/_modules/doctr/models/classification/mobilenet/tensorflow.html b/v0.2.1/_modules/doctr/models/classification/mobilenet/tensorflow.html index 6a63851276..7c448394ad 100644 --- a/v0.2.1/_modules/doctr/models/classification/mobilenet/tensorflow.html +++ b/v0.2.1/_modules/doctr/models/classification/mobilenet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.mobilenet.tensorflow - docTR documentation @@ -793,7 +793,7 @@

    Source code for doctr.models.classification.mobilenet.tensorflow

    - + diff --git a/v0.2.1/_modules/doctr/models/classification/resnet/tensorflow.html b/v0.2.1/_modules/doctr/models/classification/resnet/tensorflow.html index 095d377f31..aed4343741 100644 --- a/v0.2.1/_modules/doctr/models/classification/resnet/tensorflow.html +++ b/v0.2.1/_modules/doctr/models/classification/resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.resnet.tensorflow - docTR documentation @@ -749,7 +749,7 @@

    Source code for doctr.models.classification.resnet.tensorflow

    - + diff --git a/v0.2.1/_modules/doctr/models/classification/textnet/tensorflow.html b/v0.2.1/_modules/doctr/models/classification/textnet/tensorflow.html index ad254ebbfb..c5567d7d67 100644 --- a/v0.2.1/_modules/doctr/models/classification/textnet/tensorflow.html +++ b/v0.2.1/_modules/doctr/models/classification/textnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.textnet.tensorflow - docTR documentation @@ -611,7 +611,7 @@

    Source code for doctr.models.classification.textnet.tensorflow

    - + diff --git a/v0.2.1/_modules/doctr/models/classification/vgg/tensorflow.html b/v0.2.1/_modules/doctr/models/classification/vgg/tensorflow.html index 01ae452624..788111ae87 100644 --- a/v0.2.1/_modules/doctr/models/classification/vgg/tensorflow.html +++ b/v0.2.1/_modules/doctr/models/classification/vgg/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vgg.tensorflow - docTR documentation @@ -451,7 +451,7 @@

    Source code for doctr.models.classification.vgg.tensorflow

    - + diff --git a/v0.2.1/_modules/doctr/models/classification/vit/tensorflow.html b/v0.2.1/_modules/doctr/models/classification/vit/tensorflow.html index 1333cf6045..971ba5abe9 100644 --- a/v0.2.1/_modules/doctr/models/classification/vit/tensorflow.html +++ b/v0.2.1/_modules/doctr/models/classification/vit/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vit.tensorflow - docTR documentation @@ -533,7 +533,7 @@

    Source code for doctr.models.classification.vit.tensorflow

    - + diff --git a/v0.2.1/_modules/doctr/models/classification/zoo.html b/v0.2.1/_modules/doctr/models/classification/zoo.html index f7796a7522..3eb2a3ec4e 100644 --- a/v0.2.1/_modules/doctr/models/classification/zoo.html +++ b/v0.2.1/_modules/doctr/models/classification/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.classification.zoo - docTR documentation @@ -447,7 +447,7 @@

    Source code for doctr.models.classification.zoo

    <
    - + diff --git a/v0.2.1/_modules/doctr/models/detection/differentiable_binarization.html b/v0.2.1/_modules/doctr/models/detection/differentiable_binarization.html deleted file mode 100644 index 38e9b36ec2..0000000000 --- a/v0.2.1/_modules/doctr/models/detection/differentiable_binarization.html +++ /dev/null @@ -1,879 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.differentiable_binarization - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.differentiable_binarization

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -import cv2
    -from copy import deepcopy
    -import numpy as np
    -from shapely.geometry import Polygon
    -import pyclipper
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers
    -from typing import Union, List, Tuple, Optional, Any, Dict
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..utils import IntermediateLayerGetter, load_pretrained_params, conv_sequence
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['DBPostProcessor', 'DBNet', 'db_resnet50']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'db_resnet50': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'backbone': 'ResNet50',
    -        'fpn_layers': ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"],
    -        'fpn_channels': 128,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'DBPostProcessor',
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.2.0/db_resnet50-adcafc63.zip',
    -    },
    -}
    -
    -
    -class DBPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for DBNet adapted from the implementation of `xuannianz
    -    <https://github.com/xuannianz/DifferentiableBinarization>`_.
    -
    -    Args:
    -        unclip ratio: ratio used to unshrink polygons
    -        min_size_box: minimal length (pix) to keep a box
    -        max_candidates: maximum boxes to consider in a single page
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        unclip_ratio: Union[float, int] = 1.5,
    -        max_candidates: int = 1000,
    -        box_thresh: float = 0.1,
    -        bin_thresh: float = 0.3,
    -    ) -> None:
    -
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -        self.unclip_ratio = unclip_ratio
    -        self.max_candidates = max_candidates
    -
    -    def polygon_to_box(
    -        self,
    -        points: np.ndarray,
    -    ) -> Optional[Tuple[int, int, int, int]]:
    -        """Expand a polygon (points) by a factor unclip_ratio, and returns a 4-points box
    -
    -        Args:
    -            points: The first parameter.
    -
    -        Returns:
    -            a box in absolute coordinates (x, y, w, h)
    -        """
    -        poly = Polygon(points)
    -        distance = poly.area * self.unclip_ratio / poly.length  # compute distance to expand polygon
    -        offset = pyclipper.PyclipperOffset()
    -        offset.AddPath(points, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        _points = offset.Execute(distance)
    -        # Take biggest stack of points
    -        idx = 0
    -        if len(_points) > 1:
    -            max_size = 0
    -            for _idx, p in enumerate(_points):
    -                if len(p) > max_size:
    -                    idx = _idx
    -                    max_size = len(p)
    -            # We ensure that _points can be correctly casted to a ndarray
    -            _points = [_points[idx]]
    -        expanded_points = np.asarray(_points)  # expand polygon
    -        if len(expanded_points) < 1:
    -            return None
    -        x, y, w, h = cv2.boundingRect(expanded_points)  # compute a 4-points box from expanded polygon
    -        return x, y, w, h
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map
    -
    -        Args:
    -            pred: Pred map from differentiable binarization output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        # get contours from connected components on the bitmap
    -        contours, _ = cv2.findContours(bitmap.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    -        for contour in contours[:self.max_candidates]:
    -            # Check whether smallest enclosing bounding box is not too small
    -            if np.any(contour[:, 0].max(axis=0) - contour[:, 0].min(axis=0) < min_size_box):
    -                continue
    -            x, y, w, h = cv2.boundingRect(contour)
    -            points = np.array([[x, y], [x, y + h], [x + w, y + h], [x + w, y]])
    -            # Compute objectness
    -            score = self.box_score(pred, points)
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            _box = self.polygon_to_box(points)
    -
    -            if _box is None or _box[2] < min_size_box or _box[3] < min_size_box:  # remove to small boxes
    -                continue
    -            x, y, w, h = _box
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -class FeaturePyramidNetwork(layers.Layer, NestedObject):
    -    """Feature Pyramid Network as described in `"Feature Pyramid Networks for Object Detection"
    -    <https://arxiv.org/pdf/1612.03144.pdf>`_.
    -
    -    Args:
    -        channels: number of channel to output
    -    """
    -
    -    def __init__(
    -        self,
    -        channels: int,
    -    ) -> None:
    -        super().__init__()
    -        self.channels = channels
    -        self.upsample = layers.UpSampling2D(size=(2, 2), interpolation='nearest')
    -        self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer='he_normal') for _ in range(4)]
    -        self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2 ** idx) for idx in range(4)]
    -
    -    @staticmethod
    -    def build_upsampling(
    -        channels: int,
    -        dilation_factor: int = 1,
    -    ) -> layers.Layer:
    -        """Module which performs a 3x3 convolution followed by up-sampling
    -
    -        Args:
    -            channels: number of output channels
    -            dilation_factor (int): dilation factor to scale the convolution output before concatenation
    -
    -        Returns:
    -            a keras.layers.Layer object, wrapping these operations in a sequential module
    -
    -        """
    -
    -        _layers = conv_sequence(channels, 'relu', True, kernel_size=3)
    -
    -        if dilation_factor > 1:
    -            _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation='nearest'))
    -
    -        module = keras.Sequential(_layers)
    -
    -        return module
    -
    -    def extra_repr(self) -> str:
    -        return f"channels={self.channels}"
    -
    -    def call(
    -        self,
    -        x: List[tf.Tensor],
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # Channel mapping
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.inner_blocks, x)]
    -        # Upsample & sum
    -        for idx in range(len(results) - 1, -1):
    -            results[idx] += self.upsample(results[idx + 1])
    -        # Conv & upsample
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.layer_blocks, results)]
    -
    -        return layers.concatenate(results)
    -
    -
    -class DBNet(DetectionModel, NestedObject):
    -    """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization"
    -    <https://arxiv.org/pdf/1911.08947.pdf>`_.
    -
    -    Args:
    -        feature extractor: the backbone serving as feature extractor
    -        fpn_channels: number of channels each extracted feature maps is mapped to
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'fpn', 'probability_head', 'threshold_head', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: IntermediateLayerGetter,
    -        fpn_channels: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(cfg=cfg)
    -
    -        self.shrink_ratio = 0.4
    -        self.thresh_min = 0.3
    -        self.thresh_max = 0.7
    -        self.min_size_box = 3
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.fpn = FeaturePyramidNetwork(channels=fpn_channels)
    -        # Initialize kernels
    -        _inputs = [layers.Input(shape=in_shape[1:]) for in_shape in self.feat_extractor.output_shape]
    -        output_shape = tuple(self.fpn(_inputs).shape)
    -
    -        self.probability_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -        self.threshold_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -
    -        self.postprocessor = DBPostProcessor()
    -
    -    @staticmethod
    -    def compute_distance(
    -        xs: np.array,
    -        ys: np.array,
    -        a: np.array,
    -        b: np.array,
    -        eps: float = 1e-7,
    -    ) -> float:
    -        """Compute the distance for each point of the map (xs, ys) to the (a, b) segment
    -
    -        Args:
    -            xs : map of x coordinates (height, width)
    -            ys : map of y coordinates (height, width)
    -            a: first point defining the [ab] segment
    -            b: second point defining the [ab] segment
    -
    -        Returns:
    -            The computed distance
    -
    -        """
    -        square_dist_1 = np.square(xs - a[0]) + np.square(ys - a[1])
    -        square_dist_2 = np.square(xs - b[0]) + np.square(ys - b[1])
    -        square_dist = np.square(a[0] - b[0]) + np.square(a[1] - b[1])
    -        cosin = (square_dist - square_dist_1 - square_dist_2) / (2 * np.sqrt(square_dist_1 * square_dist_2) + eps)
    -        square_sin = 1 - np.square(cosin)
    -        square_sin = np.nan_to_num(square_sin)
    -        result = np.sqrt(square_dist_1 * square_dist_2 * square_sin / square_dist)
    -        result[cosin < 0] = np.sqrt(np.fmin(square_dist_1, square_dist_2))[cosin < 0]
    -        return result
    -
    -    def draw_thresh_map(
    -        self,
    -        polygon: np.array,
    -        canvas: np.array,
    -        mask: np.array,
    -    ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    -        """Draw a polygon treshold map on a canvas, as described in the DB paper
    -
    -        Args:
    -            polygon : array of coord., to draw the boundary of the polygon
    -            canvas : threshold map to fill with polygons
    -            mask : mask for training on threshold polygons
    -        """
    -        if polygon.ndim != 2 or polygon.shape[1] != 2:
    -            raise AttributeError("polygon should be a 2 dimensional array of coords")
    -
    -        # Augment polygon by shrink_ratio
    -        polygon_shape = Polygon(polygon)
    -        distance = polygon_shape.area * (1 - np.power(self.shrink_ratio, 2)) / polygon_shape.length
    -        subject = [tuple(coor) for coor in polygon]  # Get coord as list of tuples
    -        padding = pyclipper.PyclipperOffset()
    -        padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        padded_polygon = np.array(padding.Execute(distance)[0])
    -
    -        # Fill the mask with 1 on the new padded polygon
    -        cv2.fillPoly(mask, [padded_polygon.astype(np.int32)], 1.0)
    -
    -        # Get min/max to recover polygon after distance computation
    -        xmin = padded_polygon[:, 0].min()
    -        xmax = padded_polygon[:, 0].max()
    -        ymin = padded_polygon[:, 1].min()
    -        ymax = padded_polygon[:, 1].max()
    -        width = xmax - xmin + 1
    -        height = ymax - ymin + 1
    -        # Get absolute polygon for distance computation
    -        polygon[:, 0] = polygon[:, 0] - xmin
    -        polygon[:, 1] = polygon[:, 1] - ymin
    -        # Get absolute padded polygon
    -        xs = np.broadcast_to(np.linspace(0, width - 1, num=width).reshape(1, width), (height, width))
    -        ys = np.broadcast_to(np.linspace(0, height - 1, num=height).reshape(height, 1), (height, width))
    -
    -        # Compute distance map to fill the padded polygon
    -        distance_map = np.zeros((polygon.shape[0], height, width), dtype=np.float32)
    -        for i in range(polygon.shape[0]):
    -            j = (i + 1) % polygon.shape[0]
    -            absolute_distance = self.compute_distance(xs, ys, polygon[i], polygon[j])
    -            distance_map[i] = np.clip(absolute_distance / distance, 0, 1)
    -        distance_map = np.min(distance_map, axis=0)
    -
    -        # Clip the padded polygon inside the canvas
    -        xmin_valid = min(max(0, xmin), canvas.shape[1] - 1)
    -        xmax_valid = min(max(0, xmax), canvas.shape[1] - 1)
    -        ymin_valid = min(max(0, ymin), canvas.shape[0] - 1)
    -        ymax_valid = min(max(0, ymax), canvas.shape[0] - 1)
    -
    -        # Fill the canvas with the distances computed inside the valid padded polygon
    -        canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1] = np.fmax(
    -            1 - distance_map[
    -                ymin_valid - ymin:ymax_valid - ymin + 1,
    -                xmin_valid - xmin:xmax_valid - xmin + 1
    -            ],
    -            canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1]
    -        )
    -
    -        return polygon, canvas, mask
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor, tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.uint8)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -        thresh_target = np.zeros(output_shape, dtype=np.uint8)
    -        thresh_mask = np.ones(output_shape, dtype=np.uint8)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            polys = np.stack([
    -                abs_boxes[:, [0, 1]],
    -                abs_boxes[:, [0, 3]],
    -                abs_boxes[:, [2, 3]],
    -                abs_boxes[:, [2, 1]],
    -            ], axis=1)
    -
    -            for box, box_size, poly, is_ambiguous in zip(abs_boxes, boxes_size, polys, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -
    -                # Negative shrink for gt, as described in paper
    -                polygon = Polygon(poly)
    -                distance = polygon.area * (1 - np.power(self.shrink_ratio, 2)) / polygon.length
    -                subject = [tuple(coor) for coor in poly]
    -                padding = pyclipper.PyclipperOffset()
    -                padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -                shrinked = padding.Execute(-distance)
    -
    -                # Draw polygon on gt if it is valid
    -                if len(shrinked) == 0:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                shrinked = np.array(shrinked[0]).reshape(-1, 2)
    -                if shrinked.shape[0] <= 2 or not Polygon(shrinked).is_valid:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                cv2.fillPoly(seg_target[idx], [shrinked.astype(np.int32)], 1)
    -
    -                # Draw on both thresh map and thresh mask
    -                poly, thresh_target[idx], thresh_mask[idx] = self.draw_thresh_map(poly, thresh_target[idx],
    -                                                                                  thresh_mask[idx])
    -
    -        thresh_target = thresh_target.astype(np.float32) * (self.thresh_max - self.thresh_min) + self.thresh_min
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -        thresh_target = tf.convert_to_tensor(thresh_target, dtype=tf.float32)
    -        thresh_mask = tf.convert_to_tensor(thresh_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask, thresh_target, thresh_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        thresh_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts, masks, thresh_gts, thresh_masks from a list of boxes
    -        and a list of masks for each image. From there it computes the loss with the model output
    -
    -        Args:
    -            out_map: output feature map of the model of shape (N, H, W, C)
    -            thresh_map: threshold map of shape (N, H, W, C)
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -
    -        prob_map = tf.math.sigmoid(tf.squeeze(out_map, axis=[-1]))
    -        thresh_map = tf.math.sigmoid(tf.squeeze(thresh_map, axis=[-1]))
    -
    -        seg_target, seg_mask, thresh_target, thresh_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute balanced BCE loss for proba_map
    -        bce_scale = 5.
    -        bce_loss = tf.keras.losses.binary_crossentropy(seg_target[..., None], out_map, from_logits=True)[seg_mask]
    -
    -        neg_target = 1 - seg_target[seg_mask]
    -        positive_count = tf.math.reduce_sum(seg_target[seg_mask])
    -        negative_count = tf.math.reduce_min([tf.math.reduce_sum(neg_target), 3. * positive_count])
    -        negative_loss = bce_loss * neg_target
    -        negative_loss, _ = tf.nn.top_k(negative_loss, tf.cast(negative_count, tf.int32))
    -        sum_losses = tf.math.reduce_sum(bce_loss * seg_target[seg_mask]) + tf.math.reduce_sum(negative_loss)
    -        balanced_bce_loss = sum_losses / (positive_count + negative_count + 1e-6)
    -
    -        # Compute dice loss for approxbin_map
    -        bin_map = 1 / (1 + tf.exp(-50. * (prob_map[seg_mask] - thresh_map[seg_mask])))
    -
    -        bce_min = tf.math.reduce_min(bce_loss)
    -        weights = (bce_loss - bce_min) / (tf.math.reduce_max(bce_loss) - bce_min) + 1.
    -        inter = tf.math.reduce_sum(bin_map * seg_target[seg_mask] * weights)
    -        union = tf.math.reduce_sum(bin_map) + tf.math.reduce_sum(seg_target[seg_mask]) + 1e-8
    -        dice_loss = 1 - 2.0 * inter / union
    -
    -        # Compute l1 loss for thresh_map
    -        l1_scale = 10.
    -        if tf.reduce_any(thresh_mask):
    -            l1_loss = tf.math.reduce_mean(tf.math.abs(thresh_map[thresh_mask] - thresh_target[thresh_mask]))
    -        else:
    -            l1_loss = tf.constant(0.)
    -
    -        return l1_scale * l1_loss + bce_scale * balanced_bce_loss + dice_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        feat_maps = self.feat_extractor(x, **kwargs)
    -        feat_concat = self.fpn(feat_maps, **kwargs)
    -        logits = self.probability_head(feat_concat, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            thresh_map = self.threshold_head(feat_concat, **kwargs)
    -            loss = self.compute_loss(logits, thresh_map, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _db_resnet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> DBNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['fpn_channels'] = kwargs.get('fpn_channels', _cfg['fpn_channels'])
    -
    -    # Feature extractor
    -    resnet = tf.keras.applications.__dict__[_cfg['backbone']](
    -        include_top=False,
    -        weights=None,
    -        input_shape=_cfg['input_shape'],
    -        pooling=None,
    -    )
    -
    -    feat_extractor = IntermediateLayerGetter(
    -        resnet,
    -        _cfg['fpn_layers'],
    -    )
    -
    -    kwargs['fpn_channels'] = _cfg['fpn_channels']
    -
    -    # Build the model
    -    model = DBNet(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def db_resnet50(pretrained: bool = False, **kwargs: Any) -> DBNet: - """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" - <https://arxiv.org/pdf/1911.08947.pdf>`_, using a ResNet-50 backbone. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _db_resnet('db_resnet50', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html b/v0.2.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html index 4325d0b74a..66cef8663d 100644 --- a/v0.2.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html +++ b/v0.2.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.differentiable_binarization.tensorflow - docTR documentation @@ -759,7 +759,7 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo

    - + diff --git a/v0.2.1/_modules/doctr/models/detection/fast/tensorflow.html b/v0.2.1/_modules/doctr/models/detection/fast/tensorflow.html index 5b84d2dea1..65e1a77af8 100644 --- a/v0.2.1/_modules/doctr/models/detection/fast/tensorflow.html +++ b/v0.2.1/_modules/doctr/models/detection/fast/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.fast.tensorflow - docTR documentation @@ -769,7 +769,7 @@

    Source code for doctr.models.detection.fast.tensorflow

    - + diff --git a/v0.2.1/_modules/doctr/models/detection/linknet.html b/v0.2.1/_modules/doctr/models/detection/linknet.html deleted file mode 100644 index 129cfdce8b..0000000000 --- a/v0.2.1/_modules/doctr/models/detection/linknet.html +++ /dev/null @@ -1,644 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.linknet - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.linknet

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -import numpy as np
    -import cv2
    -from tensorflow.keras import layers, Sequential
    -from typing import Dict, Any, Tuple, Optional, List
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..backbones import ResnetStage
    -from ..utils import conv_sequence, load_pretrained_params
    -from ...utils.repr import NestedObject
    -
    -__all__ = ['LinkNet', 'linknet', 'LinkNetPostProcessor']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'linknet': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'out_chan': 1,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'LinkNetPostProcessor',
    -        'url': None,
    -    },
    -}
    -
    -
    -class LinkNetPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for LinkNet model.
    -
    -    Args:
    -        min_size_box: minimal length (pix) to keep a box
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        min_size_box: int = 3,
    -        bin_thresh: float = 0.15,
    -        box_thresh: float = 0.1,
    -    ) -> None:
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map: find connected components then filter boxes
    -
    -        Args:
    -            pred: Pred map from differentiable linknet output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        label_num, labelimage = cv2.connectedComponents(bitmap.astype(np.uint8), connectivity=4)
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        for label in range(1, label_num + 1):
    -            points = np.array(np.where(labelimage == label)[::-1]).T
    -            if points.shape[0] < 4:  # remove polygons with 3 points or less
    -                continue
    -            score = self.box_score(pred, points.reshape(-1, 2))
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            x, y, w, h = cv2.boundingRect(points)
    -            if min(w, h) < min_size_box:  # filter too small boxes
    -                continue
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -def decoder_block(in_chan: int, out_chan: int) -> Sequential:
    -    """Creates a LinkNet decoder block"""
    -
    -    return Sequential([
    -        *conv_sequence(in_chan // 4, 'relu', True, kernel_size=1),
    -        layers.Conv2DTranspose(
    -            filters=in_chan // 4,
    -            kernel_size=3,
    -            strides=2,
    -            padding="same",
    -            use_bias=False,
    -            kernel_initializer='he_normal'
    -        ),
    -        layers.BatchNormalization(),
    -        layers.Activation('relu'),
    -        *conv_sequence(out_chan, 'relu', True, kernel_size=1),
    -    ])
    -
    -
    -class LinkNetFPN(layers.Layer, NestedObject):
    -    """LinkNet Encoder-Decoder module
    -
    -    """
    -
    -    def __init__(
    -        self,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.encoder_1 = ResnetStage(num_blocks=2, output_channels=64, downsample=True)
    -        self.encoder_2 = ResnetStage(num_blocks=2, output_channels=128, downsample=True)
    -        self.encoder_3 = ResnetStage(num_blocks=2, output_channels=256, downsample=True)
    -        self.encoder_4 = ResnetStage(num_blocks=2, output_channels=512, downsample=True)
    -        self.decoder_1 = decoder_block(in_chan=64, out_chan=64)
    -        self.decoder_2 = decoder_block(in_chan=128, out_chan=64)
    -        self.decoder_3 = decoder_block(in_chan=256, out_chan=128)
    -        self.decoder_4 = decoder_block(in_chan=512, out_chan=256)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor
    -    ) -> tf.Tensor:
    -        x_1 = self.encoder_1(x)
    -        x_2 = self.encoder_2(x_1)
    -        x_3 = self.encoder_3(x_2)
    -        x_4 = self.encoder_4(x_3)
    -        y_4 = self.decoder_4(x_4)
    -        y_3 = self.decoder_3(y_4 + x_3)
    -        y_2 = self.decoder_2(y_3 + x_2)
    -        y_1 = self.decoder_1(y_2 + x_1)
    -        return y_1
    -
    -
    -class LinkNet(DetectionModel, NestedObject):
    -    """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation"
    -    <https://arxiv.org/pdf/1707.03718.pdf>`_.
    -
    -    Args:
    -        out_chan: number of channels for the output
    -    """
    -
    -    _children_names: List[str] = ['stem', 'fpn', 'classifier', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        out_chan: int = 1,
    -        input_shape: Tuple[int, int, int] = (512, 512, 3),
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(cfg=cfg)
    -
    -        self.stem = Sequential([
    -            *conv_sequence(64, 'relu', True, strides=2, kernel_size=7, input_shape=input_shape),
    -            layers.MaxPool2D(pool_size=(3, 3), strides=2, padding='same'),
    -        ])
    -
    -        self.fpn = LinkNetFPN()
    -
    -        self.classifier = Sequential([
    -            layers.Conv2DTranspose(
    -                filters=32,
    -                kernel_size=3,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -            layers.BatchNormalization(),
    -            layers.Activation('relu'),
    -            *conv_sequence(32, 'relu', True, strides=1, kernel_size=3),
    -            layers.Conv2DTranspose(
    -                filters=out_chan,
    -                kernel_size=2,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -        ])
    -
    -        self.min_size_box = 3
    -
    -        self.postprocessor = LinkNetPostProcessor()
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.bool)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            for box, box_size, is_ambiguous in zip(abs_boxes, boxes_size, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Fill polygon with 1
    -                seg_target[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = True
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts and masks from a list of boxes and a list of masks for each image
    -        Then, it computes the loss function with proba_map, gts and masks
    -
    -        Args:
    -            out_map: output feature map of the model of shape N x H x W x 1
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -        seg_target, seg_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute BCE loss
    -        return tf.math.reduce_mean(tf.keras.losses.binary_crossentropy(
    -            seg_target[seg_mask],
    -            tf.squeeze(out_map, axis=[-1])[seg_mask],
    -            from_logits=True
    -        ))
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        logits = self.stem(x)
    -        logits = self.fpn(logits)
    -        logits = self.classifier(logits)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            loss = self.compute_loss(logits, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _linknet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> LinkNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['out_chan'] = kwargs.get('out_chan', _cfg['out_chan'])
    -
    -    kwargs['out_chan'] = _cfg['out_chan']
    -    kwargs['input_shape'] = _cfg['input_shape']
    -    # Build the model
    -    model = LinkNet(cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def linknet(pretrained: bool = False, **kwargs: Any) -> LinkNet: - """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" - <https://arxiv.org/pdf/1707.03718.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import linknet - >>> model = linknet(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _linknet('linknet', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.1/_modules/doctr/models/detection/linknet/tensorflow.html b/v0.2.1/_modules/doctr/models/detection/linknet/tensorflow.html index dbb58e37cf..ce995f99d4 100644 --- a/v0.2.1/_modules/doctr/models/detection/linknet/tensorflow.html +++ b/v0.2.1/_modules/doctr/models/detection/linknet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.linknet.tensorflow - docTR documentation @@ -716,7 +716,7 @@

    Source code for doctr.models.detection.linknet.tensorflow

    - + diff --git a/v0.2.1/_modules/doctr/models/detection/zoo.html b/v0.2.1/_modules/doctr/models/detection/zoo.html index b655cdfcea..3651c4e2d3 100644 --- a/v0.2.1/_modules/doctr/models/detection/zoo.html +++ b/v0.2.1/_modules/doctr/models/detection/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.detection.zoo - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.detection.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -from typing import Any
    +from typing import Any, List
     
    -from .core import DetectionPredictor
    -from ..preprocessor import PreProcessor
    -from .. import detection
    +from doctr.file_utils import is_tf_available, is_torch_available
     
    +from .. import detection
    +from ..detection.fast import reparameterize
    +from ..preprocessor import PreProcessor
    +from .predictor import DetectionPredictor
     
     __all__ = ["detection_predictor"]
     
    -ARCHS = ['db_resnet50', 'linknet']
    +ARCHS: List[str]
     
     
    -def _predictor(arch: str, pretrained: bool, **kwargs: Any) -> DetectionPredictor:
    +if is_tf_available():
    +    ARCHS = [
    +        "db_resnet50",
    +        "db_mobilenet_v3_large",
    +        "linknet_resnet18",
    +        "linknet_resnet34",
    +        "linknet_resnet50",
    +        "fast_tiny",
    +        "fast_small",
    +        "fast_base",
    +    ]
    +elif is_torch_available():
    +    ARCHS = [
    +        "db_resnet34",
    +        "db_resnet50",
    +        "db_mobilenet_v3_large",
    +        "linknet_resnet18",
    +        "linknet_resnet34",
    +        "linknet_resnet50",
    +        "fast_tiny",
    +        "fast_small",
    +        "fast_base",
    +    ]
     
    -    if arch not in ARCHS:
    -        raise ValueError(f"unknown architecture '{arch}'")
     
    -    # Detection
    -    _model = detection.__dict__[arch](pretrained=pretrained)
    -    kwargs['mean'] = kwargs.get('mean', _model.cfg['mean'])
    -    kwargs['std'] = kwargs.get('std', _model.cfg['std'])
    -    kwargs['batch_size'] = kwargs.get('batch_size', 1)
    +def _predictor(arch: Any, pretrained: bool, assume_straight_pages: bool = True, **kwargs: Any) -> DetectionPredictor:
    +    if isinstance(arch, str):
    +        if arch not in ARCHS:
    +            raise ValueError(f"unknown architecture '{arch}'")
    +
    +        _model = detection.__dict__[arch](
    +            pretrained=pretrained,
    +            pretrained_backbone=kwargs.get("pretrained_backbone", True),
    +            assume_straight_pages=assume_straight_pages,
    +        )
    +        # Reparameterize FAST models by default to lower inference latency and memory usage
    +        if isinstance(_model, detection.FAST):
    +            _model = reparameterize(_model)
    +    else:
    +        if not isinstance(arch, (detection.DBNet, detection.LinkNet, detection.FAST)):
    +            raise ValueError(f"unknown architecture: {type(arch)}")
    +
    +        _model = arch
    +        _model.assume_straight_pages = assume_straight_pages
    +        _model.postprocessor.assume_straight_pages = assume_straight_pages
    +
    +    kwargs.pop("pretrained_backbone", None)
    +
    +    kwargs["mean"] = kwargs.get("mean", _model.cfg["mean"])
    +    kwargs["std"] = kwargs.get("std", _model.cfg["std"])
    +    kwargs["batch_size"] = kwargs.get("batch_size", 2)
         predictor = DetectionPredictor(
    -        PreProcessor(_model.cfg['input_shape'][:2], **kwargs),
    -        _model
    +        PreProcessor(_model.cfg["input_shape"][:-1] if is_tf_available() else _model.cfg["input_shape"][1:], **kwargs),
    +        _model,
         )
         return predictor
     
     
     
    -[docs] -def detection_predictor(arch: str = 'db_resnet50', pretrained: bool = False, **kwargs: Any) -> DetectionPredictor: +[docs] +def detection_predictor( + arch: Any = "fast_base", + pretrained: bool = False, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + batch_size: int = 2, + **kwargs: Any, +) -> DetectionPredictor: """Text detection architecture. - Example:: - >>> import numpy as np - >>> from doctr.models import detection_predictor - >>> model = detection_predictor(pretrained=True) - >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) - >>> out = model([input_page]) + >>> import numpy as np + >>> from doctr.models import detection_predictor + >>> model = detection_predictor(arch='db_resnet50', pretrained=True) + >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) Args: - arch: name of the architecture to use ('db_resnet50') + ---- + arch: name of the architecture or model itself to use (e.g. 'db_resnet50') pretrained: If True, returns a model pre-trained on our text detection dataset + assume_straight_pages: If True, fit straight boxes to the page + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right + batch_size: number of samples the model processes in parallel + **kwargs: optional keyword arguments passed to the architecture Returns: + ------- Detection predictor """ - - return _predictor(arch, pretrained, **kwargs)
    + return _predictor( + arch=arch, + pretrained=pretrained, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + batch_size=batch_size, + **kwargs, + )
    @@ -362,8 +449,8 @@

    Source code for doctr.models.detection.zoo

           
         
       
    - - + + diff --git a/v0.2.1/_modules/doctr/models/export.html b/v0.2.1/_modules/doctr/models/export.html deleted file mode 100644 index f25a81aa21..0000000000 --- a/v0.2.1/_modules/doctr/models/export.html +++ /dev/null @@ -1,411 +0,0 @@ - - - - - - - - - - - - doctr.models.export - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.export

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import logging
    -import numpy as np
    -import tensorflow as tf
    -from tensorflow.keras import Model
    -from typing import Tuple
    -
    -logging.getLogger("tensorflow").setLevel(logging.DEBUG)
    -
    -
    -__all__ = ['convert_to_tflite', 'convert_to_fp16', 'quantize_model']
    -
    -
    -
    -[docs] -def convert_to_tflite(tf_model: Model) -> bytes: - """Converts a model to TFLite format - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_tflite, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_tflite(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - return converter.convert()
    - - - -
    -[docs] -def convert_to_fp16(tf_model: Model) -> bytes: - """Converts a model to half precision - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_fp16, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_fp16(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the serialized FP16 model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - converter.target_spec.supported_types = [tf.float16] - return converter.convert()
    - - - -
    -[docs] -def quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) -> bytes: - """Quantize a Tensorflow model - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import quantize_model, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = quantize_model(model, (224, 224, 3)) - - Args: - tf_model: a keras model - input_shape: shape of the expected input tensor (excluding batch dimension) with channel last order - - Returns: - bytes: the serialized quantized model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - - # Float fallback for operators that do not have an integer implementation - def representative_dataset(): - for _ in range(100): - data = np.random.rand(1, *input_shape) - yield [data.astype(np.float32)] - - converter.representative_dataset = representative_dataset - converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] - converter.inference_input_type = tf.int8 - converter.inference_output_type = tf.int8 - - return converter.convert()
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.1/_modules/doctr/models/factory/hub.html b/v0.2.1/_modules/doctr/models/factory/hub.html index 8274a809f5..756b2c7a17 100644 --- a/v0.2.1/_modules/doctr/models/factory/hub.html +++ b/v0.2.1/_modules/doctr/models/factory/hub.html @@ -13,7 +13,7 @@ - + doctr.models.factory.hub - docTR documentation @@ -568,7 +568,7 @@

    Source code for doctr.models.factory.hub

         
       
    - + diff --git a/v0.2.1/_modules/doctr/models/recognition/crnn.html b/v0.2.1/_modules/doctr/models/recognition/crnn.html deleted file mode 100644 index daa2393439..0000000000 --- a/v0.2.1/_modules/doctr/models/recognition/crnn.html +++ /dev/null @@ -1,565 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.crnn - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.crnn

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import layers
    -from tensorflow.keras.models import Sequential
    -from typing import Tuple, Dict, Any, Optional, List
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel, RecognitionPostProcessor
    -
    -__all__ = ['CRNN', 'crnn_vgg16_bn', 'crnn_resnet31', 'CTCPostProcessor']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'crnn_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/crnn_vgg16_bn-748c855f.zip',
    -    },
    -    'crnn_resnet31': {
    -        'mean': (0.694, 0.695, 0.693),
    -        'std': (0.299, 0.296, 0.301),
    -        'backbone': 'resnet31', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.1/crnn_resnet31-69ab71db.zip',
    -    },
    -}
    -
    -
    -class CTCPostProcessor(RecognitionPostProcessor):
    -    """
    -    Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor
    -    ) -> List[Tuple[str, float]]:
    -        """
    -        Performs decoding of raw output with CTC and decoding of CTC predictions
    -        with label_to_idx mapping dictionnary
    -
    -        Args:
    -            logits: raw output of the model, shape BATCH_SIZE X SEQ_LEN X NUM_CLASSES + 1
    -
    -        Returns:
    -            A list of decoded words of length BATCH_SIZE
    -
    -        """
    -        # Decode CTC
    -        _decoded, _log_prob = tf.nn.ctc_beam_search_decoder(
    -            tf.transpose(logits, perm=[1, 0, 2]),
    -            tf.fill(logits.shape[0], logits.shape[1]),
    -            beam_width=1, top_paths=1,
    -        )
    -        out_idxs = tf.sparse.to_dense(_decoded[0], default_value=len(self.vocab))
    -        probs = tf.math.exp(tf.squeeze(_log_prob, axis=1))
    -
    -        # Map it to characters
    -        _decoded_strings_pred = tf.strings.reduce_join(
    -            inputs=tf.nn.embedding_lookup(self._embedding, out_idxs),
    -            axis=-1
    -        )
    -        _decoded_strings_pred = tf.strings.split(_decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -class CRNN(RecognitionModel):
    -    """Implements a CRNN architecture as described in `"An End-to-End Trainable Neural Network for Image-based
    -    Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of units in the LSTM layers
    -        cfg: configuration dictionary
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: tf.keras.Model,
    -        vocab: str,
    -        rnn_units: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(vocab=vocab, cfg=cfg)
    -        self.feat_extractor = feature_extractor
    -
    -        # Initialize kernels
    -        h, w, c = self.feat_extractor.output_shape[1:]
    -        self.max_length = w
    -
    -        self.decoder = Sequential(
    -            [
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Dense(units=len(vocab) + 1)
    -            ]
    -        )
    -        self.decoder.build(input_shape=(None, w, h * c))
    -
    -        self.postprocessor = CTCPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        target: List[str],
    -    ) -> tf.Tensor:
    -        """Compute CTC loss for the model.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        gt, seq_len = self.compute_target(target)
    -        batch_len = model_output.shape[0]
    -        input_length = model_output.shape[1] * tf.ones(shape=(batch_len))
    -        ctc_loss = tf.nn.ctc_loss(
    -            gt, model_output, seq_len, input_length, logits_time_major=False, blank_index=len(self.vocab)
    -        )
    -        return ctc_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        # B x H x W x C --> B x W x H x C
    -        transposed_feat = tf.transpose(features, perm=[0, 2, 1, 3])
    -        w, h, c = transposed_feat.get_shape().as_list()[1:]
    -        # B x W x H x C --> B x W x H * C
    -        features_seq = tf.reshape(transposed_feat, shape=(-1, w, h * c))
    -        logits = self.decoder(features_seq, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = logits
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(logits)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(logits, target)
    -
    -        return out
    -
    -
    -def _crnn(arch: str, pretrained: bool, input_shape: Optional[Tuple[int, int, int]] = None, **kwargs: Any) -> CRNN:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[_cfg['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -
    -    # Build the model
    -    model = CRNN(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a VGG-16 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_vgg16_bn - >>> model = crnn_vgg16_bn(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_vgg16_bn', pretrained, **kwargs)
    - - - -def crnn_resnet31(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a resnet31 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_resnet31 - >>> model = crnn_resnet31(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_resnet31', pretrained, **kwargs) -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.1/_modules/doctr/models/recognition/crnn/tensorflow.html b/v0.2.1/_modules/doctr/models/recognition/crnn/tensorflow.html index e50c245923..bc64da9a1b 100644 --- a/v0.2.1/_modules/doctr/models/recognition/crnn/tensorflow.html +++ b/v0.2.1/_modules/doctr/models/recognition/crnn/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.crnn.tensorflow - docTR documentation @@ -658,7 +658,7 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    - + diff --git a/v0.2.1/_modules/doctr/models/recognition/master/tensorflow.html b/v0.2.1/_modules/doctr/models/recognition/master/tensorflow.html index 152ebb7e59..aa6aa69325 100644 --- a/v0.2.1/_modules/doctr/models/recognition/master/tensorflow.html +++ b/v0.2.1/_modules/doctr/models/recognition/master/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.master.tensorflow - docTR documentation @@ -655,7 +655,7 @@

    Source code for doctr.models.recognition.master.tensorflow

    - + diff --git a/v0.2.1/_modules/doctr/models/recognition/parseq/tensorflow.html b/v0.2.1/_modules/doctr/models/recognition/parseq/tensorflow.html index 0819737dfc..b181acef53 100644 --- a/v0.2.1/_modules/doctr/models/recognition/parseq/tensorflow.html +++ b/v0.2.1/_modules/doctr/models/recognition/parseq/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.parseq.tensorflow - docTR documentation @@ -845,7 +845,7 @@

    Source code for doctr.models.recognition.parseq.tensorflow

    - + diff --git a/v0.2.1/_modules/doctr/models/recognition/sar.html b/v0.2.1/_modules/doctr/models/recognition/sar.html deleted file mode 100644 index 2482e9f156..0000000000 --- a/v0.2.1/_modules/doctr/models/recognition/sar.html +++ /dev/null @@ -1,712 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.sar - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.sar

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import Sequential, layers
    -from typing import Tuple, Dict, List, Any, Optional
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel
    -from .core import RecognitionPostProcessor
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['SAR', 'SARPostProcessor', 'sar_vgg16_bn', 'sar_resnet31']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'sar_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1-models/sar_vgg16bn-0d7e2c26.zip',
    -    },
    -    'sar_resnet31': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'resnet31', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/sar_resnet31-ea202587.zip',
    -    },
    -}
    -
    -
    -class AttentionModule(layers.Layer, NestedObject):
    -    """Implements attention module of the SAR model
    -
    -    Args:
    -        attention_units: number of hidden attention units
    -
    -    """
    -    def __init__(
    -        self,
    -        attention_units: int
    -    ) -> None:
    -
    -        super().__init__()
    -        self.hidden_state_projector = layers.Conv2D(
    -            attention_units, 1, strides=1, use_bias=False, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.features_projector = layers.Conv2D(
    -            attention_units, 3, strides=1, use_bias=True, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.attention_projector = layers.Conv2D(
    -            1, 1, strides=1, use_bias=False, padding="same", kernel_initializer='he_normal',
    -        )
    -        self.flatten = layers.Flatten()
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        hidden_state: tf.Tensor,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        [H, W] = features.get_shape().as_list()[1:3]
    -        # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units)
    -        hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs)
    -        # shape (N, H, W, vgg_units) -> (N, H, W, attention_units)
    -        features_projection = self.features_projector(features, **kwargs)
    -        projection = tf.math.tanh(hidden_state_projection + features_projection)
    -        # shape (N, H, W, attention_units) -> (N, H, W, 1)
    -        attention = self.attention_projector(projection, **kwargs)
    -        # shape (N, H, W, 1) -> (N, H * W)
    -        attention = self.flatten(attention)
    -        attention = tf.nn.softmax(attention)
    -        # shape (N, H * W) -> (N, H, W, 1)
    -        attention_map = tf.reshape(attention, [-1, H, W, 1])
    -        glimpse = tf.math.multiply(features, attention_map)
    -        # shape (N, H * W) -> (N, 1)
    -        glimpse = tf.reduce_sum(glimpse, axis=[1, 2])
    -        return glimpse
    -
    -
    -class SARDecoder(layers.Layer, NestedObject):
    -    """Implements decoder module of the SAR model
    -
    -    Args:
    -        rnn_units: number of hidden units in recurrent cells
    -        max_length: maximum length of a sequence
    -        vocab_size: number of classes in the model alphabet
    -        embedding_units: number of hidden embedding units
    -        attention_units: number of hidden attention units
    -        num_decoder_layers: number of LSTM layers to stack
    -
    -    """
    -    def __init__(
    -        self,
    -        rnn_units: int,
    -        max_length: int,
    -        vocab_size: int,
    -        embedding_units: int,
    -        attention_units: int,
    -        num_decoder_layers: int = 2,
    -        input_shape: Optional[List[Tuple[Optional[int]]]] = None,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.vocab_size = vocab_size
    -        self.lstm_decoder = layers.StackedRNNCells(
    -            [layers.LSTMCell(rnn_units, dtype=tf.float32, implementation=1) for _ in range(num_decoder_layers)]
    -        )
    -        self.embed = layers.Dense(embedding_units, use_bias=False, input_shape=(None, self.vocab_size + 1))
    -        self.attention_module = AttentionModule(attention_units)
    -        self.output_dense = layers.Dense(vocab_size + 1, use_bias=True, input_shape=(None, 2 * rnn_units))
    -        self.max_length = max_length
    -
    -        # Initialize kernels
    -        if input_shape is not None:
    -            self.attention_module.call(layers.Input(input_shape[0][1:]), layers.Input((1, 1, rnn_units)))
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        holistic: tf.Tensor,
    -        gt: Optional[tf.Tensor] = None,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # initialize states (each of shape (N, rnn_units))
    -        states = self.lstm_decoder.get_initial_state(
    -            inputs=None, batch_size=features.shape[0], dtype=tf.float32
    -        )
    -        # run first step of lstm
    -        # holistic: shape (N, rnn_units)
    -        _, states = self.lstm_decoder(holistic, states, **kwargs)
    -        # Initialize with the index of virtual START symbol (placed after <eos>)
    -        symbol = tf.fill(features.shape[0], self.vocab_size + 1)
    -        logits_list = []
    -        if kwargs.get('training') and gt is None:
    -            raise ValueError('Need to provide labels during training for teacher forcing')
    -        for t in range(self.max_length + 1):  # keep 1 step for <eos>
    -            # one-hot symbol with depth vocab_size + 1
    -            # embeded_symbol: shape (N, embedding_units)
    -            embeded_symbol = self.embed(tf.one_hot(symbol, depth=self.vocab_size + 1), **kwargs)
    -            logits, states = self.lstm_decoder(embeded_symbol, states, **kwargs)
    -            glimpse = self.attention_module(
    -                features, tf.expand_dims(tf.expand_dims(logits, axis=1), axis=1), **kwargs,
    -            )
    -            # logits: shape (N, rnn_units), glimpse: shape (N, 1)
    -            logits = tf.concat([logits, glimpse], axis=-1)
    -            # shape (N, rnn_units + 1) -> (N, vocab_size + 1)
    -            logits = self.output_dense(logits, **kwargs)
    -            # update symbol with predicted logits for t+1 step
    -            if kwargs.get('training'):
    -                symbol = gt[:, t]
    -            else:
    -                symbol = tf.argmax(logits, axis=-1)
    -            logits_list.append(logits)
    -        outputs = tf.stack(logits_list, axis=1)  # shape (N, max_length + 1, vocab_size + 1)
    -
    -        return outputs
    -
    -
    -class SAR(RecognitionModel):
    -    """Implements a SAR architecture as described in `"Show, Attend and Read:A Simple and Strong Baseline for
    -    Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of hidden units in both encoder and decoder LSTM
    -        embedding_units: number of embedding units
    -        attention_units: number of hidden units in attention module
    -        max_length: maximum word length handled by the model
    -        num_decoders: number of LSTM to stack in decoder layer
    -
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'encoder', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor,
    -        vocab: str,
    -        rnn_units: int = 512,
    -        embedding_units: int = 512,
    -        attention_units: int = 512,
    -        max_length: int = 30,
    -        num_decoders: int = 2,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(vocab=vocab, cfg=cfg)
    -
    -        self.max_length = max_length + 1  # Add 1 timestep for EOS after the longest word
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.encoder = Sequential(
    -            [
    -                layers.LSTM(units=rnn_units, return_sequences=True),
    -                layers.LSTM(units=rnn_units, return_sequences=False)
    -            ]
    -        )
    -        # Initialize the kernels (watch out for reduce_max)
    -        self.encoder.build(input_shape=(None,) + self.feat_extractor.output_shape[2:])
    -
    -        self.decoder = SARDecoder(
    -            rnn_units, max_length, len(vocab), embedding_units, attention_units, num_decoders,
    -            input_shape=[self.feat_extractor.output_shape, self.encoder.output_shape]
    -        )
    -
    -        self.postprocessor = SARPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        gt: tf.Tensor,
    -        seq_len: tf.Tensor,
    -    ) -> tf.Tensor:
    -        """Compute categorical cross-entropy loss for the model.
    -        Sequences are masked after the EOS character.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        # Input length : number of timesteps
    -        input_len = tf.shape(model_output)[1]
    -        # Add one for additional <eos> token
    -        seq_len = seq_len + 1
    -        # One-hot gt labels
    -        oh_gt = tf.one_hot(gt, depth=model_output.shape[2])
    -        # Compute loss
    -        cce = tf.nn.softmax_cross_entropy_with_logits(oh_gt, model_output)
    -        # Compute mask
    -        mask_values = tf.zeros_like(cce)
    -        mask_2d = tf.sequence_mask(seq_len, input_len)
    -        masked_loss = tf.where(mask_2d, cce, mask_values)
    -        ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32))
    -        return tf.expand_dims(ce_loss, axis=1)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        pooled_features = tf.reduce_max(features, axis=1)  # vertical max pooling
    -        encoded = self.encoder(pooled_features, **kwargs)
    -        if target is not None:
    -            gt, seq_len = self.compute_target(target)
    -        decoded_features = self.decoder(features, encoded, gt=None if target is None else gt, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = decoded_features
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(decoded_features)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(decoded_features, gt, seq_len)
    -
    -        return out
    -
    -
    -class SARPostProcessor(RecognitionPostProcessor):
    -    """Post processor for SAR architectures
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor,
    -    ) -> List[Tuple[str, float]]:
    -        # compute pred with argmax for attention models
    -        out_idxs = tf.math.argmax(logits, axis=2)
    -        # N x L
    -        probs = tf.gather(tf.nn.softmax(logits, axis=-1), out_idxs, axis=-1, batch_dims=2)
    -        # Take the minimum confidence of the sequence
    -        probs = tf.math.reduce_min(probs, axis=1)
    -
    -        # decode raw output of the model with tf_label_to_idx
    -        out_idxs = tf.cast(out_idxs, dtype='int32')
    -        decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(self._embedding, out_idxs), axis=-1)
    -        decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -def _sar(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> SAR:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -    _cfg['embedding_units'] = kwargs.get('embedding_units', _cfg['rnn_units'])
    -    _cfg['attention_units'] = kwargs.get('attention_units', _cfg['rnn_units'])
    -    _cfg['max_length'] = kwargs.get('max_length', _cfg['max_length'])
    -    _cfg['num_decoders'] = kwargs.get('num_decoders', _cfg['num_decoders'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[default_cfgs[arch]['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -    kwargs['embedding_units'] = _cfg['embedding_units']
    -    kwargs['attention_units'] = _cfg['attention_units']
    -    kwargs['max_length'] = _cfg['max_length']
    -    kwargs['num_decoders'] = _cfg['num_decoders']
    -
    -    # Build the model
    -    model = SAR(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, default_cfgs[arch]['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a VGG16 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import sar_vgg16_bn - >>> model = sar_vgg16_bn(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_vgg16_bn', pretrained, **kwargs)
    - - - -
    -[docs] -def sar_resnet31(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a resnet-31 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example: - >>> import tensorflow as tf - >>> from doctr.models import sar_resnet31 - >>> model = sar_resnet31(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_resnet31', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.1/_modules/doctr/models/recognition/sar/tensorflow.html b/v0.2.1/_modules/doctr/models/recognition/sar/tensorflow.html index 010bc2bc54..4a591e6451 100644 --- a/v0.2.1/_modules/doctr/models/recognition/sar/tensorflow.html +++ b/v0.2.1/_modules/doctr/models/recognition/sar/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.sar.tensorflow - docTR documentation @@ -757,7 +757,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    - + diff --git a/v0.2.1/_modules/doctr/models/recognition/vitstr/tensorflow.html b/v0.2.1/_modules/doctr/models/recognition/vitstr/tensorflow.html index 6e101893bf..c594d40a56 100644 --- a/v0.2.1/_modules/doctr/models/recognition/vitstr/tensorflow.html +++ b/v0.2.1/_modules/doctr/models/recognition/vitstr/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.vitstr.tensorflow - docTR documentation @@ -621,7 +621,7 @@

    Source code for doctr.models.recognition.vitstr.tensorflow

    - + diff --git a/v0.2.1/_modules/doctr/models/recognition/zoo.html b/v0.2.1/_modules/doctr/models/recognition/zoo.html index 5b13575396..f664304019 100644 --- a/v0.2.1/_modules/doctr/models/recognition/zoo.html +++ b/v0.2.1/_modules/doctr/models/recognition/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.zoo - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -from typing import Any
    +from typing import Any, List
     
    -from .core import RecognitionPredictor
    -from ..preprocessor import PreProcessor
    -from .. import recognition
    +from doctr.file_utils import is_tf_available
    +from doctr.models.preprocessor import PreProcessor
     
    +from .. import recognition
    +from .predictor import RecognitionPredictor
     
     __all__ = ["recognition_predictor"]
     
    -ARCHS = ['crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31']
    +
    +ARCHS: List[str] = [
    +    "crnn_vgg16_bn",
    +    "crnn_mobilenet_v3_small",
    +    "crnn_mobilenet_v3_large",
    +    "sar_resnet31",
    +    "master",
    +    "vitstr_small",
    +    "vitstr_base",
    +    "parseq",
    +]
     
     
    -def _predictor(arch: str, pretrained: bool, **kwargs: Any) -> RecognitionPredictor:
    +def _predictor(arch: Any, pretrained: bool, **kwargs: Any) -> RecognitionPredictor:
    +    if isinstance(arch, str):
    +        if arch not in ARCHS:
    +            raise ValueError(f"unknown architecture '{arch}'")
     
    -    if arch not in ARCHS:
    -        raise ValueError(f"unknown architecture '{arch}'")
    +        _model = recognition.__dict__[arch](
    +            pretrained=pretrained, pretrained_backbone=kwargs.get("pretrained_backbone", True)
    +        )
    +    else:
    +        if not isinstance(
    +            arch, (recognition.CRNN, recognition.SAR, recognition.MASTER, recognition.ViTSTR, recognition.PARSeq)
    +        ):
    +            raise ValueError(f"unknown architecture: {type(arch)}")
    +        _model = arch
     
    -    _model = recognition.__dict__[arch](pretrained=pretrained)
    -    kwargs['mean'] = kwargs.get('mean', _model.cfg['mean'])
    -    kwargs['std'] = kwargs.get('std', _model.cfg['std'])
    -    kwargs['batch_size'] = kwargs.get('batch_size', 32)
    -    predictor = RecognitionPredictor(
    -        PreProcessor(_model.cfg['input_shape'][:2], preserve_aspect_ratio=True, **kwargs),
    -        _model
    -    )
    +    kwargs.pop("pretrained_backbone", None)
    +
    +    kwargs["mean"] = kwargs.get("mean", _model.cfg["mean"])
    +    kwargs["std"] = kwargs.get("std", _model.cfg["std"])
    +    kwargs["batch_size"] = kwargs.get("batch_size", 128)
    +    input_shape = _model.cfg["input_shape"][:2] if is_tf_available() else _model.cfg["input_shape"][-2:]
    +    predictor = RecognitionPredictor(PreProcessor(input_shape, preserve_aspect_ratio=True, **kwargs), _model)
     
         return predictor
     
     
     
    -[docs] -def recognition_predictor(arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) -> RecognitionPredictor: +[docs] +def recognition_predictor( + arch: Any = "crnn_vgg16_bn", + pretrained: bool = False, + symmetric_pad: bool = False, + batch_size: int = 128, + **kwargs: Any, +) -> RecognitionPredictor: """Text recognition architecture. Example:: @@ -321,14 +369,18 @@

    Source code for doctr.models.recognition.zoo

            >>> out = model([input_page])
     
         Args:
    -        arch: name of the architecture to use ('crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31')
    +    ----
    +        arch: name of the architecture or model itself to use (e.g. 'crnn_vgg16_bn')
             pretrained: If True, returns a model pre-trained on our text recognition dataset
    +        symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right
    +        batch_size: number of samples the model processes in parallel
    +        **kwargs: optional parameters to be passed to the architecture
     
         Returns:
    +    -------
             Recognition predictor
         """
    -
    -    return _predictor(arch, pretrained, **kwargs)
    + return _predictor(arch=arch, pretrained=pretrained, symmetric_pad=symmetric_pad, batch_size=batch_size, **kwargs)
    @@ -362,8 +414,8 @@

    Source code for doctr.models.recognition.zoo

       
    -
    - +
    + diff --git a/v0.2.1/_modules/doctr/models/zoo.html b/v0.2.1/_modules/doctr/models/zoo.html index dec6857019..d459671648 100644 --- a/v0.2.1/_modules/doctr/models/zoo.html +++ b/v0.2.1/_modules/doctr/models/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.zoo - docTR documentation @@ -225,15 +225,42 @@

    Source code for doctr.models.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     from typing import Any
    -from .core import OCRPredictor
    +
     from .detection.zoo import detection_predictor
    +from .kie_predictor import KIEPredictor
    +from .predictor import OCRPredictor
     from .recognition.zoo import recognition_predictor
     
    +__all__ = ["ocr_predictor", "kie_predictor"]
     
    -__all__ = ["ocr_predictor"]
    -
    -
    -def _predictor(det_arch: str, reco_arch: str, pretrained: bool, det_bs=2, reco_bs=128) -> OCRPredictor:
     
    +def _predictor(
    +    det_arch: Any,
    +    reco_arch: Any,
    +    pretrained: bool,
    +    pretrained_backbone: bool = True,
    +    assume_straight_pages: bool = True,
    +    preserve_aspect_ratio: bool = True,
    +    symmetric_pad: bool = True,
    +    det_bs: int = 2,
    +    reco_bs: int = 128,
    +    detect_orientation: bool = False,
    +    straighten_pages: bool = False,
    +    detect_language: bool = False,
    +    **kwargs,
    +) -> OCRPredictor:
         # Detection
    -    det_predictor = detection_predictor(det_arch, pretrained=pretrained, batch_size=det_bs)
    +    det_predictor = detection_predictor(
    +        det_arch,
    +        pretrained=pretrained,
    +        pretrained_backbone=pretrained_backbone,
    +        batch_size=det_bs,
    +        assume_straight_pages=assume_straight_pages,
    +        preserve_aspect_ratio=preserve_aspect_ratio,
    +        symmetric_pad=symmetric_pad,
    +    )
     
         # Recognition
    -    reco_predictor = recognition_predictor(reco_arch, pretrained=pretrained, batch_size=reco_bs)
    +    reco_predictor = recognition_predictor(
    +        reco_arch,
    +        pretrained=pretrained,
    +        pretrained_backbone=pretrained_backbone,
    +        batch_size=reco_bs,
    +    )
     
    -    return OCRPredictor(det_predictor, reco_predictor)
    +    return OCRPredictor(
    +        det_predictor,
    +        reco_predictor,
    +        assume_straight_pages=assume_straight_pages,
    +        preserve_aspect_ratio=preserve_aspect_ratio,
    +        symmetric_pad=symmetric_pad,
    +        detect_orientation=detect_orientation,
    +        straighten_pages=straighten_pages,
    +        detect_language=detect_language,
    +        **kwargs,
    +    )
     
     
     
    -[docs] +[docs] def ocr_predictor( - det_arch: str = 'db_resnet50', - reco_arch: str = 'crnn_vgg16_bn', + det_arch: Any = "fast_base", + reco_arch: Any = "crnn_vgg16_bn", pretrained: bool = False, - **kwargs: Any + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + export_as_straight_boxes: bool = False, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs: Any, ) -> OCRPredictor: """End-to-end OCR architecture using one model for localization, and another for text recognition. - Example:: - >>> import numpy as np - >>> from doctr.models import ocr_predictor - >>> model = ocr_predictor(pretrained=True) - >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) - >>> out = model([input_page]) + >>> 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(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) Args: - arch: name of the architecture to use ('db_sar_vgg', 'db_sar_resnet', 'db_crnn_vgg', 'db_crnn_resnet') + ---- + det_arch: name of the detection architecture or the model itself to use + (e.g. 'db_resnet50', 'db_mobilenet_v3_large') + reco_arch: name of the recognition architecture or the model itself to use + (e.g. 'crnn_vgg16_bn', 'sar_resnet31') pretrained: If True, returns a model pre-trained on our OCR dataset + pretrained_backbone: If True, returns a model with a pretrained backbone + assume_straight_pages: if True, speeds up the inference by assuming you only pass straight pages + without rotated textual elements. + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it. + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right. + export_as_straight_boxes: when assume_straight_pages is set to False, export final predictions + (potentially rotated) as straight bounding boxes. + detect_orientation: if True, the estimated general page orientation will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + straighten_pages: if True, estimates the page general orientation + based on the segmentation map median line orientation. + Then, rotates page before passing it again to the deep learning detection module. + Doing so will improve performances for documents with page-uniform rotations. + detect_language: if True, the language prediction will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + kwargs: keyword args of `OCRPredictor` Returns: + ------- OCR predictor """ + return _predictor( + det_arch, + reco_arch, + pretrained, + pretrained_backbone=pretrained_backbone, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + export_as_straight_boxes=export_as_straight_boxes, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + )
    + + - return _predictor(det_arch, reco_arch, pretrained, **kwargs)
    +def _kie_predictor( + det_arch: Any, + reco_arch: Any, + pretrained: bool, + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + det_bs: int = 2, + reco_bs: int = 128, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs, +) -> KIEPredictor: + # Detection + det_predictor = detection_predictor( + det_arch, + pretrained=pretrained, + pretrained_backbone=pretrained_backbone, + batch_size=det_bs, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + ) + + # Recognition + reco_predictor = recognition_predictor( + reco_arch, + pretrained=pretrained, + pretrained_backbone=pretrained_backbone, + batch_size=reco_bs, + ) + + return KIEPredictor( + det_predictor, + reco_predictor, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + ) + + +
    +[docs] +def kie_predictor( + det_arch: Any = "fast_base", + reco_arch: Any = "crnn_vgg16_bn", + pretrained: bool = False, + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + export_as_straight_boxes: bool = False, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs: Any, +) -> KIEPredictor: + """End-to-end KIE architecture using one model for localization, and another for text recognition. + + >>> 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(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) + + Args: + ---- + det_arch: name of the detection architecture or the model itself to use + (e.g. 'db_resnet50', 'db_mobilenet_v3_large') + reco_arch: name of the recognition architecture or the model itself to use + (e.g. 'crnn_vgg16_bn', 'sar_resnet31') + pretrained: If True, returns a model pre-trained on our OCR dataset + pretrained_backbone: If True, returns a model with a pretrained backbone + assume_straight_pages: if True, speeds up the inference by assuming you only pass straight pages + without rotated textual elements. + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it. + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right. + export_as_straight_boxes: when assume_straight_pages is set to False, export final predictions + (potentially rotated) as straight bounding boxes. + detect_orientation: if True, the estimated general page orientation will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + straighten_pages: if True, estimates the page general orientation + based on the segmentation map median line orientation. + Then, rotates page before passing it again to the deep learning detection module. + Doing so will improve performances for documents with page-uniform rotations. + detect_language: if True, the language prediction will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + kwargs: keyword args of `OCRPredictor` + + Returns: + ------- + KIE predictor + """ + return _kie_predictor( + det_arch, + reco_arch, + pretrained, + pretrained_backbone=pretrained_backbone, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + export_as_straight_boxes=export_as_straight_boxes, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + )
    @@ -353,8 +575,8 @@

    Source code for doctr.models.zoo

           
         
       
    - - + + diff --git a/v0.2.1/_modules/doctr/transforms/modules.html b/v0.2.1/_modules/doctr/transforms/modules.html deleted file mode 100644 index ba8269e7ef..0000000000 --- a/v0.2.1/_modules/doctr/transforms/modules.html +++ /dev/null @@ -1,734 +0,0 @@ - - - - - - - - - - - - doctr.transforms.modules - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.transforms.modules

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import random
    -import tensorflow as tf
    -from typing import List, Any, Tuple, Callable
    -
    -from doctr.utils.repr import NestedObject
    -from . import functional as F
    -
    -
    -__all__ = ['Compose', 'Resize', 'Normalize', 'LambdaTransformation', 'ToGray', 'ColorInversion',
    -           'RandomBrightness', 'RandomContrast', 'RandomSaturation', 'RandomHue', 'RandomGamma', 'RandomJpegQuality',
    -           'OneOf', 'RandomApply']
    -
    -
    -
    -[docs] -class Compose(NestedObject): - """Implements a wrapper that will apply transformations sequentially - - Example:: - >>> from doctr.transforms import Compose, Resize - >>> import tensorflow as tf - >>> transfos = Compose([Resize((32, 32))]) - >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformation modules - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, x: Any) -> Any: - for t in self.transforms: - x = t(x) - - return x
    - - - -
    -[docs] -class Resize(NestedObject): - """Resizes a tensor to a target size - - Example:: - >>> from doctr.transforms import Resize - >>> import tensorflow as tf - >>> transfo = Resize((32, 32)) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - output_size: expected output size - method: interpolation method - preserve_aspect_ratio: if `True`, preserve aspect ratio and pad the rest with zeros - symmetric_pad: if `True` while preserving aspect ratio, the padding will be done symmetrically - """ - def __init__( - self, - output_size: Tuple[int, int], - method: str = 'bilinear', - preserve_aspect_ratio: bool = False, - symmetric_pad: bool = False, - ) -> None: - self.output_size = output_size - self.method = method - self.preserve_aspect_ratio = preserve_aspect_ratio - self.symmetric_pad = symmetric_pad - - def extra_repr(self) -> str: - _repr = f"output_size={self.output_size}, method='{self.method}'" - if self.preserve_aspect_ratio: - _repr += f", preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}" - return _repr - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img = tf.image.resize(img, self.output_size, self.method, self.preserve_aspect_ratio) - if self.preserve_aspect_ratio: - # pad width - if not self.symmetric_pad: - offset = (0, 0) - elif self.output_size[0] == img.shape[0]: - offset = (0, int((self.output_size[1] - img.shape[1]) / 2)) - else: - offset = (int((self.output_size[0] - img.shape[0]) / 2), 0) - img = tf.image.pad_to_bounding_box(img, *offset, *self.output_size) - return img
    - - - -
    -[docs] -class Normalize(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - mean: average value per channel - std: standard deviation per channel - """ - def __init__(self, mean: Tuple[float, float, float], std: Tuple[float, float, float]) -> None: - self.mean = tf.constant(mean, dtype=tf.float32) - self.std = tf.constant(std, dtype=tf.float32) - - def extra_repr(self) -> str: - return f"mean={self.mean.numpy().tolist()}, std={self.std.numpy().tolist()}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img -= self.mean - img /= self.std - return img
    - - - -
    -[docs] -class LambdaTransformation(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import LambdaTransformation - >>> import tensorflow as tf - >>> transfo = LambdaTransformation(lambda x: x/ 255.) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - fn: the function to be applied to the input tensor - """ - def __init__(self, fn: Callable[[tf.Tensor], tf.Tensor]) -> None: - self.fn = fn - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return self.fn(img)
    - - - -
    -[docs] -class ToGray(NestedObject): - """Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ToGray() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - """ - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.rgb_to_grayscale(img)
    - - - -
    -[docs] -class ColorInversion(NestedObject): - """Applies the following tranformation to a tensor (image or batch of images): - convert to grayscale, colorize (shift 0-values randomly), and then invert colors - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ColorInversion(min_val=0.6) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_val: range [min_val, 1] to colorize RGB pixels - """ - def __init__(self, min_val: float = 0.6) -> None: - self.min_val = min_val - - def extra_repr(self) -> str: - return f"min_val={self.min_val}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return F.invert_colors(img, self.min_val)
    - - - -
    -[docs] -class RandomBrightness(NestedObject): - """Randomly adjust brightness of a tensor (batch of images or image) by adding a delta - to all pixels - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Brightness() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - p: probability to apply transformation - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_brightness(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomContrast(NestedObject): - """Randomly adjust contrast of a tensor (batch of images or image) by adjusting - each pixel: (img - mean) * contrast_factor + mean. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Contrast() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1) - """ - def __init__(self, delta: float = .3) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_contrast(img, lower=1 - self.delta, upper=1 / (1 - self.delta))
    - - - -
    -[docs] -class RandomSaturation(NestedObject): - """Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and - increasing saturation by a factor. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Saturation() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1) - """ - def __init__(self, delta: float = .5) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_saturation(img, lower=1 - self.delta, upper=1 + self.delta)
    - - - -
    -[docs] -class RandomHue(NestedObject): - """Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Hue() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_hue(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomGamma(NestedObject): - """randomly performs gamma correction for a tensor (batch of images or image) - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Gamma() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_gamma: non-negative real number, lower bound for gamma param - max_gamma: non-negative real number, upper bound for gamma - min_gain: lower bound for constant multiplier - max_gain: upper bound for constant multiplier - """ - def __init__( - self, - min_gamma: float = 0.5, - max_gamma: float = 1.5, - min_gain: float = 0.8, - max_gain: float = 1.2, - ) -> None: - self.min_gamma = min_gamma - self.max_gamma = max_gamma - self.min_gain = min_gain - self.max_gain = max_gain - - def extra_repr(self) -> str: - return f"""gamma_range=({self.min_gamma}, {self.max_gamma}), - gain_range=({self.min_gain}, {self.max_gain})""" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - gamma = random.uniform(self.min_gamma, self.max_gamma) - gain = random.uniform(self.min_gain, self.max_gain) - return tf.image.adjust_gamma(img, gamma=gamma, gain=gain)
    - - - -
    -[docs] -class RandomJpegQuality(NestedObject): - """Randomly adjust jpeg quality of a 3 dimensional RGB image - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = JpegQuality() - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - min_quality: int between [0, 100] - max_quality: int between [0, 100] - """ - def __init__(self, min_quality: int = 60, max_quality: int = 100) -> None: - self.min_quality = min_quality - self.max_quality = max_quality - - def extra_repr(self) -> str: - return f"min_quality={self.min_quality}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_jpeg_quality( - img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality - )
    - - - -
    -[docs] -class OneOf(NestedObject): - """Randomly apply one of the input transformations - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = OneOf([JpegQuality(), Gamma()]) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformations, one only will be picked - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - # Pick transformation - transfo = self.transforms[int(random.random() * len(self.transforms))] - # Apply - return transfo(img)
    - - - -
    -[docs] -class RandomApply(NestedObject): - """Apply with a probability p the input transformation - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = RandomApply(Gamma(), p=.5) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transform: transformation to apply - p: probability to apply - """ - def __init__(self, transform: NestedObject, p: float = .5) -> None: - self.transform = transform - self.p = p - - def extra_repr(self) -> str: - return f"transform={self.transform}, p={self.p}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - if random.random() < self.p: - return self.transform(img) - return img
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.1/_modules/doctr/transforms/modules/base.html b/v0.2.1/_modules/doctr/transforms/modules/base.html index 96ebd680b7..4596df3848 100644 --- a/v0.2.1/_modules/doctr/transforms/modules/base.html +++ b/v0.2.1/_modules/doctr/transforms/modules/base.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.base - docTR documentation @@ -643,7 +643,7 @@

    Source code for doctr.transforms.modules.base

    - + diff --git a/v0.2.1/_modules/doctr/transforms/modules/tensorflow.html b/v0.2.1/_modules/doctr/transforms/modules/tensorflow.html index 0e18bcc922..acbbe96225 100644 --- a/v0.2.1/_modules/doctr/transforms/modules/tensorflow.html +++ b/v0.2.1/_modules/doctr/transforms/modules/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.tensorflow - docTR documentation @@ -956,7 +956,7 @@

    Source code for doctr.transforms.modules.tensorflow

    - + diff --git a/v0.2.1/_modules/doctr/utils/metrics.html b/v0.2.1/_modules/doctr/utils/metrics.html index f0ed19117b..8a37d5949a 100644 --- a/v0.2.1/_modules/doctr/utils/metrics.html +++ b/v0.2.1/_modules/doctr/utils/metrics.html @@ -13,7 +13,7 @@ - + doctr.utils.metrics - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.utils.metrics

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
    +
    +from typing import Dict, List, Optional, Tuple
     
     import numpy as np
    -from typing import List, Tuple, Dict
    -from unidecode import unidecode
    +from anyascii import anyascii
     from scipy.optimize import linear_sum_assignment
    +from shapely.geometry import Polygon
     
    -__all__ = ['TextMatch', 'box_iou', 'LocalizationConfusion', 'OCRMetric']
    +__all__ = [
    +    "TextMatch",
    +    "box_iou",
    +    "polygon_iou",
    +    "nms",
    +    "LocalizationConfusion",
    +    "OCRMetric",
    +    "DetectionMetric",
    +]
     
     
     def string_match(word1: str, word2: str) -> Tuple[bool, bool, bool, bool]:
    -    """Perform string comparison with multiple levels of tolerance
    +    """Performs string comparison with multiple levels of tolerance
     
         Args:
    +    ----
             word1: a string
             word2: another string
     
         Returns:
    +    -------
             a tuple with booleans specifying respectively whether the raw strings, their lower-case counterparts, their
    -            unidecode counterparts and their lower-case unidecode counterparts match
    +            anyascii counterparts and their lower-case anyascii counterparts match
         """
    -    raw_match = (word1 == word2)
    -    caseless_match = (word1.lower() == word2.lower())
    -    unidecode_match = (unidecode(word1) == unidecode(word2))
    +    raw_match = word1 == word2
    +    caseless_match = word1.lower() == word2.lower()
    +    anyascii_match = anyascii(word1) == anyascii(word2)
     
         # Warning: the order is important here otherwise the pair ("EUR", "€") cannot be matched
    -    unicase_match = (unidecode(word1).lower() == unidecode(word2).lower())
    +    unicase_match = anyascii(word1).lower() == anyascii(word2).lower()
     
    -    return raw_match, caseless_match, unidecode_match, unicase_match
    +    return raw_match, caseless_match, anyascii_match, unicase_match
     
     
     
    -[docs] +[docs] class TextMatch: - """Implements text match metric (word-level accuracy) for recognition task. + r"""Implements text match metric (word-level accuracy) for recognition task. The raw aggregated metric is computed as follows: .. math:: - \\forall X, Y \\in \\mathcal{W}^N, - TextMatch(X, Y) = \\frac{1}{N} \\sum\\limits_{i=1}^N f_{Y_i}(X_i) + \forall X, Y \in \mathcal{W}^N, + TextMatch(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N f_{Y_i}(X_i) with the indicator function :math:`f_{a}` defined as: .. math:: - \\forall a, x \\in \\mathcal{W}, - f_a(x) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } x = a \\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{W}` is the set of all possible character sequences, + \forall a, x \in \mathcal{W}, + f_a(x) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } x = a \\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{W}` is the set of all possible character sequences, :math:`N` is a strictly positive integer. - Example:: - >>> from doctr.utils import TextMatch - >>> metric = TextMatch() - >>> metric.update(['Hello', 'world'], ['hello', 'world']) - >>> metric.summary() + >>> from doctr.utils import TextMatch + >>> metric = TextMatch() + >>> metric.update(['Hello', 'world'], ['hello', 'world']) + >>> metric.summary() """ def __init__(self) -> None: self.reset() +
    +[docs] def update( self, gt: List[str], @@ -351,29 +386,32 @@

    Source code for doctr.utils.metrics

             """Update the state of the metric with new predictions
     
             Args:
    +        ----
                 gt: list of groung-truth character sequences
    -            pred: list of predicted character sequences"""
    -
    +            pred: list of predicted character sequences
    +        """
             if len(gt) != len(pred):
                 raise AssertionError("prediction size does not match with ground-truth labels size")
     
             for gt_word, pred_word in zip(gt, pred):
    -            _raw, _caseless, _unidecode, _unicase = string_match(gt_word, pred_word)
    +            _raw, _caseless, _anyascii, _unicase = string_match(gt_word, pred_word)
                 self.raw += int(_raw)
                 self.caseless += int(_caseless)
    -            self.unidecode += int(_unidecode)
    +            self.anyascii += int(_anyascii)
                 self.unicase += int(_unicase)
     
    -        self.total += len(gt)
    +        self.total += len(gt)
    +
    -[docs] +[docs] def summary(self) -> Dict[str, float]: """Computes the aggregated metrics - Returns: - a dictionary with the exact match score for the raw data, its lower-case counterpart, its unidecode - counterpart and its lower-case unidecode counterpart + Returns + ------- + a dictionary with the exact match score for the raw data, its lower-case counterpart, its anyascii + counterpart and its lower-case anyascii counterpart """ if self.total == 0: raise AssertionError("you need to update the metric before getting the summary") @@ -381,7 +419,7 @@

    Source code for doctr.utils.metrics

             return dict(
                 raw=self.raw / self.total,
                 caseless=self.caseless / self.total,
    -            unidecode=self.unidecode / self.total,
    +            anyascii=self.anyascii / self.total,
                 unicase=self.unicase / self.total,
             )
    @@ -389,24 +427,25 @@

    Source code for doctr.utils.metrics

         def reset(self) -> None:
             self.raw = 0
             self.caseless = 0
    -        self.unidecode = 0
    +        self.anyascii = 0
             self.unicase = 0
             self.total = 0
    def box_iou(boxes_1: np.ndarray, boxes_2: np.ndarray) -> np.ndarray: - """Compute the IoU between two sets of bounding boxes + """Computes the IoU between two sets of bounding boxes Args: + ---- boxes_1: bounding boxes of shape (N, 4) in format (xmin, ymin, xmax, ymax) boxes_2: bounding boxes of shape (M, 4) in format (xmin, ymin, xmax, ymax) Returns: + ------- the IoU matrix of shape (N, M) """ - - iou_mat = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32) + iou_mat: np.ndarray = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32) if boxes_1.shape[0] > 0 and boxes_2.shape[0] > 0: l1, t1, r1, b1 = np.split(boxes_1, 4, axis=1) @@ -417,62 +456,150 @@

    Source code for doctr.utils.metrics

             right = np.minimum(r1, r2.T)
             bot = np.minimum(b1, b2.T)
     
    -        intersection = np.clip(right - left, 0, np.Inf) * np.clip(bot - top, 0, np.Inf)
    +        intersection = np.clip(right - left, 0, np.inf) * np.clip(bot - top, 0, np.inf)
             union = (r1 - l1) * (b1 - t1) + ((r2 - l2) * (b2 - t2)).T - intersection
             iou_mat = intersection / union
     
         return iou_mat
     
     
    +def polygon_iou(polys_1: np.ndarray, polys_2: np.ndarray) -> np.ndarray:
    +    """Computes the IoU between two sets of rotated bounding boxes
    +
    +    Args:
    +    ----
    +        polys_1: rotated bounding boxes of shape (N, 4, 2)
    +        polys_2: rotated bounding boxes of shape (M, 4, 2)
    +        mask_shape: spatial shape of the intermediate masks
    +        use_broadcasting: if set to True, leverage broadcasting speedup by consuming more memory
    +
    +    Returns:
    +    -------
    +        the IoU matrix of shape (N, M)
    +    """
    +    if polys_1.ndim != 3 or polys_2.ndim != 3:
    +        raise AssertionError("expects boxes to be in format (N, 4, 2)")
    +
    +    iou_mat = np.zeros((polys_1.shape[0], polys_2.shape[0]), dtype=np.float32)
    +
    +    shapely_polys_1 = [Polygon(poly) for poly in polys_1]
    +    shapely_polys_2 = [Polygon(poly) for poly in polys_2]
    +
    +    for i, poly1 in enumerate(shapely_polys_1):
    +        for j, poly2 in enumerate(shapely_polys_2):
    +            intersection_area = poly1.intersection(poly2).area
    +            union_area = poly1.area + poly2.area - intersection_area
    +            iou_mat[i, j] = intersection_area / union_area
    +
    +    return iou_mat
    +
    +
    +def nms(boxes: np.ndarray, thresh: float = 0.5) -> List[int]:
    +    """Perform non-max suppression, borrowed from <https://github.com/rbgirshick/fast-rcnn>`_.
    +
    +    Args:
    +    ----
    +        boxes: np array of straight boxes: (*, 5), (xmin, ymin, xmax, ymax, score)
    +        thresh: iou threshold to perform box suppression.
    +
    +    Returns:
    +    -------
    +        A list of box indexes to keep
    +    """
    +    x1 = boxes[:, 0]
    +    y1 = boxes[:, 1]
    +    x2 = boxes[:, 2]
    +    y2 = boxes[:, 3]
    +    scores = boxes[:, 4]
    +
    +    areas = (x2 - x1) * (y2 - y1)
    +    order = scores.argsort()[::-1]
    +
    +    keep = []
    +    while order.size > 0:
    +        i = order[0]
    +        keep.append(i)
    +        xx1 = np.maximum(x1[i], x1[order[1:]])
    +        yy1 = np.maximum(y1[i], y1[order[1:]])
    +        xx2 = np.minimum(x2[i], x2[order[1:]])
    +        yy2 = np.minimum(y2[i], y2[order[1:]])
    +
    +        w = np.maximum(0.0, xx2 - xx1)
    +        h = np.maximum(0.0, yy2 - yy1)
    +        inter = w * h
    +        ovr = inter / (areas[i] + areas[order[1:]] - inter)
    +
    +        inds = np.where(ovr <= thresh)[0]
    +        order = order[inds + 1]
    +    return keep
    +
    +
     
    -[docs] +[docs] class LocalizationConfusion: - """Implements common confusion metrics and mean IoU for localization evaluation. + r"""Implements common confusion metrics and mean IoU for localization evaluation. The aggregated metrics are computed as follows: .. math:: - \\forall Y \\in \\mathcal{B}^N, \\forall X \\in \\mathcal{B}^M, \\\\ - Recall(X, Y) = \\frac{1}{N} \\sum\\limits_{i=1}^N g_{X}(Y_i) \\\\ - Precision(X, Y) = \\frac{1}{M} \\sum\\limits_{i=1}^N g_{X}(Y_i) \\\\ - meanIoU(X, Y) = \\frac{1}{M} \\sum\\limits_{i=1}^M \\max\\limits_{j \\in [1, N]} IoU(X_i, Y_j) + \forall Y \in \mathcal{B}^N, \forall X \in \mathcal{B}^M, \\ + Recall(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N g_{X}(Y_i) \\ + Precision(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M g_{X}(Y_i) \\ + meanIoU(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(X_i, Y_j) with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and :math:`y`, and the function :math:`g_{X}` defined as: .. math:: - \\forall y \\in \\mathcal{B}, - g_X(y) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } y\\mbox{ has been assigned to any }(X_i)_i\\mbox{ with an }IoU \\geq 0.5 \\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{B}` is the set of possible bounding boxes, + \forall y \in \mathcal{B}, + g_X(y) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } y\mbox{ has been assigned to any }(X_i)_i\mbox{ with an }IoU \geq 0.5 \\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. - Example:: - >>> import numpy as np - >>> from doctr.utils import LocalizationConfusion - >>> metric = LocalizationConfusion(iou_thresh=0.5) - >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]])) - >>> metric.summary() + >>> import numpy as np + >>> from doctr.utils import LocalizationConfusion + >>> metric = LocalizationConfusion(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]])) + >>> metric.summary() Args: + ---- iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format """ - def __init__(self, iou_thresh: float = 0.5) -> None: + def __init__( + self, + iou_thresh: float = 0.5, + use_polygons: bool = False, + ) -> None: self.iou_thresh = iou_thresh + self.use_polygons = use_polygons self.reset() +
    +[docs] def update(self, gts: np.ndarray, preds: np.ndarray) -> None: + """Updates the metric + Args: + ---- + gts: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones + preds: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones + """ if preds.shape[0] > 0: # Compute IoU - iou_mat = box_iou(gts, preds) - self.tot_iou += float(iou_mat.max(axis=1).sum()) + if self.use_polygons: + iou_mat = polygon_iou(gts, preds) + else: + iou_mat = box_iou(gts, preds) + self.tot_iou += float(iou_mat.max(axis=0).sum()) # Assign pairs gt_indices, pred_indices = linear_sum_assignment(-iou_mat) @@ -480,17 +607,18 @@

    Source code for doctr.utils.metrics

     
             # Update counts
             self.num_gts += gts.shape[0]
    -        self.num_preds += preds.shape[0]
    +        self.num_preds += preds.shape[0]
    +
    -[docs] - def summary(self) -> Tuple[float, float, float]: +[docs] + def summary(self) -> Tuple[Optional[float], Optional[float], Optional[float]]: """Computes the aggregated metrics - Returns: + Returns + ------- a tuple with the recall, precision and meanIoU scores """ - # Recall recall = self.matches / self.num_gts if self.num_gts > 0 else None @@ -498,7 +626,7 @@

    Source code for doctr.utils.metrics

             precision = self.matches / self.num_preds if self.num_preds > 0 else None
     
             # mean IoU
    -        mean_iou = self.tot_iou / self.num_preds if self.num_preds > 0 else None
    +        mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None
     
             return recall, precision, mean_iou
    @@ -507,57 +635,65 @@

    Source code for doctr.utils.metrics

             self.num_gts = 0
             self.num_preds = 0
             self.matches = 0
    -        self.tot_iou = 0.
    + self.tot_iou = 0.0
    -[docs] +[docs] class OCRMetric: - """Implements end-to-end OCR metric. + r"""Implements an end-to-end OCR metric. The aggregated metrics are computed as follows: .. math:: - \\forall (B, L) \\in \\mathcal{B}^N \\times \\mathcal{L}^N, - \\forall (\\hat{B}, \\hat{L}) \\in \\mathcal{B}^M \\times \\mathcal{L}^M, \\\\ - Recall(B, \\hat{B}, L, \\hat{L}) = \\frac{1}{N} \\sum\\limits_{i=1}^N h_{B,L}(\\hat{B}_i, \\hat{L}_i) \\\\ - Precision(B, \\hat{B}, L, \\hat{L}) = \\frac{1}{M} \\sum\\limits_{i=1}^N h_{B,L}(\\hat{B}_i, \\hat{L}_i) \\\\ - meanIoU(B, \\hat{B}) = \\frac{1}{M} \\sum\\limits_{i=1}^M \\max\\limits_{j \\in [1, N]} IoU(\\hat{B}_i, B_j) + \forall (B, L) \in \mathcal{B}^N \times \mathcal{L}^N, + \forall (\hat{B}, \hat{L}) \in \mathcal{B}^M \times \mathcal{L}^M, \\ + Recall(B, \hat{B}, L, \hat{L}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ + Precision(B, \hat{B}, L, \hat{L}) = \frac{1}{M} \sum\limits_{i=1}^M h_{B,L}(\hat{B}_i, \hat{L}_i) \\ + meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j) with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and :math:`y`, and the function :math:`h_{B, L}` defined as: .. math:: - \\forall (b, l) \\in \\mathcal{B} \\times \\mathcal{L}, - h_{B,L}(b, l) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } b\\mbox{ has been assigned to a given }B_j\\mbox{ with an } \\\\ - & IoU \\geq 0.5 \\mbox{ and that for this assignment, } l = L_j\\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{B}` is the set of possible bounding boxes, - :math:`\\mathcal{L}` is the set of possible character sequences, + \forall (b, l) \in \mathcal{B} \times \mathcal{L}, + h_{B,L}(b, l) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ + & IoU \geq 0.5 \mbox{ and that for this assignment, } l = L_j\\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, + :math:`\mathcal{L}` is the set of possible character sequences, :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. - Example:: - >>> import numpy as np - >>> from doctr.utils import OCRMetric - >>> metric = OCRMetric(iou_thresh=0.5) - >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), - ['hello'], ['hello', 'world']) - >>> metric.summary() + >>> import numpy as np + >>> from doctr.utils import OCRMetric + >>> metric = OCRMetric(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), + >>> ['hello'], ['hello', 'world']) + >>> metric.summary() Args: + ---- iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format """ - def __init__(self, iou_thresh: float = 0.5) -> None: + def __init__( + self, + iou_thresh: float = 0.5, + use_polygons: bool = False, + ) -> None: self.iou_thresh = iou_thresh + self.use_polygons = use_polygons self.reset() +
    +[docs] def update( self, gt_boxes: np.ndarray, @@ -565,44 +701,58 @@

    Source code for doctr.utils.metrics

             gt_labels: List[str],
             pred_labels: List[str],
         ) -> None:
    +        """Updates the metric
     
    +        Args:
    +        ----
    +            gt_boxes: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones
    +            pred_boxes: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones
    +            gt_labels: a list of N string labels
    +            pred_labels: a list of M string labels
    +        """
             if gt_boxes.shape[0] != len(gt_labels) or pred_boxes.shape[0] != len(pred_labels):
    -            raise AssertionError("there should be the same number of boxes and string both for the ground truth "
    -                                 "and the predictions")
    +            raise AssertionError(
    +                "there should be the same number of boxes and string both for the ground truth and the predictions"
    +            )
     
             # Compute IoU
             if pred_boxes.shape[0] > 0:
    -            iou_mat = box_iou(gt_boxes, pred_boxes)
    -            self.tot_iou += float(iou_mat.max(axis=1).sum())
    +            if self.use_polygons:
    +                iou_mat = polygon_iou(gt_boxes, pred_boxes)
    +            else:
    +                iou_mat = box_iou(gt_boxes, pred_boxes)
    +
    +            self.tot_iou += float(iou_mat.max(axis=0).sum())
     
                 # Assign pairs
                 gt_indices, pred_indices = linear_sum_assignment(-iou_mat)
                 is_kept = iou_mat[gt_indices, pred_indices] >= self.iou_thresh
                 # String comparison
                 for gt_idx, pred_idx in zip(gt_indices[is_kept], pred_indices[is_kept]):
    -                _raw, _caseless, _unidecode, _unicase = string_match(gt_labels[gt_idx], pred_labels[pred_idx])
    +                _raw, _caseless, _anyascii, _unicase = string_match(gt_labels[gt_idx], pred_labels[pred_idx])
                     self.raw_matches += int(_raw)
                     self.caseless_matches += int(_caseless)
    -                self.unidecode_matches += int(_unidecode)
    +                self.anyascii_matches += int(_anyascii)
                     self.unicase_matches += int(_unicase)
     
             self.num_gts += gt_boxes.shape[0]
    -        self.num_preds += pred_boxes.shape[0]
    +        self.num_preds += pred_boxes.shape[0]
    +
    -[docs] - def summary(self) -> Tuple[Dict[str, float], Dict[str, float], float]: +[docs] + def summary(self) -> Tuple[Dict[str, Optional[float]], Dict[str, Optional[float]], Optional[float]]: """Computes the aggregated metrics - Returns: - a tuple with the recall & precision for each string comparison flexibility and the mean IoU + Returns + ------- + a tuple with the recall & precision for each string comparison and the mean IoU """ - # Recall recall = dict( raw=self.raw_matches / self.num_gts if self.num_gts > 0 else None, caseless=self.caseless_matches / self.num_gts if self.num_gts > 0 else None, - unidecode=self.unidecode_matches / self.num_gts if self.num_gts > 0 else None, + anyascii=self.anyascii_matches / self.num_gts if self.num_gts > 0 else None, unicase=self.unicase_matches / self.num_gts if self.num_gts > 0 else None, ) @@ -610,12 +760,12 @@

    Source code for doctr.utils.metrics

             precision = dict(
                 raw=self.raw_matches / self.num_preds if self.num_preds > 0 else None,
                 caseless=self.caseless_matches / self.num_preds if self.num_preds > 0 else None,
    -            unidecode=self.unidecode_matches / self.num_preds if self.num_preds > 0 else None,
    +            anyascii=self.anyascii_matches / self.num_preds if self.num_preds > 0 else None,
                 unicase=self.unicase_matches / self.num_preds if self.num_preds > 0 else None,
             )
     
             # mean IoU (overall detected boxes)
    -        mean_iou = self.tot_iou / self.num_preds if self.num_preds > 0 else None
    +        mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None
     
             return recall, precision, mean_iou
    @@ -623,12 +773,136 @@

    Source code for doctr.utils.metrics

         def reset(self) -> None:
             self.num_gts = 0
             self.num_preds = 0
    -        self.tot_iou = 0.
    +        self.tot_iou = 0.0
             self.raw_matches = 0
             self.caseless_matches = 0
    -        self.unidecode_matches = 0
    +        self.anyascii_matches = 0
             self.unicase_matches = 0
    + + +
    +[docs] +class DetectionMetric: + r"""Implements an object detection metric. + + The aggregated metrics are computed as follows: + + .. math:: + \forall (B, C) \in \mathcal{B}^N \times \mathcal{C}^N, + \forall (\hat{B}, \hat{C}) \in \mathcal{B}^M \times \mathcal{C}^M, \\ + Recall(B, \hat{B}, C, \hat{C}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,C}(\hat{B}_i, \hat{C}_i) \\ + Precision(B, \hat{B}, C, \hat{C}) = \frac{1}{M} \sum\limits_{i=1}^M h_{B,C}(\hat{B}_i, \hat{C}_i) \\ + meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j) + + with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and + :math:`y`, and the function :math:`h_{B, C}` defined as: + + .. math:: + \forall (b, c) \in \mathcal{B} \times \mathcal{C}, + h_{B,C}(b, c) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ + & IoU \geq 0.5 \mbox{ and that for this assignment, } c = C_j\\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, + :math:`\mathcal{C}` is the set of possible class indices, + :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. + + >>> import numpy as np + >>> from doctr.utils import DetectionMetric + >>> metric = DetectionMetric(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), + >>> np.zeros(1, dtype=np.int64), np.array([0, 1], dtype=np.int64)) + >>> metric.summary() + + Args: + ---- + iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format + """ + + def __init__( + self, + iou_thresh: float = 0.5, + use_polygons: bool = False, + ) -> None: + self.iou_thresh = iou_thresh + self.use_polygons = use_polygons + self.reset() + +
    +[docs] + def update( + self, + gt_boxes: np.ndarray, + pred_boxes: np.ndarray, + gt_labels: np.ndarray, + pred_labels: np.ndarray, + ) -> None: + """Updates the metric + + Args: + ---- + gt_boxes: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones + pred_boxes: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones + gt_labels: an array of class indices of shape (N,) + pred_labels: an array of class indices of shape (M,) + """ + if gt_boxes.shape[0] != gt_labels.shape[0] or pred_boxes.shape[0] != pred_labels.shape[0]: + raise AssertionError( + "there should be the same number of boxes and string both for the ground truth and the predictions" + ) + + # Compute IoU + if pred_boxes.shape[0] > 0: + if self.use_polygons: + iou_mat = polygon_iou(gt_boxes, pred_boxes) + else: + iou_mat = box_iou(gt_boxes, pred_boxes) + + self.tot_iou += float(iou_mat.max(axis=0).sum()) + + # Assign pairs + gt_indices, pred_indices = linear_sum_assignment(-iou_mat) + is_kept = iou_mat[gt_indices, pred_indices] >= self.iou_thresh + # Category comparison + self.num_matches += int((gt_labels[gt_indices[is_kept]] == pred_labels[pred_indices[is_kept]]).sum()) + + self.num_gts += gt_boxes.shape[0] + self.num_preds += pred_boxes.shape[0]
    + + +
    +[docs] + def summary(self) -> Tuple[Optional[float], Optional[float], Optional[float]]: + """Computes the aggregated metrics + + Returns + ------- + a tuple with the recall & precision for each class prediction and the mean IoU + """ + # Recall + recall = self.num_matches / self.num_gts if self.num_gts > 0 else None + + # Precision + precision = self.num_matches / self.num_preds if self.num_preds > 0 else None + + # mean IoU (overall detected boxes) + mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None + + return recall, precision, mean_iou
    + + + def reset(self) -> None: + self.num_gts = 0 + self.num_preds = 0 + self.tot_iou = 0.0 + self.num_matches = 0
    +
    @@ -661,8 +935,8 @@

    Source code for doctr.utils.metrics

           
         
       
    - - + + diff --git a/v0.2.1/_modules/doctr/utils/visualization.html b/v0.2.1/_modules/doctr/utils/visualization.html index 75fce020ad..c818be6d7b 100644 --- a/v0.2.1/_modules/doctr/utils/visualization.html +++ b/v0.2.1/_modules/doctr/utils/visualization.html @@ -13,7 +13,7 @@ - + doctr.utils.visualization - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.utils.visualization

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
    +import colorsys
    +from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple, Union
     
    -import matplotlib.pyplot as plt
    -from matplotlib.figure import Figure
    +import cv2
     import matplotlib.patches as patches
    -import mplcursors
    +import matplotlib.pyplot as plt
     import numpy as np
    -from typing import Tuple, List, Dict, Any
    +from matplotlib.figure import Figure
     
    -from .common_types import BoundingBox
    +from .common_types import BoundingBox, Polygon4P
     
    -__all__ = ['visualize_page']
    +__all__ = ["visualize_page", "visualize_kie_page", "draw_boxes"]
     
     
    -def create_rect_patch(
    +def rect_patch(
         geometry: BoundingBox,
    -    label: str,
         page_dimensions: Tuple[int, int],
    -    color: Tuple[int, int, int],
    +    label: Optional[str] = None,
    +    color: Tuple[float, float, float] = (0, 0, 0),
         alpha: float = 0.3,
         linewidth: int = 2,
         fill: bool = True,
    -) -> patches.Patch:
    -    """Create a matplotlib patch (rectangle) bounding the element
    +    preserve_aspect_ratio: bool = False,
    +) -> patches.Rectangle:
    +    """Create a matplotlib rectangular patch for the element
     
         Args:
    +    ----
             geometry: bounding box of the element
    +        page_dimensions: dimensions of the Page in format (height, width)
             label: label to display when hovered
    -        page_dimensions: dimensions of the Page
             color: color to draw box
             alpha: opacity parameter to fill the boxes, 0 = transparent
             linewidth: line width
    +        fill: whether the patch should be filled
    +        preserve_aspect_ratio: pass True if you passed True to the predictor
     
         Returns:
    +    -------
             a rectangular Patch
         """
    -    h, w = page_dimensions
    +    if len(geometry) != 2 or any(not isinstance(elt, tuple) or len(elt) != 2 for elt in geometry):
    +        raise ValueError("invalid geometry format")
    +
    +    # Unpack
    +    height, width = page_dimensions
         (xmin, ymin), (xmax, ymax) = geometry
    -    xmin, xmax = xmin * w, xmax * w
    -    ymin, ymax = ymin * h, ymax * h
    -    rect = patches.Rectangle(
    +    # Switch to absolute coords
    +    if preserve_aspect_ratio:
    +        width = height = max(height, width)
    +    xmin, w = xmin * width, (xmax - xmin) * width
    +    ymin, h = ymin * height, (ymax - ymin) * height
    +
    +    return patches.Rectangle(
             (xmin, ymin),
    -        xmax - xmin,
    -        ymax - ymin,
    +        w,
    +        h,
             fill=fill,
             linewidth=linewidth,
             edgecolor=(*color, alpha),
             facecolor=(*color, alpha),
    -        label=label
    +        label=label,
         )
    -    return rect
    +
    +
    +def polygon_patch(
    +    geometry: np.ndarray,
    +    page_dimensions: Tuple[int, int],
    +    label: Optional[str] = None,
    +    color: Tuple[float, float, float] = (0, 0, 0),
    +    alpha: float = 0.3,
    +    linewidth: int = 2,
    +    fill: bool = True,
    +    preserve_aspect_ratio: bool = False,
    +) -> patches.Polygon:
    +    """Create a matplotlib polygon patch for the element
    +
    +    Args:
    +    ----
    +        geometry: bounding box of the element
    +        page_dimensions: dimensions of the Page in format (height, width)
    +        label: label to display when hovered
    +        color: color to draw box
    +        alpha: opacity parameter to fill the boxes, 0 = transparent
    +        linewidth: line width
    +        fill: whether the patch should be filled
    +        preserve_aspect_ratio: pass True if you passed True to the predictor
    +
    +    Returns:
    +    -------
    +        a polygon Patch
    +    """
    +    if not geometry.shape == (4, 2):
    +        raise ValueError("invalid geometry format")
    +
    +    # Unpack
    +    height, width = page_dimensions
    +    geometry[:, 0] = geometry[:, 0] * (max(width, height) if preserve_aspect_ratio else width)
    +    geometry[:, 1] = geometry[:, 1] * (max(width, height) if preserve_aspect_ratio else height)
    +
    +    return patches.Polygon(
    +        geometry,
    +        fill=fill,
    +        linewidth=linewidth,
    +        edgecolor=(*color, alpha),
    +        facecolor=(*color, alpha),
    +        label=label,
    +    )
    +
    +
    +def create_obj_patch(
    +    geometry: Union[BoundingBox, Polygon4P, np.ndarray],
    +    page_dimensions: Tuple[int, int],
    +    **kwargs: Any,
    +) -> patches.Patch:
    +    """Create a matplotlib patch for the element
    +
    +    Args:
    +    ----
    +        geometry: bounding box (straight or rotated) of the element
    +        page_dimensions: dimensions of the page in format (height, width)
    +        **kwargs: keyword arguments for the patch
    +
    +    Returns:
    +    -------
    +        a matplotlib Patch
    +    """
    +    if isinstance(geometry, tuple):
    +        if len(geometry) == 2:  # straight word BB (2 pts)
    +            return rect_patch(geometry, page_dimensions, **kwargs)
    +        elif len(geometry) == 4:  # rotated word BB (4 pts)
    +            return polygon_patch(np.asarray(geometry), page_dimensions, **kwargs)
    +    elif isinstance(geometry, np.ndarray) and geometry.shape == (4, 2):  # rotated line
    +        return polygon_patch(geometry, page_dimensions, **kwargs)
    +    raise ValueError("invalid geometry format")
    +
    +
    +def get_colors(num_colors: int) -> List[Tuple[float, float, float]]:
    +    """Generate num_colors color for matplotlib
    +
    +    Args:
    +    ----
    +        num_colors: number of colors to generate
    +
    +    Returns:
    +    -------
    +        colors: list of generated colors
    +    """
    +    colors = []
    +    for i in np.arange(0.0, 360.0, 360.0 / num_colors):
    +        hue = i / 360.0
    +        lightness = (50 + np.random.rand() * 10) / 100.0
    +        saturation = (90 + np.random.rand() * 10) / 100.0
    +        colors.append(colorsys.hls_to_rgb(hue, lightness, saturation))
    +    return colors
     
     
     
    -[docs] +[docs] def visualize_page( page: Dict[str, Any], image: np.ndarray, @@ -344,18 +472,18 @@

    Source code for doctr.utils.visualization

     ) -> Figure:
         """Visualize a full page with predicted blocks, lines and words
     
    -    Example::
    -        >>> import numpy as np
    -        >>> import matplotlib.pyplot as plt
    -        >>> from doctr.utils.visualization import visualize_page
    -        >>> from doctr.models import ocr_db_crnn
    -        >>> model = ocr_db_crnn(pretrained=True)
    -        >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    -        >>> out = model([[input_page]])
    -        >>> visualize_page(out[0].pages[0].export(), input_page)
    -        >>> plt.show()
    +    >>> import numpy as np
    +    >>> import matplotlib.pyplot as plt
    +    >>> from doctr.utils.visualization import visualize_page
    +    >>> from doctr.models import ocr_db_crnn
    +    >>> model = ocr_db_crnn(pretrained=True)
    +    >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    +    >>> out = model([[input_page]])
    +    >>> visualize_page(out[0].pages[0].export(), input_page)
    +    >>> plt.show()
     
         Args:
    +    ----
             page: the exported Page of a Document
             image: np array of the page, needs to have the same shape than page['dimensions']
             words_only: whether only words should be displayed
    @@ -363,6 +491,11 @@ 

    Source code for doctr.utils.visualization

             scale: figsize of the largest windows side
             interactive: whether the plot should be interactive
             add_labels: for static plot, adds text labels on top of bounding box
    +        **kwargs: keyword arguments for the polygon patch
    +
    +    Returns:
    +    -------
    +        the matplotlib figure
         """
         # Get proper scale and aspect ratio
         h, w = image.shape[:2]
    @@ -371,58 +504,189 @@ 

    Source code for doctr.utils.visualization

         # Display the image
         ax.imshow(image)
         # hide both axis
    -    ax.axis('off')
    +    ax.axis("off")
     
         if interactive:
             artists: List[patches.Patch] = []  # instantiate an empty list of patches (to be drawn on the page)
     
    -    for block in page['blocks']:
    +    for block in page["blocks"]:
             if not words_only:
    -            rect = create_rect_patch(block['geometry'], 'block', page['dimensions'], (0, 1, 0), linewidth=1, **kwargs)
    +            rect = create_obj_patch(
    +                block["geometry"], page["dimensions"], label="block", color=(0, 1, 0), linewidth=1, **kwargs
    +            )
                 # add patch on figure
                 ax.add_patch(rect)
                 if interactive:
                     # add patch to cursor's artists
                     artists.append(rect)
     
    -        for line in block['lines']:
    +        for line in block["lines"]:
                 if not words_only:
    -                rect = create_rect_patch(line['geometry'], 'line', page['dimensions'], (1, 0, 0), linewidth=1, **kwargs)
    +                rect = create_obj_patch(
    +                    line["geometry"], page["dimensions"], label="line", color=(1, 0, 0), linewidth=1, **kwargs
    +                )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
     
    -            for word in line['words']:
    -                rect = create_rect_patch(word['geometry'], f"{word['value']} (confidence: {word['confidence']:.2%})",
    -                                         page['dimensions'], (0, 0, 1), **kwargs)
    +            for word in line["words"]:
    +                rect = create_obj_patch(
    +                    word["geometry"],
    +                    page["dimensions"],
    +                    label=f"{word['value']} (confidence: {word['confidence']:.2%})",
    +                    color=(0, 0, 1),
    +                    **kwargs,
    +                )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
                     elif add_labels:
    -                    ax.text(
    -                        int(page['dimensions'][1] * word['geometry'][0][0]),
    -                        int(page['dimensions'][0] * word['geometry'][0][1]),
    -                        word['value'],
    -                        size=10,
    -                        alpha=0.5,
    -                        color=(0, 0, 1),
    -                    )
    +                    if len(word["geometry"]) == 5:
    +                        text_loc = (
    +                            int(page["dimensions"][1] * (word["geometry"][0] - word["geometry"][2] / 2)),
    +                            int(page["dimensions"][0] * (word["geometry"][1] - word["geometry"][3] / 2)),
    +                        )
    +                    else:
    +                        text_loc = (
    +                            int(page["dimensions"][1] * word["geometry"][0][0]),
    +                            int(page["dimensions"][0] * word["geometry"][0][1]),
    +                        )
    +
    +                    if len(word["geometry"]) == 2:
    +                        # We draw only if boxes are in straight format
    +                        ax.text(
    +                            *text_loc,
    +                            word["value"],
    +                            size=10,
    +                            alpha=0.5,
    +                            color=(0, 0, 1),
    +                        )
     
             if display_artefacts:
    -            for artefact in block['artefacts']:
    -                rect = create_rect_patch(artefact['geometry'], 'artefact', page['dimensions'], (0.5, 0.5, 0.5),
    -                                         linewidth=1, **kwargs)
    +            for artefact in block["artefacts"]:
    +                rect = create_obj_patch(
    +                    artefact["geometry"],
    +                    page["dimensions"],
    +                    label="artefact",
    +                    color=(0.5, 0.5, 0.5),
    +                    linewidth=1,
    +                    **kwargs,
    +                )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
     
         if interactive:
    +        import mplcursors
    +
             # Create mlp Cursor to hover patches in artists
             mplcursors.Cursor(artists, hover=2).connect("add", lambda sel: sel.annotation.set_text(sel.artist.get_label()))
    -    fig.tight_layout()
    +    fig.tight_layout(pad=0.0)
     
         return fig
    + + +def visualize_kie_page( + page: Dict[str, Any], + image: np.ndarray, + words_only: bool = False, + display_artefacts: bool = True, + scale: float = 10, + interactive: bool = True, + add_labels: bool = True, + **kwargs: Any, +) -> Figure: + """Visualize a full page with predicted blocks, lines and words + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from doctr.utils.visualization import visualize_page + >>> from doctr.models import ocr_db_crnn + >>> model = ocr_db_crnn(pretrained=True) + >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) + >>> out = model([[input_page]]) + >>> visualize_kie_page(out[0].pages[0].export(), input_page) + >>> plt.show() + + Args: + ---- + page: the exported Page of a Document + image: np array of the page, needs to have the same shape than page['dimensions'] + words_only: whether only words should be displayed + display_artefacts: whether artefacts should be displayed + scale: figsize of the largest windows side + interactive: whether the plot should be interactive + add_labels: for static plot, adds text labels on top of bounding box + **kwargs: keyword arguments for the polygon patch + + Returns: + ------- + the matplotlib figure + """ + # Get proper scale and aspect ratio + h, w = image.shape[:2] + size = (scale * w / h, scale) if h > w else (scale, h / w * scale) + fig, ax = plt.subplots(figsize=size) + # Display the image + ax.imshow(image) + # hide both axis + ax.axis("off") + + if interactive: + artists: List[patches.Patch] = [] # instantiate an empty list of patches (to be drawn on the page) + + colors = {k: color for color, k in zip(get_colors(len(page["predictions"])), page["predictions"])} + for key, value in page["predictions"].items(): + for prediction in value: + if not words_only: + rect = create_obj_patch( + prediction["geometry"], + page["dimensions"], + label=f"{key} \n {prediction['value']} (confidence: {prediction['confidence']:.2%}", + color=colors[key], + linewidth=1, + **kwargs, + ) + # add patch on figure + ax.add_patch(rect) + if interactive: + # add patch to cursor's artists + artists.append(rect) + + if interactive: + import mplcursors + + # Create mlp Cursor to hover patches in artists + mplcursors.Cursor(artists, hover=2).connect("add", lambda sel: sel.annotation.set_text(sel.artist.get_label())) + fig.tight_layout(pad=0.0) + + return fig + + +def draw_boxes(boxes: np.ndarray, image: np.ndarray, color: Optional[Tuple[int, int, int]] = None, **kwargs) -> None: + """Draw an array of relative straight boxes on an image + + Args: + ---- + boxes: array of relative boxes, of shape (*, 4) + image: np array, float32 or uint8 + color: color to use for bounding box edges + **kwargs: keyword arguments from `matplotlib.pyplot.plot` + """ + h, w = image.shape[:2] + # Convert boxes to absolute coords + _boxes = deepcopy(boxes) + _boxes[:, [0, 2]] *= w + _boxes[:, [1, 3]] *= h + _boxes = _boxes.astype(np.int32) + for box in _boxes.tolist(): + xmin, ymin, xmax, ymax = box + image = cv2.rectangle( + image, (xmin, ymin), (xmax, ymax), color=color if isinstance(color, tuple) else (0, 0, 255), thickness=2 + ) + plt.imshow(image) + plt.plot(**kwargs)
    @@ -455,8 +719,8 @@

    Source code for doctr.utils.visualization

           
         
       
    - - + + diff --git a/v0.2.1/_modules/index.html b/v0.2.1/_modules/index.html index b3ffa8c863..5793c44f20 100644 --- a/v0.2.1/_modules/index.html +++ b/v0.2.1/_modules/index.html @@ -13,7 +13,7 @@ - + Overview: module code - docTR documentation @@ -225,20 +225,42 @@ - - + + diff --git a/v0.2.1/_sources/changelog.rst.txt b/v0.2.1/_sources/changelog.rst.txt index 0370519549..35befe7b96 100644 --- a/v0.2.1/_sources/changelog.rst.txt +++ b/v0.2.1/_sources/changelog.rst.txt @@ -1,6 +1,58 @@ Changelog ========= +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 `_ diff --git a/v0.2.1/_sources/datasets.rst.txt b/v0.2.1/_sources/datasets.rst.txt deleted file mode 100644 index 31a5663285..0000000000 --- a/v0.2.1/_sources/datasets.rst.txt +++ /dev/null @@ -1,68 +0,0 @@ -doctr.datasets -============== - -.. currentmodule:: doctr.datasets - -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. - - -.. _datasets: - -Available Datasets ------------------- -The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL. - -.. autoclass:: doctr.datasets.core.VisionDataset - - -Here are all datasets that are available through DocTR: - -.. autoclass:: FUNSD -.. autoclass:: SROIE -.. autoclass:: CORD -.. autoclass:: OCRDataset - - -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. - -.. autoclass:: doctr.datasets.loader.DataLoader - - -.. _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 - * - digits - - 10 - - 0123456789 - * - ascii_letters - - 52 - - abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ - * - punctuation - - 32 - - !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ - * - currency - - 5 - - £€¥¢฿ - * - latin - - 96 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~° - * - french - - 154 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿ - -.. autofunction:: encode_sequences diff --git a/v0.2.1/_sources/documents.rst.txt b/v0.2.1/_sources/documents.rst.txt deleted file mode 100644 index 655730073e..0000000000 --- a/v0.2.1/_sources/documents.rst.txt +++ /dev/null @@ -1,87 +0,0 @@ -doctr.documents -=============== - - -.. currentmodule:: doctr.documents - -The documents module enables users to easily access content from documents and export analysis -results to structured formats. - - -Document structure ------------------- - -Structural organization of the documents. - -Word -^^^^ -A Word is an uninterrupted sequence of characters. - -.. autoclass:: Word - -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 - -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 - - -Document -^^^^^^^^ - -A Document is a collection of Pages. - -.. autoclass:: Document - - .. automethod:: show - - -File reading ------------- - -High-performance file reading and conversion to processable structured data. - -.. autofunction:: read_pdf - -.. autofunction:: read_img - -.. autofunction:: read_html - - -.. autoclass:: DocumentFile - - .. automethod:: from_pdf - - .. automethod:: from_url - - .. automethod:: from_images - -.. autoclass:: PDF - - .. automethod:: as_images - - .. automethod:: get_words - - .. automethod:: get_artefacts diff --git a/v0.2.1/_sources/getting_started/installing.rst.txt b/v0.2.1/_sources/getting_started/installing.rst.txt index e764e734a7..39e79aa3dd 100644 --- a/v0.2.1/_sources/getting_started/installing.rst.txt +++ b/v0.2.1/_sources/getting_started/installing.rst.txt @@ -3,7 +3,7 @@ Installation ************ -This library requires `Python `_ 3.9 or higher. +This library requires `Python `_ 3.10 or higher. Prerequisites diff --git a/v0.2.1/_sources/index.rst.txt b/v0.2.1/_sources/index.rst.txt index afe926c6df..53251db142 100644 --- a/v0.2.1/_sources/index.rst.txt +++ b/v0.2.1/_sources/index.rst.txt @@ -1,7 +1,8 @@ -DocTR: Document Text Recognition -================================ +******************************** +docTR: Document Text Recognition +******************************** -State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 +State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 & PyTorch .. image:: https://github.com/mindee/doctr/releases/download/v0.2.0/ocr.png :align: center @@ -9,38 +10,29 @@ State-of-the-art Optical Character Recognition made seamless & accessible to any DocTR provides an easy and powerful way to extract valuable information from your documents: -* |:receipt:| **for automation**: seemlessly process documents for Natural Language Understanding tasks: we provide OCR predictors to parse textual information (localize and identify each word) 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. -Welcome to the documentation of `DocTR `_! - - Main Features ------------- * |:robot:| Robust 2-stage (detection + recognition) OCR predictors with pretrained parameters * |:zap:| User-friendly, 3 lines of code to load a document and extract text with a predictor -* |:rocket:| State-of-the-art performances on public document datasets, comparable with GoogleVision/AWS Textract +* |: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, small dependencies -* |:tools:| Daily maintained -* |:factory:| Easy integration - +* |:bird:| Light package, minimal dependencies +* |:tools:| Actively maintained by Mindee +* |:factory:| Easy integration (available templates for browser demo & API deployment) -Getting Started ---------------- .. toctree:: :maxdepth: 2 + :caption: Getting started + :hidden: - installing - - -Build & train your predictor -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* Compose your own end-to-end OCR predictor: mix and match detection & recognition predictors (all-pretrained) -* Fine-tune or train from scratch any detection or recognition model to specialize on your data + getting_started/installing + notebooks Model zoo @@ -48,35 +40,83 @@ Model zoo Text detection models """"""""""""""""""""" - * `DBNet `_ (Differentiable Binarization) - * `LinkNet `_ +* 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 `_ (Show, Attend and Read) - * `CRNN `_ (Convolutional Recurrent Neural Network) +* 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" `_ 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 `_. +* 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" `_. .. toctree:: :maxdepth: 2 - :caption: Notes + :caption: Using docTR + :hidden: - changelog + 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 + + +.. toctree:: + :maxdepth: 2 + :caption: Community + :hidden: + + community/resources .. toctree:: :maxdepth: 2 :caption: Package Reference + :hidden: - datasets - documents - models - transforms - utils + 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/v0.2.1/_sources/installing.rst.txt b/v0.2.1/_sources/installing.rst.txt deleted file mode 100644 index c8ea72a834..0000000000 --- a/v0.2.1/_sources/installing.rst.txt +++ /dev/null @@ -1,41 +0,0 @@ - -************ -Installation -************ - -This library requires Python 3.6 or higher. - - -Prerequisites -============= - -If you are running another OS than Linux, you will need a few extra dependencies. - -For MacOS users, you can install them as follows: - -.. code:: shell - - brew install cairo pango gdk-pixbuf libffi - -For Windows users, those dependencies are included in GTK. You can find the latest installer over `here `_. - - -Via Python Package -================== - -Install the last stable release of the package using pip: - -.. code:: bash - - pip install python-doctr - - -Via Git -======= - -Install the library in developper mode: - -.. code:: bash - - git clone https://github.com/mindee/doctr.git - pip install -e doctr/. diff --git a/v0.2.1/_sources/models.rst.txt b/v0.2.1/_sources/models.rst.txt deleted file mode 100644 index 9f2276e03f..0000000000 --- a/v0.2.1/_sources/models.rst.txt +++ /dev/null @@ -1,223 +0,0 @@ -doctr.models -============ - -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. - -.. currentmodule:: doctr.models - -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 TensorFlow model. -* Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable. - - -Text Detection --------------- -Localizing text elements in images - -+---------------------------------------------------+----------------------------+----------------------------+---------+ -| | FUNSD | CORD | | -+==================+=================+==============+============+===============+============+===============+=========+ -| **Architecture** | **Input shape** | **# params** | **Recall** | **Precision** | **Recall** | **Precision** | **FPS** | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ -| db_resnet50 | (1024, 1024, 3) | 25.2 M | 82.14 | 87.64 | 92.49 | 89.66 | 2.1 | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ - -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 combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for detection -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for detection is the following: - -1. resize each input image to the target size (bilinear interpolation by default) with potential deformation. -2. batch images together -3. normalize the batch using the training data statistics - - -Detection models -^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - -.. autofunction:: doctr.models.detection.db_resnet50 -.. autofunction:: doctr.models.detection.linknet - - -Post-processing detections -^^^^^^^^^^^^^^^^^^^^^^^^^^ -The purpose of this block is to turn the model output (binary segmentation map for instance), into a set of bounding boxes. - - -Detection predictors -^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information. - -.. autofunction:: doctr.models.detection.detection_predictor - - -Text Recognition ----------------- -Identifying strings in images - -.. list-table:: Text recognition model zoo - :widths: 20 20 15 10 10 10 - :header-rows: 1 - - * - Architecture - - Input shape - - # params - - FUNSD - - CORD - - FPS - * - crnn_vgg16_bn - - (32, 128, 3) - - 15.8M - - 86.02 - - 91.3 - - 12.8 - * - sar_vgg16_bn - - (32, 128, 3) - - 21.5M - - 86.2 - - 91.7 - - 3.3 - * - sar_resnet31 - - (32, 128, 3) - - 53.1M - - **86.3** - - **92.1** - - 2.7 - -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 metrics being used are available in :ref:`metrics`. - -All these recognition models are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for recognition -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for recognition is the following: - -1. resize each input image to the target size (bilinear interpolation by default) without deformation. -2. pad the image to the target size (with zeros by default) -3. batch images together -4. normalize the batch using the training data statistics - -Recognition models -^^^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - - -.. autofunction:: doctr.models.recognition.crnn_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_resnet31 - -Post-processing outputs -^^^^^^^^^^^^^^^^^^^^^^^ -The purpose of this block is to turn the model output (symbol classification for the sequence), into a set of strings. - -Recognition predictors -^^^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage. - -.. autofunction:: doctr.models.recognition.recognition_predictor - - -End-to-End OCR --------------- -Predictors that localize and identify text elements in images - -+-----------------------------+--------------------------------------+--------------------------------------+ -| | FUNSD | CORD | -+=============================+============+===============+=========+============+===============+=========+ -| **Architecture** | **Recall** | **Precision** | **FPS** | **Recall** | **Precision** | **FPS** | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + crnn_vgg16_bn | 70.08 | 74.77 | 0.85 | 82.19 | **79.67** | 1.6 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_vgg16_bn | N/A | N/A | 0.49 | N/A | N/A | 1.0 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_resnet31 | N/A | N/A | 0.27 | N/A | N/A | 0.83 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision text detection | 0.595 | 0.625 | | 0.753 | 0.700 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision doc. text detection | 0.640 | 0.533 | | 0.689 | 0.611 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| AWS textract | **0.781** | **0.830** | | **0.875** | 0.660 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ - -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`. - -All recognition models of predictors are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Results on private ocr datasets - -+------------------------------------+----------------------------+----------------------------+----------------------------+ -| | Receipts | Invoices | IDs | -+====================================+============+===============+============+===============+============+===============+ -| **Architecture** | **Recall** | **Precision** | **Recall** | **Precision** | **Recall** | **Precision** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| db_resnet50 + crnn_vgg16_bn (ours) | **78.90** | **81.01** | 65.68 | **69.86** | **49.48** | **50.46** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| Gvision doc. text detection | 68.91 | 59.89 | 63.20 | 52.85 | 43.70 | 29.21 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| AWS textract | 75.77 | 77.70 | **70.47** | 69.13 | 46.39 | 43.32 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ - - -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. - -.. autofunction:: doctr.models.zoo.ocr_predictor - - -Model export ------------- -Utility functions to make the most of document analysis models. - -.. currentmodule:: doctr.models.export - -Model compression -^^^^^^^^^^^^^^^^^ - -.. autofunction:: convert_to_tflite - -.. autofunction:: convert_to_fp16 - -.. autofunction:: quantize_model - -Using SavedModel -^^^^^^^^^^^^^^^^ - -Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -`SavedModel `_ format as follows: - - - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> _ = model(input_t, training=False) - >>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/') - -And loaded just as easily: - - - >>> import tensorflow as tf - >>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/') diff --git a/v0.2.1/_sources/transforms.rst.txt b/v0.2.1/_sources/transforms.rst.txt deleted file mode 100644 index 0230fe75f5..0000000000 --- a/v0.2.1/_sources/transforms.rst.txt +++ /dev/null @@ -1,32 +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: - -.. autoclass:: Resize -.. autoclass:: Normalize -.. autoclass:: LambdaTransformation -.. autoclass:: ToGray -.. autoclass:: ColorInversion -.. autoclass:: RandomBrightness -.. autoclass:: RandomContrast -.. autoclass:: RandomSaturation -.. autoclass:: RandomHue -.. autoclass:: RandomGamma -.. autoclass:: RandomJpegQuality - - -Composing transformations ---------------------------------------------- -It is common to require several transformations to be performed consecutively. - -.. autoclass:: Compose -.. autoclass:: OneOf -.. autoclass:: RandomApply diff --git a/v0.2.1/_sources/utils.rst.txt b/v0.2.1/_sources/utils.rst.txt deleted file mode 100644 index 69c1abe0eb..0000000000 --- a/v0.2.1/_sources/utils.rst.txt +++ /dev/null @@ -1,36 +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 - - -.. _metrics: - -Task evaluation ---------------- -Implementations of task-specific metrics to easily assess your model performances. - -.. currentmodule:: doctr.utils.metrics - -.. autoclass:: TextMatch - - .. automethod:: summary - -.. autoclass:: LocalizationConfusion - - .. automethod:: summary - -.. autoclass:: OCRMetric - - .. automethod:: summary diff --git a/v0.2.1/_static/basic.css b/v0.2.1/_static/basic.css index f316efcb47..7ebbd6d07b 100644 --- a/v0.2.1/_static/basic.css +++ b/v0.2.1/_static/basic.css @@ -1,12 +1,5 @@ /* - * basic.css - * ~~~~~~~~~ - * * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ /* -- main layout ----------------------------------------------------------- */ @@ -115,15 +108,11 @@ img { /* -- search page ----------------------------------------------------------- */ ul.search { - margin: 10px 0 0 20px; - padding: 0; + margin-top: 10px; } ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; + padding: 5px 0; } ul.search li a { diff --git a/v0.2.1/_static/doctools.js b/v0.2.1/_static/doctools.js index 4d67807d17..0398ebb9f0 100644 --- a/v0.2.1/_static/doctools.js +++ b/v0.2.1/_static/doctools.js @@ -1,12 +1,5 @@ /* - * doctools.js - * ~~~~~~~~~~~ - * * Base JavaScript utilities for all Sphinx HTML documentation. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; diff --git a/v0.2.1/_static/documentation_options.js b/v0.2.1/_static/documentation_options.js index d266000142..4f656fdbea 100644 --- a/v0.2.1/_static/documentation_options.js +++ b/v0.2.1/_static/documentation_options.js @@ -1,5 +1,5 @@ const DOCUMENTATION_OPTIONS = { - VERSION: '0.2.1a0-git', + VERSION: '0.10.1a0-git', LANGUAGE: 'en', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/v0.2.1/_static/language_data.js b/v0.2.1/_static/language_data.js index 367b8ed81b..c7fe6c6faf 100644 --- a/v0.2.1/_static/language_data.js +++ b/v0.2.1/_static/language_data.js @@ -1,13 +1,6 @@ /* - * language_data.js - * ~~~~~~~~~~~~~~~~ - * * This script contains the language-specific data used by searchtools.js, * namely the list of stopwords, stemmer, scorer and splitter. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; diff --git a/v0.2.1/_static/searchtools.js b/v0.2.1/_static/searchtools.js index b08d58c9b9..2c774d17af 100644 --- a/v0.2.1/_static/searchtools.js +++ b/v0.2.1/_static/searchtools.js @@ -1,12 +1,5 @@ /* - * searchtools.js - * ~~~~~~~~~~~~~~~~ - * * Sphinx JavaScript utilities for the full-text search. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; @@ -20,7 +13,7 @@ if (typeof Scorer === "undefined") { // and returns the new score. /* score: result => { - const [docname, title, anchor, descr, score, filename] = result + const [docname, title, anchor, descr, score, filename, kind] = result return score }, */ @@ -47,6 +40,14 @@ if (typeof Scorer === "undefined") { }; } +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + const _removeChildren = (element) => { while (element && element.lastChild) element.removeChild(element.lastChild); }; @@ -64,9 +65,13 @@ const _displayItem = (item, searchTerms, highlightTerms) => { const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; const contentRoot = document.documentElement.dataset.content_root; - const [docName, title, anchor, descr, score, _filename] = item; + const [docName, title, anchor, descr, score, _filename, kind] = item; let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); let requestUrl; let linkUrl; if (docBuilder === "dirhtml") { @@ -115,8 +120,10 @@ const _finishSearch = (resultCount) => { "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." ); else - Search.status.innerText = _( - "Search finished, found ${resultCount} page(s) matching the search query." + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, ).replace('${resultCount}', resultCount); }; const _displayNextItem = ( @@ -138,7 +145,7 @@ const _displayNextItem = ( else _finishSearch(resultCount); }; // Helper function used by query() to order search results. -// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. // Order the results by score (in opposite order of appearance, since the // `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. const _orderResultsByScoreThenName = (a, b) => { @@ -248,6 +255,7 @@ const Search = { searchSummary.classList.add("search-summary"); searchSummary.innerText = ""; const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); searchList.classList.add("search"); const out = document.getElementById("search-results"); @@ -318,7 +326,7 @@ const Search = { const indexEntries = Search._index.indexentries; // Collect multiple result groups to be sorted separately and then ordered. - // Each is an array of [docname, title, anchor, descr, score, filename]. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. const normalResults = []; const nonMainIndexResults = []; @@ -337,6 +345,7 @@ const Search = { null, score + boost, filenames[file], + SearchResultKind.title, ]); } } @@ -354,6 +363,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.index, ]; if (isMain) { normalResults.push(result); @@ -475,6 +485,7 @@ const Search = { descr, score, filenames[match[0]], + SearchResultKind.object, ]); }; Object.keys(objects).forEach((prefix) => @@ -585,6 +596,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.text, ]); } return results; diff --git a/v0.2.1/changelog.html b/v0.2.1/changelog.html index 5e11507468..fc45a50384 100644 --- a/v0.2.1/changelog.html +++ b/v0.2.1/changelog.html @@ -12,9 +12,9 @@ gtag('js', new Date()); gtag('config', 'G-40DVRMX8T4'); - + - + Changelog - docTR documentation @@ -226,20 +226,42 @@ + diff --git a/v0.2.1/community/resources.html b/v0.2.1/community/resources.html index 2564037893..9a1988258c 100644 --- a/v0.2.1/community/resources.html +++ b/v0.2.1/community/resources.html @@ -14,7 +14,7 @@ - + Community resources - docTR documentation @@ -389,7 +389,7 @@

    Community resources - + diff --git a/v0.2.1/contributing/code_of_conduct.html b/v0.2.1/contributing/code_of_conduct.html index 5ea4a1f99d..03422dbb4d 100644 --- a/v0.2.1/contributing/code_of_conduct.html +++ b/v0.2.1/contributing/code_of_conduct.html @@ -14,7 +14,7 @@ - + Contributor Covenant Code of Conduct - docTR documentation @@ -504,7 +504,7 @@

    Attribution - + diff --git a/v0.2.1/contributing/contributing.html b/v0.2.1/contributing/contributing.html index e5a85682c6..05e2b3641b 100644 --- a/v0.2.1/contributing/contributing.html +++ b/v0.2.1/contributing/contributing.html @@ -14,7 +14,7 @@ - + Contributing to docTR - docTR documentation @@ -481,7 +481,7 @@

    Let’s connect - + diff --git a/v0.2.1/datasets.html b/v0.2.1/datasets.html deleted file mode 100644 index 26c04f21ab..0000000000 --- a/v0.2.1/datasets.html +++ /dev/null @@ -1,585 +0,0 @@ - - - - - - - - - - - - - doctr.datasets - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.datasets

    -

    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.

    -
    -

    Available Datasets

    -

    The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL.

    -
    -
    -class doctr.datasets.core.VisionDataset(url: str, file_name: str | None = None, file_hash: str | None = None, extract_archive: bool = False, download: bool = False, overwrite: bool = False)[source]
    -

    Implements an abstract dataset

    -
    -
    Parameters:
    -
      -
    • url – URL of the dataset

    • -
    • file_name – name of the file once downloaded

    • -
    • file_hash – expected SHA256 of the file

    • -
    • extract_archive – whether the downloaded file is an archive to be extracted

    • -
    • download – whether the dataset should be downloaded if not present on disk

    • -
    • overwrite – whether the archive should be re-extracted

    • -
    -
    -
    -
    - -

    Here are all datasets that are available through DocTR:

    -
    -
    -class doctr.datasets.FUNSD(train: bool = True, sample_transforms: Callable[[Tensor], Tensor] | None = None, **kwargs: Any)[source]
    -

    FUNSD dataset from “FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents”.

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD
    ->>> train_set = FUNSD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.SROIE(train: bool = True, sample_transforms: Callable[[Tensor], Tensor] | None = None, **kwargs: Any)[source]
    -

    SROIE dataset from “ICDAR2019 Competition on Scanned Receipt OCR and Information Extraction”.

    -
    -
    Example::
    >>> from doctr.datasets import SROIE
    ->>> train_set = SROIE(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.CORD(train: bool = True, sample_transforms: Callable[[Tensor], Tensor] | None = None, **kwargs: Any)[source]
    -

    CORD dataset from “CORD: A Consolidated Receipt Dataset forPost-OCR Parsing”.

    -
    -
    Example::
    >>> from doctr.datasets import CORD
    ->>> train_set = CORD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.OCRDataset(img_folder: str, label_file: str, sample_transforms: Callable[[Tensor], Tensor] | None = None, **kwargs: Any)[source]
    -

    Implements an OCR dataset

    -
    -
    Parameters:
    -
      -
    • img_folder – local path to image folder (all jpg at the root)

    • -
    • label_file – local path to the label file

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -

    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.

    -
    -
    -class doctr.datasets.loader.DataLoader(dataset, shuffle: bool = True, batch_size: int = 1, drop_last: bool = False, workers: int | None = None)[source]
    -

    Implements a dataset wrapper for fast data loading

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD, 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)
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • dataset – the dataset

    • -
    • shuffle – whether the samples should be shuffled before passing it to the iterator

    • -
    • batch_size – number of elements in each batch

    • -
    • drop_last – if True, drops the last batch if it isn’t full

    • -
    • workers – number of workers to use for data loading

    • -
    -
    -
    -
    - -
    -
    -

    Supported Vocabs

    -

    Since textual content has to be encoded properly for models to interpret them efficiently, DocTR supports multiple sets -of vocabs.

    -
    - - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    DocTR Vocabs

    Name

    size

    characters

    digits

    10

    0123456789

    ascii_letters

    52

    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

    punctuation

    32

    !”#$%&'()*+,-./:;<=>?@[\]^_`{|}~

    currency

    5

    £€¥¢฿

    latin

    96

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°

    french

    154

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿

    -
    -
    -
    -doctr.datasets.encode_sequences(sequences: List[str], vocab: str, target_size: int | None = None, eos: int = -1, **kwargs: Any) ndarray[source]
    -

    Encode character sequences using a given vocab as mapping

    -
    -
    Parameters:
    -
      -
    • sequences – the list of character sequences of size N

    • -
    • vocab – the ordered vocab to use for encoding

    • -
    • target_size – maximum length of the encoded data

    • -
    • eos – encoding of End Of String

    • -
    -
    -
    Returns:
    -

    the padded encoded data as a tensor

    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.1/documents.html b/v0.2.1/documents.html deleted file mode 100644 index e3925d4b59..0000000000 --- a/v0.2.1/documents.html +++ /dev/null @@ -1,772 +0,0 @@ - - - - - - - - - - - - - doctr.documents - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.documents

    -

    The documents module enables users to easily access content from documents and export analysis -results to structured formats.

    -
    -

    Document structure

    -

    Structural organization of the documents.

    -
    -

    Word

    -

    A Word is an uninterrupted sequence of characters.

    -
    -
    -class doctr.documents.Word(value: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]])[source]
    -

    Implements a word element

    -
    -
    Parameters:
    -
      -
    • value – the text string of the word

    • -
    • confidence – the confidence associated with the text prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to

    • -
    • size (the page's)

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Line(words: List[Word], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | None = None)[source]
    -

    Implements a line element as a collection of words

    -
    -
    Parameters:
    -
      -
    • words – list of word elements

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all words in it.

    • -
    -
    -
    -
    - -
    -
    -

    Artefact

    -

    An Artefact is a non-textual element (e.g. QR code, picture, chart, signature, logo, etc.).

    -
    -
    -class doctr.documents.Artefact(artefact_type: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]])[source]
    -

    Implements a non-textual element

    -
    -
    Parameters:
    -
      -
    • artefact_type – the type of artefact

    • -
    • confidence – the confidence of the type prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size.

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Block(lines: List[Line] = [], artefacts: List[Artefact] = [], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | None = None)[source]
    -

    Implements a block element as a collection of lines and artefacts

    -
    -
    Parameters:
    -
      -
    • lines – list of line elements

    • -
    • artefacts – list of artefacts

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all lines and artefacts in it.

    • -
    -
    -
    -
    - -
    -
    -

    Page

    -

    A Page is a collection of Blocks that were on the same physical page.

    -
    -
    -class doctr.documents.Page(blocks: List[Block], page_idx: int, dimensions: Tuple[int, int], orientation: Dict[str, Any] | None = None, language: Dict[str, Any] | None = None)[source]
    -

    Implements a page element as a collection of blocks

    -
    -
    Parameters:
    -
      -
    • blocks – list of block elements

    • -
    • page_idx – the index of the page in the input raw document

    • -
    • dimensions – the page size in pixels in format (width, height)

    • -
    • orientation – a dictionary with the value of the rotation angle in degress and confidence of the prediction

    • -
    • language – a dictionary with the language value and confidence of the prediction

    • -
    -
    -
    -
    -
    -show(page: ndarray, interactive: bool = True, **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -
      -
    • page – image encoded as a numpy array in uint8

    • -
    • interactive – whether the display should be interactive

    • -
    -
    -
    -
    - -
    - -
    -
    -

    Document

    -

    A Document is a collection of Pages.

    -
    -
    -class doctr.documents.Document(pages: List[Page])[source]
    -

    Implements a document element as a collection of pages

    -
    -
    Parameters:
    -

    pages – list of page elements

    -
    -
    -
    -
    -show(pages: List[ndarray], **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -

    pages – list of images encoded as numpy arrays in uint8

    -
    -
    -
    - -
    - -
    -
    -
    -

    File reading

    -

    High-performance file reading and conversion to processable structured data.

    -
    -
    -doctr.documents.read_pdf(file: str | Path | bytes, **kwargs: Any) Document[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_pdf
    ->>> doc = read_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_img(file: str | Path | bytes, output_size: Tuple[int, int] | None = None, rgb_output: bool = True) ndarray[source]
    -

    Read an image file into numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_img
    ->>> page = read_img("path/to/your/doc.jpg")
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • file – the path to the image file

    • -
    • output_size – the expected output size of each page in format H x W

    • -
    • rgb_output – whether the output ndarray channel order should be RGB instead of BGR.

    • -
    -
    -
    Returns:
    -

    the page decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_html(url: str, **kwargs: Any) bytes[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_html
    ->>> doc = read_html("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – URL of the target web page

    -
    -
    Returns:
    -

    decoded PDF file as a bytes stream

    -
    -
    -
    - -
    -
    -class doctr.documents.DocumentFile[source]
    -

    Read a document from multiple extensions

    -
    -
    -classmethod from_pdf(file: str | Path | bytes, **kwargs) PDF[source]
    -

    Read a PDF file

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file or a binary stream

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_url(url: str, **kwargs) PDF[source]
    -

    Interpret a web page as a PDF document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_url("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – the URL of the target web page

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_images(files: Sequence[str | Path | bytes] | str | Path | bytes, **kwargs) List[ndarray][source]
    -

    Read an image file (or a collection of image files) and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"])
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    files – the path to the image file or a binary stream, or a collection of those

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    - -
    -
    -class doctr.documents.PDF(doc: Document)[source]
    -

    PDF document template

    -
    -
    Parameters:
    -

    doc – input PDF document

    -
    -
    -
    -
    -as_images(**kwargs) List[ndarray][source]
    -

    Convert all document pages to images

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of convert_page_to_numpy

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -get_words(**kwargs) List[List[Tuple[Tuple[float, float, float, float], str]]][source]
    -

    Get the annotations for all words in the document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of fitz.Page.getTextWords

    -
    -
    Returns:
    -

    the list of pages annotations, represented as a list of tuple (bounding box, value)

    -
    -
    -
    - -
    -
    -get_artefacts() List[List[Tuple[float, float, float, float]]][source]
    -

    Get the artefacts for the entire document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts()
    -
    -
    -
    -
    -
    -
    Returns:
    -

    the list of pages artefacts, represented as a list of bounding boxes

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.1/genindex.html b/v0.2.1/genindex.html index 3a0f1cd884..21520455b4 100644 --- a/v0.2.1/genindex.html +++ b/v0.2.1/genindex.html @@ -13,7 +13,7 @@ - Index - docTR documentation + Index - docTR documentation @@ -224,20 +224,42 @@

    +
    +

    U

    + + +
    +
    +

    V

    @@ -551,7 +711,13 @@

    V

    W

    +
    @@ -589,8 +755,8 @@

    W

    - - + + diff --git a/v0.2.1/getting_started/installing.html b/v0.2.1/getting_started/installing.html index a488e9a030..af3b58193e 100644 --- a/v0.2.1/getting_started/installing.html +++ b/v0.2.1/getting_started/installing.html @@ -14,7 +14,7 @@ - + Installation - docTR documentation @@ -305,7 +305,7 @@

    Installation

    -

    This library requires Python 3.9 or higher.

    +

    This library requires Python 3.10 or higher.

    Prerequisites

    Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so:

    @@ -435,7 +435,7 @@

    Via Git - + diff --git a/v0.2.1/index.html b/v0.2.1/index.html index 3a22825833..3a06afc6d9 100644 --- a/v0.2.1/index.html +++ b/v0.2.1/index.html @@ -12,9 +12,9 @@ gtag('js', new Date()); gtag('config', 'G-40DVRMX8T4'); - + - + docTR documentation @@ -226,20 +226,42 @@
    -

    DocTR: Document Text Recognition

    -

    State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2

    +

    docTR: Document Text Recognition

    +

    State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 & PyTorch

    https://github.com/mindee/doctr/releases/download/v0.2.0/ocr.png

    DocTR provides an easy and powerful way to extract valuable information from your documents:

      -
    • 🧾 for automation: seemlessly process documents for Natural Language Understanding tasks: we provide OCR predictors to parse textual information (localize and identify each word) from your documents.

    • +
    • 🧾 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.

    • 👩‍🔬 for research: quickly compare your own architectures speed & performances with state-of-art models on public datasets.

    -

    Welcome to the documentation of DocTR!

    Main Features

    • 🤖 Robust 2-stage (detection + recognition) OCR predictors with pretrained parameters

    • ⚡ User-friendly, 3 lines of code to load a document and extract text with a predictor

    • -
    • 🚀 State-of-the-art performances on public document datasets, comparable with GoogleVision/AWS Textract

    • +
    • 🚀 State-of-the-art performance on public document datasets, comparable with GoogleVision/AWS Textract

    • ⚡ Optimized for inference speed on both CPU & GPU

    • -
    • 🐦 Light package, small dependencies

    • -
    • 🛠️ Daily maintained

    • -
    • 🏭 Easy integration

    • +
    • 🐦 Light package, minimal dependencies

    • +
    • 🛠️ Actively maintained by Mindee

    • +
    • 🏭 Easy integration (available templates for browser demo & API deployment)

    -
    -
    -

    Getting Started

    -
    -

    Build & train your predictor

    -
      -
    • Compose your own end-to-end OCR predictor: mix and match detection & recognition predictors (all-pretrained)

    • -
    • Fine-tune or train from scratch any detection or recognition model to specialize on your data

    • -
    -

    Model zoo

    Text detection models

    -
    -

    Text recognition models

    -
    -

    Supported datasets

    -
    -
    +
    +
    +
    +
    +
    @@ -404,7 +381,7 @@

    Supported datasets - +
    Next @@ -444,10 +421,8 @@

    Supported datasets + diff --git a/v0.2.1/installing.html b/v0.2.1/installing.html deleted file mode 100644 index 7c8f802dee..0000000000 --- a/v0.2.1/installing.html +++ /dev/null @@ -1,390 +0,0 @@ - - - - - - - - - - - - - Installation - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    Installation

    -

    This library requires Python 3.6 or higher.

    -
    -

    Prerequisites

    -

    If you are running another OS than Linux, you will need a few extra dependencies.

    -

    For MacOS users, you can install them as follows:

    -
    brew install cairo pango gdk-pixbuf libffi
    -
    -
    -

    For Windows users, those dependencies are included in GTK. You can find the latest installer over here.

    -
    -
    -

    Via Python Package

    -

    Install the last stable release of the package using pip:

    -
    pip install python-doctr
    -
    -
    -
    -
    -

    Via Git

    -

    Install the library in developper mode:

    -
    git clone https://github.com/mindee/doctr.git
    -pip install -e doctr/.
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.1/models.html b/v0.2.1/models.html deleted file mode 100644 index e8cb1c9fd9..0000000000 --- a/v0.2.1/models.html +++ /dev/null @@ -1,989 +0,0 @@ - - - - - - - - - - - - - doctr.models - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.models

    -

    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 TensorFlow model.

    • -
    • Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable.

    • -
    -
    -

    Text Detection

    -

    Localizing text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Input shape

    # params

    Recall

    Precision

    Recall

    Precision

    FPS

    db_resnet50

    (1024, 1024, 3)

    25.2 M

    82.14

    87.64

    92.49

    89.66

    2.1

    -
    -

    All text detection models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for detection

    -

    In DocTR, the pre-processing scheme for detection is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) with potential deformation.

    2. -
    3. batch images together

    4. -
    5. normalize the batch using the training data statistics

    6. -
    -
    -
    -

    Detection models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.detection.db_resnet50(pretrained: bool = False, **kwargs: Any) DBNet[source]
    -

    DBNet as described in “Real-time Scene Text Detection with Differentiable Binarization”, using a ResNet-50 backbone.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -doctr.models.detection.linknet(pretrained: bool = False, **kwargs: Any) LinkNet[source]
    -

    LinkNet as described in “LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import linknet
    ->>> model = linknet(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -

    Post-processing detections

    -

    The purpose of this block is to turn the model output (binary segmentation map for instance), into a set of bounding boxes.

    -
    -
    -

    Detection predictors

    -

    Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information.

    -
    -
    -doctr.models.detection.detection_predictor(arch: str = 'db_resnet50', pretrained: bool = False, **kwargs: Any) DetectionPredictor[source]
    -

    Text detection architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import detection_predictor
    ->>> model = detection_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_resnet50’)

    • -
    • pretrained – If True, returns a model pre-trained on our text detection dataset

    • -
    -
    -
    Returns:
    -

    Detection predictor

    -
    -
    -
    - -
    -
    -
    -

    Text Recognition

    -

    Identifying strings in images

    -
    - - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Text recognition model zoo

    Architecture

    Input shape

    # params

    FUNSD

    CORD

    FPS

    crnn_vgg16_bn

    (32, 128, 3)

    15.8M

    86.02

    91.3

    12.8

    sar_vgg16_bn

    (32, 128, 3)

    21.5M

    86.2

    91.7

    3.3

    sar_resnet31

    (32, 128, 3)

    53.1M

    86.3

    92.1

    2.7

    -
    -

    All text recognition models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All these recognition models are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for recognition

    -

    In DocTR, the pre-processing scheme for recognition is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) without deformation.

    2. -
    3. pad the image to the target size (with zeros by default)

    4. -
    5. batch images together

    6. -
    7. normalize the batch using the training data statistics

    8. -
    -
    -
    -

    Recognition models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.recognition.crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) CRNN[source]
    -

    CRNN with a VGG-16 backbone as described in “An End-to-End Trainable Neural Network for Image-based -Sequence Recognition and Its Application to Scene Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import crnn_vgg16_bn
    ->>> model = crnn_vgg16_bn(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a VGG16 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_vgg16_bn
    ->>> model = sar_vgg16_bn(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_resnet31(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a resnet-31 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -

    Example

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_resnet31
    ->>> model = sar_resnet31(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -

    Post-processing outputs

    -

    The purpose of this block is to turn the model output (symbol classification for the sequence), into a set of strings.

    -
    -
    -

    Recognition predictors

    -

    Combining the right components around a given architecture for easier usage.

    -
    -
    -doctr.models.recognition.recognition_predictor(arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) RecognitionPredictor[source]
    -

    Text recognition architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import recognition_predictor
    ->>> model = recognition_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(32, 128, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘crnn_vgg16_bn’, ‘crnn_resnet31’, ‘sar_vgg16_bn’, ‘sar_resnet31’)

    • -
    • pretrained – If True, returns a model pre-trained on our text recognition dataset

    • -
    -
    -
    Returns:
    -

    Recognition predictor

    -
    -
    -
    - -
    -
    -
    -

    End-to-End OCR

    -

    Predictors that localize and identify text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Recall

    Precision

    FPS

    Recall

    Precision

    FPS

    db_resnet50 + crnn_vgg16_bn

    70.08

    74.77

    0.85

    82.19

    79.67

    1.6

    db_resnet50 + sar_vgg16_bn

    N/A

    N/A

    0.49

    N/A

    N/A

    1.0

    db_resnet50 + sar_resnet31

    N/A

    N/A

    0.27

    N/A

    N/A

    0.83

    Gvision text detection

    0.595

    0.625

    0.753

    0.700

    Gvision doc. text detection

    0.640

    0.533

    0.689

    0.611

    AWS textract

    0.781

    0.830

    0.875

    0.660

    -
    -

    All OCR models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All recognition models of predictors are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -

    Results on private ocr datasets

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Receipts

    Invoices

    IDs

    Architecture

    Recall

    Precision

    Recall

    Precision

    Recall

    Precision

    db_resnet50 + crnn_vgg16_bn (ours)

    78.90

    81.01

    65.68

    69.86

    49.48

    50.46

    Gvision doc. text detection

    68.91

    59.89

    63.20

    52.85

    43.70

    29.21

    AWS textract

    75.77

    77.70

    70.47

    69.13

    46.39

    43.32

    -
    -
    -

    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.

    -
    -
    -doctr.models.zoo.ocr_predictor(det_arch: str = 'db_resnet50', reco_arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) OCRPredictor[source]
    -

    End-to-end OCR architecture using one model for localization, and another for text recognition.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import ocr_predictor
    ->>> model = ocr_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_sar_vgg’, ‘db_sar_resnet’, ‘db_crnn_vgg’, ‘db_crnn_resnet’)

    • -
    • pretrained – If True, returns a model pre-trained on our OCR dataset

    • -
    -
    -
    Returns:
    -

    OCR predictor

    -
    -
    -
    - -
    -
    -
    -

    Model export

    -

    Utility functions to make the most of document analysis models.

    -
    -

    Model compression

    -
    -
    -doctr.models.export.convert_to_tflite(tf_model: Model) bytes[source]
    -

    Converts a model to TFLite format

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_tflite, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_tflite(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.convert_to_fp16(tf_model: Model) bytes[source]
    -

    Converts a model to half precision

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_fp16, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_fp16(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the serialized FP16 model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) bytes[source]
    -

    Quantize a Tensorflow model

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import quantize_model, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = quantize_model(model, (224, 224, 3))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • tf_model – a keras model

    • -
    • input_shape – shape of the expected input tensor (excluding batch dimension) with channel last order

    • -
    -
    -
    Returns:
    -

    the serialized quantized model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -

    Using SavedModel

    -

    Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -SavedModel format as follows:

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> _ = model(input_t, training=False)
    ->>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/')
    -
    -
    -

    And loaded just as easily:

    -
    >>> import tensorflow as tf
    ->>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/')
    -
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.1/modules/contrib.html b/v0.2.1/modules/contrib.html index e99f6b3f74..7fb86b8b38 100644 --- a/v0.2.1/modules/contrib.html +++ b/v0.2.1/modules/contrib.html @@ -14,7 +14,7 @@ - + doctr.contrib - docTR documentation @@ -380,7 +380,7 @@

    Supported contribution modules - + diff --git a/v0.2.1/modules/datasets.html b/v0.2.1/modules/datasets.html index 456e10b172..380a986793 100644 --- a/v0.2.1/modules/datasets.html +++ b/v0.2.1/modules/datasets.html @@ -14,7 +14,7 @@ - + doctr.datasets - docTR documentation @@ -1081,7 +1081,7 @@

    Returns:

    - + diff --git a/v0.2.1/modules/io.html b/v0.2.1/modules/io.html index 01eadaa4b8..24c41954be 100644 --- a/v0.2.1/modules/io.html +++ b/v0.2.1/modules/io.html @@ -14,7 +14,7 @@ - + doctr.io - docTR documentation @@ -760,7 +760,7 @@

    Returns: - + diff --git a/v0.2.1/modules/models.html b/v0.2.1/modules/models.html index c465cc0586..91b8810a6a 100644 --- a/v0.2.1/modules/models.html +++ b/v0.2.1/modules/models.html @@ -14,7 +14,7 @@ - + doctr.models - docTR documentation @@ -1612,7 +1612,7 @@

    Args: - + diff --git a/v0.2.1/modules/transforms.html b/v0.2.1/modules/transforms.html index 30f7a2631a..c5ead3f3ce 100644 --- a/v0.2.1/modules/transforms.html +++ b/v0.2.1/modules/transforms.html @@ -14,7 +14,7 @@ - + doctr.transforms - docTR documentation @@ -835,7 +835,7 @@

    Args:< - + diff --git a/v0.2.1/modules/utils.html b/v0.2.1/modules/utils.html index 888a32c321..b7f6fc570b 100644 --- a/v0.2.1/modules/utils.html +++ b/v0.2.1/modules/utils.html @@ -14,7 +14,7 @@ - + doctr.utils - docTR documentation @@ -715,7 +715,7 @@

    Args: - + diff --git a/v0.2.1/notebooks.html b/v0.2.1/notebooks.html index f97771aebb..d36539f59e 100644 --- a/v0.2.1/notebooks.html +++ b/v0.2.1/notebooks.html @@ -14,7 +14,7 @@ - + docTR Notebooks - docTR documentation @@ -391,7 +391,7 @@

    docTR Notebooks - + diff --git a/v0.2.1/objects.inv b/v0.2.1/objects.inv index 839fd8a5bb508f60f28c61de8ab46d1f92e8ca27..c1700f291b7b6bc7252a625eb1acde3a63b67eca 100644 GIT binary patch delta 4088 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 delta 1848 zcmV-82gmrwAeax3I{`A0JwAV>TFY+RI27IUD+Ek1m|4)UQ?wXl*TzjIgQW4;Zf0rF z5^W2SMOCESre8mNNEAs))T67)(mCgnyboToiAc!uS%es(n9w}L-nD#(W-4H?%P!sl2TxeMJl69sQ*B?Lt{pNVNa%EwVNAz|L?zP`a2 zoQ5XNNsL|Z_6aJu)*@kX#wmaQF=zOILfb$3t2-_I;Zs7w*O8oM`81I>qbKrRaL<^X zNHmUo7bke4T|u24a&mv(#b!8mmYN+*jBzb;bb$ykxcI32qPtL5Ez#thMkYbD4> z%yy`Z%E>Y)LT3?XSk7t|?RPn*DQ55I&TLDlGu5U*^|Z@!9K|6c`81&< zPG3{iAPgnr9~sFRZ?)13&e@KU-630i_%#J~NMeR3XnZSB%Hp4RCtr^d$Z<%HX)ISE z%+qvtIUW}ucKd&Hs)+GC%`2uDMR^$_?-swJF=bLe2@(C{$VspqrSw4ZGw;nwLh=XU zT+xV|yNvCOo1>GoMrZd>GAIqckGtk6D(yP2_&c zH`1o(ZJ;i^fuGW$21$Qd)0F440?p+lmM-PN2lpb+Hj#8PANa*U9QaiY< zemZD94ja!WVld?H9;e4T@1*~n6?KzR)1XVc?GglH^#gJBEk1huC4RvQC;SsUsvls8 zx37QNoNE!wMYL#veYFkrDY`rn*%r-Hl19Q3xpb(8mUinPJp;XLk#Mf?y(=n^b@Vsx zLA+sKk~P*oQFA37Gi22>E|@jQd=oMpna@4FzDkw~Bg8DR2S-;FK3B z0{K4y3G;5});K6|pVd$PPr&yra1gg`;%=>(>Gnhf5LUoDg9VN_l@77#)mt_A2l2>} zj43-}8;LrdTH;`ZyDxbg^&8aY%B3z@oduv~yVm3To7o;M|qJK-m&>rrTApMf5em7nBrgZ0f2`><*ky z8;fCvkbH(F^vNZca_T;?PpL#b8I+R$laTJ|ORhjXRajtDw$dMlwZN=?0@@O$*&D1r zD9)2?cguj4{gB^(;{O5w-$ohV1W11<7K6u!-Wfba*Wo~ev+9&3O2$_MOd{9 z0q#)qH81J4d5;x~1v#spPq6Yxw%O~f`#ly--pVU0tmgPz5!0A86V9yyD-#Tii0KKX z)f?}`Q#+!}G4^fC2DaLq*A2R*IHYSaRSo>QRR&Oua(*tc)YLmQ$Bp_fvw(jVnMh?` zu-iKwBbPO_Zgn;dwjnsGTa%DuNotMyB4E&C#00A|Tr~@e_`{Z-hD7`^0r($RI>a$@J%>dA diff --git a/v0.2.1/py-modindex.html b/v0.2.1/py-modindex.html deleted file mode 100644 index c1569be607..0000000000 --- a/v0.2.1/py-modindex.html +++ /dev/null @@ -1,330 +0,0 @@ - - - - - - - - - - - Python Module Index - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    - -
    -

    Python Module Index

    - -
    - - - - - - - - - - - -
     
    d
    - doctr -
    - -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.2.1/search.html b/v0.2.1/search.html index fef17f06e7..d050f5eac7 100644 --- a/v0.2.1/search.html +++ b/v0.2.1/search.html @@ -14,7 +14,7 @@ - + Search - docTR documentation @@ -226,20 +226,42 @@ - - + + diff --git a/v0.2.1/searchindex.js b/v0.2.1/searchindex.js index f4ed29ff32..6f154115ab 100644 --- a/v0.2.1/searchindex.js +++ b/v0.2.1/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles": {"Artefact": [[2, "artefact"]], "Available Datasets": [[1, "available-datasets"]], "Block": [[2, "block"]], "Build & train your predictor": [[3, "build-train-your-predictor"]], "Changelog": [[0, null]], "Composing transformations": [[6, "composing-transformations"]], "Data Loading": [[1, "data-loading"]], "Detection models": [[5, "detection-models"]], "Detection predictors": [[5, "detection-predictors"]], "DocTR Vocabs": [[1, "id1"]], "DocTR: Document Text Recognition": [[3, null]], "Document": [[2, "document"]], "Document structure": [[2, "document-structure"]], "End-to-End OCR": [[5, "end-to-end-ocr"]], "File reading": [[2, "file-reading"]], "Getting Started": [[3, "getting-started"]], "Installation": [[4, null]], "Line": [[2, "line"]], "Main Features": [[3, "main-features"]], "Model compression": [[5, "model-compression"]], "Model export": [[5, "model-export"]], "Model zoo": [[3, "model-zoo"]], "Notes": [[3, null]], "Package Reference": [[3, null]], "Page": [[2, "page"]], "Post-processing detections": [[5, "post-processing-detections"]], "Post-processing outputs": [[5, "post-processing-outputs"]], "Pre-processing for detection": [[5, "pre-processing-for-detection"]], "Pre-processing for recognition": [[5, "pre-processing-for-recognition"]], "Prerequisites": [[4, "prerequisites"]], "Recognition models": [[5, "recognition-models"]], "Recognition predictors": [[5, "recognition-predictors"]], "Supported Vocabs": [[1, "supported-vocabs"]], "Supported datasets": [[3, "supported-datasets"]], "Supported transformations": [[6, "supported-transformations"]], "Task evaluation": [[7, "task-evaluation"]], "Text Detection": [[5, "text-detection"]], "Text Recognition": [[5, "text-recognition"]], "Text detection models": [[3, "text-detection-models"]], "Text recognition model zoo": [[5, "id2"]], "Text recognition models": [[3, "text-recognition-models"]], "Two-stage approaches": [[5, "two-stage-approaches"]], "Using SavedModel": [[5, "using-savedmodel"]], "Via Git": [[4, "via-git"]], "Via Python Package": [[4, "via-python-package"]], "Visualization": [[7, "visualization"]], "Word": [[2, "word"]], "doctr.datasets": [[1, null]], "doctr.documents": [[2, null]], "doctr.models": [[5, null]], "doctr.transforms": [[6, null]], "doctr.utils": [[7, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]]}, "docnames": ["changelog", "datasets", "documents", "index", "installing", "models", "transforms", "utils"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "datasets.rst", "documents.rst", "index.rst", "installing.rst", "models.rst", "transforms.rst", "utils.rst"], "indexentries": {"artefact (class in doctr.documents)": [[2, "doctr.documents.Artefact", false]], "as_images() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.as_images", false]], "block (class in doctr.documents)": [[2, "doctr.documents.Block", false]], "colorinversion (class in doctr.transforms)": [[6, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[6, "doctr.transforms.Compose", false]], "convert_to_fp16() (in module doctr.models.export)": [[5, "doctr.models.export.convert_to_fp16", false]], "convert_to_tflite() (in module doctr.models.export)": [[5, "doctr.models.export.convert_to_tflite", false]], "cord (class in doctr.datasets)": [[1, "doctr.datasets.CORD", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.crnn_vgg16_bn", false]], "dataloader (class in doctr.datasets.loader)": [[1, "doctr.datasets.loader.DataLoader", false]], "db_resnet50() (in module doctr.models.detection)": [[5, "doctr.models.detection.db_resnet50", false]], "detection_predictor() (in module doctr.models.detection)": [[5, "doctr.models.detection.detection_predictor", false]], "document (class in doctr.documents)": [[2, "doctr.documents.Document", false]], "documentfile (class in doctr.documents)": [[2, "doctr.documents.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[1, "doctr.datasets.encode_sequences", false]], "from_images() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_images", false]], "from_pdf() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_pdf", false]], "from_url() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[1, "doctr.datasets.FUNSD", false]], "get_artefacts() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.get_artefacts", false]], "get_words() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.get_words", false]], "lambdatransformation (class in doctr.transforms)": [[6, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.documents)": [[2, "doctr.documents.Line", false]], "linknet() (in module doctr.models.detection)": [[5, "doctr.models.detection.linknet", false]], "localizationconfusion (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.LocalizationConfusion", false]], "normalize (class in doctr.transforms)": [[6, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models.zoo)": [[5, "doctr.models.zoo.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[1, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[6, "doctr.transforms.OneOf", false]], "page (class in doctr.documents)": [[2, "doctr.documents.Page", false]], "pdf (class in doctr.documents)": [[2, "doctr.documents.PDF", false]], "quantize_model() (in module doctr.models.export)": [[5, "doctr.models.export.quantize_model", false]], "randomapply (class in doctr.transforms)": [[6, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[6, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[6, "doctr.transforms.RandomContrast", false]], "randomgamma (class in doctr.transforms)": [[6, "doctr.transforms.RandomGamma", false]], "randomhue (class in doctr.transforms)": [[6, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[6, "doctr.transforms.RandomJpegQuality", false]], "randomsaturation (class in doctr.transforms)": [[6, "doctr.transforms.RandomSaturation", false]], "read_html() (in module doctr.documents)": [[2, "doctr.documents.read_html", false]], "read_img() (in module doctr.documents)": [[2, "doctr.documents.read_img", false]], "read_pdf() (in module doctr.documents)": [[2, "doctr.documents.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.recognition_predictor", false]], "resize (class in doctr.transforms)": [[6, "doctr.transforms.Resize", false]], "sar_resnet31() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.sar_resnet31", false]], "sar_vgg16_bn() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.sar_vgg16_bn", false]], "show() (doctr.documents.document method)": [[2, "doctr.documents.Document.show", false]], "show() (doctr.documents.page method)": [[2, "doctr.documents.Page.show", false]], "sroie (class in doctr.datasets)": [[1, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[7, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[7, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[7, "doctr.utils.metrics.TextMatch.summary", false]], "textmatch (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.TextMatch", false]], "togray (class in doctr.transforms)": [[6, "doctr.transforms.ToGray", false]], "visiondataset (class in doctr.datasets.core)": [[1, "doctr.datasets.core.VisionDataset", false]], "visualize_page() (in module doctr.utils.visualization)": [[7, "doctr.utils.visualization.visualize_page", false]], "word (class in doctr.documents)": [[2, "doctr.documents.Word", false]]}, "objects": {"doctr.datasets": [[1, 0, 1, "", "CORD"], [1, 0, 1, "", "FUNSD"], [1, 0, 1, "", "OCRDataset"], [1, 0, 1, "", "SROIE"], [1, 1, 1, "", "encode_sequences"]], "doctr.datasets.core": [[1, 0, 1, "", "VisionDataset"]], "doctr.datasets.loader": [[1, 0, 1, "", "DataLoader"]], "doctr.documents": [[2, 0, 1, "", "Artefact"], [2, 0, 1, "", "Block"], [2, 0, 1, "", "Document"], [2, 0, 1, "", "DocumentFile"], [2, 0, 1, "", "Line"], [2, 0, 1, "", "PDF"], [2, 0, 1, "", "Page"], [2, 0, 1, "", "Word"], [2, 1, 1, "", "read_html"], [2, 1, 1, "", "read_img"], [2, 1, 1, "", "read_pdf"]], "doctr.documents.Document": [[2, 2, 1, "", "show"]], "doctr.documents.DocumentFile": [[2, 2, 1, "", "from_images"], [2, 2, 1, "", "from_pdf"], [2, 2, 1, "", "from_url"]], "doctr.documents.PDF": [[2, 2, 1, "", "as_images"], [2, 2, 1, "", "get_artefacts"], [2, 2, 1, "", "get_words"]], "doctr.documents.Page": [[2, 2, 1, "", "show"]], "doctr.models.detection": [[5, 1, 1, "", "db_resnet50"], [5, 1, 1, "", "detection_predictor"], [5, 1, 1, "", "linknet"]], "doctr.models.export": [[5, 1, 1, "", "convert_to_fp16"], [5, 1, 1, "", "convert_to_tflite"], [5, 1, 1, "", "quantize_model"]], "doctr.models.recognition": [[5, 1, 1, "", "crnn_vgg16_bn"], [5, 1, 1, "", "recognition_predictor"], [5, 1, 1, "", "sar_resnet31"], [5, 1, 1, "", "sar_vgg16_bn"]], "doctr.models.zoo": [[5, 1, 1, "", "ocr_predictor"]], "doctr.transforms": [[6, 0, 1, "", "ColorInversion"], [6, 0, 1, "", "Compose"], [6, 0, 1, "", "LambdaTransformation"], [6, 0, 1, "", "Normalize"], [6, 0, 1, "", "OneOf"], [6, 0, 1, "", "RandomApply"], [6, 0, 1, "", "RandomBrightness"], [6, 0, 1, "", "RandomContrast"], [6, 0, 1, "", "RandomGamma"], [6, 0, 1, "", "RandomHue"], [6, 0, 1, "", "RandomJpegQuality"], [6, 0, 1, "", "RandomSaturation"], [6, 0, 1, "", "Resize"], [6, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[7, 0, 1, "", "LocalizationConfusion"], [7, 0, 1, "", "OCRMetric"], [7, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.LocalizationConfusion": [[7, 2, 1, "", "summary"]], "doctr.utils.metrics.OCRMetric": [[7, 2, 1, "", "summary"]], "doctr.utils.metrics.TextMatch": [[7, 2, 1, "", "summary"]], "doctr.utils.visualization": [[7, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [2, 7], "0": [1, 3, 5, 6, 7], "00": [], "01": 5, "0123456789": 1, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 1, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": [], "02": 5, "02562": [], "03": 3, "035": [], "0361328125": [], "04": [], "05": 3, "06": [], "06640625": [], "07": [], "08": 5, "09": [], "0966796875": [], "1": [1, 3, 5, 6, 7], "10": [1, 7], "100": [5, 6, 7], "1000": 5, "101": [], "1024": 5, "104": [], "106": [], "108": [], "1095": [], "11": 3, "110": 7, "1107": [], "114": [], "115": [], "1156": [], "116": [], "118": [], "11800h": [], "11th": [], "12": 5, "120": [], "123": [], "126": [], "1268": [], "128": 5, "13": 5, "130": [], "13068": [], "131": [], "1337891": [], "1357421875": [], "1396484375": [], "14": 5, "1420": [], "14470v1": [], "149": [], "15": 5, "150": 7, "154": 1, "1552": [], "16": 5, "1630859375": [], "1684": [], "16x16": [], "17": [], "1778": [], "1782": [], "18": 3, "185546875": [], "19": 5, "1900": [], "1910": [], "19342": [], "19370": [], "195": [], "19598": [], "199": 5, "1999": [], "1m": 5, "2": [3, 5, 6], "20": 5, "200": 7, "2000": [], "2003": [], "2012": [], "2013": [], "2015": [], "2019": 3, "2021": 3, "2023": [], "207901": [], "21": 5, "2103": [], "2186": [], "21888": [], "22": [], "224": [5, 6], "225": 6, "22672": [], "229": 6, "23": [], "233": [], "236": [], "24": [], "246": [], "249": [], "25": 5, "2504": [], "255": [5, 6, 7], "256": 5, "257": [], "26": [], "26032": [], "264": [], "27": 5, "2700": [], "2710": [], "2749": [], "28": [], "287": [], "29": 5, "296": [], "299": [], "2d": [], "3": [2, 3, 4, 5, 6, 7], "30": [], "300": [], "3000": [], "301": [], "30595": 5, "30ghz": [], "31": 5, "32": [1, 5, 6], "3232421875": [], "33": [], "33402": [], "33608": [], "34": [], "340": [], "3456": [], "3515625": [], "36": [], "360": [], "37": [], "38": [], "39": 5, "4": [], "40": [], "406": 6, "41": [], "42": [], "43": 5, "44": [], "45": [], "456": 6, "46": 5, "47": 5, "472": [], "48": 5, "485": 6, "49": 5, "49377": [], "5": [1, 6, 7], "50": 5, "51": [], "51171875": [], "512": [], "52": [1, 5], "529": [], "53": 5, "533": 5, "54": [], "540": [], "5478515625": [], "55": [], "56": [], "57": [], "58": [], "580": [], "5810546875": [], "583": [], "59": 5, "595": 5, "597": [], "5k": [], "5m": 5, "6": [4, 5, 6], "60": 6, "600": [5, 7], "61": [], "611": 5, "62": [], "625": 5, "626": [], "629": [], "63": 5, "630": [], "64": [5, 6], "640": 5, "641": [], "647": [], "65": 5, "66": 5, "660": 5, "664": [], "666": [], "67": 5, "672": [], "68": 5, "689": 5, "69": 5, "693": [], "694": [], "695": [], "6m": [], "7": 5, "70": [5, 7], "700": 5, "701": [], "702": [], "707470": [], "71": [], "7100000": [], "713": [], "7141797": [], "7149": [], "72": [], "72dpi": [], "73": [], "73257": [], "733": [], "74": 5, "745": [], "75": 5, "753": 5, "7581382": [], "76": [], "77": 5, "772": [], "772875": [], "78": 5, "780": [], "781": 5, "783": [], "785": [], "789": [], "79": 5, "793533": [], "796": [], "798": [], "7m": [], "8": [5, 6], "80": [], "800": [5, 7], "81": 5, "817": [], "82": 5, "8275l": 5, "83": 5, "830": 5, "84": [], "849": [], "85": 5, "8564453125": [], "857": [], "85875": [], "86": 5, "860": [], "8603515625": [], "862": [], "863": [], "87": 5, "8707": [], "875": 5, "88": [], "89": 5, "8m": 5, "9": [], "90": 5, "90k": [], "90kdict32px": [], "91": 5, "913": [], "914085328578949": [], "917": [], "92": 5, "921": [], "93": [], "94": [], "95": 7, "9578408598899841": [], "96": 1, "97": [], "98": [], "99": [], "9949972033500671": [], "A": [1, 2, 3, 5], "And": 5, "As": [], "Be": [], "Being": [], "By": [], "For": [4, 5], "If": [2, 4, 5], "In": 5, "It": 6, "Its": 5, "No": [], "Of": 1, "Or": [], "The": [1, 2, 5, 7], "Then": 5, "To": [], "_": [1, 5], "__call__": [], "_build": [], "_i": 7, "ab": [], "abc": [], "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 1, "abdef": [], "abl": [], "about": 5, "abov": 5, "abstract": 1, "abstractdataset": [], "abus": [], "accent": [], "accept": [], "access": [1, 2, 3], "account": [], "accur": [], "accuraci": 7, "achiev": [], "act": [], "action": [], "activ": [], "ad": 6, "adapt": [], "add": [6, 7], "add_hook": [], "add_label": 7, "addit": [], "addition": 5, "address": 2, "adjust": 6, "advanc": [], "advantag": [], "advis": [], "aesthet": [], "affect": [], "after": [], "ag": [], "again": [], "aggreg": [1, 7], "aggress": [], "align": 2, "all": [1, 2, 3, 5, 6, 7], "allow": [], "along": 5, "alreadi": [], "also": [], "alwai": [], "an": [1, 2, 3, 5, 7], "analysi": [2, 5], "ancient_greek": [], "andrej": [], "angl": 2, "ani": [1, 2, 3, 5, 7], "annot": 2, "anot": [], "anoth": [1, 4, 5], "answer": [], "anyascii": [], "anyon": 3, "anyth": [], "api": [], "apolog": [], "apologi": [], "app": [], "appear": [], "appli": [1, 6], "applic": 5, "appoint": [], "appreci": [], "appropri": [], "ar": [1, 2, 4, 5, 6, 7], "arab": [], "arabic_diacrit": [], "arabic_lett": [], "arabic_punctu": [], "arbitrarili": [], "arch": 5, "architectur": [3, 5], "archiv": 1, "area": [], "argument": [1, 2], "around": 5, "arrai": [2, 7], "art": 3, "artefact": 7, "artefact_typ": 2, "articl": [], "artifici": [], "arxiv": [], "as_imag": 2, "asarrai": 7, "ascii_lett": 1, "aspect": 6, "assess": 7, "assign": 7, "associ": 2, "assum": [], "assume_straight_pag": [], "astyp": [5, 7], "attack": [], "attend": [3, 5], "attent": [], "autoclass": [], "autom": 3, "automat": [], "autoregress": [], "avail": [3, 5, 6], "averag": [5, 6], "avoid": [], "aw": [3, 5], "awar": [], "azur": [], "b": 7, "b_j": 7, "back": [], "backbon": 5, "backend": 5, "background": [], "bangla": [], "bar": [], "bar_cod": [], "baranovskij": [], "base": 5, "baselin": 5, "batch": [1, 5, 6], "batch_siz": 1, "bblanchon": [], "bbox": [], "becaus": [], "been": [5, 7], "befor": 1, "begin": 7, "behavior": [], "being": [5, 7], "belong": [], "benchmark": [], "best": [], "better": [], "between": [6, 7], "bgr": 2, "bilinear": [5, 6], "bin_thresh": [], "binar": [3, 5], "binari": [2, 5], "bit": [], "block": [5, 7], "block_1_1": [], "blur": [], "bmvc": [], "bn": [], "bodi": [], "bool": [1, 2, 5, 6, 7], "boolean": [], "both": [3, 5, 6], "bottom": [], "bound": [2, 5, 6, 7], "box": [2, 5, 7], "box_thresh": [], "brew": 4, "bright": 6, "browser": [], "build": [], "built": [], "byte": [2, 5], "c": [], "c5": 5, "c_j": [], "cach": [], "cache_sampl": [], "cairo": 4, "call": [], "callabl": [1, 6], "can": [1, 4, 5], "capabl": 5, "case": 7, "cf": 5, "cfg": [], "challeng": [], "challenge2_test_task12_imag": [], "challenge2_test_task1_gt": [], "challenge2_training_task12_imag": [], "challenge2_training_task1_gt": [], "chang": [], "changelog": 3, "channel": [2, 5, 6], "channel_prior": [], "channelshuffl": [], "charact": [1, 2, 3, 5, 7], "charactergener": [], "characterist": [], "charg": 5, "charset": [], "chart": 2, "check": [], "checkpoint": [], "chip": [], "christian": [], "ci": [], "clarifi": [], "clariti": [], "class": [1, 2, 6, 7], "class_nam": [], "classif": 5, "classmethod": 2, "clear": [], "clone": 4, "close": [], "co": [], "code": [2, 3], "codecov": [], "colab": [], "collate_fn": [], "collect": 2, "color": 6, "colorinvers": 6, "column": 2, "com": [2, 4], "combin": 5, "command": [], "comment": [], "commit": [], "common": [6, 7], "commun": [], "compar": 3, "comparison": 7, "competit": 1, "compil": [], "complaint": [], "complementari": 7, "complet": [], "compon": 5, "compos": [1, 3, 5], "comprehens": [], "comput": [5, 7], "conf_threshold": [], "confid": 2, "config": [], "configur": [], "confus": 7, "consecut": [5, 6], "consequ": [], "consid": [2, 7], "consist": [], "consolid": [1, 3], "constant": 6, "construct": [], "contact": [], "contain": [], "content": [1, 2], "context": [], "contib": [], "continu": [], "contrast": 6, "contrast_factor": 6, "contrib": [], "contribut": [], "contributor": [], "conv_sequ": 5, "convers": 2, "convert": [2, 5, 6], "convert_page_to_numpi": 2, "convert_to_fp16": 5, "convert_to_tflit": 5, "convolut": 3, "cool": [], "coordin": 2, "cord": [1, 3, 5], "core": [1, 7], "corner": [], "correct": 6, "correspond": 5, "could": [], "counterpart": 7, "cover": [], "coverag": [], "cpu": [3, 5], "creat": [], "crnn": [3, 5], "crnn_mobilenet_v3_larg": [], "crnn_mobilenet_v3_smal": [], "crnn_resnet31": 5, "crnn_vgg16_bn": 5, "crop": 5, "crop_orient": [], "crop_orientation_predictor": [], "crop_param": [], "cuda": [], "currenc": 1, "current": [], "custom": [], "custom_crop_orientation_model": [], "custom_page_orientation_model": [], "customhook": [], "cvit": [], "czczup": [], "czech": [], "d": [], "daili": 3, "danish": [], "data": [2, 3, 5, 6, 7], "dataload": 1, "dataset": 5, "dataset_info": [], "date": [], "db": [], "db_crnn_resnet": 5, "db_crnn_vgg": 5, "db_mobilenet_v3_larg": [], "db_resnet34": [], "db_resnet50": 5, "db_sar_resnet": 5, "db_sar_vgg": 5, "dbnet": [3, 5], "deal": [], "decis": [], "decod": 2, "decode_img_as_tensor": [], "dedic": [], "deem": [], "deep": 5, "def": [], "default": [2, 5], "defer": 1, "defin": 7, "deform": 5, "degre": [], "degress": 2, "delet": [], "delimit": [], "delta": 6, "demo": [], "demonstr": [], "depend": [3, 4], "deploi": [], "deploy": [], "derogatori": [], "describ": 5, "descript": [], "design": 6, "desir": [], "det_arch": 5, "det_b": [], "det_model": [], "det_param": [], "det_predictor": [], "detail": [], "detect": [], "detect_languag": [], "detect_orient": [], "detection_predictor": 5, "detection_task": [], "detectiondataset": [], "detectionmetr": [], "detectionpredictor": 5, "detector": [], "deterior": [], "determin": [], "dev": [], "develop": [], "developp": 4, "deviat": 6, "devic": [], "dict": [2, 7], "dictionari": [2, 7], "differ": [], "differenti": [3, 5], "digit": 1, "dimens": [2, 5, 7], "dimension": 6, "direct": [], "directli": 5, "directori": [], "disabl": [], "disable_crop_orient": [], "disable_page_orient": [], "disclaim": 5, "discuss": [], "disk": 1, "disparag": [], "displai": [2, 7], "display_artefact": 7, "distanc": [], "distribut": 6, "div": [], "divers": [], "divid": [], "do": [], "doc": [2, 5], "docartefact": [], "docstr": [], "doctr": 4, "doctr_cache_dir": [], "doctr_multiprocessing_dis": [], "document": [1, 5, 7], "documentbuild": [], "documentfil": 2, "doesn": [], "don": [], "done": 6, "download": 1, "downsiz": [], "draw": 6, "drop": 1, "drop_last": 1, "dtype": 5, "dual": [], "dummi": [], "dummy_img": [], "dummy_input": [], "dure": [], "dutch": [], "dynam": [], "dynamic_seq_length": [], "e": [2, 4], "each": [1, 2, 3, 5, 6, 7], "eas": [], "easi": [3, 7], "easier": 5, "easili": [2, 5, 7], "econom": [], "edit": [], "educ": [], "effect": [], "effici": [1, 5], "either": 5, "element": [1, 2, 5], "els": [], "email": [], "empathi": [], "en": [], "enabl": 2, "enclos": 2, "encod": [1, 2, 5], "encode_sequ": 1, "encount": [], "encrypt": [], "end": [1, 3, 7], "english": [], "enough": 5, "ensur": [], "entir": 2, "entri": [], "environ": [], "eo": 1, "equiv": [], "error": [], "estim": [], "etc": 2, "ethnic": [], "evalu": [1, 3, 5], "event": [], "everyon": [], "everyth": [], "exact": 7, "exactmatch": [], "exampl": [1, 2, 5, 6, 7], "exchang": [], "exclud": 5, "execut": [], "exist": [], "expand": [], "expect": [1, 2, 5, 6], "experi": 5, "explan": 5, "explicit": [], "exploit": 5, "export": [2, 3, 7], "export_as_straight_box": [], "export_as_xml": [], "export_model_to_onnx": [], "express": 6, "extens": 2, "extern": [], "extra": 4, "extract": [1, 3], "extract_arch": 1, "extractor": 5, "f_": 7, "f_a": 7, "factor": 6, "fair": [], "fairli": [], "fals": [1, 5, 6], "faq": [], "fascan": [], "fast": 1, "fast_bas": [], "fast_smal": [], "fast_tini": [], "faster": [], "fasterrcnn_mobilenet_v3_large_fpn": [], "favorit": [], "featur": [5, 7], "feed": 5, "feedback": [], "feel": [], "felix92": [], "few": 4, "figsiz": 7, "figur": 7, "file": [1, 3], "file_hash": 1, "file_nam": 1, "final": [], "find": 4, "fine": 3, "finnish": [], "first": [], "firsthand": [], "fit": [], "fitz": 2, "flag": [], "flexibl": 7, "flip": [], "float": [2, 6, 7], "float32": 5, "fn": 6, "focu": [], "focus": [], "folder": [1, 5], "follow": [4, 5, 6, 7], "font": [], "font_famili": [], "foral": 7, "forc": [], "forg": [], "form": [1, 3], "format": [2, 5], "forpost": [1, 3], "forum": [], "found": [], "fp": 5, "fp16": 5, "frac": 7, "frame": 5, "framework": 1, "free": [], "french": [1, 5], "friendli": 3, "from": [1, 2, 3, 5, 6, 7], "from_hub": [], "from_imag": 2, "from_pdf": 2, "from_url": 2, "full": [1, 5, 7], "fulli": [], "function": [5, 6, 7], "funsd": [1, 3, 5], "further": [], "futur": [], "g": 2, "g_": 7, "g_x": 7, "gallagh": [], "gamma": 6, "gaussian": 6, "gaussianblur": [], "gaussiannois": [], "gdk": 4, "gen": [], "gender": [], "gener": [], "generic_cyrillic_lett": [], "geometri": 2, "geq": 7, "german": [], "get": 2, "get_artefact": 2, "get_word": 2, "gettextword": 2, "git": 3, "github": 4, "give": [], "given": [1, 2, 5, 7], "global": [], "go": [], "good": [], "googl": [], "googlevis": 3, "gpu": 3, "gracefulli": [], "graph": 2, "grayscal": 6, "ground": 7, "groung": [], "group": [], "gt": [], "gt_box": [], "gt_label": [], "gtk": 4, "guid": [], "guidanc": [], "gvision": 5, "h": 2, "h_": 7, "ha": [1, 7], "half": 5, "handl": 1, "handwrit": [], "handwritten": [], "harass": [], "hardwar": [], "harm": [], "hat": 7, "have": [1, 5, 7], "head": [], "healthi": [], "hebrew": [], "height": 2, "hello": 7, "help": [], "here": [1, 4, 6], "hf": [], "hf_hub_download": [], "high": 2, "higher": 4, "hindi": [], "hindi_digit": [], "hocr": [], "hook": [], "horizont": 2, "hous": [], "how": [], "howev": [], "hsv": 6, "html": [], "http": [2, 4], "hub": [], "hue": 6, "huggingfac": [], "hw": [], "i": [1, 2, 5, 6, 7], "i7": [], "ibrahimov": [], "ic03": [], "ic13": [], "icdar": 3, "icdar2019": 1, "id": 5, "ident": [], "identifi": [3, 5], "ignor": [], "ignore_acc": [], "ignore_cas": [], "iiit": [], "iiit5k": [], "iiithw": [], "imag": [1, 2, 5, 6, 7], "imagenet": [], "imageri": [], "images_90k_norm": [], "img": [1, 6], "img_cont": [], "img_fold": 1, "img_path": [], "img_transform": [], "imgur5k": [], "imgur5k_annot": [], "imlist": [], "impact": [], "implement": [1, 2, 5, 6, 7], "import": [1, 2, 5, 6, 7], "improv": [], "inappropri": [], "incid": [], "includ": [4, 5], "inclus": [], "increas": 6, "independ": [], "index": 2, "indic": 7, "individu": [], "infer": [3, 6], "inform": [1, 3, 5], "inherit": [1, 5], "input": [2, 5, 6], "input_crop": [], "input_pag": [5, 7], "input_shap": 5, "input_t": 5, "input_tensor": 5, "inspir": 6, "instal": 3, "instanc": 5, "instanti": 5, "instead": 2, "insult": [], "int": [1, 2, 5, 6], "int64": [], "integ": 7, "integr": 3, "intel": [], "interact": [2, 7], "interfac": [], "interoper": [], "interpol": [5, 6], "interpret": [1, 2], "intersect": 7, "invert": 6, "investig": [], "invis": [], "invoic": 5, "involv": 5, "io": [], "iou": 7, "iou_thresh": 7, "iou_threshold": [], "irregular": 5, "isn": 1, "issu": [], "italian": [], "iter": 1, "its": [1, 2, 5, 7], "itself": [], "j": 7, "jame": [], "job": [], "join": [], "jpeg": 6, "jpegqual": 6, "jpg": [1, 2], "json": [], "json_output": [], "jump": [], "just": 5, "kei": [], "kera": 5, "kernel": [], "kernel_s": 5, "kernel_shap": [], "keywoard": [], "keyword": [1, 2], "kie": [], "kie_predictor": [], "kiepredictor": [], "kind": [], "know": [], "kwarg": [1, 2, 5, 7], "l": 7, "l_j": 7, "label": [1, 7], "label_fil": 1, "label_fold": [], "label_path": [], "labels_path": [], "ladder": [], "lambda": 6, "lambdatransform": 6, "lang": [], "languag": [2, 3], "larg": [], "largest": 7, "last": [1, 4, 5], "latenc": [], "later": [], "latest": 4, "latin": 1, "layer": [], "layout": [], "lead": [], "leader": [], "learn": 5, "least": [], "left": 7, "legacy_french": [], "length": 1, "less": [], "let": 5, "letter": [], "level": [5, 7], "levenshtein": [], "leverag": [], "lf": [], "libffi": 4, "librari": 4, "light": 3, "lightweight": [], "like": [], "limits_": 7, "line": [3, 7], "line_1_1": [], "link": [], "linknet": [3, 5], "linknet_resnet18": [], "linknet_resnet34": [], "linknet_resnet50": [], "linux": 4, "list": [1, 2, 6], "ll": 7, "load": [3, 5], "load_state_dict": [], "load_weight": [], "loader": 1, "loc_pr": [], "local": [1, 3, 5, 7], "localis": [], "localizationconfus": 7, "locat": [], "login": [], "login_to_hub": [], "logo": 2, "love": [], "lower": [6, 7], "m": [5, 7], "m1": [], "macbook": [], "machin": [], "maco": 4, "made": 3, "magc_resnet31": [], "mai": [], "mail": [], "main": [], "maintain": 3, "mainten": [], "make": [5, 7], "mani": [], "manipul": [], "map": [1, 5], "map_loc": [], "master": [], "match": [3, 7], "mathcal": 7, "matplotlib": 7, "max": 7, "max_angl": [], "max_area": [], "max_char": [], "max_delta": 6, "max_dist": [], "max_gain": 6, "max_gamma": 6, "max_qual": 6, "max_ratio": [], "maximum": 1, "maxval": [5, 6], "mbox": 7, "mean": [6, 7], "meaniou": 7, "meant": 2, "measur": 5, "media": [], "median": [], "meet": [], "member": [], "memori": [], "mention": [], "merg": [], "messag": [], "meta": [], "metadata": [], "metal": [], "method": 6, "metric": [5, 7], "middl": [], "might": 5, "min": [], "min_area": [], "min_char": [], "min_gain": 6, "min_gamma": 6, "min_qual": 6, "min_ratio": [], "min_val": 6, "minde": 4, "minim": [], "minimalist": [], "minimum": 7, "minval": 6, "miss": [], "mistak": [], "mix": 3, "mixed_float16": [], "mixed_precis": [], "mjsynth": [], "mnt": [], "mobilenet": [], "mobilenet_v3_larg": [], "mobilenet_v3_large_r": [], "mobilenet_v3_smal": [], "mobilenet_v3_small_crop_orient": [], "mobilenet_v3_small_page_orient": [], "mobilenet_v3_small_r": [], "mobilenetv3": [], "modal": [], "mode": 4, "model": [1, 7], "model_nam": [], "model_path": [], "moder": [], "modif": [], "modifi": [], "modul": [2, 5, 6, 7], "more": [], "moscardi": [], "most": 5, "mozilla": [], "multi": [], "multilingu": [], "multipl": [1, 2, 6], "multipli": 6, "multiprocess": [], "my": [], "my_awesome_model": [], "my_hook": [], "n": [1, 5, 7], "na": [], "name": [1, 5], "nation": [], "natur": 3, "ndarrai": [1, 2, 7], "necessari": [], "need": [4, 7], "neg": 6, "nest": [], "nestedobject": 6, "netraj": [], "network": [3, 5], "neural": [3, 5], "new": [], "newer": [], "next": 1, "nois": [], "noisi": [1, 3], "non": [2, 6, 7], "none": [1, 2], "normal": [5, 6], "norwegian": [], "note": 0, "now": [], "np": [5, 7], "num_output_channel": [], "num_sampl": [], "number": [1, 6, 7], "numpi": [2, 5, 7], "o": 4, "obb": [], "obj_detect": [], "object": 1, "objectness_scor": [], "oblig": [], "obtain": [], "occupi": [], "ocr": [1, 3, 7], "ocr_carea": [], "ocr_db_crnn": 7, "ocr_lin": [], "ocr_pag": [], "ocr_par": [], "ocr_predictor": 5, "ocrdataset": 1, "ocrmetr": 7, "ocrpredictor": 5, "ocrx_word": [], "offens": [], "offici": [], "offlin": [], "offset": 6, "onc": [1, 5], "one": [1, 5, 6], "oneof": 6, "ones": [], "onli": [6, 7], "onlin": [], "onnx": [], "onnxruntim": [], "onnxtr": [], "opac": [], "opacity_rang": [], "open": [], "opinion": [], "optic": [3, 5], "optim": 3, "option": [], "order": [1, 2, 5], "org": [], "organ": 2, "orient": 2, "orientationpredictor": [], "other": [], "otherwis": 7, "our": 5, "out": [5, 6, 7], "outpout": [], "output": [2, 6], "output_s": [2, 6], "outsid": [], "over": [4, 7], "overal": [], "overlai": 2, "overview": [], "overwrit": 1, "overwritten": [], "own": 3, "p": 6, "packag": 7, "pad": [1, 5, 6], "page": [5, 7], "page1": 2, "page2": 2, "page_1": [], "page_idx": 2, "page_orientation_predictor": [], "page_param": [], "pair": 7, "pango": 4, "paper": [], "par_1_1": [], "paragraph": [], "paragraph_break": [], "parallel": [], "param": [5, 6], "paramet": [1, 2, 3, 5, 6, 7], "pars": [1, 3], "parseq": [], "part": 6, "parti": [], "partial": [], "particip": [], "pass": [1, 5], "password": [], "patch": [], "path": [1, 2, 5], "path_to_checkpoint": [], "path_to_custom_model": [], "path_to_pt": [], "patil": [], "pattern": [], "pdf": 2, "pdfpage": [], "peopl": [], "per": [5, 6], "perform": [2, 3, 5, 6, 7], "period": [], "permiss": [], "permut": [], "persian_lett": [], "person": [], "phase": [], "photo": [], "physic": 2, "pick": 6, "pictur": 2, "pip": 4, "pipelin": [], "pixbuf": 4, "pixel": [2, 6], "platinum": 5, "pleas": [], "plot": 7, "plt": 7, "plug": [], "plugin": [], "png": 2, "point": [], "polici": [], "polish": [], "polit": [], "polygon": [], "pool": [], "portugues": [], "posit": 7, "possibl": 7, "post": [], "postprocessor": [], "potenti": 5, "power": 3, "ppageno": [], "pre": [], "precis": [5, 7], "pred": [], "pred_box": [], "pred_label": [], "predefin": 1, "predict": [2, 7], "predictor": [], "prefer": 1, "preinstal": [], "preprocessor": 5, "prerequisit": 3, "present": 1, "preserv": 6, "preserve_aspect_ratio": 6, "pretrain": [3, 5, 7], "pretrained_backbon": [], "print": [], "prior": [], "privaci": [], "privat": 5, "probabl": 6, "problem": [], "procedur": 6, "process": [2, 3], "processor": 5, "produc": 5, "product": [], "profession": [], "project": [], "promptli": [], "proper": [], "properli": 1, "properti": 5, "provid": [3, 5], "public": 3, "publicli": [], "publish": [], "pull": [], "punctuat": 1, "pure": [], "purpos": 5, "push_to_hf_hub": [], "py": [], "pypdfium2": [], "pyplot": 7, "python": 3, "python3": [], "pytorch": [], "q": [], "qr": 2, "qr_code": [], "qualiti": 6, "quantiz": 5, "quantize_model": 5, "question": [], "quickli": 3, "quicktour": [], "r": [], "race": [], "ramdisk": [], "rand": [5, 7], "random": [5, 6, 7], "randomappli": 6, "randombright": 6, "randomcontrast": 6, "randomcrop": [], "randomgamma": 6, "randomhorizontalflip": [], "randomhu": 6, "randomjpegqu": 6, "randomli": 6, "randomres": [], "randomrot": [], "randomsatur": 6, "randomshadow": [], "rang": 6, "rassi": [], "ratio": 6, "raw": [2, 7], "re": 1, "read": [3, 5], "read_html": 2, "read_img": 2, "read_img_as_numpi": [], "read_img_as_tensor": [], "read_pdf": 2, "readi": [], "real": [5, 6], "realli": [], "reason": [], "rebuild": [], "rebuilt": [], "recal": [5, 7], "receipt": [1, 3, 5], "reco_arch": 5, "reco_b": [], "reco_model": [], "reco_param": [], "reco_predictor": [], "recogn": [], "recognit": 7, "recognition_predictor": 5, "recognition_task": [], "recognitiondataset": [], "recognitionpredictor": 5, "rectangular": [], "recurr": 3, "reduc": 6, "refer": [], "regardless": [], "region": [], "regroup": 7, "regular": [], "reject": [], "rel": 2, "relat": [], "releas": [0, 4], "relev": [], "religion": [], "relu": 5, "remov": [], "render": [], "repo": [], "repo_id": [], "report": [], "repositori": [], "repres": [2, 5], "represent": 5, "request": [], "requir": [4, 6], "research": 3, "residu": [], "resiz": [5, 6], "resnet": 5, "resnet18": [], "resnet31": [], "resnet34": [], "resnet50": [], "resolv": 2, "resolve_block": [], "resolve_lin": [], "resourc": [], "respect": [], "rest": [6, 7], "restrict": [], "result": [2, 5], "return": [1, 2, 5, 7], "reusabl": 5, "review": [], "rgb": [2, 6], "rgb_mode": [], "rgb_output": 2, "right": [5, 7], "roboflow": [], "robust": 3, "root": 1, "rotat": 2, "run": 4, "same": [2, 7], "sampl": 1, "sample_transform": 1, "sanjin": [], "sar": [3, 5], "sar_resnet31": 5, "sar_vgg16_bn": 5, "satur": 6, "save": [1, 5], "saved_model": 5, "scale": 7, "scale_rang": [], "scan": [1, 3], "scene": 5, "scheme": 5, "score": 7, "scratch": 3, "script": [], "seamless": 3, "seamlessli": [], "search": [], "searchabl": [], "sec": [], "second": 5, "section": [], "secur": [], "see": [], "seemlessli": 3, "seen": 5, "segment": 5, "self": [], "semant": 5, "send": [], "sens": 7, "sensit": [], "separ": 5, "sequenc": [1, 2, 5, 7], "sequenti": [5, 6], "seri": [], "serial": 5, "serialized_model": 5, "seriou": [], "set": [1, 5, 7], "set_global_polici": [], "sever": [2, 6], "sex": [], "sexual": [], "sha256": 1, "shade": [], "shape": [2, 5, 6, 7], "share": [], "shift": 6, "shm": [], "should": [1, 2, 7], "show": [2, 3, 5, 7], "showcas": [], "shuffl": 1, "side": 7, "signatur": 2, "signific": 1, "simpl": 5, "simpler": [], "sinc": 1, "singl": [], "single_img_doc": [], "size": [1, 2, 5, 6], "skew": [], "slack": [], "slightli": [], "small": 3, "smallest": 2, "snapshot_download": [], "snippet": [], "so": [], "social": [], "socio": [], "some": [], "someth": [], "somewher": [], "sort": [], "sourc": [1, 2, 5, 6, 7], "space": [], "span": [], "spanish": [], "spatial": 2, "special": 3, "specif": [1, 5, 7], "specifi": 2, "speed": [3, 5], "sphinx": [], "sroie": [1, 3], "stabl": 4, "stackoverflow": [], "stage": 3, "standalon": [], "standard": 6, "start": [], "state": 3, "static": 7, "statist": 5, "statu": [], "std": 6, "step": [], "still": [], "str": [1, 2, 5, 6, 7], "straight": [], "straighten": [], "straighten_pag": [], "straigten_pag": [], "stream": 2, "street": [], "strict": [], "strictli": 7, "string": [1, 2, 5, 7], "strive": [], "strong": 5, "structur": [3, 5], "subset": [1, 5], "suggest": [], "sum": 7, "summari": 7, "support": 5, "sustain": [], "svhn": [], "svt": [], "swedish": [], "symbol": 5, "symmetr": 6, "symmetric_pad": 6, "synthet": [], "synthtext": [], "system": [], "t": 1, "tabl": [], "take": [], "target": [1, 2, 5, 6], "target_s": 1, "task": [1, 3, 5], "task2": [], "team": [], "techminde": [], "templat": 2, "tensor": [1, 5, 6], "tensorflow": [3, 5, 6], "tensorspec": [], "term": [], "test": [], "test_set": [], "text": [2, 7], "text_output": [], "textmatch": 7, "textnet": [], "textnet_bas": [], "textnet_smal": [], "textnet_tini": [], "textract": [3, 5], "textstylebrush": [], "textual": [1, 2, 3], "tf": [5, 6], "tf_model": 5, "tflite": 5, "than": [4, 7], "thank": [], "thei": [], "them": [1, 4], "thi": [4, 5, 7], "thing": [], "third": [], "those": [2, 4, 5], "threaten": [], "threshold": [], "through": [1, 6], "tilman": [], "time": [1, 5, 7], "tini": [], "titl": 2, "tm": [], "tmp": [], "togeth": [2, 5], "tograi": 6, "tool": [], "top": 7, "topic": [], "torch": [], "torchvis": 6, "total": [], "toward": [], "train": [1, 5, 6], "train_it": 1, "train_load": 1, "train_pytorch": [], "train_set": 1, "train_tensorflow": [], "trainabl": 5, "tranform": 6, "transcrib": [], "transfer": [], "transfo": 6, "transform": [1, 3], "translat": [], "troll": [], "true": [1, 2, 5, 6, 7], "truth": 7, "tune": 3, "tupl": [2, 5, 6, 7], "turn": 5, "two": 2, "txt": [], "type": [2, 5], "typic": [], "u": [], "ucsd": [], "udac": [], "uint8": [2, 5, 7], "ukrainian": [], "unaccept": [], "underli": 1, "underneath": 2, "understand": [1, 3], "unidecod": 7, "uniform": [5, 6], "uniformli": [], "uninterrupt": 2, "union": 7, "unit": [], "unittest": [], "unlock": [], "unoffici": [], "unprofession": [], "unsolicit": [], "unsupervis": [], "unwelcom": [], "up": 5, "updat": 7, "upgrad": [], "upper": 6, "uppercas": [], "url": [1, 2], "us": [1, 4, 7], "usabl": 5, "usag": 5, "use_polygon": [], "useabl": [], "user": [2, 3, 4], "utf": [], "util": [3, 5], "v0": 3, "v1": [], "v3": [], "valid": [], "valu": [2, 6], "valuabl": 3, "variabl": [], "varieti": [], "veri": [], "verifi": 1, "verma": [], "version": 5, "vgg": 5, "vgg16": 5, "vgg16_bn_r": [], "via": 3, "video": [], "vietnames": [], "view": [], "viewpoint": [], "violat": [], "visibl": [], "vision": [], "visiondataset": 1, "visiontransform": [], "visual": 3, "visualize_pag": 7, "vit_": [], "vit_b": [], "vitstr": [], "vitstr_bas": [], "vitstr_smal": [], "viz": [], "vocab": [3, 5], "vocabulari": [], "w": [2, 7], "w3": [], "wa": [], "wai": [1, 3, 5], "want": [], "warm": 5, "warmup": [], "wasn": [], "we": [2, 3, 5, 6], "weasyprint": [], "web": 2, "websit": [], "welcom": 3, "well": [], "were": 2, "what": [], "when": [], "whenev": [], "where": [2, 7], "whether": [1, 2, 7], "which": 5, "whichev": [], "while": 6, "why": [], "width": 2, "wiki": [], "wildreceipt": [], "window": [4, 7], "wish": [], "within": [], "without": 5, "wonder": [], "word": [3, 5, 7], "word_1_1": [], "word_1_2": [], "word_1_3": [], "wordgener": [], "words_onli": 7, "work": [], "worker": 1, "workflow": [], "worklow": [], "world": 7, "worth": [], "wrap": [], "wrapper": [1, 6], "write": [], "written": 2, "www": 2, "x": [2, 6, 7], "x12larg": 5, "x_ascend": [], "x_descend": [], "x_i": 7, "x_size": [], "x_wconf": [], "xeon": 5, "xhtml": [], "xmax": 2, "xmin": 2, "xml": [], "xml_bytes_str": [], "xml_element": [], "xml_output": [], "xmln": [], "y": 7, "y_i": 7, "y_j": 7, "yet": [], "ymax": 2, "ymin": 2, "yolov8": [], "you": [4, 5], "your": [1, 2, 5, 7], "yoursit": 2, "yugesh": [], "zero": [5, 6], "zoo": [], "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 1, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": [], "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": [], "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": [], "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": [], "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": [], "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": [], "\u00e4\u00f6\u00e4\u00f6": [], "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": [], "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": [], "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": [], "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": [], "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": [], "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": [], "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": [], "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": [], "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": [], "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": [], "\u067e\u0686\u06a2\u06a4\u06af": [], "\u0905": [], "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": [], "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": [], "\u0950": [], "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": [], "\u09bd": [], "\u09ce": [], "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": []}, "titles": ["Changelog", "doctr.datasets", "doctr.documents", "DocTR: Document Text Recognition", "Installation", "doctr.models", "doctr.transforms", "doctr.utils"], "titleterms": {"": [], "0": 0, "01": [], "02": [], "03": 0, "04": [], "05": 0, "07": [], "08": [], "09": [], "1": 0, "10": [], "11": 0, "12": [], "18": 0, "2": 0, "2021": 0, "2022": [], "2023": [], "2024": [], "21": [], "22": [], "27": [], "28": [], "29": [], "3": [], "31": [], "4": [], "5": [], "6": [], "7": [], "8": [], "9": [], "advanc": [], "approach": 5, "architectur": [], "arg": [], "artefact": 2, "artefactdetect": [], "attribut": [], "avail": 1, "aw": [], "ban": [], "block": 2, "bug": [], "build": 3, "changelog": 0, "choos": [], "classif": [], "code": [], "codebas": [], "commit": [], "commun": [], "compos": 6, "compress": 5, "conda": [], "conduct": [], "connect": [], "content": [], "continu": [], "contrib": [], "contribut": [], "contributor": [], "convent": [], "correct": [], "coven": [], "custom": [], "data": 1, "dataload": [], "dataset": [1, 3], "detect": [3, 5], "develop": [], "do": [], "doctr": [1, 2, 3, 5, 6, 7], "document": [2, 3], "end": 5, "enforc": [], "evalu": 7, "export": 5, "factori": [], "featur": 3, "feedback": [], "file": 2, "from": [], "gener": [], "get": 3, "git": 4, "guidelin": [], "half": [], "hub": [], "huggingfac": [], "i": [], "implement": [], "infer": [], "instal": 4, "integr": [], "io": [], "lambda": [], "let": [], "line": 2, "linux": [], "load": 1, "loader": [], "main": 3, "mode": [], "model": [3, 5], "modifi": [], "modul": [], "name": [], "note": 3, "notebook": [], "object": [], "ocr": 5, "onli": [], "onnx": [], "optim": [], "option": [], "orient": [], "our": [], "output": 5, "own": [], "packag": [3, 4], "page": 2, "perman": [], "pipelin": [], "pledg": [], "post": 5, "pre": 5, "precis": [], "predictor": [3, 5], "prepar": [], "prerequisit": 4, "pretrain": [], "process": 5, "push": [], "python": 4, "qualiti": [], "question": [], "read": 2, "readi": [], "recognit": [3, 5], "refer": 3, "report": [], "request": [], "resourc": [], "respons": [], "return": [], "right": [], "savedmodel": 5, "scope": [], "share": [], "should": [], "stage": 5, "standard": [], "start": 3, "structur": 2, "style": [], "support": [1, 3, 6], "synthet": [], "task": 7, "temporari": [], "test": [], "text": [3, 5], "train": 3, "transform": 6, "two": 5, "unit": [], "us": 5, "util": 7, "v0": 0, "verif": [], "via": 4, "visual": 7, "vocab": 1, "warn": [], "what": [], "word": 2, "your": 3, "zoo": [3, 5]}}) \ No newline at end of file +Search.setIndex({"alltitles": {"1. Correction": [[2, "correction"]], "2. Warning": [[2, "warning"]], "3. Temporary Ban": [[2, "temporary-ban"]], "4. Permanent Ban": [[2, "permanent-ban"]], "AWS Lambda": [[14, null]], "Advanced options": [[19, "advanced-options"]], "Args:": [[7, "args"], [7, "id4"], [7, "id7"], [7, "id10"], [7, "id13"], [7, "id16"], [7, "id19"], [7, "id22"], [7, "id25"], [7, "id29"], [7, "id32"], [7, "id37"], [7, "id40"], [7, "id46"], [7, "id49"], [7, "id50"], [7, "id51"], [7, "id54"], [7, "id57"], [7, "id60"], [7, "id61"], [8, "args"], [8, "id2"], [8, "id3"], [8, "id4"], [8, "id5"], [8, "id6"], [8, "id7"], [8, "id10"], [8, "id12"], [8, "id14"], [8, "id16"], [8, "id20"], [8, "id24"], [8, "id28"], [9, "args"], [9, "id3"], [9, "id8"], [9, "id13"], [9, "id17"], [9, "id21"], [9, "id26"], [9, "id31"], [9, "id36"], [9, "id41"], [9, "id46"], [9, "id50"], [9, "id54"], [9, "id59"], [9, "id63"], [9, "id68"], [9, "id73"], [9, "id77"], [9, "id81"], [9, "id85"], [9, "id90"], [9, "id95"], [9, "id99"], [9, "id104"], [9, "id109"], [9, "id114"], [9, "id119"], [9, "id123"], [9, "id127"], [9, "id132"], [9, "id137"], [9, "id142"], [9, "id146"], [9, "id150"], [9, "id155"], [9, "id159"], [9, "id163"], [9, "id167"], [9, "id169"], [9, "id171"], [9, "id173"], [10, "args"], [10, "id1"], [10, "id2"], [10, "id3"], [10, "id4"], [10, "id5"], [10, "id6"], [10, "id7"], [10, "id8"], [10, "id9"], [10, "id10"], [10, "id11"], [10, "id12"], [10, "id13"], [10, "id14"], [10, "id15"], [10, "id16"], [10, "id17"], [10, "id18"], [10, "id19"], [11, "args"], [11, "id3"], [11, "id4"], [11, "id5"], [11, "id6"], [11, "id7"], [11, "id8"], [11, "id9"]], "Artefact": [[8, "artefact"]], "ArtefactDetection": [[16, "artefactdetection"]], "Attribution": [[2, "attribution"]], "Available Datasets": [[17, "available-datasets"]], "Available architectures": [[19, "available-architectures"], [19, "id1"], [19, "id2"]], "Available contribution modules": [[16, "available-contribution-modules"]], "Block": [[8, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[17, null]], "Choosing the right model": [[19, null]], "Classification": [[15, "classification"]], "Code quality": [[3, "code-quality"]], "Code style verification": [[3, "code-style-verification"]], "Codebase structure": [[3, "codebase-structure"]], "Commits": [[3, "commits"]], "Community resources": [[1, null]], "Composing transformations": [[10, "composing-transformations"]], "Continuous Integration": [[3, "continuous-integration"]], "Contributing to docTR": [[3, null]], "Contributor Covenant Code of Conduct": [[2, null]], "Custom dataset loader": [[7, "custom-dataset-loader"]], "Custom orientation classification models": [[13, "custom-orientation-classification-models"]], "Data Loading": [[17, "data-loading"]], "Dataloader": [[7, "dataloader"]], "Detection": [[15, "detection"], [17, "detection"]], "Detection predictors": [[19, "detection-predictors"]], "Developer mode installation": [[3, "developer-mode-installation"]], "Developing docTR": [[3, "developing-doctr"]], "Document": [[8, "document"]], "Document structure": [[8, "document-structure"]], "End-to-End OCR": [[19, "end-to-end-ocr"]], "Enforcement": [[2, "enforcement"]], "Enforcement Guidelines": [[2, "enforcement-guidelines"]], "Enforcement Responsibilities": [[2, "enforcement-responsibilities"]], "Export to ONNX": [[18, "export-to-onnx"]], "Feature requests & bug report": [[3, "feature-requests-bug-report"]], "Feedback": [[3, "feedback"]], "File reading": [[8, "file-reading"]], "Half-precision": [[18, "half-precision"]], "Installation": [[4, null]], "Integrate contributions into your pipeline": [[16, null]], "Let\u2019s connect": [[3, "let-s-connect"]], "Line": [[8, "line"]], "Loading from Huggingface Hub": [[15, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[13, "loading-your-custom-trained-model"]], "Loading your custom trained orientation classification model": [[13, "loading-your-custom-trained-orientation-classification-model"]], "Main Features": [[5, "main-features"]], "Model optimization": [[18, "model-optimization"]], "Model zoo": [[5, "model-zoo"]], "Modifying the documentation": [[3, "modifying-the-documentation"]], "Naming conventions": [[15, "naming-conventions"]], "OCR": [[17, "ocr"]], "Object Detection": [[17, "object-detection"]], "Our Pledge": [[2, "our-pledge"]], "Our Standards": [[2, "our-standards"]], "Page": [[8, "page"]], "Preparing your model for inference": [[18, null]], "Prerequisites": [[4, "prerequisites"]], "Pretrained community models": [[15, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[15, "pushing-to-the-huggingface-hub"]], "Questions": [[3, "questions"]], "Recognition": [[15, "recognition"], [17, "recognition"]], "Recognition predictors": [[19, "recognition-predictors"]], "Returns:": [[7, "returns"], [8, "returns"], [8, "id11"], [8, "id13"], [8, "id15"], [8, "id19"], [8, "id23"], [8, "id27"], [8, "id31"], [9, "returns"], [9, "id6"], [9, "id11"], [9, "id16"], [9, "id20"], [9, "id24"], [9, "id29"], [9, "id34"], [9, "id39"], [9, "id44"], [9, "id49"], [9, "id53"], [9, "id57"], [9, "id62"], [9, "id66"], [9, "id71"], [9, "id76"], [9, "id80"], [9, "id84"], [9, "id88"], [9, "id93"], [9, "id98"], [9, "id102"], [9, "id107"], [9, "id112"], [9, "id117"], [9, "id122"], [9, "id126"], [9, "id130"], [9, "id135"], [9, "id140"], [9, "id145"], [9, "id149"], [9, "id153"], [9, "id158"], [9, "id162"], [9, "id166"], [9, "id168"], [9, "id170"], [9, "id172"], [11, "returns"]], "Scope": [[2, "scope"]], "Share your model with the community": [[15, null]], "Supported Vocabs": [[7, "supported-vocabs"]], "Supported contribution modules": [[6, "supported-contribution-modules"]], "Supported datasets": [[5, "supported-datasets"]], "Supported transformations": [[10, "supported-transformations"]], "Synthetic dataset generator": [[7, "synthetic-dataset-generator"], [17, "synthetic-dataset-generator"]], "Task evaluation": [[11, "task-evaluation"]], "Text Detection": [[19, "text-detection"]], "Text Recognition": [[19, "text-recognition"]], "Text detection models": [[5, "text-detection-models"]], "Text recognition models": [[5, "text-recognition-models"]], "Train your own model": [[13, null]], "Two-stage approaches": [[19, "two-stage-approaches"]], "Unit tests": [[3, "unit-tests"]], "Use your own datasets": [[17, "use-your-own-datasets"]], "Using your ONNX exported model": [[18, "using-your-onnx-exported-model"]], "Via Conda (Only for Linux)": [[4, "via-conda-only-for-linux"]], "Via Git": [[4, "via-git"]], "Via Python Package": [[4, "via-python-package"]], "Visualization": [[11, "visualization"]], "What should I do with the output?": [[19, "what-should-i-do-with-the-output"]], "Word": [[8, "word"]], "docTR Notebooks": [[12, null]], "docTR Vocabs": [[7, "id62"]], "docTR: Document Text Recognition": [[5, null]], "doctr.contrib": [[6, null]], "doctr.datasets": [[7, null], [7, "datasets"]], "doctr.io": [[8, null]], "doctr.models": [[9, null]], "doctr.models.classification": [[9, "doctr-models-classification"]], "doctr.models.detection": [[9, "doctr-models-detection"]], "doctr.models.factory": [[9, "doctr-models-factory"]], "doctr.models.recognition": [[9, "doctr-models-recognition"]], "doctr.models.zoo": [[9, "doctr-models-zoo"]], "doctr.transforms": [[10, null]], "doctr.utils": [[11, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.10.0 (2024-10-21)": [[0, "v0-10-0-2024-10-21"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]], "v0.7.0 (2023-09-09)": [[0, "v0-7-0-2023-09-09"]], "v0.8.0 (2024-02-28)": [[0, "v0-8-0-2024-02-28"]], "v0.8.1 (2024-03-04)": [[0, "v0-8-1-2024-03-04"]], "v0.9.0 (2024-08-08)": [[0, "v0-9-0-2024-08-08"]]}, "docnames": ["changelog", "community/resources", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/contrib", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_contrib_modules", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "community/resources.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/contrib.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_contrib_modules.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[8, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[8, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[10, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[7, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[10, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[10, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[7, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[7, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[9, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[7, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[7, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[8, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[8, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[7, "doctr.datasets.encode_sequences", false]], "fast_base() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_base", false]], "fast_small() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_small", false]], "fast_tiny() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_tiny", false]], "from_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[7, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[10, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[10, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[7, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[7, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[7, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[7, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[7, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[9, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[10, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[8, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet34() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[7, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_crop_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_crop_orientation", false]], "mobilenet_v3_small_page_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_page_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[10, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[9, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[7, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[10, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[8, "doctr.io.Page", false]], "page_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.page_orientation_predictor", false]], "parseq() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[10, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[10, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[10, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[10, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[10, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[10, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[10, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[10, "doctr.transforms.RandomJpegQuality", false]], "randomresize (class in doctr.transforms)": [[10, "doctr.transforms.RandomResize", false]], "randomrotate (class in doctr.transforms)": [[10, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[10, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[10, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[8, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[8, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[8, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[7, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[10, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[8, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[8, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[7, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[7, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[7, "doctr.datasets.SVT", false]], "synthtext (class in doctr.datasets)": [[7, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.TextMatch", false]], "textnet_base() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_base", false]], "textnet_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_small", false]], "textnet_tiny() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_tiny", false]], "togray (class in doctr.transforms)": [[10, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[11, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_small", false]], "wildreceipt (class in doctr.datasets)": [[7, "doctr.datasets.WILDRECEIPT", false]], "word (class in doctr.io)": [[8, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[7, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[7, 0, 1, "", "CORD"], [7, 0, 1, "", "CharacterGenerator"], [7, 0, 1, "", "DetectionDataset"], [7, 0, 1, "", "DocArtefacts"], [7, 0, 1, "", "FUNSD"], [7, 0, 1, "", "IC03"], [7, 0, 1, "", "IC13"], [7, 0, 1, "", "IIIT5K"], [7, 0, 1, "", "IIITHWS"], [7, 0, 1, "", "IMGUR5K"], [7, 0, 1, "", "MJSynth"], [7, 0, 1, "", "OCRDataset"], [7, 0, 1, "", "RecognitionDataset"], [7, 0, 1, "", "SROIE"], [7, 0, 1, "", "SVHN"], [7, 0, 1, "", "SVT"], [7, 0, 1, "", "SynthText"], [7, 0, 1, "", "WILDRECEIPT"], [7, 0, 1, "", "WordGenerator"], [7, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[7, 0, 1, "", "DataLoader"]], "doctr.io": [[8, 0, 1, "", "Artefact"], [8, 0, 1, "", "Block"], [8, 0, 1, "", "Document"], [8, 0, 1, "", "DocumentFile"], [8, 0, 1, "", "Line"], [8, 0, 1, "", "Page"], [8, 0, 1, "", "Word"], [8, 1, 1, "", "decode_img_as_tensor"], [8, 1, 1, "", "read_html"], [8, 1, 1, "", "read_img_as_numpy"], [8, 1, 1, "", "read_img_as_tensor"], [8, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[8, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[8, 2, 1, "", "from_images"], [8, 2, 1, "", "from_pdf"], [8, 2, 1, "", "from_url"]], "doctr.io.Page": [[8, 2, 1, "", "show"]], "doctr.models": [[9, 1, 1, "", "kie_predictor"], [9, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[9, 1, 1, "", "crop_orientation_predictor"], [9, 1, 1, "", "magc_resnet31"], [9, 1, 1, "", "mobilenet_v3_large"], [9, 1, 1, "", "mobilenet_v3_large_r"], [9, 1, 1, "", "mobilenet_v3_small"], [9, 1, 1, "", "mobilenet_v3_small_crop_orientation"], [9, 1, 1, "", "mobilenet_v3_small_page_orientation"], [9, 1, 1, "", "mobilenet_v3_small_r"], [9, 1, 1, "", "page_orientation_predictor"], [9, 1, 1, "", "resnet18"], [9, 1, 1, "", "resnet31"], [9, 1, 1, "", "resnet34"], [9, 1, 1, "", "resnet50"], [9, 1, 1, "", "textnet_base"], [9, 1, 1, "", "textnet_small"], [9, 1, 1, "", "textnet_tiny"], [9, 1, 1, "", "vgg16_bn_r"], [9, 1, 1, "", "vit_b"], [9, 1, 1, "", "vit_s"]], "doctr.models.detection": [[9, 1, 1, "", "db_mobilenet_v3_large"], [9, 1, 1, "", "db_resnet50"], [9, 1, 1, "", "detection_predictor"], [9, 1, 1, "", "fast_base"], [9, 1, 1, "", "fast_small"], [9, 1, 1, "", "fast_tiny"], [9, 1, 1, "", "linknet_resnet18"], [9, 1, 1, "", "linknet_resnet34"], [9, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[9, 1, 1, "", "from_hub"], [9, 1, 1, "", "login_to_hub"], [9, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[9, 1, 1, "", "crnn_mobilenet_v3_large"], [9, 1, 1, "", "crnn_mobilenet_v3_small"], [9, 1, 1, "", "crnn_vgg16_bn"], [9, 1, 1, "", "master"], [9, 1, 1, "", "parseq"], [9, 1, 1, "", "recognition_predictor"], [9, 1, 1, "", "sar_resnet31"], [9, 1, 1, "", "vitstr_base"], [9, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[10, 0, 1, "", "ChannelShuffle"], [10, 0, 1, "", "ColorInversion"], [10, 0, 1, "", "Compose"], [10, 0, 1, "", "GaussianBlur"], [10, 0, 1, "", "GaussianNoise"], [10, 0, 1, "", "LambdaTransformation"], [10, 0, 1, "", "Normalize"], [10, 0, 1, "", "OneOf"], [10, 0, 1, "", "RandomApply"], [10, 0, 1, "", "RandomBrightness"], [10, 0, 1, "", "RandomContrast"], [10, 0, 1, "", "RandomCrop"], [10, 0, 1, "", "RandomGamma"], [10, 0, 1, "", "RandomHorizontalFlip"], [10, 0, 1, "", "RandomHue"], [10, 0, 1, "", "RandomJpegQuality"], [10, 0, 1, "", "RandomResize"], [10, 0, 1, "", "RandomRotate"], [10, 0, 1, "", "RandomSaturation"], [10, 0, 1, "", "RandomShadow"], [10, 0, 1, "", "Resize"], [10, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[11, 0, 1, "", "DetectionMetric"], [11, 0, 1, "", "LocalizationConfusion"], [11, 0, 1, "", "OCRMetric"], [11, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.visualization": [[11, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [2, 8, 9, 11, 15, 18], "0": [2, 4, 7, 10, 11, 13, 16, 17, 19], "00": 19, "01": 19, "0123456789": 7, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "02562": 9, "03": 19, "035": 19, "0361328125": 19, "04": 19, "05": 19, "06": 19, "06640625": 19, "07": 19, "08": [10, 19], "09": 19, "0966796875": 19, "1": [7, 8, 9, 10, 11, 13, 17, 19], "10": [4, 7, 11, 19], "100": [7, 10, 11, 17, 19], "1000": 19, "101": 7, "1024": [9, 13, 19], "104": 7, "106": 7, "108": 7, "1095": 17, "11": 19, "110": 11, "1107": 17, "114": 7, "115": 7, "1156": 17, "116": 7, "118": 7, "11800h": 19, "11th": 19, "12": 19, "120": 7, "123": 7, "126": 7, "1268": 17, "128": [9, 13, 18, 19], "13": 19, "130": 7, "13068": 17, "131": 7, "1337891": 17, "1357421875": 19, "1396484375": 19, "14": 19, "1420": 19, "14470v1": 7, "149": 17, "15": 19, "150": [11, 19], "1552": 19, "16": [9, 18, 19], "1630859375": 19, "1684": 19, "16x16": 9, "17": 19, "1778": 19, "1782": 19, "18": [9, 19], "185546875": 19, "1900": 19, "1910": 9, "19342": 17, "19370": 17, "195": 7, "19598": 17, "199": 19, "1999": 19, "2": [4, 5, 7, 8, 9, 10, 16, 19], "20": 19, "200": 11, "2000": 17, "2003": [5, 7], "2012": 7, "2013": [5, 7], "2015": 7, "2019": 5, "2023": 1, "207901": 17, "21": 19, "2103": 7, "2186": 17, "21888": 17, "22": 19, "224": [9, 10], "225": 10, "22672": 17, "229": [10, 17], "23": 19, "233": 17, "236": 7, "24": 19, "246": 17, "249": 17, "25": 19, "2504": 19, "255": [8, 9, 10, 11, 19], "256": 9, "257": 17, "26": 19, "26032": 17, "264": 13, "27": 19, "2700": 17, "2710": 19, "2749": 13, "28": 19, "287": 13, "29": 19, "296": 13, "299": 13, "2d": 19, "3": [4, 5, 8, 9, 10, 11, 18, 19], "30": 19, "300": 17, "3000": 17, "301": 13, "30595": 19, "30ghz": 19, "31": 9, "32": [7, 9, 10, 13, 17, 18, 19], "3232421875": 19, "33": [10, 19], "33402": 17, "33608": 17, "34": [9, 19], "340": 19, "3456": 19, "3515625": 19, "36": 19, "360": 17, "37": [7, 19], "38": 19, "39": 19, "4": [9, 10, 11, 19], "40": 19, "406": 10, "41": 19, "42": 19, "43": 19, "44": 19, "45": 19, "456": 10, "46": 19, "47": 19, "472": 17, "48": [7, 19], "485": 10, "49": 19, "49377": 17, "5": [7, 10, 11, 16, 19], "50": [9, 17, 19], "51": 19, "51171875": 19, "512": 9, "52": [7, 19], "529": 19, "53": 19, "54": 19, "540": 19, "5478515625": 19, "55": 19, "56": 19, "57": 19, "58": [7, 19], "580": 19, "5810546875": 19, "583": 19, "59": 19, "597": 19, "5k": [5, 7], "5m": 19, "6": [10, 19], "60": 10, "600": [9, 11, 19], "61": 19, "62": 19, "626": 17, "63": 19, "64": [9, 10, 19], "641": 19, "647": 17, "65": 19, "66": 19, "67": 19, "68": 19, "69": 19, "693": 13, "694": 13, "695": 13, "6m": 19, "7": 19, "70": [7, 11, 19], "707470": 17, "71": [7, 19], "7100000": 17, "7141797": 17, "7149": 17, "72": 19, "72dpi": 8, "73": 19, "73257": 17, "74": 19, "75": [10, 19], "7581382": 17, "76": 19, "77": 19, "772": 13, "772875": 17, "78": 19, "785": 13, "79": 19, "793533": 17, "796": 17, "798": 13, "7m": 19, "8": [9, 10, 19], "80": 19, "800": [9, 11, 17, 19], "81": 19, "82": 19, "83": 19, "84": 19, "849": 17, "85": 19, "8564453125": 19, "857": 19, "85875": 17, "86": 19, "8603515625": 19, "87": 19, "8707": 17, "88": 19, "89": 19, "9": [10, 19], "90": 19, "90k": 7, "90kdict32px": 7, "91": 19, "914085328578949": 19, "92": 19, "93": 19, "94": [7, 19], "95": [11, 19], "9578408598899841": 19, "96": 19, "97": 19, "98": 19, "99": 19, "9949972033500671": 19, "A": [2, 3, 5, 7, 8, 9, 12, 18], "As": 3, "Be": 19, "Being": 2, "By": 14, "For": [2, 3, 4, 13, 19], "If": [3, 8, 9, 13, 19], "In": [3, 7, 17], "It": [10, 15, 16, 18], "Its": [5, 9], "No": [2, 19], "Of": 7, "Or": [16, 18], "The": [2, 3, 7, 8, 11, 14, 16, 17, 18, 19], "Then": 9, "To": [3, 4, 14, 15, 16, 18, 19], "_": [2, 7, 9], "__call__": 19, "_build": 3, "_i": 11, "ab": 7, "abc": 18, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "abdef": [7, 17], "abl": [17, 19], "about": [2, 17, 19], "abov": 19, "abstract": 1, "abstractdataset": 7, "abus": 2, "accept": 2, "access": [5, 8, 17, 19], "account": [2, 15], "accur": 19, "accuraci": 11, "achiev": 18, "act": 2, "action": 2, "activ": 5, "ad": [3, 9, 10], "adapt": 2, "add": [10, 11, 15, 19], "add_hook": 19, "add_label": 11, "addit": [3, 4, 8, 16, 19], "addition": [3, 19], "address": [2, 8], "adjust": 10, "advanc": 2, "advantag": 18, "advis": 3, "aesthet": [5, 7], "affect": 2, "after": [15, 19], "ag": 2, "again": 9, "aggreg": [11, 17], "aggress": 2, "align": [2, 8, 10], "all": [2, 3, 6, 7, 8, 10, 11, 16, 17, 19], "allow": [2, 18], "along": 19, "alreadi": [3, 18], "also": [2, 9, 15, 16, 17, 19], "alwai": 17, "an": [2, 3, 5, 7, 8, 9, 11, 16, 18, 19], "analysi": [8, 16], "ancient_greek": 7, "andrej": 1, "angl": [8, 10], "ani": [2, 7, 8, 9, 10, 11, 18, 19], "annot": 7, "anot": 17, "anoth": [9, 13, 17], "answer": 2, "anyascii": 11, "anyon": 5, "anyth": 16, "api": [3, 5], "apolog": 2, "apologi": 2, "app": 3, "appear": 2, "appli": [2, 7, 10], "applic": [5, 9], "appoint": 2, "appreci": 15, "appropri": [2, 3, 19], "ar": [2, 3, 4, 6, 7, 8, 10, 11, 12, 16, 17, 19], "arab": 7, "arabic_diacrit": 7, "arabic_lett": 7, "arabic_punctu": 7, "arbitrarili": [5, 9], "arch": [9, 15], "architectur": [5, 9, 15, 16], "area": 19, "argument": [7, 8, 9, 11, 13, 19], "around": 2, "arrai": [8, 10, 11], "art": [5, 16], "artefact": [11, 16, 19], "artefact_typ": 8, "articl": 1, "artifici": [5, 7], "arxiv": [7, 9], "asarrai": 11, "ascii_lett": 7, "aspect": [5, 9, 10, 19], "assess": 11, "assign": 11, "associ": 8, "assum": 9, "assume_straight_pag": [9, 13, 19], "astyp": [9, 11, 19], "attack": 2, "attend": [5, 9], "attent": [2, 9], "autom": 5, "automat": 19, "autoregress": [5, 9], "avail": [2, 5, 6, 10], "averag": [10, 19], "avoid": [2, 4], "aw": [5, 19], "awar": 19, "azur": 19, "b": [9, 11, 19], "b_j": 11, "back": 3, "backbon": 9, "backend": 19, "background": 17, "bangla": 7, "bar": 16, "bar_cod": 17, "baranovskij": 1, "base": [5, 9, 16], "baselin": [5, 9, 19], "batch": [7, 9, 10, 16, 17, 19], "batch_siz": [7, 9, 13, 16, 17, 18], "bblanchon": 4, "bbox": 19, "becaus": 14, "been": [3, 11, 17, 19], "befor": [7, 9, 10, 19], "begin": 11, "behavior": [2, 19], "being": [11, 19], "belong": 19, "benchmark": 19, "best": [1, 2], "better": [12, 19], "between": [10, 11, 19], "bgr": 8, "bilinear": 10, "bin_thresh": 19, "binar": [5, 9, 19], "binari": [8, 18, 19], "bit": 18, "block": [11, 19], "block_1_1": 19, "blur": 10, "bmvc": 7, "bn": 15, "bodi": [2, 19], "bool": [7, 8, 9, 10, 11], "boolean": [9, 19], "both": [5, 7, 10, 17, 19], "bottom": [9, 19], "bound": [7, 8, 9, 10, 11, 16, 17, 19], "box": [7, 8, 9, 10, 11, 16, 17, 19], "box_thresh": 19, "bright": 10, "browser": [3, 5], "build": [3, 4, 18], "built": 3, "byte": [8, 19], "c": [4, 8, 11], "c_j": 11, "cach": [3, 7, 14], "cache_sampl": 7, "call": 18, "callabl": [7, 10], "can": [3, 4, 13, 14, 15, 16, 17, 19], "capabl": [3, 12, 19], "case": [7, 11], "cf": 19, "cfg": 19, "challeng": 7, "challenge2_test_task12_imag": 7, "challenge2_test_task1_gt": 7, "challenge2_training_task12_imag": 7, "challenge2_training_task1_gt": 7, "chang": [14, 19], "channel": [2, 3, 8, 10], "channel_prior": 4, "channelshuffl": 10, "charact": [5, 7, 8, 11, 17, 19], "charactergener": [7, 17], "characterist": 2, "charg": 19, "charset": 19, "chart": 8, "check": [3, 15, 19], "checkpoint": 9, "chip": 4, "christian": 1, "ci": 3, "clarifi": 2, "clariti": 2, "class": [2, 7, 8, 10, 11, 19], "class_nam": 13, "classif": [17, 19], "classmethod": 8, "clear": 3, "clone": 4, "close": 3, "co": 15, "code": [5, 8, 16], "codecov": 3, "colab": 12, "collate_fn": 7, "collect": [8, 16], "color": 10, "colorinvers": 10, "column": 8, "com": [2, 4, 8, 9, 15], "combin": 19, "command": [3, 16], "comment": 2, "commit": 2, "common": [2, 10, 11, 18], "commun": 2, "compar": 5, "comparison": [11, 19], "competit": 7, "compil": [12, 19], "complaint": 2, "complementari": 11, "complet": 3, "compon": 19, "compos": [7, 19], "comprehens": 19, "comput": [7, 11, 18, 19], "conf_threshold": 16, "confid": [8, 19], "config": [4, 9], "configur": 9, "confus": 11, "consecut": [10, 19], "consequ": 2, "consid": [2, 3, 7, 8, 11, 19], "consist": 19, "consolid": [5, 7], "constant": 10, "construct": 2, "contact": 2, "contain": [1, 6, 7, 12, 17, 19], "content": [7, 8, 19], "context": 9, "contib": 4, "continu": 2, "contrast": 10, "contrast_factor": 10, "contrib": [4, 16], "contribut": 2, "contributor": 3, "convers": 8, "convert": [8, 10], "convolut": 9, "cool": 1, "coordin": [8, 19], "cord": [5, 7, 17, 19], "core": [11, 19], "corner": 19, "correct": 10, "correspond": [4, 8, 10, 19], "could": [2, 16], "counterpart": 11, "cover": 3, "coverag": 3, "cpu": [5, 13, 18], "creat": [1, 15], "crnn": [5, 9, 15], "crnn_mobilenet_v3_larg": [9, 15, 19], "crnn_mobilenet_v3_smal": [9, 18, 19], "crnn_vgg16_bn": [9, 13, 15, 19], "crop": [8, 9, 10, 13, 17, 19], "crop_orient": [8, 19], "crop_orientation_predictor": [9, 13], "crop_param": 13, "cuda": 18, "currenc": 7, "current": [3, 13, 19], "custom": [15, 16, 18, 19], "custom_crop_orientation_model": 13, "custom_page_orientation_model": 13, "customhook": 19, "cvit": 5, "czczup": 9, "czech": 7, "d": [7, 17], "danish": 7, "data": [5, 7, 8, 10, 11, 13, 15], "dataload": 17, "dataset": [9, 13, 19], "dataset_info": 7, "date": [13, 19], "db": 15, "db_mobilenet_v3_larg": [9, 15, 19], "db_resnet34": 19, "db_resnet50": [9, 13, 15, 19], "dbnet": [5, 9], "deal": [12, 19], "decis": 2, "decod": 8, "decode_img_as_tensor": 8, "dedic": 18, "deem": 2, "deep": [9, 19], "def": 19, "default": [4, 8, 13, 14, 19], "defer": 17, "defin": [11, 18], "degre": [8, 10, 19], "degress": 8, "delet": 3, "delimit": 19, "delta": 10, "demo": [3, 5], "demonstr": 2, "depend": [3, 4, 5, 19], "deploi": 3, "deploy": 5, "derogatori": 2, "describ": 9, "descript": 12, "design": 10, "desir": 8, "det_arch": [9, 13, 15, 18], "det_b": 19, "det_model": [13, 15, 18], "det_param": 13, "det_predictor": [13, 19], "detail": [13, 19], "detect": [1, 7, 8, 11, 12, 13, 16], "detect_languag": 9, "detect_orient": [9, 13, 19], "detection_predictor": [9, 19], "detection_task": [7, 17], "detectiondataset": [7, 17], "detectionmetr": 11, "detectionpredictor": [9, 13], "detector": [5, 9, 16], "deterior": 9, "determin": 2, "dev": [3, 14], "develop": 4, "deviat": 10, "devic": 18, "dict": [8, 11, 19], "dictionari": [8, 11], "differ": 2, "differenti": [5, 9], "digit": [5, 7, 17], "dimens": [8, 11, 19], "dimension": 10, "direct": 7, "directli": [15, 19], "directori": [3, 14], "disabl": [2, 14, 19], "disable_crop_orient": 19, "disable_page_orient": 19, "disclaim": 19, "discuss": 3, "disparag": 2, "displai": [8, 11], "display_artefact": 11, "distribut": 10, "div": 19, "divers": 2, "divid": 8, "do": [3, 4, 9], "doc": [3, 8, 16, 18, 19], "docartefact": [7, 17], "docstr": 3, "doctr": [1, 4, 13, 14, 15, 16, 17, 18, 19], "doctr_cache_dir": 14, "doctr_multiprocessing_dis": 14, "document": [1, 7, 9, 11, 12, 13, 16, 17, 18, 19], "documentbuild": 19, "documentfil": [8, 13, 15, 16, 18], "doesn": 18, "don": [13, 19], "done": 10, "download": [7, 17], "downsiz": 9, "draw": 10, "drop": 7, "drop_last": 7, "dtype": [8, 9, 10, 11, 18], "dual": [5, 7], "dummi": 15, "dummy_img": 19, "dummy_input": 18, "dure": 2, "dutch": 7, "dynam": [7, 16], "dynamic_seq_length": 7, "e": [2, 3, 4, 8, 9], "each": [5, 7, 8, 9, 10, 11, 17, 19], "eas": 3, "easi": [5, 11, 15, 18], "easili": [8, 11, 13, 15, 17, 19], "econom": 2, "edit": 2, "educ": 2, "effect": 19, "effici": [3, 5, 7, 9], "either": [11, 19], "element": [7, 8, 9, 19], "els": [3, 16], "email": 2, "empathi": 2, "en": 19, "enabl": [7, 8], "enclos": 8, "encod": [5, 7, 8, 9, 19], "encode_sequ": 7, "encount": 3, "encrypt": 8, "end": [5, 7, 9, 11], "english": [7, 17], "enough": [3, 19], "ensur": 3, "entri": 7, "environ": [2, 14], "eo": 7, "equiv": 19, "estim": 9, "etc": [8, 16], "ethnic": 2, "evalu": [17, 19], "event": 2, "everyon": 2, "everyth": [3, 19], "exact": [11, 19], "exampl": [2, 3, 5, 7, 9, 15, 19], "exchang": 18, "execut": 19, "exist": 15, "expand": 10, "expect": [8, 10, 11], "experi": 2, "explan": [2, 19], "explicit": 2, "exploit": [5, 9], "export": [8, 9, 11, 12, 16, 19], "export_as_straight_box": [9, 19], "export_as_xml": 19, "export_model_to_onnx": 18, "express": [2, 10], "extens": 8, "extern": [2, 17], "extract": [1, 5, 7], "extractor": 9, "f_": 11, "f_a": 11, "factor": 10, "fair": 2, "fairli": 2, "fals": [7, 8, 9, 10, 11, 13, 19], "faq": 2, "fascan": 15, "fast": [5, 7, 9], "fast_bas": [9, 19], "fast_smal": [9, 19], "fast_tini": [9, 19], "faster": [5, 9, 18], "fasterrcnn_mobilenet_v3_large_fpn": 9, "favorit": 19, "featur": [4, 9, 11, 12, 13, 16], "feedback": 2, "feel": [3, 15], "felix92": 15, "few": [18, 19], "figsiz": 11, "figur": [11, 16], "file": [3, 7], "final": 9, "find": [3, 17], "fine": 1, "finnish": 7, "first": [3, 7], "firsthand": 7, "fit": [9, 19], "flag": 19, "flip": 10, "float": [8, 10, 11, 18], "float32": [8, 9, 10, 18], "fn": 10, "focu": 15, "focus": [2, 7], "folder": 7, "follow": [2, 3, 4, 7, 10, 11, 13, 14, 15, 16, 19], "font": 7, "font_famili": 7, "foral": 11, "forc": 3, "forg": 4, "form": [5, 7, 19], "format": [8, 11, 13, 17, 18, 19], "forpost": [5, 7], "forum": 3, "found": 1, "fp16": 18, "frac": 11, "framework": [4, 15, 17, 19], "free": [2, 3, 15], "french": [7, 13, 15, 19], "friendli": 5, "from": [1, 2, 5, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19], "from_hub": [9, 15], "from_imag": [8, 15, 16, 18], "from_pdf": 8, "from_url": 8, "full": [7, 11, 19], "function": [7, 10, 11, 16], "funsd": [5, 7, 17, 19], "further": 17, "futur": 7, "g": [8, 9], "g_": 11, "g_x": 11, "gallagh": 1, "gamma": 10, "gaussian": 10, "gaussianblur": 10, "gaussiannois": 10, "gen": 19, "gender": 2, "gener": [3, 5, 8, 9], "generic_cyrillic_lett": 7, "geometri": [5, 8, 19], "geq": 11, "german": [7, 13, 15], "get": [18, 19], "git": 15, "github": [3, 4, 9, 15], "give": [2, 16], "given": [7, 8, 10, 11, 19], "global": 9, "go": 19, "good": 18, "googl": 3, "googlevis": 5, "gpu": [5, 16, 18], "gracefulli": 2, "graph": [5, 7, 8], "grayscal": 10, "ground": 11, "groung": 11, "group": [5, 19], "gt": 11, "gt_box": 11, "gt_label": 11, "guid": 3, "guidanc": 17, "gvision": 19, "h": [8, 9, 10], "h_": 11, "ha": [3, 7, 11, 17], "handl": [12, 17, 19], "handwrit": 7, "handwritten": 17, "harass": 2, "hardwar": 19, "harm": 2, "hat": 11, "have": [2, 3, 11, 13, 15, 17, 18, 19], "head": [9, 19], "healthi": 2, "hebrew": 7, "height": [8, 10], "hello": [11, 19], "help": 18, "here": [6, 10, 12, 16, 17, 19], "hf": 9, "hf_hub_download": 9, "high": 8, "higher": [4, 7, 19], "hindi": 7, "hindi_digit": 7, "hocr": 19, "hook": 19, "horizont": [8, 10, 19], "hous": 7, "how": [1, 3, 12, 13, 15, 17], "howev": 17, "hsv": 10, "html": [2, 3, 4, 8, 19], "http": [2, 4, 7, 8, 9, 15, 19], "hub": 9, "hue": 10, "huggingfac": 9, "hw": 7, "i": [2, 3, 7, 8, 9, 10, 11, 14, 15, 16, 17, 18], "i7": 19, "ibrahimov": 1, "ic03": [5, 7, 17], "ic13": [5, 7, 17], "icdar": [5, 7], "icdar2019": 7, "id": 19, "ident": 2, "identifi": 5, "iiit": [5, 7], "iiit5k": [7, 17], "iiithw": [5, 7, 17], "imag": [1, 5, 7, 8, 9, 10, 11, 15, 16, 17, 19], "imagenet": 9, "imageri": 2, "images_90k_norm": 7, "img": [7, 10, 17, 18], "img_cont": 8, "img_fold": [7, 17], "img_path": 8, "img_transform": 7, "imgur5k": [5, 7, 17], "imgur5k_annot": 7, "imlist": 7, "impact": 2, "implement": [7, 8, 9, 10, 11, 19], "import": [7, 8, 9, 10, 11, 13, 15, 16, 17, 18, 19], "improv": 9, "inappropri": 2, "incid": 2, "includ": [2, 7, 17, 18], "inclus": 2, "increas": 10, "independ": 10, "index": [3, 8], "indic": 11, "individu": 2, "infer": [5, 9, 10, 16, 19], "inform": [1, 2, 3, 5, 7, 17], "input": [3, 8, 9, 10, 18, 19], "input_crop": 9, "input_pag": [9, 11, 19], "input_shap": 18, "input_tensor": 9, "inspir": [2, 10], "instal": [15, 16, 18], "instanc": [2, 19], "instanti": [9, 19], "instead": [7, 8, 9], "insult": 2, "int": [7, 8, 9, 10], "int64": 11, "integ": 11, "integr": [1, 5, 15, 17], "intel": 19, "interact": [2, 8, 11], "interfac": [15, 18], "interoper": 18, "interpol": 10, "interpret": [7, 8], "intersect": 11, "invert": 10, "investig": 2, "invis": 2, "involv": [2, 19], "io": [13, 15, 16, 18], "iou": 11, "iou_thresh": 11, "iou_threshold": 16, "irregular": [5, 9, 17], "isn": 7, "issu": [2, 3, 15], "italian": 7, "iter": [7, 10, 17, 19], "its": [8, 9, 10, 11, 17, 19], "itself": [9, 15], "j": 11, "jame": 1, "job": 3, "join": 3, "jpeg": 10, "jpegqual": 10, "jpg": [7, 8, 15, 18], "json": [7, 17, 19], "json_output": 19, "jump": 3, "just": 2, "kei": [5, 7], "kera": [9, 18], "kernel": [5, 9, 10], "kernel_shap": 10, "keywoard": 9, "keyword": [7, 8, 9, 11], "kie": [9, 13], "kie_predictor": [9, 13], "kiepredictor": 9, "kind": 2, "know": [3, 18], "kwarg": [7, 8, 9, 11], "l": 11, "l_j": 11, "label": [7, 11, 16, 17], "label_fil": [7, 17], "label_fold": 7, "label_path": [7, 17], "labels_path": [7, 17], "ladder": 2, "lambda": 10, "lambdatransform": 10, "lang": 19, "languag": [2, 5, 7, 8, 9, 15, 19], "larg": [9, 15], "largest": 11, "last": [4, 7], "latenc": 9, "later": 3, "latest": 19, "latin": 7, "layer": 18, "layout": 19, "lead": 2, "leader": 2, "learn": [2, 5, 9, 18, 19], "least": 4, "left": [11, 19], "legacy_french": 7, "length": [7, 19], "less": [18, 19], "level": [2, 7, 11, 19], "leverag": 12, "lf": 15, "librari": [3, 4, 12, 13], "light": 5, "lightweight": 18, "like": 2, "limits_": 11, "line": [5, 9, 11, 19], "line_1_1": 19, "link": 13, "linknet": [5, 9], "linknet_resnet18": [9, 13, 18, 19], "linknet_resnet34": [9, 18, 19], "linknet_resnet50": [9, 19], "list": [7, 8, 10, 11, 15], "ll": 11, "load": [5, 7, 9, 16, 18], "load_state_dict": 13, "load_weight": 13, "loc_pr": 19, "local": [3, 5, 7, 9, 11, 17, 19], "localis": 7, "localizationconfus": 11, "locat": [3, 8, 19], "login": 9, "login_to_hub": [9, 15], "logo": [8, 16, 17], "love": 15, "lower": [10, 11, 19], "m": [3, 11, 19], "m1": 4, "macbook": 4, "machin": 18, "made": 5, "magc_resnet31": 9, "mai": [2, 3], "mail": 2, "main": 12, "maintain": 5, "mainten": 3, "make": [2, 3, 11, 13, 14, 15, 18, 19], "mani": [17, 19], "manipul": 19, "map": [7, 9], "map_loc": 13, "master": [5, 9, 19], "match": [11, 19], "mathcal": 11, "matplotlib": [8, 11], "max": [7, 10, 11], "max_angl": 10, "max_area": 10, "max_char": [7, 17], "max_delta": 10, "max_gain": 10, "max_gamma": 10, "max_qual": 10, "max_ratio": 10, "maximum": [7, 10], "maxval": [9, 10], "mbox": 11, "mean": [10, 11, 13], "meaniou": 11, "meant": [8, 18], "measur": 19, "media": 2, "median": 9, "meet": 13, "member": 2, "memori": [14, 18], "mention": 19, "merg": 7, "messag": 3, "meta": 19, "metadata": 18, "metal": 4, "method": [8, 10, 19], "metric": [11, 19], "middl": 19, "might": [18, 19], "min": 10, "min_area": 10, "min_char": [7, 17], "min_gain": 10, "min_gamma": 10, "min_qual": 10, "min_ratio": 10, "min_val": 10, "minde": [1, 2, 4, 5, 9], "minim": [3, 5], "minimalist": [5, 9], "minimum": [4, 7, 10, 11, 19], "minval": 10, "miss": 4, "mistak": 2, "mixed_float16": 18, "mixed_precis": 18, "mjsynth": [5, 7, 17], "mnt": 7, "mobilenet": [9, 15], "mobilenet_v3_larg": 9, "mobilenet_v3_large_r": 9, "mobilenet_v3_smal": [9, 13], "mobilenet_v3_small_crop_orient": [9, 13], "mobilenet_v3_small_page_orient": [9, 13], "mobilenet_v3_small_r": 9, "mobilenetv3": 9, "modal": [5, 7], "mode": 4, "model": [7, 11, 14, 16, 17], "model_nam": [9, 15, 18], "model_path": [16, 18], "moder": 2, "modif": 3, "modifi": [9, 14, 19], "modul": [4, 8, 9, 10, 11, 19], "more": [3, 17, 19], "moscardi": 1, "most": 19, "mozilla": 2, "multi": [5, 9], "multilingu": [7, 15], "multipl": [7, 8, 10, 19], "multipli": 10, "multiprocess": 14, "my": 9, "my_awesome_model": 15, "my_hook": 19, "n": [7, 11], "name": [7, 9, 18, 19], "nation": 2, "natur": [2, 5, 7], "ndarrai": [7, 8, 10, 11], "necessari": [4, 13, 14], "need": [3, 4, 7, 11, 13, 14, 15, 16, 19], "neg": 10, "nest": 19, "netraj": 1, "network": [5, 7, 9, 18], "neural": [5, 7, 9, 18], "new": [3, 11], "next": [7, 17], "nois": 10, "noisi": [5, 7], "non": [5, 7, 8, 9, 10, 11], "none": [7, 8, 9, 10, 11, 19], "normal": [9, 10], "norwegian": 7, "note": [0, 3, 7, 9, 13, 15, 16, 18], "now": 3, "np": [9, 10, 11, 19], "num_output_channel": 10, "num_sampl": [7, 17], "number": [7, 9, 10, 11, 19], "numpi": [8, 9, 11, 19], "o": 4, "obb": 16, "obj_detect": 15, "object": [7, 8, 11, 16, 19], "objectness_scor": [8, 19], "oblig": 2, "obtain": 19, "occupi": 18, "ocr": [1, 5, 7, 9, 11, 15], "ocr_carea": 19, "ocr_db_crnn": 11, "ocr_lin": 19, "ocr_pag": 19, "ocr_par": 19, "ocr_predictor": [9, 13, 15, 18, 19], "ocrdataset": [7, 17], "ocrmetr": 11, "ocrpredictor": [9, 13], "ocrx_word": 19, "offens": 2, "offici": [2, 9], "offlin": 2, "offset": 10, "onc": 19, "one": [3, 7, 9, 10, 13, 15, 19], "oneof": 10, "ones": [7, 11], "onli": [3, 9, 10, 11, 13, 15, 17, 18, 19], "onlin": 2, "onnx": 16, "onnxruntim": [16, 18], "onnxtr": 18, "opac": 10, "opacity_rang": 10, "open": [1, 2, 3, 15, 18], "opinion": 2, "optic": [5, 19], "optim": [5, 19], "option": [7, 9, 13], "order": [3, 7, 8, 10], "org": [2, 7, 9, 19], "organ": 8, "orient": [2, 8, 9, 12, 16, 19], "orientationpredictor": 9, "other": [2, 3], "otherwis": [2, 8, 11], "our": [1, 3, 9, 19], "out": [3, 9, 10, 11, 19], "outpout": 19, "output": [8, 10, 18], "output_s": [8, 10], "outsid": 14, "over": [7, 11, 19], "overal": [2, 9], "overlai": 8, "overview": 16, "overwrit": 13, "overwritten": 15, "own": 5, "p": [10, 19], "packag": [3, 5, 11, 14, 16, 17, 18], "pad": [7, 9, 10, 19], "page": [4, 7, 9, 11, 13, 19], "page1": 8, "page2": 8, "page_1": 19, "page_idx": [8, 19], "page_orientation_predictor": [9, 13], "page_param": 13, "pair": 11, "paper": 9, "par_1_1": 19, "paragraph": 19, "paragraph_break": 19, "parallel": 9, "param": [10, 19], "paramet": [5, 8, 9, 18], "pars": [5, 7], "parseq": [5, 9, 15, 18, 19], "part": [7, 10, 19], "parti": 4, "partial": 19, "particip": 2, "pass": [7, 8, 9, 13, 19], "password": 8, "patch": [9, 11], "path": [7, 8, 16, 17, 18], "path_to_checkpoint": 13, "path_to_custom_model": 18, "path_to_pt": 13, "patil": 1, "pattern": 2, "pdf": [8, 9, 12], "pdfpage": 8, "peopl": 2, "per": [10, 19], "perform": [5, 8, 9, 10, 11, 14, 18, 19], "period": 2, "permiss": 2, "permut": [5, 9], "persian_lett": 7, "person": [2, 17], "phase": 19, "photo": 17, "physic": [2, 8], "pick": 10, "pictur": 8, "pip": [3, 4, 16, 18], "pipelin": 19, "pixel": [8, 10, 19], "pleas": 3, "plot": 11, "plt": 11, "plug": 15, "plugin": 4, "png": 8, "point": 18, "polici": 14, "polish": 7, "polit": 2, "polygon": [7, 11, 19], "pool": 9, "portugues": 7, "posit": [2, 11], "possibl": [3, 11, 15, 19], "post": [2, 19], "postprocessor": 19, "potenti": 9, "power": 5, "ppageno": 19, "pre": [3, 9, 18], "precis": [11, 19], "pred": 11, "pred_box": 11, "pred_label": 11, "predefin": 17, "predict": [8, 9, 11, 19], "predictor": [5, 8, 9, 12, 13, 15, 18], "prefer": 17, "preinstal": 4, "preprocessor": [13, 19], "prerequisit": 15, "present": 12, "preserv": [9, 10, 19], "preserve_aspect_ratio": [8, 9, 10, 13, 19], "pretrain": [5, 9, 11, 13, 18, 19], "pretrained_backbon": [9, 13], "print": 19, "prior": 7, "privaci": 2, "privat": 2, "probabl": [1, 10], "problem": 3, "procedur": 10, "process": [3, 5, 8, 9, 13, 19], "processor": 19, "produc": [12, 19], "product": 18, "profession": 2, "project": [3, 17], "promptli": 2, "proper": 3, "properli": 7, "provid": [2, 3, 5, 15, 16, 17, 19], "public": [2, 5], "publicli": 19, "publish": 2, "pull": 15, "punctuat": 7, "pure": 7, "purpos": 3, "push_to_hf_hub": [9, 15], "py": 15, "pypdfium2": [4, 8], "pyplot": [8, 11], "python": [1, 3, 16], "python3": 15, "pytorch": [4, 5, 9, 10, 13, 15, 18, 19], "q": 3, "qr": [8, 16], "qr_code": 17, "qualiti": 10, "question": 2, "quickli": 5, "quicktour": 12, "r": 19, "race": 2, "ramdisk": 7, "rand": [9, 10, 11, 18, 19], "random": [9, 10, 11, 19], "randomappli": 10, "randombright": 10, "randomcontrast": 10, "randomcrop": 10, "randomgamma": 10, "randomhorizontalflip": 10, "randomhu": 10, "randomjpegqu": 10, "randomli": 10, "randomres": 10, "randomrot": 10, "randomsatur": 10, "randomshadow": 10, "rang": 10, "rassi": 15, "ratio": [9, 10, 19], "raw": [8, 11], "re": 18, "read": [5, 7, 9], "read_html": 8, "read_img_as_numpi": 8, "read_img_as_tensor": 8, "read_pdf": 8, "readi": 18, "real": [1, 5, 9, 10], "realli": 1, "reason": [2, 5, 7], "rebuild": 3, "rebuilt": 3, "recal": [11, 19], "receipt": [5, 7, 19], "reco_arch": [9, 13, 15, 18], "reco_b": 19, "reco_model": [13, 15, 18], "reco_param": 13, "reco_predictor": 13, "recogn": 19, "recognit": [7, 11, 12, 13], "recognition_predictor": [9, 19], "recognition_task": [7, 17], "recognitiondataset": [7, 17], "recognitionpredictor": [9, 13], "rectangular": 9, "reduc": [4, 10], "refer": [3, 4, 13, 15, 16, 17, 19], "regardless": 2, "region": 19, "regroup": 11, "regular": 17, "reject": 2, "rel": [8, 10, 11, 19], "relat": 8, "releas": [0, 4], "relev": 16, "religion": 2, "remov": 2, "render": [8, 19], "repo": 9, "repo_id": [9, 15], "report": 2, "repositori": [7, 9, 15], "repres": [2, 18, 19], "represent": [5, 9], "request": [2, 15], "requir": [4, 10, 18], "research": 5, "residu": 9, "resiz": [10, 19], "resnet": 9, "resnet18": [9, 15], "resnet31": 9, "resnet34": 9, "resnet50": [9, 15], "resolv": 8, "resolve_block": 19, "resolve_lin": 19, "resourc": 17, "respect": 2, "rest": [3, 10, 11], "restrict": 14, "result": [3, 7, 8, 12, 15, 18, 19], "return": 19, "reusabl": 19, "review": 2, "rgb": [8, 10], "rgb_mode": 8, "rgb_output": 8, "right": [2, 9, 11], "roboflow": 1, "robust": [5, 7], "root": 7, "rotat": [7, 8, 9, 10, 11, 12, 13, 17, 19], "run": [3, 4, 9], "same": [3, 8, 11, 17, 18, 19], "sampl": [7, 9, 17, 19], "sample_transform": 7, "sanjin": 1, "sar": [5, 9], "sar_resnet31": [9, 19], "satur": 10, "save": [9, 17], "scale": [8, 9, 10, 11], "scale_rang": 10, "scan": [5, 7], "scene": [5, 7, 9], "score": [8, 11], "script": [3, 17], "seamless": 5, "seamlessli": [5, 19], "search": [1, 9], "searchabl": 12, "sec": 19, "second": 19, "section": [1, 13, 15, 16, 18, 19], "secur": [2, 14], "see": [2, 3], "seen": 19, "segment": [5, 9, 19], "self": 19, "semant": [5, 9], "send": 19, "sens": 11, "sensit": 17, "separ": 19, "sequenc": [5, 7, 8, 9, 11, 19], "sequenti": [10, 19], "seri": 2, "seriou": 2, "set": [2, 4, 7, 9, 11, 14, 16, 19], "set_global_polici": 18, "sever": [8, 10, 19], "sex": 2, "sexual": 2, "shade": 10, "shape": [5, 8, 9, 10, 11, 19], "share": [14, 17], "shift": 10, "shm": 14, "should": [3, 7, 8, 10, 11], "show": [5, 8, 9, 11, 13, 15, 16], "showcas": [3, 12], "shuffl": [7, 10], "side": 11, "signatur": 8, "signific": 17, "simpl": [5, 9, 18], "simpler": 9, "sinc": [7, 17], "singl": [2, 3, 5, 7], "single_img_doc": 18, "size": [2, 7, 8, 10, 16, 19], "skew": 19, "slack": 3, "slightli": 9, "small": [3, 9, 19], "smallest": 8, "snapshot_download": 9, "snippet": 19, "so": [3, 4, 7, 9, 15, 17], "social": 2, "socio": 2, "some": [1, 4, 12, 15, 17], "someth": 3, "somewher": 3, "sort": 2, "sourc": [1, 7, 8, 9, 10, 11, 15], "space": [2, 19], "span": 19, "spanish": 7, "spatial": [5, 7, 8], "specif": [3, 4, 11, 13, 17, 19], "specifi": [2, 7, 8], "speed": [5, 9, 19], "sphinx": 3, "sroie": [5, 7, 17], "stabl": 4, "stackoverflow": 3, "stage": 5, "standalon": 12, "standard": 10, "start": 7, "state": [1, 5, 11, 16], "static": 11, "statist": 1, "statu": 2, "std": [10, 13], "step": 14, "still": 19, "str": [7, 8, 9, 10, 11], "straight": [7, 9, 17, 19], "straighten": 19, "straighten_pag": [9, 13, 19], "straigten_pag": 13, "stream": 8, "street": [5, 7], "strict": 4, "strictli": 11, "string": [7, 8, 11, 19], "strive": 4, "strong": [5, 9], "structur": [18, 19], "subset": [7, 19], "suggest": [3, 15], "sum": 11, "summari": 11, "support": [4, 13, 16, 18, 19], "sustain": 2, "svhn": [5, 7, 17], "svt": [7, 17], "swedish": 7, "symmetr": [9, 10, 19], "symmetric_pad": [9, 10, 19], "synthet": 5, "synthtext": [5, 7, 17], "system": 19, "t": [3, 7, 13, 18, 19], "tabl": [15, 16, 17], "take": [2, 7, 19], "target": [7, 8, 10, 11, 17], "target_s": 7, "task": [5, 7, 9, 15, 17, 19], "task2": 7, "team": 4, "techminde": 4, "templat": [3, 5], "tensor": [7, 8, 10, 19], "tensorflow": [4, 5, 8, 9, 10, 13, 15, 18, 19], "tensorspec": 18, "term": 2, "test": [7, 17], "test_set": 7, "text": [1, 7, 8, 9, 11, 17], "text_output": 19, "textmatch": 11, "textnet": 9, "textnet_bas": 9, "textnet_smal": 9, "textnet_tini": 9, "textract": [5, 19], "textstylebrush": [5, 7], "textual": [5, 7, 8, 9, 19], "tf": [4, 8, 9, 10, 15, 18], "than": [3, 11, 15], "thank": 3, "thei": [2, 11], "them": [7, 19], "thi": [1, 2, 3, 4, 6, 7, 10, 11, 13, 14, 15, 17, 18, 19], "thing": [18, 19], "third": 4, "those": [2, 8, 19], "threaten": 2, "threshold": 19, "through": [2, 10, 16, 17], "tilman": 15, "time": [1, 2, 5, 9, 11, 17], "tini": 9, "titl": [8, 19], "tm": 19, "tmp": 14, "togeth": [3, 8], "tograi": 10, "tool": [1, 17], "top": [11, 18, 19], "topic": 3, "torch": [4, 10, 13, 15, 18], "torchvis": 10, "total": 13, "toward": [2, 4], "train": [3, 7, 9, 10, 15, 16, 17, 18, 19], "train_it": [7, 17], "train_load": [7, 17], "train_pytorch": 15, "train_set": [7, 17], "train_tensorflow": 15, "trainabl": [5, 9], "tranform": 10, "transcrib": 19, "transfer": [5, 7], "transfo": 10, "transform": [5, 7, 9], "translat": 2, "troll": 2, "true": [7, 8, 9, 10, 11, 13, 14, 15, 17, 18, 19], "truth": 11, "tune": [1, 18], "tupl": [7, 8, 10, 11], "two": [8, 14], "txt": 7, "type": [8, 11, 15, 18, 19], "typic": 19, "u": [2, 3], "ucsd": 7, "udac": 3, "uint8": [8, 9, 11, 19], "ukrainian": 7, "unaccept": 2, "underli": [17, 19], "underneath": 8, "understand": [5, 7, 19], "uniform": [9, 10], "uniformli": 10, "uninterrupt": [8, 19], "union": 11, "unit": 1, "unittest": 3, "unlock": 8, "unoffici": 9, "unprofession": 2, "unsolicit": 2, "unsupervis": 5, "unwelcom": 2, "up": [9, 19], "updat": 11, "upgrad": 3, "upper": [7, 10], "uppercas": 17, "url": 8, "us": [2, 3, 4, 7, 9, 11, 12, 13, 14, 15, 16, 19], "usabl": 19, "usag": [14, 18], "use_polygon": [7, 11, 17], "useabl": 19, "user": [5, 8, 12], "utf": 19, "util": 18, "v1": 15, "v3": [9, 15, 19], "valid": 17, "valu": [3, 8, 10, 19], "valuabl": 5, "variabl": 14, "varieti": 7, "veri": 9, "verma": 1, "version": [2, 3, 4, 18, 19], "vgg": 9, "vgg16": 15, "vgg16_bn_r": 9, "via": 2, "video": 1, "vietnames": 7, "view": [5, 7], "viewpoint": 2, "violat": 2, "visibl": 2, "vision": [5, 7, 9], "visiondataset": 7, "visiontransform": 9, "visual": [4, 5, 16], "visualize_pag": 11, "vit_": 9, "vit_b": 9, "vitstr": [5, 9, 18], "vitstr_bas": [9, 19], "vitstr_smal": [9, 13, 18, 19], "viz": 4, "vocab": [13, 15, 17, 18, 19], "vocabulari": [7, 13, 15], "w": [8, 9, 10, 11], "w3": 19, "wa": 2, "wai": [2, 5, 17], "want": [3, 18, 19], "warmup": 19, "wasn": 3, "we": [1, 2, 3, 4, 5, 8, 10, 13, 15, 17, 18, 19], "weasyprint": 8, "web": [3, 8], "websit": 7, "welcom": 2, "well": [1, 2, 18], "were": [2, 8, 19], "what": [1, 2], "when": [2, 3, 9], "whenev": 3, "where": [3, 8, 10, 11], "whether": [3, 7, 8, 10, 11, 17, 19], "which": [2, 9, 14, 16, 17, 19], "whichev": 4, "while": [10, 19], "why": 2, "width": [8, 10], "wiki": 2, "wildreceipt": [5, 7, 17], "window": [9, 11], "wish": 3, "within": 2, "without": [2, 7, 9], "wonder": 3, "word": [5, 7, 9, 11, 19], "word_1_1": 19, "word_1_2": 19, "word_1_3": 19, "wordgener": [7, 17], "words_onli": 11, "work": [1, 13, 14, 19], "workflow": 3, "worklow": 3, "world": [11, 19], "worth": 9, "wrap": 19, "wrapper": [7, 10], "write": 14, "written": [2, 8], "www": [2, 8, 19], "x": [8, 10, 11], "x_ascend": 19, "x_descend": 19, "x_i": 11, "x_size": 19, "x_wconf": 19, "xhtml": 19, "xmax": 8, "xmin": 8, "xml": 19, "xml_bytes_str": 19, "xml_element": 19, "xml_output": 19, "xmln": 19, "y": 11, "y_i": 11, "y_j": 11, "yet": 16, "ymax": 8, "ymin": 8, "yolov8": 16, "you": [3, 4, 7, 8, 9, 13, 14, 15, 16, 17, 18, 19], "your": [3, 5, 8, 11, 19], "yoursit": 8, "yugesh": 1, "zero": [10, 11], "zoo": 13, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 7, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 7, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": 7, "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 7, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 7, "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 7, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 7, "\u00e4\u00f6\u00e4\u00f6": 7, "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 7, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": 7, "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": 7, "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": 7, "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": 7, "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": 7, "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0905": 7, "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": 7, "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": 7, "\u0950": 7, "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": 7, "\u09bd": 7, "\u09ce": 7, "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": 7}, "titles": ["Changelog", "Community resources", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.contrib", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Integrate contributions into your pipeline", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 3, "0": 0, "01": 0, "02": 0, "03": 0, "04": 0, "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 2], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 2], "2021": 0, "2022": 0, "2023": 0, "2024": 0, "21": 0, "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 2], "31": 0, "4": [0, 2], "5": 0, "6": 0, "7": 0, "8": 0, "9": 0, "advanc": 19, "approach": 19, "architectur": 19, "arg": [7, 8, 9, 10, 11], "artefact": 8, "artefactdetect": 16, "attribut": 2, "avail": [16, 17, 19], "aw": 14, "ban": 2, "block": 8, "bug": 3, "changelog": 0, "choos": [17, 19], "classif": [9, 13, 15], "code": [2, 3], "codebas": 3, "commit": 3, "commun": [1, 15], "compos": 10, "conda": 4, "conduct": 2, "connect": 3, "continu": 3, "contrib": 6, "contribut": [3, 6, 16], "contributor": 2, "convent": 15, "correct": 2, "coven": 2, "custom": [7, 13], "data": 17, "dataload": 7, "dataset": [5, 7, 17], "detect": [5, 9, 15, 17, 19], "develop": 3, "do": 19, "doctr": [3, 5, 6, 7, 8, 9, 10, 11, 12], "document": [3, 5, 8], "end": 19, "enforc": 2, "evalu": 11, "export": 18, "factori": 9, "featur": [3, 5], "feedback": 3, "file": 8, "from": 15, "gener": [7, 17], "git": 4, "guidelin": 2, "half": 18, "hub": 15, "huggingfac": 15, "i": 19, "infer": 18, "instal": [3, 4], "integr": [3, 16], "io": 8, "lambda": 14, "let": 3, "line": 8, "linux": 4, "load": [13, 15, 17], "loader": 7, "main": 5, "mode": 3, "model": [5, 9, 13, 15, 18, 19], "modifi": 3, "modul": [6, 16], "name": 15, "notebook": 12, "object": 17, "ocr": [17, 19], "onli": 4, "onnx": 18, "optim": 18, "option": 19, "orient": 13, "our": 2, "output": 19, "own": [13, 17], "packag": 4, "page": 8, "perman": 2, "pipelin": 16, "pledg": 2, "precis": 18, "predictor": 19, "prepar": 18, "prerequisit": 4, "pretrain": 15, "push": 15, "python": 4, "qualiti": 3, "question": 3, "read": 8, "readi": 17, "recognit": [5, 9, 15, 17, 19], "report": 3, "request": 3, "resourc": 1, "respons": 2, "return": [7, 8, 9, 11], "right": 19, "scope": 2, "share": 15, "should": 19, "stage": 19, "standard": 2, "structur": [3, 8], "style": 3, "support": [5, 6, 7, 10], "synthet": [7, 17], "task": 11, "temporari": 2, "test": 3, "text": [5, 19], "train": 13, "transform": 10, "two": 19, "unit": 3, "us": [17, 18], "util": 11, "v0": 0, "verif": 3, "via": 4, "visual": 11, "vocab": 7, "warn": 2, "what": 19, "word": 8, "your": [13, 15, 16, 17, 18], "zoo": [5, 9]}}) \ No newline at end of file diff --git a/v0.2.1/transforms.html b/v0.2.1/transforms.html deleted file mode 100644 index 7f48d92039..0000000000 --- a/v0.2.1/transforms.html +++ /dev/null @@ -1,684 +0,0 @@ - - - - - - - - - - - - - doctr.transforms - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    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:

    -
    -
    -class doctr.transforms.Resize(output_size: Tuple[int, int], method: str = 'bilinear', preserve_aspect_ratio: bool = False, symmetric_pad: bool = False)[source]
    -

    Resizes a tensor to a target size

    -
    -
    Example::
    >>> from doctr.transforms import Resize
    ->>> import tensorflow as tf
    ->>> transfo = Resize((32, 32))
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • output_size – expected output size

    • -
    • method – interpolation method

    • -
    • preserve_aspect_ratio – if True, preserve aspect ratio and pad the rest with zeros

    • -
    • symmetric_pad – if True while preserving aspect ratio, the padding will be done symmetrically

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.Normalize(mean: Tuple[float, float, float], std: Tuple[float, float, float])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • mean – average value per channel

    • -
    • std – standard deviation per channel

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.LambdaTransformation(fn: Callable[[Tensor], Tensor])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import LambdaTransformation
    ->>> import tensorflow as tf
    ->>> transfo = LambdaTransformation(lambda x: x/ 255.)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    fn – the function to be applied to the input tensor

    -
    -
    -
    - -
    -
    -class doctr.transforms.ToGray[source]
    -

    Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ToGray()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    - -
    -
    -class doctr.transforms.ColorInversion(min_val: float = 0.6)[source]
    -

    Applies the following tranformation to a tensor (image or batch of images): -convert to grayscale, colorize (shift 0-values randomly), and then invert colors

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ColorInversion(min_val=0.6)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    min_val – range [min_val, 1] to colorize RGB pixels

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomBrightness(max_delta: float = 0.3)[source]
    -

    Randomly adjust brightness of a tensor (batch of images or image) by adding a delta -to all pixels

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Brightness()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    • -
    • p – probability to apply transformation

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomContrast(delta: float = 0.3)[source]
    -

    Randomly adjust contrast of a tensor (batch of images or image) by adjusting -each pixel: (img - mean) * contrast_factor + mean.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Contrast()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomSaturation(delta: float = 0.5)[source]
    -

    Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and -increasing saturation by a factor.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Saturation()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomHue(max_delta: float = 0.3)[source]
    -

    Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Hue()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomGamma(min_gamma: float = 0.5, max_gamma: float = 1.5, min_gain: float = 0.8, max_gain: float = 1.2)[source]
    -

    randomly performs gamma correction for a tensor (batch of images or image)

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Gamma()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_gamma – non-negative real number, lower bound for gamma param

    • -
    • max_gamma – non-negative real number, upper bound for gamma

    • -
    • min_gain – lower bound for constant multiplier

    • -
    • max_gain – upper bound for constant multiplier

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomJpegQuality(min_quality: int = 60, max_quality: int = 100)[source]
    -

    Randomly adjust jpeg quality of a 3 dimensional RGB image

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = JpegQuality()
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_quality – int between [0, 100]

    • -
    • max_quality – int between [0, 100]

    • -
    -
    -
    -
    - -
    -
    -

    Composing transformations

    -

    It is common to require several transformations to be performed consecutively.

    -
    -
    -class doctr.transforms.Compose(transforms: List[NestedObject])[source]
    -

    Implements a wrapper that will apply transformations sequentially

    -
    -
    Example::
    >>> from doctr.transforms import Compose, Resize
    ->>> import tensorflow as tf
    ->>> transfos = Compose([Resize((32, 32))])
    ->>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformation modules

    -
    -
    -
    - -
    -
    -class doctr.transforms.OneOf(transforms: List[NestedObject])[source]
    -

    Randomly apply one of the input transformations

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = OneOf([JpegQuality(), Gamma()])
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformations, one only will be picked

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomApply(transform: NestedObject, p: float = 0.5)[source]
    -

    Apply with a probability p the input transformation

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = RandomApply(Gamma(), p=.5)
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • transform – transformation to apply

    • -
    • p – probability to apply

    • -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.2.1/using_doctr/custom_models_training.html b/v0.2.1/using_doctr/custom_models_training.html index df39d8d568..b714c1f971 100644 --- a/v0.2.1/using_doctr/custom_models_training.html +++ b/v0.2.1/using_doctr/custom_models_training.html @@ -14,7 +14,7 @@ - + Train your own model - docTR documentation @@ -619,7 +619,7 @@

    Loading your custom trained orientation classification model - + diff --git a/v0.2.1/using_doctr/running_on_aws.html b/v0.2.1/using_doctr/running_on_aws.html index 16ceaca7a1..808ea541cd 100644 --- a/v0.2.1/using_doctr/running_on_aws.html +++ b/v0.2.1/using_doctr/running_on_aws.html @@ -14,7 +14,7 @@ - + AWS Lambda - docTR documentation @@ -362,7 +362,7 @@

    AWS Lambda - + diff --git a/v0.2.1/using_doctr/sharing_models.html b/v0.2.1/using_doctr/sharing_models.html index d76b4017f4..c9e978400a 100644 --- a/v0.2.1/using_doctr/sharing_models.html +++ b/v0.2.1/using_doctr/sharing_models.html @@ -14,7 +14,7 @@ - + Share your model with the community - docTR documentation @@ -544,7 +544,7 @@

    Recognition - + diff --git a/v0.2.1/using_doctr/using_contrib_modules.html b/v0.2.1/using_doctr/using_contrib_modules.html index 50598dae5d..0c5fffdf9f 100644 --- a/v0.2.1/using_doctr/using_contrib_modules.html +++ b/v0.2.1/using_doctr/using_contrib_modules.html @@ -14,7 +14,7 @@ - + Integrate contributions into your pipeline - docTR documentation @@ -415,7 +415,7 @@

    ArtefactDetection - + diff --git a/v0.2.1/using_doctr/using_datasets.html b/v0.2.1/using_doctr/using_datasets.html index 460476dbbf..8a7d4f0a64 100644 --- a/v0.2.1/using_doctr/using_datasets.html +++ b/v0.2.1/using_doctr/using_datasets.html @@ -14,7 +14,7 @@ - + Choose a ready to use dataset - docTR documentation @@ -642,7 +642,7 @@

    Data Loading - + diff --git a/v0.2.1/using_doctr/using_model_export.html b/v0.2.1/using_doctr/using_model_export.html index 6124c00ebe..6790dd0642 100644 --- a/v0.2.1/using_doctr/using_model_export.html +++ b/v0.2.1/using_doctr/using_model_export.html @@ -14,7 +14,7 @@ - + Preparing your model for inference - docTR documentation @@ -467,7 +467,7 @@

    Using your ONNX exported model - + diff --git a/v0.2.1/using_doctr/using_models.html b/v0.2.1/using_doctr/using_models.html index 61f1f5ab7a..9ead8498e1 100644 --- a/v0.2.1/using_doctr/using_models.html +++ b/v0.2.1/using_doctr/using_models.html @@ -14,7 +14,7 @@ - + Choosing the right model - docTR documentation @@ -1253,7 +1253,7 @@

    Advanced options - + diff --git a/v0.2.1/utils.html b/v0.2.1/utils.html deleted file mode 100644 index 5630a0b847..0000000000 --- a/v0.2.1/utils.html +++ /dev/null @@ -1,574 +0,0 @@ - - - - - - - - - - - - - doctr.utils - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.utils

    -

    This module regroups non-core features that are complementary to the rest of the package.

    -
    -

    Visualization

    -

    Easy-to-use functions to make sense of your model’s predictions.

    -
    -
    -doctr.utils.visualization.visualize_page(page: Dict[str, Any], image: ndarray, words_only: bool = True, display_artefacts: bool = True, scale: float = 10, interactive: bool = True, add_labels: bool = True, **kwargs: Any) Figure[source]
    -

    Visualize a full page with predicted blocks, lines and words

    -
    -
    Example::
    >>> import numpy as np
    ->>> import matplotlib.pyplot as plt
    ->>> from doctr.utils.visualization import visualize_page
    ->>> from doctr.models import ocr_db_crnn
    ->>> model = ocr_db_crnn(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([[input_page]])
    ->>> visualize_page(out[0].pages[0].export(), input_page)
    ->>> plt.show()
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • page – the exported Page of a Document

    • -
    • image – np array of the page, needs to have the same shape than page[‘dimensions’]

    • -
    • words_only – whether only words should be displayed

    • -
    • display_artefacts – whether artefacts should be displayed

    • -
    • scale – figsize of the largest windows side

    • -
    • interactive – whether the plot should be interactive

    • -
    • add_labels – for static plot, adds text labels on top of bounding box

    • -
    -
    -
    -
    - -
    -
    -

    Task evaluation

    -

    Implementations of task-specific metrics to easily assess your model performances.

    -
    -
    -class doctr.utils.metrics.TextMatch[source]
    -

    Implements text match metric (word-level accuracy) for recognition task.

    -

    The raw aggregated metric is computed as follows:

    -
    -
    -\[\forall X, Y \in \mathcal{W}^N, -TextMatch(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N f_{Y_i}(X_i)\]
    -
    -

    with the indicator function \(f_{a}\) defined as:

    -
    -
    -\[\begin{split}\forall a, x \in \mathcal{W}, -f_a(x) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } x = a \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{W}\) is the set of all possible character sequences, -\(N\) is a strictly positive integer.

    -
    -
    Example::
    >>> from doctr.utils import TextMatch
    ->>> metric = TextMatch()
    ->>> metric.update(['Hello', 'world'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    -summary() Dict[str, float][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a dictionary with the exact match score for the raw data, its lower-case counterpart, its unidecode -counterpart and its lower-case unidecode counterpart

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.LocalizationConfusion(iou_thresh: float = 0.5)[source]
    -

    Implements common confusion metrics and mean IoU for localization evaluation.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall Y \in \mathcal{B}^N, \forall X \in \mathcal{B}^M, \\ -Recall(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -Precision(X, Y) = \frac{1}{M} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -meanIoU(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(X_i, Y_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(g_{X}\) defined as:

    -
    -
    -\[\begin{split}\forall y \in \mathcal{B}, -g_X(y) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } y\mbox{ has been assigned to any }(X_i)_i\mbox{ with an }IoU \geq 0.5 \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import LocalizationConfusion
    ->>> metric = LocalizationConfusion(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]))
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[float, float, float][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall, precision and meanIoU scores

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.OCRMetric(iou_thresh: float = 0.5)[source]
    -

    Implements end-to-end OCR metric.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall (B, L) \in \mathcal{B}^N \times \mathcal{L}^N, -\forall (\hat{B}, \hat{L}) \in \mathcal{B}^M \times \mathcal{L}^M, \\ -Recall(B, \hat{B}, L, \hat{L}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -Precision(B, \hat{B}, L, \hat{L}) = \frac{1}{M} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(h_{B, L}\) defined as:

    -
    -
    -\[\begin{split}\forall (b, l) \in \mathcal{B} \times \mathcal{L}, -h_{B,L}(b, l) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ - & IoU \geq 0.5 \mbox{ and that for this assignment, } l = L_j\\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(\mathcal{L}\) is the set of possible character sequences, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import OCRMetric
    ->>> metric = OCRMetric(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]),
    -['hello'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[Dict[str, float], Dict[str, float], float][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall & precision for each string comparison flexibility and the mean IoU

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.3.0/_modules/doctr/datasets/cord.html b/v0.3.0/_modules/doctr/datasets/cord.html index f98ee6901c..55b0584830 100644 --- a/v0.3.0/_modules/doctr/datasets/cord.html +++ b/v0.3.0/_modules/doctr/datasets/cord.html @@ -13,7 +13,7 @@ - + doctr.datasets.cord - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.cord

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
     from .datasets import VisionDataset
    -from doctr.utils.geometry import fit_rbbox
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['CORD']
    +__all__ = ["CORD"]
     
     
     
    -[docs] +[docs] class CORD(VisionDataset): """CORD dataset from `"CORD: A Consolidated Receipt Dataset forPost-OCR Parsing" <https://openreview.net/pdf?id=SJl3z659UH>`_. - Example:: - >>> from doctr.datasets import CORD - >>> train_set = CORD(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/cord-grid.png&src=0 + :align: center + + >>> from doctr.datasets import CORD + >>> train_set = CORD(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - TRAIN = ('https://github.com/mindee/doctr/releases/download/v0.1.1/cord_train.zip', - '45f9dc77f126490f3e52d7cb4f70ef3c57e649ea86d19d862a2757c9c455d7f8') - TEST = ('https://github.com/mindee/doctr/releases/download/v0.1.1/cord_test.zip', - '8c895e3d6f7e1161c5b7245e3723ce15c04d84be89eaa6093949b75a66fb3c58') + TRAIN = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/cord_train.zip&src=0", + "45f9dc77f126490f3e52d7cb4f70ef3c57e649ea86d19d862a2757c9c455d7f8", + "cord_train.zip", + ) + + TEST = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/cord_test.zip&src=0", + "8c895e3d6f7e1161c5b7245e3723ce15c04d84be89eaa6093949b75a66fb3c58", + "cord_test.zip", + ) def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + url, sha256, name = self.TRAIN if train else self.TEST + super().__init__( + url, + name, + sha256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - url, sha256 = self.TRAIN if train else self.TEST - super().__init__(url, None, sha256, True, **kwargs) - - # # List images - self.root = os.path.join(self._root, 'image') - self.data: List[Tuple[str, Dict[str, Any]]] = [] + # List images + tmp_root = os.path.join(self.root, "image") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] self.train = train - self.sample_transforms = sample_transforms - for img_path in os.listdir(self.root): + np_dtype = np.float32 + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking CORD", total=len(os.listdir(tmp_root))): # File existence check - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem _targets = [] - with open(os.path.join(self._root, 'json', f"{stem}.json"), 'rb') as f: + with open(os.path.join(self.root, "json", f"{stem}.json"), "rb") as f: label = json.load(f) for line in label["valid_line"]: for word in line["words"]: if len(word["text"]) > 0: x = word["quad"]["x1"], word["quad"]["x2"], word["quad"]["x3"], word["quad"]["x4"] y = word["quad"]["y1"], word["quad"]["y2"], word["quad"]["y3"], word["quad"]["y4"] - if rotated_bbox: - box = list(fit_rbbox(np.array([ - [x[0], y[0]], - [x[1], y[1]], - [x[2], y[2]], - [x[3], y[3]], - ], dtype=np.float32))) + box: Union[List[float], np.ndarray] + if use_polygons: + # (x, y) coordinates of top left, top right, bottom right, bottom left corners + box = np.array( + [ + [x[0], y[0]], + [x[1], y[1]], + [x[2], y[2]], + [x[3], y[3]], + ], + dtype=np_dtype, + ) else: - # Reduce 8 coords to 4 + # Reduce 8 coords to 4 -> xmin, ymin, xmax, ymax box = [min(x), min(y), max(x), max(y)] - _targets.append((word['text'], box)) + _targets.append((word["text"], box)) text_targets, box_targets = zip(*_targets) - self.data.append(( - img_path, - dict(boxes=np.asarray(box_targets, dtype=int).clip(min=0), labels=text_targets) - )) + if recognition_task: + crops = crop_bboxes_from_image( + img_path=os.path.join(tmp_root, img_path), geoms=np.asarray(box_targets, dtype=int).clip(min=0) + ) + for crop, label in zip(crops, list(text_targets)): + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, np.asarray(box_targets, dtype=int).clip(min=0))) + else: + self.data.append(( + img_path, + dict(boxes=np.asarray(box_targets, dtype=int).clip(min=0), labels=list(text_targets)), + )) + + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -397,8 +461,8 @@

    Source code for doctr.datasets.cord

           
         
       
    -
    - + + diff --git a/v0.3.0/_modules/doctr/datasets/core.html b/v0.3.0/_modules/doctr/datasets/core.html deleted file mode 100644 index b3dcc29ff9..0000000000 --- a/v0.3.0/_modules/doctr/datasets/core.html +++ /dev/null @@ -1,417 +0,0 @@ - - - - - - - - - - - - doctr.datasets.core - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.core

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from pathlib import Path
    -from zipfile import ZipFile
    -from typing import List, Any, Optional, Tuple
    -import tensorflow as tf
    -
    -from doctr.models.utils import download_from_url
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset:
    -
    -    data: List[Any] = []
    -
    -    def __len__(self):
    -        return len(self.data)
    -
    -    def __getitem__(
    -        self,
    -        index: int
    -    ) -> Tuple[tf.Tensor, Any]:
    -
    -        img_name, target = self.data[index]
    -        # Read image
    -        img = tf.io.read_file(os.path.join(self.root, img_name))
    -        img = tf.image.decode_jpeg(img, channels=3)
    -        if self.sample_transforms is not None:
    -            img = self.sample_transforms(img)
    -
    -        return img, target
    -
    -    def extra_repr(self) -> str:
    -        return ""
    -
    -    def __repr__(self) -> str:
    -        return f"{self.__class__.__name__}({self.extra_repr()})"
    -
    -    @staticmethod
    -    def collate_fn(samples: List[Tuple[tf.Tensor, Any]]) -> Tuple[tf.Tensor, List[Any]]:
    -
    -        images, targets = zip(*samples)
    -        images = tf.stack(images, axis=0)
    -
    -        return images, list(targets)
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset): - """Implements an abstract dataset - - Args: - url: URL of the dataset - file_name: name of the file once downloaded - file_hash: expected SHA256 of the file - extract_archive: whether the downloaded file is an archive to be extracted - download: whether the dataset should be downloaded if not present on disk - overwrite: whether the archive should be re-extracted - """ - - def __init__( - self, - url: str, - file_name: Optional[str] = None, - file_hash: Optional[str] = None, - extract_archive: bool = False, - download: bool = False, - overwrite: bool = False, - ) -> None: - - dataset_cache = os.path.join(os.path.expanduser('~'), '.cache', 'doctr', 'datasets') - - file_name = file_name if isinstance(file_name, str) else os.path.basename(url) - # Download the file if not present - archive_path = os.path.join(dataset_cache, file_name) - - if not os.path.exists(archive_path) and not download: - raise ValueError("the dataset needs to be downloaded first with download=True") - - archive_path = download_from_url(url, file_name, file_hash, cache_subdir='datasets') - - # Extract the archive - if extract_archive: - archive_path = Path(archive_path) - dataset_path = archive_path.parent.joinpath(archive_path.stem) - if not dataset_path.is_dir() or overwrite: - with ZipFile(archive_path, 'r') as f: - f.extractall(path=dataset_path) - - # List images - self._root = dataset_path if extract_archive else archive_path - self.data: List[Any] = []
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.0/_modules/doctr/datasets/datasets/tensorflow.html b/v0.3.0/_modules/doctr/datasets/datasets/tensorflow.html deleted file mode 100644 index a236abd9fe..0000000000 --- a/v0.3.0/_modules/doctr/datasets/datasets/tensorflow.html +++ /dev/null @@ -1,356 +0,0 @@ - - - - - - - - - - - - doctr.datasets.datasets.tensorflow - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.datasets.tensorflow

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from typing import List, Any, Tuple
    -import tensorflow as tf
    -
    -from .base import _AbstractDataset, _VisionDataset
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset(_AbstractDataset):
    -
    -    def _read_sample(self, index: int) -> Tuple[tf.Tensor, Any]:
    -        img_name, target = self.data[index]
    -        # Read image
    -        img = tf.io.read_file(os.path.join(self.root, img_name))
    -        img = tf.image.decode_jpeg(img, channels=3)
    -
    -        return img, target
    -
    -    @staticmethod
    -    def collate_fn(samples: List[Tuple[tf.Tensor, Any]]) -> Tuple[tf.Tensor, List[Any]]:
    -
    -        images, targets = zip(*samples)
    -        images = tf.stack(images, axis=0)
    -
    -        return images, list(targets)
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset, _VisionDataset): - pass
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.0/_modules/doctr/datasets/detection.html b/v0.3.0/_modules/doctr/datasets/detection.html index 739563e466..718001e4cf 100644 --- a/v0.3.0/_modules/doctr/datasets/detection.html +++ b/v0.3.0/_modules/doctr/datasets/detection.html @@ -13,7 +13,7 @@ - + doctr.datasets.detection - docTR documentation @@ -430,7 +430,7 @@

    Source code for doctr.datasets.detection

         
       
    - + diff --git a/v0.3.0/_modules/doctr/datasets/doc_artefacts.html b/v0.3.0/_modules/doctr/datasets/doc_artefacts.html index 3313ae4660..94c32aaa0f 100644 --- a/v0.3.0/_modules/doctr/datasets/doc_artefacts.html +++ b/v0.3.0/_modules/doctr/datasets/doc_artefacts.html @@ -13,7 +13,7 @@ - + doctr.datasets.doc_artefacts - docTR documentation @@ -414,7 +414,7 @@

    Source code for doctr.datasets.doc_artefacts

       
    - + diff --git a/v0.3.0/_modules/doctr/datasets/funsd.html b/v0.3.0/_modules/doctr/datasets/funsd.html index 35d7ad4cf5..f08612f9fa 100644 --- a/v0.3.0/_modules/doctr/datasets/funsd.html +++ b/v0.3.0/_modules/doctr/datasets/funsd.html @@ -13,7 +13,7 @@ - + doctr.datasets.funsd - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.funsd

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
     from .datasets import VisionDataset
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['FUNSD']
    +__all__ = ["FUNSD"]
     
     
     
    -[docs] +[docs] class FUNSD(VisionDataset): """FUNSD dataset from `"FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents" <https://arxiv.org/pdf/1905.13538.pdf>`_. - Example:: - >>> from doctr.datasets import FUNSD - >>> train_set = FUNSD(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/funsd-grid.png&src=0 + :align: center + + >>> from doctr.datasets import FUNSD + >>> train_set = FUNSD(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - URL = 'https://guillaumejaume.github.io/FUNSD/dataset.zip' - SHA256 = 'c31735649e4f441bcbb4fd0f379574f7520b42286e80b01d80b445649d54761f' - FILE_NAME = 'funsd.zip' + URL = "https://guillaumejaume.github.io/FUNSD/dataset.zip" + SHA256 = "c31735649e4f441bcbb4fd0f379574f7520b42286e80b01d80b445649d54761f" + FILE_NAME = "funsd.zip" def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + super().__init__( + self.URL, + self.FILE_NAME, + self.SHA256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - super().__init__(self.URL, self.FILE_NAME, self.SHA256, True, **kwargs) self.train = train - self.sample_transforms = sample_transforms + np_dtype = np.float32 # Use the subset - subfolder = os.path.join('dataset', 'training_data' if train else 'testing_data') + subfolder = os.path.join("dataset", "training_data" if train else "testing_data") # # List images - self.root = os.path.join(self._root, subfolder, 'images') - self.data: List[Tuple[str, Dict[str, Any]]] = [] - for img_path in os.listdir(self.root): + tmp_root = os.path.join(self.root, subfolder, "images") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking FUNSD", total=len(os.listdir(tmp_root))): # File existence check - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem - with open(os.path.join(self._root, subfolder, 'annotations', f"{stem}.json"), 'rb') as f: + with open(os.path.join(self.root, subfolder, "annotations", f"{stem}.json"), "rb") as f: data = json.load(f) - _targets = [(word['text'], word['box']) for block in data['form'] - for word in block['words'] if len(word['text']) > 0] + _targets = [ + (word["text"], word["box"]) + for block in data["form"] + for word in block["words"] + if len(word["text"]) > 0 + ] text_targets, box_targets = zip(*_targets) - if rotated_bbox: - # box_targets: xmin, ymin, xmax, ymax -> x, y, w, h, alpha = 0 - box_targets = [ + if use_polygons: + # xmin, ymin, xmax, ymax -> (x, y) coordinates of top left, top right, bottom right, bottom left corners + box_targets = [ # type: ignore[assignment] [ - (box[0] + box[2]) / 2, (box[1] + box[3]) / 2, box[2] - box[0], box[3] - box[1], 0 - ] for box in box_targets + [box[0], box[1]], + [box[2], box[1]], + [box[2], box[3]], + [box[0], box[3]], + ] + for box in box_targets ] - self.data.append((img_path, dict(boxes=np.asarray(box_targets, dtype=int), labels=text_targets))) + if recognition_task: + crops = crop_bboxes_from_image( + img_path=os.path.join(tmp_root, img_path), geoms=np.asarray(box_targets, dtype=np_dtype) + ) + for crop, label in zip(crops, list(text_targets)): + # filter labels with unknown characters + if not any(char in label for char in ["☑", "☐", "\uf703", "\uf702"]): + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, np.asarray(box_targets, dtype=np_dtype))) + else: + self.data.append(( + img_path, + dict(boxes=np.asarray(box_targets, dtype=np_dtype), labels=list(text_targets)), + )) + + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -386,8 +453,8 @@

    Source code for doctr.datasets.funsd

           
         
       
    -
    - + + diff --git a/v0.3.0/_modules/doctr/datasets/generator/tensorflow.html b/v0.3.0/_modules/doctr/datasets/generator/tensorflow.html index 9f562582d9..a3e619f720 100644 --- a/v0.3.0/_modules/doctr/datasets/generator/tensorflow.html +++ b/v0.3.0/_modules/doctr/datasets/generator/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.datasets.generator.tensorflow - docTR documentation @@ -395,7 +395,7 @@

    Source code for doctr.datasets.generator.tensorflow

    - + diff --git a/v0.3.0/_modules/doctr/datasets/ic03.html b/v0.3.0/_modules/doctr/datasets/ic03.html index 3d221d07de..60e54a8a4b 100644 --- a/v0.3.0/_modules/doctr/datasets/ic03.html +++ b/v0.3.0/_modules/doctr/datasets/ic03.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic03 - docTR documentation @@ -468,7 +468,7 @@

    Source code for doctr.datasets.ic03

         
       
    - + diff --git a/v0.3.0/_modules/doctr/datasets/ic13.html b/v0.3.0/_modules/doctr/datasets/ic13.html index 8137e08e9f..219c98dcd1 100644 --- a/v0.3.0/_modules/doctr/datasets/ic13.html +++ b/v0.3.0/_modules/doctr/datasets/ic13.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic13 - docTR documentation @@ -440,7 +440,7 @@

    Source code for doctr.datasets.ic13

         
       
    - + diff --git a/v0.3.0/_modules/doctr/datasets/iiit5k.html b/v0.3.0/_modules/doctr/datasets/iiit5k.html index 1fc8ecfb27..b49c80fe18 100644 --- a/v0.3.0/_modules/doctr/datasets/iiit5k.html +++ b/v0.3.0/_modules/doctr/datasets/iiit5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiit5k - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.datasets.iiit5k

         
       
    - + diff --git a/v0.3.0/_modules/doctr/datasets/iiithws.html b/v0.3.0/_modules/doctr/datasets/iiithws.html index 07f5b13685..f7220afbc7 100644 --- a/v0.3.0/_modules/doctr/datasets/iiithws.html +++ b/v0.3.0/_modules/doctr/datasets/iiithws.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiithws - docTR documentation @@ -407,7 +407,7 @@

    Source code for doctr.datasets.iiithws

         
       
    - + diff --git a/v0.3.0/_modules/doctr/datasets/imgur5k.html b/v0.3.0/_modules/doctr/datasets/imgur5k.html index 68d433ca62..51c6545db4 100644 --- a/v0.3.0/_modules/doctr/datasets/imgur5k.html +++ b/v0.3.0/_modules/doctr/datasets/imgur5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.imgur5k - docTR documentation @@ -488,7 +488,7 @@

    Source code for doctr.datasets.imgur5k

         
       
    - + diff --git a/v0.3.0/_modules/doctr/datasets/loader.html b/v0.3.0/_modules/doctr/datasets/loader.html index d32e6da298..ed80350ef0 100644 --- a/v0.3.0/_modules/doctr/datasets/loader.html +++ b/v0.3.0/_modules/doctr/datasets/loader.html @@ -13,7 +13,7 @@ - + doctr.datasets.loader - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.loader

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import math
    -import tensorflow as tf
    -import numpy as np
    -from typing import Optional
    +from typing import Callable, Optional
     
    -from .multithreading import multithread_exec
    +import numpy as np
    +import tensorflow as tf
     
     __all__ = ["DataLoader"]
     
    @@ -293,12 +314,13 @@ 

    Source code for doctr.datasets.loader

         """Collate multiple elements into batches
     
         Args:
    +    ----
             samples: list of N tuples containing M elements
     
         Returns:
    +    -------
             Tuple of M sequences contianing N elements each
         """
    -
         batch_data = zip(*samples)
     
         tf_data = tuple(tf.stack(elt, axis=0) for elt in batch_data)
    @@ -307,23 +329,23 @@ 

    Source code for doctr.datasets.loader

     
     
     
    -[docs] +[docs] class DataLoader: """Implements a dataset wrapper for fast data loading - Example:: - >>> from doctr.datasets import FUNSD, 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) + >>> 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) Args: + ---- dataset: the dataset shuffle: whether the samples should be shuffled before passing it to the iterator batch_size: number of elements in each batch drop_last: if `True`, drops the last batch if it isn't full - workers: number of workers to use for data loading + collate_fn: function to merge samples into a batch """ def __init__( @@ -332,17 +354,22 @@

    Source code for doctr.datasets.loader

             shuffle: bool = True,
             batch_size: int = 1,
             drop_last: bool = False,
    -        workers: Optional[int] = None,
    +        collate_fn: Optional[Callable] = None,
         ) -> None:
             self.dataset = dataset
             self.shuffle = shuffle
             self.batch_size = batch_size
             nb = len(self.dataset) / batch_size
             self.num_batches = math.floor(nb) if drop_last else math.ceil(nb)
    -        self.collate_fn = self.dataset.collate_fn if hasattr(self.dataset, 'collate_fn') else default_collate
    -        self.workers = workers
    +        if collate_fn is None:
    +            self.collate_fn = self.dataset.collate_fn if hasattr(self.dataset, "collate_fn") else default_collate
    +        else:
    +            self.collate_fn = collate_fn
             self.reset()
     
    +    def __len__(self) -> int:
    +        return self.num_batches
    +
         def reset(self) -> None:
             # Updates indices after each epoch
             self._num_yielded = 0
    @@ -358,9 +385,9 @@ 

    Source code for doctr.datasets.loader

             if self._num_yielded < self.num_batches:
                 # Get next indices
                 idx = self._num_yielded * self.batch_size
    -            indices = self.indices[idx: min(len(self.dataset), idx + self.batch_size)]
    +            indices = self.indices[idx : min(len(self.dataset), idx + self.batch_size)]
     
    -            samples = multithread_exec(self.dataset.__getitem__, indices, threads=self.workers)
    +            samples = list(map(self.dataset.__getitem__, indices))
     
                 batch_data = self.collate_fn(samples)
     
    @@ -401,8 +428,8 @@ 

    Source code for doctr.datasets.loader

           
         
       
    -
    - +
    + diff --git a/v0.3.0/_modules/doctr/datasets/mjsynth.html b/v0.3.0/_modules/doctr/datasets/mjsynth.html index 77bb01d523..df34e49cf9 100644 --- a/v0.3.0/_modules/doctr/datasets/mjsynth.html +++ b/v0.3.0/_modules/doctr/datasets/mjsynth.html @@ -13,7 +13,7 @@ - + doctr.datasets.mjsynth - docTR documentation @@ -438,7 +438,7 @@

    Source code for doctr.datasets.mjsynth

         
       
    - + diff --git a/v0.3.0/_modules/doctr/datasets/ocr.html b/v0.3.0/_modules/doctr/datasets/ocr.html index 11297d5952..ce1ed8b0d4 100644 --- a/v0.3.0/_modules/doctr/datasets/ocr.html +++ b/v0.3.0/_modules/doctr/datasets/ocr.html @@ -13,7 +13,7 @@ - + doctr.datasets.ocr - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.ocr

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple
     
    -from .datasets import AbstractDataset
    -from doctr.utils.geometry import fit_rbbox
    +import numpy as np
     
    +from .datasets import AbstractDataset
     
    -__all__ = ['OCRDataset']
    +__all__ = ["OCRDataset"]
     
     
     
    -[docs] +[docs] class OCRDataset(AbstractDataset): """Implements an OCR dataset + >>> from doctr.datasets import OCRDataset + >>> train_set = OCRDataset(img_folder="/path/to/images", + >>> label_file="/path/to/labels.json") + >>> img, target = train_set[0] + Args: + ---- img_folder: local path to image folder (all jpg at the root) label_file: local path to the label file - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) - **kwargs: keyword arguments from `VisionDataset`. + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + **kwargs: keyword arguments from `AbstractDataset`. """ def __init__( self, img_folder: str, label_file: str, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, **kwargs: Any, ) -> None: - - self.sample_transforms = sample_transforms - self.root = img_folder + super().__init__(img_folder, **kwargs) # List images self.data: List[Tuple[str, Dict[str, Any]]] = [] - with open(label_file, 'rb') as f: + np_dtype = np.float32 + with open(label_file, "rb") as f: data = json.load(f) - for file_dic in data: + for img_name, annotations in data.items(): # Get image path - img_name = Path(os.path.basename(file_dic["raw-archive-filepath"])).stem + '.jpg' + img_name = Path(img_name) # File existence check if not os.path.exists(os.path.join(self.root, img_name)): raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_name)}") # handle empty images - if (len(file_dic["coordinates"]) == 0 or - (len(file_dic["coordinates"]) == 1 and file_dic["coordinates"][0] == "N/A")): - self.data.append((img_name, dict(boxes=np.zeros((0, 4), dtype=np.float32), labels=[]))) + if len(annotations["typed_words"]) == 0: + self.data.append((img_name, dict(boxes=np.zeros((0, 4), dtype=np_dtype), labels=[]))) continue - is_valid: List[bool] = [] - box_targets: List[List[float]] = [] - for box in file_dic["coordinates"]: - if rotated_bbox: - x, y, w, h, alpha = fit_rbbox(np.asarray(box, dtype=np.float32)) - box = [x, y, w, h, alpha] - is_valid.append(w > 0 and h > 0) - else: - xs, ys = zip(*box) - box = [min(xs), min(ys), max(xs), max(ys)] - is_valid.append(box[0] < box[2] and box[1] < box[3]) - if is_valid[-1]: - box_targets.append(box) + # Unpack the straight boxes (xmin, ymin, xmax, ymax) + geoms = [list(map(float, obj["geometry"][:4])) for obj in annotations["typed_words"]] + if use_polygons: + # (x, y) coordinates of top left, top right, bottom right, bottom left corners + geoms = [ + [geom[:2], [geom[2], geom[1]], geom[2:], [geom[0], geom[3]]] # type: ignore[list-item] + for geom in geoms + ] + + text_targets = [obj["value"] for obj in annotations["typed_words"]] - text_targets = [word for word, _valid in zip(file_dic["string"], is_valid) if _valid] - self.data.append((img_name, dict(boxes=np.asarray(box_targets, dtype=np.float32), labels=text_targets)))
    + self.data.append((img_name, dict(boxes=np.asarray(geoms, dtype=np_dtype), labels=text_targets)))
    @@ -383,8 +402,8 @@

    Source code for doctr.datasets.ocr

           
         
       
    - - + + diff --git a/v0.3.0/_modules/doctr/datasets/recognition.html b/v0.3.0/_modules/doctr/datasets/recognition.html index 512c70c308..1754789364 100644 --- a/v0.3.0/_modules/doctr/datasets/recognition.html +++ b/v0.3.0/_modules/doctr/datasets/recognition.html @@ -13,7 +13,7 @@ - + doctr.datasets.recognition - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.datasets.recognition

         
       
    - + diff --git a/v0.3.0/_modules/doctr/datasets/sroie.html b/v0.3.0/_modules/doctr/datasets/sroie.html index 66fd4ca3e0..04cf10bda2 100644 --- a/v0.3.0/_modules/doctr/datasets/sroie.html +++ b/v0.3.0/_modules/doctr/datasets/sroie.html @@ -13,7 +13,7 @@ - + doctr.datasets.sroie - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.sroie

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import csv
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
     from .datasets import VisionDataset
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['SROIE']
    +__all__ = ["SROIE"]
     
     
     
    -[docs] +[docs] class SROIE(VisionDataset): """SROIE dataset from `"ICDAR2019 Competition on Scanned Receipt OCR and Information Extraction" <https://arxiv.org/pdf/2103.10213.pdf>`_. - Example:: - >>> from doctr.datasets import SROIE - >>> train_set = SROIE(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/sroie-grid.png&src=0 + :align: center + + >>> from doctr.datasets import SROIE + >>> train_set = SROIE(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - TRAIN = ('https://github.com/mindee/doctr/releases/download/v0.1.1/sroie2019_train_task1.zip', - 'd4fa9e60abb03500d83299c845b9c87fd9c9430d1aeac96b83c5d0bb0ab27f6f') - TEST = ('https://github.com/mindee/doctr/releases/download/v0.1.1/sroie2019_test.zip', - '41b3c746a20226fddc80d86d4b2a903d43b5be4f521dd1bbe759dbf8844745e2') + TRAIN = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/sroie2019_train_task1.zip&src=0", + "d4fa9e60abb03500d83299c845b9c87fd9c9430d1aeac96b83c5d0bb0ab27f6f", + "sroie2019_train_task1.zip", + ) + TEST = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/sroie2019_test.zip&src=0", + "41b3c746a20226fddc80d86d4b2a903d43b5be4f521dd1bbe759dbf8844745e2", + "sroie2019_test.zip", + ) def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + url, sha256, name = self.TRAIN if train else self.TEST + super().__init__( + url, + name, + sha256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - url, sha256 = self.TRAIN if train else self.TEST - super().__init__(url, None, sha256, True, **kwargs) - self.sample_transforms = sample_transforms self.train = train - if rotated_bbox: - raise NotImplementedError + tmp_root = os.path.join(self.root, "images") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] + np_dtype = np.float32 - # # List images - self.root = os.path.join(self._root, 'images') - self.data: List[Tuple[str, Dict[str, Any]]] = [] - for img_path in os.listdir(self.root): + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking SROIE", total=len(os.listdir(tmp_root))): # File existence check - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem - _targets = [] - with open(os.path.join(self._root, 'annotations', f"{stem}.txt"), encoding='latin') as f: - for row in csv.reader(f, delimiter=','): - # Safeguard for blank lines - if len(row) > 0: - # Label may contain commas - label = ",".join(row[8:]) - # Reduce 8 coords to 4 - p1_x, p1_y, p2_x, p2_y, p3_x, p3_y, p4_x, p4_y = map(int, row[:8]) - left, right = min(p1_x, p2_x, p3_x, p4_x), max(p1_x, p2_x, p3_x, p4_x) - top, bot = min(p1_y, p2_y, p3_y, p4_y), max(p1_y, p2_y, p3_y, p4_y) - if len(label) > 0: - _targets.append((label, [left, top, right, bot])) - - text_targets, box_targets = zip(*_targets) - - self.data.append((img_path, dict(boxes=np.asarray(box_targets, dtype=np.float32), labels=text_targets))) + with open(os.path.join(self.root, "annotations", f"{stem}.txt"), encoding="latin") as f: + _rows = [row for row in list(csv.reader(f, delimiter=",")) if len(row) > 0] + + labels = [",".join(row[8:]) for row in _rows] + # reorder coordinates (8 -> (4,2) -> + # (x, y) coordinates of top left, top right, bottom right, bottom left corners) and filter empty lines + coords: np.ndarray = np.stack( + [np.array(list(map(int, row[:8])), dtype=np_dtype).reshape((4, 2)) for row in _rows], axis=0 + ) + + if not use_polygons: + # xmin, ymin, xmax, ymax + coords = np.concatenate((coords.min(axis=1), coords.max(axis=1)), axis=1) + + if recognition_task: + crops = crop_bboxes_from_image(img_path=os.path.join(tmp_root, img_path), geoms=coords) + for crop, label in zip(crops, labels): + if crop.shape[0] > 0 and crop.shape[1] > 0 and len(label) > 0: + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, coords)) + else: + self.data.append((img_path, dict(boxes=coords, labels=labels))) + + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -390,8 +444,8 @@

    Source code for doctr.datasets.sroie

           
         
       
    -
    - + + diff --git a/v0.3.0/_modules/doctr/datasets/svhn.html b/v0.3.0/_modules/doctr/datasets/svhn.html index 48e4e4d210..60e02b1b3b 100644 --- a/v0.3.0/_modules/doctr/datasets/svhn.html +++ b/v0.3.0/_modules/doctr/datasets/svhn.html @@ -13,7 +13,7 @@ - + doctr.datasets.svhn - docTR documentation @@ -473,7 +473,7 @@

    Source code for doctr.datasets.svhn

         
       
    - + diff --git a/v0.3.0/_modules/doctr/datasets/svt.html b/v0.3.0/_modules/doctr/datasets/svt.html index 4144dc6b9b..a997fcbb50 100644 --- a/v0.3.0/_modules/doctr/datasets/svt.html +++ b/v0.3.0/_modules/doctr/datasets/svt.html @@ -13,7 +13,7 @@ - + doctr.datasets.svt - docTR documentation @@ -459,7 +459,7 @@

    Source code for doctr.datasets.svt

         
       
    - + diff --git a/v0.3.0/_modules/doctr/datasets/synthtext.html b/v0.3.0/_modules/doctr/datasets/synthtext.html index 3b9de506a7..c776e1d673 100644 --- a/v0.3.0/_modules/doctr/datasets/synthtext.html +++ b/v0.3.0/_modules/doctr/datasets/synthtext.html @@ -13,7 +13,7 @@ - + doctr.datasets.synthtext - docTR documentation @@ -470,7 +470,7 @@

    Source code for doctr.datasets.synthtext

         
       
    - + diff --git a/v0.3.0/_modules/doctr/datasets/utils.html b/v0.3.0/_modules/doctr/datasets/utils.html index 2259698c0f..bde9304597 100644 --- a/v0.3.0/_modules/doctr/datasets/utils.html +++ b/v0.3.0/_modules/doctr/datasets/utils.html @@ -13,7 +13,7 @@ - + doctr.datasets.utils - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.utils

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import string
     import unicodedata
    +from collections.abc import Sequence
    +from functools import partial
    +from pathlib import Path
    +from typing import Any, Dict, List, Optional, Tuple, TypeVar, Union
    +from typing import Sequence as SequenceType
    +
     import numpy as np
    -from typing import List, Optional, Any
    +from PIL import Image
    +
    +from doctr.io.image import get_img_shape
    +from doctr.utils.geometry import convert_to_relative_coords, extract_crops, extract_rcrops
     
     from .vocabs import VOCABS
     
    -__all__ = ['translate', 'encode_sequence', 'decode_sequence', 'encode_sequences']
    +__all__ = ["translate", "encode_string", "decode_sequence", "encode_sequences", "pre_transform_multiclass"]
    +
    +ImageTensor = TypeVar("ImageTensor")
     
     
     def translate(
         input_string: str,
         vocab_name: str,
    -    unknown_char: str = '■',
    +    unknown_char: str = "■",
     ) -> str:
         """Translate a string input in a given vocabulary
     
         Args:
    +    ----
             input_string: input string to translate
             vocab_name: vocabulary to use (french, latin, ...)
             unknown_char: unknown character for non-translatable characters
     
         Returns:
    -        A string translated in a given vocab"""
    -
    +    -------
    +        A string translated in a given vocab
    +    """
         if VOCABS.get(vocab_name) is None:
             raise KeyError("output vocabulary must be in vocabs dictionnary")
     
    -    translated = ''
    +    translated = ""
         for char in input_string:
             if char not in VOCABS[vocab_name]:
                 # we need to translate char into a vocab char
    @@ -315,51 +350,63 @@ 

    Source code for doctr.datasets.utils

                     # remove whitespaces
                     continue
                 # normalize character if it is not in vocab
    -            char = unicodedata.normalize('NFD', char).encode('ascii', 'ignore').decode('ascii')
    -            if char == '' or char not in VOCABS[vocab_name]:
    +            char = unicodedata.normalize("NFD", char).encode("ascii", "ignore").decode("ascii")
    +            if char == "" or char not in VOCABS[vocab_name]:
                     # if normalization fails or char still not in vocab, return unknown character)
                     char = unknown_char
             translated += char
         return translated
     
     
    -def encode_sequence(
    +def encode_string(
         input_string: str,
         vocab: str,
     ) -> List[int]:
         """Given a predefined mapping, encode the string to a sequence of numbers
     
         Args:
    +    ----
             input_string: string to encode
             vocab: vocabulary (string), the encoding is given by the indexing of the character sequence
     
         Returns:
    -        A list encoding the input_string"""
    -
    -    return list(map(vocab.index, input_string))  # type: ignore[arg-type]
    +    -------
    +        A list encoding the input_string
    +    """
    +    try:
    +        return list(map(vocab.index, input_string))
    +    except ValueError:
    +        raise ValueError(
    +            f"some characters cannot be found in 'vocab'. \
    +                         Please check the input string {input_string} and the vocabulary {vocab}"
    +        )
     
     
     def decode_sequence(
    -    input_array: np.array,
    +    input_seq: Union[np.ndarray, SequenceType[int]],
         mapping: str,
     ) -> str:
         """Given a predefined mapping, decode the sequence of numbers to a string
     
         Args:
    -        input_array: array to decode
    +    ----
    +        input_seq: array to decode
             mapping: vocabulary (string), the encoding is given by the indexing of the character sequence
     
         Returns:
    -        A string, decoded from input_array"""
    -
    -    if not input_array.dtype == np.int_ or input_array.max() >= len(mapping):
    +    -------
    +        A string, decoded from input_seq
    +    """
    +    if not isinstance(input_seq, (Sequence, np.ndarray)):
    +        raise TypeError("Invalid sequence type")
    +    if isinstance(input_seq, np.ndarray) and (input_seq.dtype != np.int_ or input_seq.max() >= len(mapping)):
             raise AssertionError("Input must be an array of int, with max less than mapping size")
    -    decoded = ''.join(mapping[idx] for idx in input_array)
    -    return decoded
    +
    +    return "".join(map(mapping.__getitem__, input_seq))
     
     
     
    -[docs] +[docs] def encode_sequences( sequences: List[str], vocab: str, @@ -367,48 +414,53 @@

    Source code for doctr.datasets.utils

         eos: int = -1,
         sos: Optional[int] = None,
         pad: Optional[int] = None,
    -    **kwargs: Any,
    +    dynamic_seq_length: bool = False,
     ) -> np.ndarray:
         """Encode character sequences using a given vocab as mapping
     
         Args:
    +    ----
             sequences: the list of character sequences of size N
             vocab: the ordered vocab to use for encoding
             target_size: maximum length of the encoded data
             eos: encoding of End Of String
             sos: optional encoding of Start Of String
             pad: optional encoding for padding. In case of padding, all sequences are followed by 1 EOS then PAD
    +        dynamic_seq_length: if `target_size` is specified, uses it as upper bound and enables dynamic sequence size
     
         Returns:
    +    -------
             the padded encoded data as a tensor
         """
    -
         if 0 <= eos < len(vocab):
             raise ValueError("argument 'eos' needs to be outside of vocab possible indices")
     
    -    if not isinstance(target_size, int):
    -        target_size = max(len(w) for w in sequences)
    -        if sos:
    -            target_size += 1
    -        if pad:
    -            target_size += 1
    +    if not isinstance(target_size, int) or dynamic_seq_length:
    +        # Maximum string length + EOS
    +        max_length = max(len(w) for w in sequences) + 1
    +        if isinstance(sos, int):
    +            max_length += 1
    +        if isinstance(pad, int):
    +            max_length += 1
    +        target_size = max_length if not isinstance(target_size, int) else min(max_length, target_size)
     
         # Pad all sequences
    -    if pad:  # pad with padding symbol
    +    if isinstance(pad, int):  # pad with padding symbol
             if 0 <= pad < len(vocab):
                 raise ValueError("argument 'pad' needs to be outside of vocab possible indices")
             # In that case, add EOS at the end of the word before padding
    -        encoded_data = np.full([len(sequences), target_size], pad, dtype=np.int32)
    +        default_symbol = pad
         else:  # pad with eos symbol
    -        encoded_data = np.full([len(sequences), target_size], eos, dtype=np.int32)
    +        default_symbol = eos
    +    encoded_data: np.ndarray = np.full([len(sequences), target_size], default_symbol, dtype=np.int32)
     
    -    for idx, seq in enumerate(sequences):
    -        encoded_seq = encode_sequence(seq, vocab)
    -        if pad:  # add eos at the end of the sequence
    -            encoded_seq.append(eos)
    -        encoded_data[idx, :min(len(encoded_seq), target_size)] = encoded_seq[:min(len(encoded_seq), target_size)]
    +    # Encode the strings
    +    for idx, seq in enumerate(map(partial(encode_string, vocab=vocab), sequences)):
    +        if isinstance(pad, int):  # add eos at the end of the sequence
    +            seq.append(eos)
    +        encoded_data[idx, : min(len(seq), target_size)] = seq[: min(len(seq), target_size)]
     
    -    if sos:  # place eos symbol at the beginning of each sequence
    +    if isinstance(sos, int):  # place sos symbol at the beginning of each sequence
             if 0 <= sos < len(vocab):
                 raise ValueError("argument 'sos' needs to be outside of vocab possible indices")
             encoded_data = np.roll(encoded_data, 1)
    @@ -416,6 +468,59 @@ 

    Source code for doctr.datasets.utils

     
         return encoded_data
    + + +def convert_target_to_relative( + img: ImageTensor, target: Union[np.ndarray, Dict[str, Any]] +) -> Tuple[ImageTensor, Union[Dict[str, Any], np.ndarray]]: + if isinstance(target, np.ndarray): + target = convert_to_relative_coords(target, get_img_shape(img)) + else: + target["boxes"] = convert_to_relative_coords(target["boxes"], get_img_shape(img)) + return img, target + + +def crop_bboxes_from_image(img_path: Union[str, Path], geoms: np.ndarray) -> List[np.ndarray]: + """Crop a set of bounding boxes from an image + + Args: + ---- + img_path: path to the image + geoms: a array of polygons of shape (N, 4, 2) or of straight boxes of shape (N, 4) + + Returns: + ------- + a list of cropped images + """ + with Image.open(img_path) as pil_img: + img: np.ndarray = np.array(pil_img.convert("RGB")) + # Polygon + if geoms.ndim == 3 and geoms.shape[1:] == (4, 2): + return extract_rcrops(img, geoms.astype(dtype=int)) + if geoms.ndim == 2 and geoms.shape[1] == 4: + return extract_crops(img, geoms.astype(dtype=int)) + raise ValueError("Invalid geometry format") + + +def pre_transform_multiclass(img, target: Tuple[np.ndarray, List]) -> Tuple[np.ndarray, Dict[str, List]]: + """Converts multiclass target to relative coordinates. + + Args: + ---- + img: Image + target: tuple of target polygons and their classes names + + Returns: + ------- + Image and dictionary of boxes, with class names as keys + """ + boxes = convert_to_relative_coords(target[0], get_img_shape(img)) + boxes_classes = target[1] + boxes_dict: Dict = {k: [] for k in sorted(set(boxes_classes))} + for k, poly in zip(boxes_classes, boxes): + boxes_dict[k].append(poly) + boxes_dict = {k: np.stack(v, axis=0) for k, v in boxes_dict.items()} + return img, boxes_dict
    @@ -448,8 +553,8 @@

    Source code for doctr.datasets.utils

           
         
       
    - - + + diff --git a/v0.3.0/_modules/doctr/datasets/wildreceipt.html b/v0.3.0/_modules/doctr/datasets/wildreceipt.html index c543ee7cac..12c6aebd14 100644 --- a/v0.3.0/_modules/doctr/datasets/wildreceipt.html +++ b/v0.3.0/_modules/doctr/datasets/wildreceipt.html @@ -13,7 +13,7 @@ - + doctr.datasets.wildreceipt - docTR documentation @@ -454,7 +454,7 @@

    Source code for doctr.datasets.wildreceipt

         
       
    - + diff --git a/v0.3.0/_modules/doctr/documents/elements.html b/v0.3.0/_modules/doctr/documents/elements.html deleted file mode 100644 index 10c1e142d2..0000000000 --- a/v0.3.0/_modules/doctr/documents/elements.html +++ /dev/null @@ -1,577 +0,0 @@ - - - - - - - - - - - - doctr.documents.elements - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.elements

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import matplotlib.pyplot as plt
    -from typing import Tuple, Dict, List, Any, Optional, Union
    -
    -from doctr.utils.geometry import resolve_enclosing_bbox, resolve_enclosing_rbbox
    -from doctr.utils.visualization import visualize_page
    -from doctr.utils.common_types import BoundingBox, RotatedBbox
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['Element', 'Word', 'Artefact', 'Line', 'Block', 'Page', 'Document']
    -
    -
    -class Element(NestedObject):
    -    """Implements an abstract document element with exporting and text rendering capabilities"""
    -
    -    _exported_keys: List[str] = []
    -
    -    def __init__(self, **kwargs: Any) -> None:
    -        self._children_names: List[str] = []
    -        for k, v in kwargs.items():
    -            setattr(self, k, v)
    -            self._children_names.append(k)
    -
    -    def export(self) -> Dict[str, Any]:
    -        """Exports the object into a nested dict format"""
    -
    -        export_dict = {k: getattr(self, k) for k in self._exported_keys}
    -        for children_name in self._children_names:
    -            export_dict[children_name] = [c.export() for c in getattr(self, children_name)]
    -
    -        return export_dict
    -
    -    def render(self) -> str:
    -        raise NotImplementedError
    -
    -
    -
    -[docs] -class Word(Element): - """Implements a word element - - Args: - value: the text string of the word - confidence: the confidence associated with the text prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size - """ - - _exported_keys: List[str] = ["value", "confidence", "geometry"] - - def __init__(self, value: str, confidence: float, geometry: Union[BoundingBox, RotatedBbox]) -> None: - super().__init__() - self.value = value - self.confidence = confidence - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return self.value - - def extra_repr(self) -> str: - return f"value='{self.value}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Artefact(Element): - """Implements a non-textual element - - Args: - artefact_type: the type of artefact - confidence: the confidence of the type prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. - """ - - _exported_keys: List[str] = ["geometry", "type", "confidence"] - - def __init__(self, artefact_type: str, confidence: float, geometry: BoundingBox) -> None: - super().__init__() - self.geometry = geometry - self.type = artefact_type - self.confidence = confidence - - def render(self) -> str: - """Renders the full text of the element""" - return f"[{self.type.upper()}]" - - def extra_repr(self) -> str: - return f"type='{self.type}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Line(Element): - """Implements a line element as a collection of words - - Args: - words: list of word elements - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all words in it. - """ - - _exported_keys: List[str] = ["geometry"] - words: List[Word] = [] - - def __init__( - self, - words: List[Word], - geometry: Optional[Union[BoundingBox, RotatedBbox]] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - # Check whether this is a rotated or straight box - box_resolution_fn = resolve_enclosing_rbbox if len(words[0].geometry) == 5 else resolve_enclosing_bbox - geometry = box_resolution_fn([w.geometry for w in words]) # type: ignore[operator, misc] - - super().__init__(words=words) - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return " ".join(w.render() for w in self.words)
    - - - -
    -[docs] -class Block(Element): - """Implements a block element as a collection of lines and artefacts - - Args: - lines: list of line elements - artefacts: list of artefacts - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all lines and artefacts in it. - """ - - _exported_keys: List[str] = ["geometry"] - lines: List[Line] = [] - artefacts: List[Artefact] = [] - - def __init__( - self, - lines: List[Line] = [], - artefacts: List[Artefact] = [], - geometry: Optional[Union[BoundingBox, RotatedBbox]] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - line_boxes = [word.geometry for line in lines for word in line.words] - artefact_boxes = [artefact.geometry for artefact in artefacts] - box_resolution_fn = resolve_enclosing_rbbox if len(lines[0].geometry) == 5 else resolve_enclosing_bbox - geometry = box_resolution_fn(line_boxes + artefact_boxes) # type: ignore[operator, arg-type] - - super().__init__(lines=lines, artefacts=artefacts) - self.geometry = geometry - - def render(self, line_break: str = '\n') -> str: - """Renders the full text of the element""" - return line_break.join(line.render() for line in self.lines)
    - - - -
    -[docs] -class Page(Element): - """Implements a page element as a collection of blocks - - Args: - blocks: list of block elements - page_idx: the index of the page in the input raw document - dimensions: the page size in pixels in format (width, height) - orientation: a dictionary with the value of the rotation angle in degress and confidence of the prediction - language: a dictionary with the language value and confidence of the prediction - """ - - _exported_keys: List[str] = ["page_idx", "dimensions", "orientation", "language"] - blocks: List[Block] = [] - - def __init__( - self, - blocks: List[Block], - page_idx: int, - dimensions: Tuple[int, int], - orientation: Optional[Dict[str, Any]] = None, - language: Optional[Dict[str, Any]] = None, - ) -> None: - super().__init__(blocks=blocks) - self.page_idx = page_idx - self.dimensions = dimensions - self.orientation = orientation if isinstance(orientation, dict) else dict(value=None, confidence=None) - self.language = language if isinstance(language, dict) else dict(value=None, confidence=None) - - def render(self, block_break: str = '\n\n') -> str: - """Renders the full text of the element""" - return block_break.join(b.render() for b in self.blocks) - - def extra_repr(self) -> str: - return f"dimensions={self.dimensions}" - -
    -[docs] - def show( - self, page: np.ndarray, interactive: bool = True, **kwargs - ) -> None: - """Overlay the result on a given image - - Args: - page: image encoded as a numpy array in uint8 - interactive: whether the display should be interactive - """ - visualize_page(self.export(), page, interactive=interactive) - plt.show(**kwargs)
    -
    - - - -
    -[docs] -class Document(Element): - """Implements a document element as a collection of pages - - Args: - pages: list of page elements - """ - - pages: List[Page] = [] - - def __init__( - self, - pages: List[Page], - ) -> None: - super().__init__(pages=pages) - - def render(self, page_break: str = '\n\n\n\n') -> str: - """Renders the full text of the element""" - return page_break.join(p.render() for p in self.pages) - -
    -[docs] - def show(self, pages: List[np.ndarray], **kwargs) -> None: - """Overlay the result on a given image - - Args: - pages: list of images encoded as numpy arrays in uint8 - """ - for img, result in zip(pages, self.pages): - result.show(img, **kwargs)
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.0/_modules/doctr/documents/reader.html b/v0.3.0/_modules/doctr/documents/reader.html deleted file mode 100644 index cdcd814b6c..0000000000 --- a/v0.3.0/_modules/doctr/documents/reader.html +++ /dev/null @@ -1,612 +0,0 @@ - - - - - - - - - - - - doctr.documents.reader - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.reader

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import cv2
    -from pathlib import Path
    -import fitz
    -from weasyprint import HTML
    -from typing import List, Tuple, Optional, Any, Union, Sequence, Dict
    -
    -__all__ = ['read_pdf', 'read_img', 'read_html', 'DocumentFile', 'PDF']
    -
    -
    -AbstractPath = Union[str, Path]
    -AbstractFile = Union[AbstractPath, bytes]
    -Bbox = Tuple[float, float, float, float]
    -
    -
    -
    -[docs] -def read_img( - file: AbstractFile, - output_size: Optional[Tuple[int, int]] = None, - rgb_output: bool = True, -) -> np.ndarray: - """Read an image file into numpy format - - Example:: - >>> from doctr.documents import read_img - >>> page = read_img("path/to/your/doc.jpg") - - Args: - file: the path to the image file - output_size: the expected output size of each page in format H x W - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - Returns: - the page decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)): - if not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - img = cv2.imread(str(file), cv2.IMREAD_COLOR) - elif isinstance(file, bytes): - file = np.frombuffer(file, np.uint8) - img = cv2.imdecode(file, cv2.IMREAD_COLOR) - else: - raise TypeError("unsupported object type for argument 'file'") - - # Validity check - if img is None: - raise ValueError("unable to read file.") - # Resizing - if isinstance(output_size, tuple): - img = cv2.resize(img, output_size[::-1], interpolation=cv2.INTER_LINEAR) - # Switch the channel order - if rgb_output: - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - return img
    - - - -
    -[docs] -def read_pdf(file: AbstractFile, **kwargs: Any) -> fitz.Document: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_pdf - >>> doc = read_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)) and not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - - fitz_args: Dict[str, AbstractFile] = {} - - if isinstance(file, (str, Path)): - fitz_args['filename'] = file - elif isinstance(file, bytes): - fitz_args['stream'] = file - else: - raise TypeError("unsupported object type for argument 'file'") - - # Read pages with fitz and convert them to numpy ndarrays - return fitz.open(**fitz_args, filetype="pdf", **kwargs)
    - - - -def convert_page_to_numpy( - page: fitz.fitz.Page, - output_size: Optional[Tuple[int, int]] = None, - bgr_output: bool = False, - default_scales: Tuple[float, float] = (2, 2), -) -> np.ndarray: - """Convert a fitz page to a numpy-formatted image - - Args: - page: the page of a file read with PyMuPDF - output_size: the expected output size of each page in format H x W. Default goes to 840 x 595 for A4 pdf, - if you want to increase the resolution while preserving the original A4 aspect ratio can pass (1024, 726) - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - default_scales: spatial scaling to be applied when output_size is not specified where (1, 1) - corresponds to 72 dpi rendering. - - Returns: - the rendered image in numpy format - """ - - # If no output size is specified, keep the origin one - if output_size is not None: - scales = (output_size[1] / page.MediaBox[2], output_size[0] / page.MediaBox[3]) - else: - # Default 72 DPI (scales of (1, 1)) is unnecessarily low - scales = default_scales - - transform_matrix = fitz.Matrix(*scales) - - # Generate the pixel map using the transformation matrix - pixmap = page.getPixmap(matrix=transform_matrix) - # Decode it into a numpy - img = np.frombuffer(pixmap.samples, dtype=np.uint8).reshape(pixmap.height, pixmap.width, 3) - - # Switch the channel order - if bgr_output: - img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) - - return img - - -
    -[docs] -def read_html(url: str, **kwargs: Any) -> bytes: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_html - >>> doc = read_html("https://www.yoursite.com") - - Args: - url: URL of the target web page - Returns: - decoded PDF file as a bytes stream - """ - - return HTML(url, **kwargs).write_pdf()
    - - - -
    -[docs] -class PDF: - """PDF document template - - Args: - doc: input PDF document - """ - def __init__(self, doc: fitz.Document) -> None: - self.doc = doc - -
    -[docs] - def as_images(self, **kwargs) -> List[np.ndarray]: - """Convert all document pages to images - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images() - - Args: - kwargs: keyword arguments of `convert_page_to_numpy` - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - return [convert_page_to_numpy(page, **kwargs) for page in self.doc]
    - - - def get_page_words(self, idx, **kwargs) -> List[Tuple[Bbox, str]]: - """Get the annotations for all words of a given page""" - - # xmin, ymin, xmax, ymax, value, block_idx, line_idx, word_idx - return [(info[:4], info[4]) for info in self.doc[idx].getTextWords(**kwargs)] - -
    -[docs] - def get_words(self, **kwargs) -> List[List[Tuple[Bbox, str]]]: - """Get the annotations for all words in the document - - Example:: - >>> from doctr.documents import DocumentFile - >>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words() - - Args: - kwargs: keyword arguments of `fitz.Page.getTextWords` - Returns: - the list of pages annotations, represented as a list of tuple (bounding box, value) - """ - return [self.get_page_words(idx, **kwargs) for idx in range(len(self.doc))]
    - - - def get_page_artefacts(self, idx) -> List[Tuple[float, float, float, float]]: - return [tuple(self.doc[idx].getImageBbox(artefact)) # type: ignore[misc] - for artefact in self.doc[idx].get_images(full=True)] - -
    -[docs] - def get_artefacts(self) -> List[List[Tuple[float, float, float, float]]]: - """Get the artefacts for the entire document - - Example:: - >>> from doctr.documents import DocumentFile - >>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts() - - Returns: - the list of pages artefacts, represented as a list of bounding boxes - """ - - return [self.get_page_artefacts(idx) for idx in range(len(self.doc))]
    -
    - - - -
    -[docs] -class DocumentFile: - """Read a document from multiple extensions""" - -
    -[docs] - @classmethod - def from_pdf(cls, file: AbstractFile, **kwargs) -> PDF: - """Read a PDF file - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file or a binary stream - Returns: - a PDF document - """ - - doc = read_pdf(file, **kwargs) - - return PDF(doc)
    - - -
    -[docs] - @classmethod - def from_url(cls, url: str, **kwargs) -> PDF: - """Interpret a web page as a PDF document - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_url("https://www.yoursite.com") - - Args: - url: the URL of the target web page - Returns: - a PDF document - """ - pdf_stream = read_html(url) - return cls.from_pdf(pdf_stream, **kwargs)
    - - -
    -[docs] - @classmethod - def from_images(cls, files: Union[Sequence[AbstractFile], AbstractFile], **kwargs) -> List[np.ndarray]: - """Read an image file (or a collection of image files) and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"]) - - Args: - files: the path to the image file or a binary stream, or a collection of those - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - if isinstance(files, (str, Path, bytes)): - files = [files] - - return [read_img(file, **kwargs) for file in files]
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.0/_modules/doctr/io/elements.html b/v0.3.0/_modules/doctr/io/elements.html index 753a47455c..e049d6ce30 100644 --- a/v0.3.0/_modules/doctr/io/elements.html +++ b/v0.3.0/_modules/doctr/io/elements.html @@ -13,7 +13,7 @@ - + doctr.io.elements - docTR documentation @@ -1008,7 +1008,7 @@

    Source code for doctr.io.elements

         
       
    - + diff --git a/v0.3.0/_modules/doctr/io/html.html b/v0.3.0/_modules/doctr/io/html.html index 7ad5b97031..be73631500 100644 --- a/v0.3.0/_modules/doctr/io/html.html +++ b/v0.3.0/_modules/doctr/io/html.html @@ -13,7 +13,7 @@ - + doctr.io.html - docTR documentation @@ -360,7 +360,7 @@

    Source code for doctr.io.html

         
       
    - + diff --git a/v0.3.0/_modules/doctr/io/image/base.html b/v0.3.0/_modules/doctr/io/image/base.html index 336b4bff0e..a50c95d595 100644 --- a/v0.3.0/_modules/doctr/io/image/base.html +++ b/v0.3.0/_modules/doctr/io/image/base.html @@ -13,7 +13,7 @@ - + doctr.io.image.base - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.io.image.base

         
       
    - + diff --git a/v0.3.0/_modules/doctr/io/image/tensorflow.html b/v0.3.0/_modules/doctr/io/image/tensorflow.html index f1846820a3..3b9e731756 100644 --- a/v0.3.0/_modules/doctr/io/image/tensorflow.html +++ b/v0.3.0/_modules/doctr/io/image/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.io.image.tensorflow - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.io.image.tensorflow

         
       
    - + diff --git a/v0.3.0/_modules/doctr/io/pdf.html b/v0.3.0/_modules/doctr/io/pdf.html index e3abf6960b..e5b94811c3 100644 --- a/v0.3.0/_modules/doctr/io/pdf.html +++ b/v0.3.0/_modules/doctr/io/pdf.html @@ -13,7 +13,7 @@ - + doctr.io.pdf - docTR documentation @@ -377,7 +377,7 @@

    Source code for doctr.io.pdf

         
       
    - + diff --git a/v0.3.0/_modules/doctr/io/reader.html b/v0.3.0/_modules/doctr/io/reader.html index c1ddc26edd..d36e5bb553 100644 --- a/v0.3.0/_modules/doctr/io/reader.html +++ b/v0.3.0/_modules/doctr/io/reader.html @@ -13,7 +13,7 @@ - + doctr.io.reader - docTR documentation @@ -426,7 +426,7 @@

    Source code for doctr.io.reader

         
       
    - + diff --git a/v0.3.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html b/v0.3.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html index 9f074805c1..61a010d548 100644 --- a/v0.3.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html +++ b/v0.3.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.magc_resnet.tensorflow - docTR documentation @@ -531,7 +531,7 @@

    Source code for doctr.models.classification.magc_resnet.tensorflow

    - + diff --git a/v0.3.0/_modules/doctr/models/classification/mobilenet/tensorflow.html b/v0.3.0/_modules/doctr/models/classification/mobilenet/tensorflow.html index 6a63851276..7c448394ad 100644 --- a/v0.3.0/_modules/doctr/models/classification/mobilenet/tensorflow.html +++ b/v0.3.0/_modules/doctr/models/classification/mobilenet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.mobilenet.tensorflow - docTR documentation @@ -793,7 +793,7 @@

    Source code for doctr.models.classification.mobilenet.tensorflow

    - + diff --git a/v0.3.0/_modules/doctr/models/classification/resnet/tensorflow.html b/v0.3.0/_modules/doctr/models/classification/resnet/tensorflow.html index 095d377f31..aed4343741 100644 --- a/v0.3.0/_modules/doctr/models/classification/resnet/tensorflow.html +++ b/v0.3.0/_modules/doctr/models/classification/resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.resnet.tensorflow - docTR documentation @@ -749,7 +749,7 @@

    Source code for doctr.models.classification.resnet.tensorflow

    - + diff --git a/v0.3.0/_modules/doctr/models/classification/textnet/tensorflow.html b/v0.3.0/_modules/doctr/models/classification/textnet/tensorflow.html index ad254ebbfb..c5567d7d67 100644 --- a/v0.3.0/_modules/doctr/models/classification/textnet/tensorflow.html +++ b/v0.3.0/_modules/doctr/models/classification/textnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.textnet.tensorflow - docTR documentation @@ -611,7 +611,7 @@

    Source code for doctr.models.classification.textnet.tensorflow

    - + diff --git a/v0.3.0/_modules/doctr/models/classification/vgg/tensorflow.html b/v0.3.0/_modules/doctr/models/classification/vgg/tensorflow.html index 01ae452624..788111ae87 100644 --- a/v0.3.0/_modules/doctr/models/classification/vgg/tensorflow.html +++ b/v0.3.0/_modules/doctr/models/classification/vgg/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vgg.tensorflow - docTR documentation @@ -451,7 +451,7 @@

    Source code for doctr.models.classification.vgg.tensorflow

    - + diff --git a/v0.3.0/_modules/doctr/models/classification/vit/tensorflow.html b/v0.3.0/_modules/doctr/models/classification/vit/tensorflow.html index 1333cf6045..971ba5abe9 100644 --- a/v0.3.0/_modules/doctr/models/classification/vit/tensorflow.html +++ b/v0.3.0/_modules/doctr/models/classification/vit/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vit.tensorflow - docTR documentation @@ -533,7 +533,7 @@

    Source code for doctr.models.classification.vit.tensorflow

    - + diff --git a/v0.3.0/_modules/doctr/models/classification/zoo.html b/v0.3.0/_modules/doctr/models/classification/zoo.html index f7796a7522..3eb2a3ec4e 100644 --- a/v0.3.0/_modules/doctr/models/classification/zoo.html +++ b/v0.3.0/_modules/doctr/models/classification/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.classification.zoo - docTR documentation @@ -447,7 +447,7 @@

    Source code for doctr.models.classification.zoo

    <
    - + diff --git a/v0.3.0/_modules/doctr/models/detection/differentiable_binarization.html b/v0.3.0/_modules/doctr/models/detection/differentiable_binarization.html deleted file mode 100644 index 38e9b36ec2..0000000000 --- a/v0.3.0/_modules/doctr/models/detection/differentiable_binarization.html +++ /dev/null @@ -1,879 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.differentiable_binarization - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.differentiable_binarization

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -import cv2
    -from copy import deepcopy
    -import numpy as np
    -from shapely.geometry import Polygon
    -import pyclipper
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers
    -from typing import Union, List, Tuple, Optional, Any, Dict
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..utils import IntermediateLayerGetter, load_pretrained_params, conv_sequence
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['DBPostProcessor', 'DBNet', 'db_resnet50']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'db_resnet50': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'backbone': 'ResNet50',
    -        'fpn_layers': ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"],
    -        'fpn_channels': 128,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'DBPostProcessor',
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.2.0/db_resnet50-adcafc63.zip',
    -    },
    -}
    -
    -
    -class DBPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for DBNet adapted from the implementation of `xuannianz
    -    <https://github.com/xuannianz/DifferentiableBinarization>`_.
    -
    -    Args:
    -        unclip ratio: ratio used to unshrink polygons
    -        min_size_box: minimal length (pix) to keep a box
    -        max_candidates: maximum boxes to consider in a single page
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        unclip_ratio: Union[float, int] = 1.5,
    -        max_candidates: int = 1000,
    -        box_thresh: float = 0.1,
    -        bin_thresh: float = 0.3,
    -    ) -> None:
    -
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -        self.unclip_ratio = unclip_ratio
    -        self.max_candidates = max_candidates
    -
    -    def polygon_to_box(
    -        self,
    -        points: np.ndarray,
    -    ) -> Optional[Tuple[int, int, int, int]]:
    -        """Expand a polygon (points) by a factor unclip_ratio, and returns a 4-points box
    -
    -        Args:
    -            points: The first parameter.
    -
    -        Returns:
    -            a box in absolute coordinates (x, y, w, h)
    -        """
    -        poly = Polygon(points)
    -        distance = poly.area * self.unclip_ratio / poly.length  # compute distance to expand polygon
    -        offset = pyclipper.PyclipperOffset()
    -        offset.AddPath(points, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        _points = offset.Execute(distance)
    -        # Take biggest stack of points
    -        idx = 0
    -        if len(_points) > 1:
    -            max_size = 0
    -            for _idx, p in enumerate(_points):
    -                if len(p) > max_size:
    -                    idx = _idx
    -                    max_size = len(p)
    -            # We ensure that _points can be correctly casted to a ndarray
    -            _points = [_points[idx]]
    -        expanded_points = np.asarray(_points)  # expand polygon
    -        if len(expanded_points) < 1:
    -            return None
    -        x, y, w, h = cv2.boundingRect(expanded_points)  # compute a 4-points box from expanded polygon
    -        return x, y, w, h
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map
    -
    -        Args:
    -            pred: Pred map from differentiable binarization output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        # get contours from connected components on the bitmap
    -        contours, _ = cv2.findContours(bitmap.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    -        for contour in contours[:self.max_candidates]:
    -            # Check whether smallest enclosing bounding box is not too small
    -            if np.any(contour[:, 0].max(axis=0) - contour[:, 0].min(axis=0) < min_size_box):
    -                continue
    -            x, y, w, h = cv2.boundingRect(contour)
    -            points = np.array([[x, y], [x, y + h], [x + w, y + h], [x + w, y]])
    -            # Compute objectness
    -            score = self.box_score(pred, points)
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            _box = self.polygon_to_box(points)
    -
    -            if _box is None or _box[2] < min_size_box or _box[3] < min_size_box:  # remove to small boxes
    -                continue
    -            x, y, w, h = _box
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -class FeaturePyramidNetwork(layers.Layer, NestedObject):
    -    """Feature Pyramid Network as described in `"Feature Pyramid Networks for Object Detection"
    -    <https://arxiv.org/pdf/1612.03144.pdf>`_.
    -
    -    Args:
    -        channels: number of channel to output
    -    """
    -
    -    def __init__(
    -        self,
    -        channels: int,
    -    ) -> None:
    -        super().__init__()
    -        self.channels = channels
    -        self.upsample = layers.UpSampling2D(size=(2, 2), interpolation='nearest')
    -        self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer='he_normal') for _ in range(4)]
    -        self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2 ** idx) for idx in range(4)]
    -
    -    @staticmethod
    -    def build_upsampling(
    -        channels: int,
    -        dilation_factor: int = 1,
    -    ) -> layers.Layer:
    -        """Module which performs a 3x3 convolution followed by up-sampling
    -
    -        Args:
    -            channels: number of output channels
    -            dilation_factor (int): dilation factor to scale the convolution output before concatenation
    -
    -        Returns:
    -            a keras.layers.Layer object, wrapping these operations in a sequential module
    -
    -        """
    -
    -        _layers = conv_sequence(channels, 'relu', True, kernel_size=3)
    -
    -        if dilation_factor > 1:
    -            _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation='nearest'))
    -
    -        module = keras.Sequential(_layers)
    -
    -        return module
    -
    -    def extra_repr(self) -> str:
    -        return f"channels={self.channels}"
    -
    -    def call(
    -        self,
    -        x: List[tf.Tensor],
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # Channel mapping
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.inner_blocks, x)]
    -        # Upsample & sum
    -        for idx in range(len(results) - 1, -1):
    -            results[idx] += self.upsample(results[idx + 1])
    -        # Conv & upsample
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.layer_blocks, results)]
    -
    -        return layers.concatenate(results)
    -
    -
    -class DBNet(DetectionModel, NestedObject):
    -    """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization"
    -    <https://arxiv.org/pdf/1911.08947.pdf>`_.
    -
    -    Args:
    -        feature extractor: the backbone serving as feature extractor
    -        fpn_channels: number of channels each extracted feature maps is mapped to
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'fpn', 'probability_head', 'threshold_head', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: IntermediateLayerGetter,
    -        fpn_channels: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(cfg=cfg)
    -
    -        self.shrink_ratio = 0.4
    -        self.thresh_min = 0.3
    -        self.thresh_max = 0.7
    -        self.min_size_box = 3
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.fpn = FeaturePyramidNetwork(channels=fpn_channels)
    -        # Initialize kernels
    -        _inputs = [layers.Input(shape=in_shape[1:]) for in_shape in self.feat_extractor.output_shape]
    -        output_shape = tuple(self.fpn(_inputs).shape)
    -
    -        self.probability_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -        self.threshold_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -
    -        self.postprocessor = DBPostProcessor()
    -
    -    @staticmethod
    -    def compute_distance(
    -        xs: np.array,
    -        ys: np.array,
    -        a: np.array,
    -        b: np.array,
    -        eps: float = 1e-7,
    -    ) -> float:
    -        """Compute the distance for each point of the map (xs, ys) to the (a, b) segment
    -
    -        Args:
    -            xs : map of x coordinates (height, width)
    -            ys : map of y coordinates (height, width)
    -            a: first point defining the [ab] segment
    -            b: second point defining the [ab] segment
    -
    -        Returns:
    -            The computed distance
    -
    -        """
    -        square_dist_1 = np.square(xs - a[0]) + np.square(ys - a[1])
    -        square_dist_2 = np.square(xs - b[0]) + np.square(ys - b[1])
    -        square_dist = np.square(a[0] - b[0]) + np.square(a[1] - b[1])
    -        cosin = (square_dist - square_dist_1 - square_dist_2) / (2 * np.sqrt(square_dist_1 * square_dist_2) + eps)
    -        square_sin = 1 - np.square(cosin)
    -        square_sin = np.nan_to_num(square_sin)
    -        result = np.sqrt(square_dist_1 * square_dist_2 * square_sin / square_dist)
    -        result[cosin < 0] = np.sqrt(np.fmin(square_dist_1, square_dist_2))[cosin < 0]
    -        return result
    -
    -    def draw_thresh_map(
    -        self,
    -        polygon: np.array,
    -        canvas: np.array,
    -        mask: np.array,
    -    ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    -        """Draw a polygon treshold map on a canvas, as described in the DB paper
    -
    -        Args:
    -            polygon : array of coord., to draw the boundary of the polygon
    -            canvas : threshold map to fill with polygons
    -            mask : mask for training on threshold polygons
    -        """
    -        if polygon.ndim != 2 or polygon.shape[1] != 2:
    -            raise AttributeError("polygon should be a 2 dimensional array of coords")
    -
    -        # Augment polygon by shrink_ratio
    -        polygon_shape = Polygon(polygon)
    -        distance = polygon_shape.area * (1 - np.power(self.shrink_ratio, 2)) / polygon_shape.length
    -        subject = [tuple(coor) for coor in polygon]  # Get coord as list of tuples
    -        padding = pyclipper.PyclipperOffset()
    -        padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        padded_polygon = np.array(padding.Execute(distance)[0])
    -
    -        # Fill the mask with 1 on the new padded polygon
    -        cv2.fillPoly(mask, [padded_polygon.astype(np.int32)], 1.0)
    -
    -        # Get min/max to recover polygon after distance computation
    -        xmin = padded_polygon[:, 0].min()
    -        xmax = padded_polygon[:, 0].max()
    -        ymin = padded_polygon[:, 1].min()
    -        ymax = padded_polygon[:, 1].max()
    -        width = xmax - xmin + 1
    -        height = ymax - ymin + 1
    -        # Get absolute polygon for distance computation
    -        polygon[:, 0] = polygon[:, 0] - xmin
    -        polygon[:, 1] = polygon[:, 1] - ymin
    -        # Get absolute padded polygon
    -        xs = np.broadcast_to(np.linspace(0, width - 1, num=width).reshape(1, width), (height, width))
    -        ys = np.broadcast_to(np.linspace(0, height - 1, num=height).reshape(height, 1), (height, width))
    -
    -        # Compute distance map to fill the padded polygon
    -        distance_map = np.zeros((polygon.shape[0], height, width), dtype=np.float32)
    -        for i in range(polygon.shape[0]):
    -            j = (i + 1) % polygon.shape[0]
    -            absolute_distance = self.compute_distance(xs, ys, polygon[i], polygon[j])
    -            distance_map[i] = np.clip(absolute_distance / distance, 0, 1)
    -        distance_map = np.min(distance_map, axis=0)
    -
    -        # Clip the padded polygon inside the canvas
    -        xmin_valid = min(max(0, xmin), canvas.shape[1] - 1)
    -        xmax_valid = min(max(0, xmax), canvas.shape[1] - 1)
    -        ymin_valid = min(max(0, ymin), canvas.shape[0] - 1)
    -        ymax_valid = min(max(0, ymax), canvas.shape[0] - 1)
    -
    -        # Fill the canvas with the distances computed inside the valid padded polygon
    -        canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1] = np.fmax(
    -            1 - distance_map[
    -                ymin_valid - ymin:ymax_valid - ymin + 1,
    -                xmin_valid - xmin:xmax_valid - xmin + 1
    -            ],
    -            canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1]
    -        )
    -
    -        return polygon, canvas, mask
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor, tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.uint8)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -        thresh_target = np.zeros(output_shape, dtype=np.uint8)
    -        thresh_mask = np.ones(output_shape, dtype=np.uint8)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            polys = np.stack([
    -                abs_boxes[:, [0, 1]],
    -                abs_boxes[:, [0, 3]],
    -                abs_boxes[:, [2, 3]],
    -                abs_boxes[:, [2, 1]],
    -            ], axis=1)
    -
    -            for box, box_size, poly, is_ambiguous in zip(abs_boxes, boxes_size, polys, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -
    -                # Negative shrink for gt, as described in paper
    -                polygon = Polygon(poly)
    -                distance = polygon.area * (1 - np.power(self.shrink_ratio, 2)) / polygon.length
    -                subject = [tuple(coor) for coor in poly]
    -                padding = pyclipper.PyclipperOffset()
    -                padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -                shrinked = padding.Execute(-distance)
    -
    -                # Draw polygon on gt if it is valid
    -                if len(shrinked) == 0:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                shrinked = np.array(shrinked[0]).reshape(-1, 2)
    -                if shrinked.shape[0] <= 2 or not Polygon(shrinked).is_valid:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                cv2.fillPoly(seg_target[idx], [shrinked.astype(np.int32)], 1)
    -
    -                # Draw on both thresh map and thresh mask
    -                poly, thresh_target[idx], thresh_mask[idx] = self.draw_thresh_map(poly, thresh_target[idx],
    -                                                                                  thresh_mask[idx])
    -
    -        thresh_target = thresh_target.astype(np.float32) * (self.thresh_max - self.thresh_min) + self.thresh_min
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -        thresh_target = tf.convert_to_tensor(thresh_target, dtype=tf.float32)
    -        thresh_mask = tf.convert_to_tensor(thresh_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask, thresh_target, thresh_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        thresh_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts, masks, thresh_gts, thresh_masks from a list of boxes
    -        and a list of masks for each image. From there it computes the loss with the model output
    -
    -        Args:
    -            out_map: output feature map of the model of shape (N, H, W, C)
    -            thresh_map: threshold map of shape (N, H, W, C)
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -
    -        prob_map = tf.math.sigmoid(tf.squeeze(out_map, axis=[-1]))
    -        thresh_map = tf.math.sigmoid(tf.squeeze(thresh_map, axis=[-1]))
    -
    -        seg_target, seg_mask, thresh_target, thresh_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute balanced BCE loss for proba_map
    -        bce_scale = 5.
    -        bce_loss = tf.keras.losses.binary_crossentropy(seg_target[..., None], out_map, from_logits=True)[seg_mask]
    -
    -        neg_target = 1 - seg_target[seg_mask]
    -        positive_count = tf.math.reduce_sum(seg_target[seg_mask])
    -        negative_count = tf.math.reduce_min([tf.math.reduce_sum(neg_target), 3. * positive_count])
    -        negative_loss = bce_loss * neg_target
    -        negative_loss, _ = tf.nn.top_k(negative_loss, tf.cast(negative_count, tf.int32))
    -        sum_losses = tf.math.reduce_sum(bce_loss * seg_target[seg_mask]) + tf.math.reduce_sum(negative_loss)
    -        balanced_bce_loss = sum_losses / (positive_count + negative_count + 1e-6)
    -
    -        # Compute dice loss for approxbin_map
    -        bin_map = 1 / (1 + tf.exp(-50. * (prob_map[seg_mask] - thresh_map[seg_mask])))
    -
    -        bce_min = tf.math.reduce_min(bce_loss)
    -        weights = (bce_loss - bce_min) / (tf.math.reduce_max(bce_loss) - bce_min) + 1.
    -        inter = tf.math.reduce_sum(bin_map * seg_target[seg_mask] * weights)
    -        union = tf.math.reduce_sum(bin_map) + tf.math.reduce_sum(seg_target[seg_mask]) + 1e-8
    -        dice_loss = 1 - 2.0 * inter / union
    -
    -        # Compute l1 loss for thresh_map
    -        l1_scale = 10.
    -        if tf.reduce_any(thresh_mask):
    -            l1_loss = tf.math.reduce_mean(tf.math.abs(thresh_map[thresh_mask] - thresh_target[thresh_mask]))
    -        else:
    -            l1_loss = tf.constant(0.)
    -
    -        return l1_scale * l1_loss + bce_scale * balanced_bce_loss + dice_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        feat_maps = self.feat_extractor(x, **kwargs)
    -        feat_concat = self.fpn(feat_maps, **kwargs)
    -        logits = self.probability_head(feat_concat, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            thresh_map = self.threshold_head(feat_concat, **kwargs)
    -            loss = self.compute_loss(logits, thresh_map, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _db_resnet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> DBNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['fpn_channels'] = kwargs.get('fpn_channels', _cfg['fpn_channels'])
    -
    -    # Feature extractor
    -    resnet = tf.keras.applications.__dict__[_cfg['backbone']](
    -        include_top=False,
    -        weights=None,
    -        input_shape=_cfg['input_shape'],
    -        pooling=None,
    -    )
    -
    -    feat_extractor = IntermediateLayerGetter(
    -        resnet,
    -        _cfg['fpn_layers'],
    -    )
    -
    -    kwargs['fpn_channels'] = _cfg['fpn_channels']
    -
    -    # Build the model
    -    model = DBNet(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def db_resnet50(pretrained: bool = False, **kwargs: Any) -> DBNet: - """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" - <https://arxiv.org/pdf/1911.08947.pdf>`_, using a ResNet-50 backbone. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _db_resnet('db_resnet50', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html b/v0.3.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html index 9145c7c3fd..66cef8663d 100644 --- a/v0.3.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html +++ b/v0.3.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.differentiable_binarization.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     # Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
     
     from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers
    -from typing import List, Tuple, Optional, Any, Dict
    +from typing import Any, Dict, List, Optional, Tuple
     
    +import numpy as np
    +import tensorflow as tf
    +from tensorflow.keras import Model, Sequential, layers, losses
    +from tensorflow.keras.applications import ResNet50
    +
    +from doctr.file_utils import CLASS_NAME
    +from doctr.models.utils import (
    +    IntermediateLayerGetter,
    +    _bf16_to_float32,
    +    _build_model,
    +    conv_sequence,
    +    load_pretrained_params,
    +)
     from doctr.utils.repr import NestedObject
    -from doctr.models.utils import IntermediateLayerGetter, load_pretrained_params, conv_sequence
    +
    +from ...classification import mobilenet_v3_large
     from .base import DBPostProcessor, _DBNet
     
    -__all__ = ['DBNet', 'db_resnet50']
    +__all__ = ["DBNet", "db_resnet50", "db_mobilenet_v3_large"]
     
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'db_resnet50': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'backbone': 'ResNet50',
    -        'fpn_layers': ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"],
    -        'fpn_channels': 128,
    -        'input_shape': (1024, 1024, 3),
    -        'rotated_bbox': False,
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.2.0/db_resnet50-adcafc63.zip',
    +    "db_resnet50": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/db_resnet50-649fa22b.weights.h5&src=0",
    +    },
    +    "db_mobilenet_v3_large": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/db_mobilenet_v3_large-ee2e1dbe.weights.h5&src=0",
         },
     }
     
    @@ -313,6 +348,7 @@ 

    Source code for doctr.models.detection.differentiable_binarization.tensorflo <https://arxiv.org/pdf/1612.03144.pdf>`_. Args: + ---- channels: number of channel to output """ @@ -322,9 +358,9 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo ) -> None: super().__init__() self.channels = channels - self.upsample = layers.UpSampling2D(size=(2, 2), interpolation='nearest') - self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer='he_normal') for _ in range(4)] - self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2 ** idx) for idx in range(4)] + self.upsample = layers.UpSampling2D(size=(2, 2), interpolation="nearest") + self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer="he_normal") for _ in range(4)] + self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2**idx) for idx in range(4)] @staticmethod def build_upsampling( @@ -334,20 +370,21 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo """Module which performs a 3x3 convolution followed by up-sampling Args: + ---- channels: number of output channels dilation_factor (int): dilation factor to scale the convolution output before concatenation Returns: + ------- a keras.layers.Layer object, wrapping these operations in a sequential module """ - - _layers = conv_sequence(channels, 'relu', True, kernel_size=3) + _layers = conv_sequence(channels, "relu", True, kernel_size=3) if dilation_factor > 1: - _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation='nearest')) + _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation="nearest")) - module = keras.Sequential(_layers) + module = Sequential(_layers) return module @@ -359,7 +396,6 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo x: List[tf.Tensor], **kwargs: Any, ) -> tf.Tensor: - # Channel mapping results = [block(fmap, **kwargs) for block, fmap in zip(self.inner_blocks, x)] # Upsample & sum @@ -371,200 +407,324 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo return layers.concatenate(results) -class DBNet(_DBNet, keras.Model, NestedObject): +class DBNet(_DBNet, Model, NestedObject): """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" <https://arxiv.org/pdf/1911.08947.pdf>`_. Args: + ---- feature extractor: the backbone serving as feature extractor fpn_channels: number of channels each extracted feature maps is mapped to + bin_thresh: threshold for binarization + box_thresh: minimal objectness score to consider a box + assume_straight_pages: if True, fit straight bounding boxes only + exportable: onnx exportable returns only logits + cfg: the configuration dict of the model + class_names: list of class names """ - _children_names: List[str] = ['feat_extractor', 'fpn', 'probability_head', 'threshold_head', 'postprocessor'] + _children_names: List[str] = ["feat_extractor", "fpn", "probability_head", "threshold_head", "postprocessor"] def __init__( self, feature_extractor: IntermediateLayerGetter, - fpn_channels: int = 128, - rotated_bbox: bool = False, + fpn_channels: int = 128, # to be set to 256 to represent the author's initial idea + bin_thresh: float = 0.3, + box_thresh: float = 0.1, + assume_straight_pages: bool = True, + exportable: bool = False, cfg: Optional[Dict[str, Any]] = None, + class_names: List[str] = [CLASS_NAME], ) -> None: - super().__init__() + self.class_names = class_names + num_classes: int = len(self.class_names) self.cfg = cfg self.feat_extractor = feature_extractor - self.rotated_bbox = rotated_bbox + self.exportable = exportable + self.assume_straight_pages = assume_straight_pages self.fpn = FeaturePyramidNetwork(channels=fpn_channels) # Initialize kernels _inputs = [layers.Input(shape=in_shape[1:]) for in_shape in self.feat_extractor.output_shape] output_shape = tuple(self.fpn(_inputs).shape) - self.probability_head = keras.Sequential( - [ - *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]), - layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'), - layers.BatchNormalization(), - layers.Activation('relu'), - layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'), - ] + self.probability_head = Sequential([ + *conv_sequence(64, "relu", True, kernel_size=3, input_shape=output_shape[1:]), + layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer="he_normal"), + layers.BatchNormalization(), + layers.Activation("relu"), + layers.Conv2DTranspose(num_classes, 2, strides=2, kernel_initializer="he_normal"), + ]) + self.threshold_head = Sequential([ + *conv_sequence(64, "relu", True, kernel_size=3, input_shape=output_shape[1:]), + layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer="he_normal"), + layers.BatchNormalization(), + layers.Activation("relu"), + layers.Conv2DTranspose(num_classes, 2, strides=2, kernel_initializer="he_normal"), + ]) + + self.postprocessor = DBPostProcessor( + assume_straight_pages=assume_straight_pages, bin_thresh=bin_thresh, box_thresh=box_thresh ) - self.threshold_head = keras.Sequential( - [ - *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]), - layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'), - layers.BatchNormalization(), - layers.Activation('relu'), - layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'), - ] - ) - - self.postprocessor = DBPostProcessor(rotated_bbox=rotated_bbox) def compute_loss( self, out_map: tf.Tensor, thresh_map: tf.Tensor, - target: List[Dict[str, Any]] + target: List[Dict[str, np.ndarray]], + gamma: float = 2.0, + alpha: float = 0.5, + eps: float = 1e-8, ) -> tf.Tensor: """Compute a batch of gts, masks, thresh_gts, thresh_masks from a list of boxes and a list of masks for each image. From there it computes the loss with the model output Args: + ---- out_map: output feature map of the model of shape (N, H, W, C) thresh_map: threshold map of shape (N, H, W, C) target: list of dictionary where each dict has a `boxes` and a `flags` entry + gamma: modulating factor in the focal loss formula + alpha: balancing factor in the focal loss formula + eps: epsilon factor in dice loss Returns: + ------- A loss tensor """ + if gamma < 0: + raise ValueError("Value of gamma should be greater than or equal to zero.") - prob_map = tf.math.sigmoid(tf.squeeze(out_map, axis=[-1])) - thresh_map = tf.math.sigmoid(tf.squeeze(thresh_map, axis=[-1])) + prob_map = tf.math.sigmoid(out_map) + thresh_map = tf.math.sigmoid(thresh_map) - seg_target, seg_mask, thresh_target, thresh_mask = self.compute_target(target, out_map.shape[:3]) - seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32) + seg_target, seg_mask, thresh_target, thresh_mask = self.build_target(target, out_map.shape[1:], True) + seg_target = tf.convert_to_tensor(seg_target, dtype=out_map.dtype) seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool) - thresh_target = tf.convert_to_tensor(thresh_target, dtype=tf.float32) + seg_mask = tf.cast(seg_mask, tf.float32) + thresh_target = tf.convert_to_tensor(thresh_target, dtype=out_map.dtype) thresh_mask = tf.convert_to_tensor(thresh_mask, dtype=tf.bool) - # Compute balanced BCE loss for proba_map - bce_scale = 5. - bce_loss = tf.keras.losses.binary_crossentropy(seg_target[..., None], out_map, from_logits=True)[seg_mask] - - neg_target = 1 - seg_target[seg_mask] - positive_count = tf.math.reduce_sum(seg_target[seg_mask]) - negative_count = tf.math.reduce_min([tf.math.reduce_sum(neg_target), 3. * positive_count]) - negative_loss = bce_loss * neg_target - negative_loss, _ = tf.nn.top_k(negative_loss, tf.cast(negative_count, tf.int32)) - sum_losses = tf.math.reduce_sum(bce_loss * seg_target[seg_mask]) + tf.math.reduce_sum(negative_loss) - balanced_bce_loss = sum_losses / (positive_count + negative_count + 1e-6) - - # Compute dice loss for approxbin_map - bin_map = 1 / (1 + tf.exp(-50. * (prob_map[seg_mask] - thresh_map[seg_mask]))) - - bce_min = tf.math.reduce_min(bce_loss) - weights = (bce_loss - bce_min) / (tf.math.reduce_max(bce_loss) - bce_min) + 1. - inter = tf.math.reduce_sum(bin_map * seg_target[seg_mask] * weights) - union = tf.math.reduce_sum(bin_map) + tf.math.reduce_sum(seg_target[seg_mask]) + 1e-8 - dice_loss = 1 - 2.0 * inter / union + # Focal loss + focal_scale = 10.0 + bce_loss = losses.binary_crossentropy(seg_target[..., None], out_map[..., None], from_logits=True) + + # Convert logits to prob, compute gamma factor + p_t = (seg_target * prob_map) + ((1 - seg_target) * (1 - prob_map)) + alpha_t = seg_target * alpha + (1 - seg_target) * (1 - alpha) + # Unreduced loss + focal_loss = alpha_t * (1 - p_t) ** gamma * bce_loss + # Class reduced + focal_loss = tf.reduce_sum(seg_mask * focal_loss, (0, 1, 2, 3)) / tf.reduce_sum(seg_mask, (0, 1, 2, 3)) + + # Compute dice loss for each class or for approx binary_map + if len(self.class_names) > 1: + dice_map = tf.nn.softmax(out_map, axis=-1) + else: + # compute binary map instead + dice_map = 1.0 / (1.0 + tf.exp(-50 * (prob_map - thresh_map))) + # Class-reduced dice loss + inter = tf.reduce_sum(seg_mask * dice_map * seg_target, axis=[0, 1, 2]) + cardinality = tf.reduce_sum(seg_mask * (dice_map + seg_target), axis=[0, 1, 2]) + dice_loss = tf.reduce_mean(1 - 2 * inter / (cardinality + eps)) # Compute l1 loss for thresh_map - l1_scale = 10. if tf.reduce_any(thresh_mask): - l1_loss = tf.math.reduce_mean(tf.math.abs(thresh_map[thresh_mask] - thresh_target[thresh_mask])) + thresh_mask = tf.cast(thresh_mask, tf.float32) + l1_loss = tf.reduce_sum(tf.abs(thresh_map - thresh_target) * thresh_mask) / ( + tf.reduce_sum(thresh_mask) + eps + ) else: - l1_loss = tf.constant(0.) + l1_loss = tf.constant(0.0) - return l1_scale * l1_loss + bce_scale * balanced_bce_loss + dice_loss + return l1_loss + focal_scale * focal_loss + dice_loss def call( self, x: tf.Tensor, - target: Optional[List[Dict[str, Any]]] = None, + target: Optional[List[Dict[str, np.ndarray]]] = None, return_model_output: bool = False, - return_boxes: bool = False, + return_preds: bool = False, **kwargs: Any, ) -> Dict[str, Any]: - feat_maps = self.feat_extractor(x, **kwargs) feat_concat = self.fpn(feat_maps, **kwargs) logits = self.probability_head(feat_concat, **kwargs) out: Dict[str, tf.Tensor] = {} - if return_model_output or target is None or return_boxes: - prob_map = tf.math.sigmoid(logits) + if self.exportable: + out["logits"] = logits + return out + + if return_model_output or target is None or return_preds: + prob_map = _bf16_to_float32(tf.math.sigmoid(logits)) if return_model_output: out["out_map"] = prob_map - if target is None or return_boxes: - # Post-process boxes - out["preds"] = self.postprocessor(tf.squeeze(prob_map, axis=-1).numpy()) + if target is None or return_preds: + # Post-process boxes (keep only text predictions) + out["preds"] = [dict(zip(self.class_names, preds)) for preds in self.postprocessor(prob_map.numpy())] if target is not None: thresh_map = self.threshold_head(feat_concat, **kwargs) loss = self.compute_loss(logits, thresh_map, target) - out['loss'] = loss + out["loss"] = loss return out -def _db_resnet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> DBNet: +def _db_resnet( + arch: str, + pretrained: bool, + backbone_fn, + fpn_layers: List[str], + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> DBNet: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['fpn_channels'] = kwargs.get('fpn_channels', _cfg['fpn_channels']) - _cfg['rotated_bbox'] = kwargs.get('rotated_bbox', _cfg['rotated_bbox']) + _cfg["input_shape"] = input_shape or _cfg["input_shape"] + if not kwargs.get("class_names", None): + kwargs["class_names"] = _cfg.get("class_names", [CLASS_NAME]) + else: + kwargs["class_names"] = sorted(kwargs["class_names"]) # Feature extractor - resnet = tf.keras.applications.__dict__[_cfg['backbone']]( - include_top=False, - weights=None, - input_shape=_cfg['input_shape'], - pooling=None, + feat_extractor = IntermediateLayerGetter( + backbone_fn( + weights="imagenet" if pretrained_backbone else None, + include_top=False, + pooling=None, + input_shape=_cfg["input_shape"], + ), + fpn_layers, ) + # Build the model + model = DBNet(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) + + # Load pretrained parameters + if pretrained: + # The given class_names differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, + _cfg["url"], + skip_mismatch=kwargs["class_names"] != default_cfgs[arch].get("class_names", [CLASS_NAME]), + ) + + return model + + +def _db_mobilenet( + arch: str, + pretrained: bool, + backbone_fn, + fpn_layers: List[str], + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> DBNet: + pretrained_backbone = pretrained_backbone and not pretrained + + # Patch the config + _cfg = deepcopy(default_cfgs[arch]) + _cfg["input_shape"] = input_shape or _cfg["input_shape"] + if not kwargs.get("class_names", None): + kwargs["class_names"] = default_cfgs[arch].get("class_names", [CLASS_NAME]) + else: + kwargs["class_names"] = sorted(kwargs["class_names"]) + + # Feature extractor feat_extractor = IntermediateLayerGetter( - resnet, - _cfg['fpn_layers'], + backbone_fn( + input_shape=_cfg["input_shape"], + include_top=False, + pretrained=pretrained_backbone, + ), + fpn_layers, ) - kwargs['fpn_channels'] = _cfg['fpn_channels'] - kwargs['rotated_bbox'] = _cfg['rotated_bbox'] - # Build the model model = DBNet(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) # Load pretrained parameters if pretrained: - load_pretrained_params(model, _cfg['url']) + # The given class_names differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, + _cfg["url"], + skip_mismatch=kwargs["class_names"] != default_cfgs[arch].get("class_names", [CLASS_NAME]), + ) return model
    -[docs] +[docs] def db_resnet50(pretrained: bool = False, **kwargs: Any) -> DBNet: """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" <https://arxiv.org/pdf/1911.08947.pdf>`_, using a ResNet-50 backbone. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import db_resnet50 + >>> model = db_resnet50(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the DBNet architecture Returns: + ------- text detection architecture """ + return _db_resnet( + "db_resnet50", + pretrained, + ResNet50, + ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"], + **kwargs, + )
    + + + +
    +[docs] +def db_mobilenet_v3_large(pretrained: bool = False, **kwargs: Any) -> DBNet: + """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" + <https://arxiv.org/pdf/1911.08947.pdf>`_, using a mobilenet v3 large backbone. + + >>> import tensorflow as tf + >>> from doctr.models import db_mobilenet_v3_large + >>> model = db_mobilenet_v3_large(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) - return _db_resnet('db_resnet50', pretrained, **kwargs)
    + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the DBNet architecture + + Returns: + ------- + text detection architecture + """ + return _db_mobilenet( + "db_mobilenet_v3_large", + pretrained, + mobilenet_v3_large, + ["inverted_2", "inverted_5", "inverted_11", "final_block"], + **kwargs, + )

    @@ -598,8 +758,8 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo - - + + diff --git a/v0.3.0/_modules/doctr/models/detection/fast/tensorflow.html b/v0.3.0/_modules/doctr/models/detection/fast/tensorflow.html index 5b84d2dea1..65e1a77af8 100644 --- a/v0.3.0/_modules/doctr/models/detection/fast/tensorflow.html +++ b/v0.3.0/_modules/doctr/models/detection/fast/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.fast.tensorflow - docTR documentation @@ -769,7 +769,7 @@

    Source code for doctr.models.detection.fast.tensorflow

    - + diff --git a/v0.3.0/_modules/doctr/models/detection/linknet.html b/v0.3.0/_modules/doctr/models/detection/linknet.html deleted file mode 100644 index 129cfdce8b..0000000000 --- a/v0.3.0/_modules/doctr/models/detection/linknet.html +++ /dev/null @@ -1,644 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.linknet - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.linknet

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -import numpy as np
    -import cv2
    -from tensorflow.keras import layers, Sequential
    -from typing import Dict, Any, Tuple, Optional, List
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..backbones import ResnetStage
    -from ..utils import conv_sequence, load_pretrained_params
    -from ...utils.repr import NestedObject
    -
    -__all__ = ['LinkNet', 'linknet', 'LinkNetPostProcessor']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'linknet': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'out_chan': 1,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'LinkNetPostProcessor',
    -        'url': None,
    -    },
    -}
    -
    -
    -class LinkNetPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for LinkNet model.
    -
    -    Args:
    -        min_size_box: minimal length (pix) to keep a box
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        min_size_box: int = 3,
    -        bin_thresh: float = 0.15,
    -        box_thresh: float = 0.1,
    -    ) -> None:
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map: find connected components then filter boxes
    -
    -        Args:
    -            pred: Pred map from differentiable linknet output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        label_num, labelimage = cv2.connectedComponents(bitmap.astype(np.uint8), connectivity=4)
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        for label in range(1, label_num + 1):
    -            points = np.array(np.where(labelimage == label)[::-1]).T
    -            if points.shape[0] < 4:  # remove polygons with 3 points or less
    -                continue
    -            score = self.box_score(pred, points.reshape(-1, 2))
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            x, y, w, h = cv2.boundingRect(points)
    -            if min(w, h) < min_size_box:  # filter too small boxes
    -                continue
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -def decoder_block(in_chan: int, out_chan: int) -> Sequential:
    -    """Creates a LinkNet decoder block"""
    -
    -    return Sequential([
    -        *conv_sequence(in_chan // 4, 'relu', True, kernel_size=1),
    -        layers.Conv2DTranspose(
    -            filters=in_chan // 4,
    -            kernel_size=3,
    -            strides=2,
    -            padding="same",
    -            use_bias=False,
    -            kernel_initializer='he_normal'
    -        ),
    -        layers.BatchNormalization(),
    -        layers.Activation('relu'),
    -        *conv_sequence(out_chan, 'relu', True, kernel_size=1),
    -    ])
    -
    -
    -class LinkNetFPN(layers.Layer, NestedObject):
    -    """LinkNet Encoder-Decoder module
    -
    -    """
    -
    -    def __init__(
    -        self,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.encoder_1 = ResnetStage(num_blocks=2, output_channels=64, downsample=True)
    -        self.encoder_2 = ResnetStage(num_blocks=2, output_channels=128, downsample=True)
    -        self.encoder_3 = ResnetStage(num_blocks=2, output_channels=256, downsample=True)
    -        self.encoder_4 = ResnetStage(num_blocks=2, output_channels=512, downsample=True)
    -        self.decoder_1 = decoder_block(in_chan=64, out_chan=64)
    -        self.decoder_2 = decoder_block(in_chan=128, out_chan=64)
    -        self.decoder_3 = decoder_block(in_chan=256, out_chan=128)
    -        self.decoder_4 = decoder_block(in_chan=512, out_chan=256)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor
    -    ) -> tf.Tensor:
    -        x_1 = self.encoder_1(x)
    -        x_2 = self.encoder_2(x_1)
    -        x_3 = self.encoder_3(x_2)
    -        x_4 = self.encoder_4(x_3)
    -        y_4 = self.decoder_4(x_4)
    -        y_3 = self.decoder_3(y_4 + x_3)
    -        y_2 = self.decoder_2(y_3 + x_2)
    -        y_1 = self.decoder_1(y_2 + x_1)
    -        return y_1
    -
    -
    -class LinkNet(DetectionModel, NestedObject):
    -    """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation"
    -    <https://arxiv.org/pdf/1707.03718.pdf>`_.
    -
    -    Args:
    -        out_chan: number of channels for the output
    -    """
    -
    -    _children_names: List[str] = ['stem', 'fpn', 'classifier', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        out_chan: int = 1,
    -        input_shape: Tuple[int, int, int] = (512, 512, 3),
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(cfg=cfg)
    -
    -        self.stem = Sequential([
    -            *conv_sequence(64, 'relu', True, strides=2, kernel_size=7, input_shape=input_shape),
    -            layers.MaxPool2D(pool_size=(3, 3), strides=2, padding='same'),
    -        ])
    -
    -        self.fpn = LinkNetFPN()
    -
    -        self.classifier = Sequential([
    -            layers.Conv2DTranspose(
    -                filters=32,
    -                kernel_size=3,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -            layers.BatchNormalization(),
    -            layers.Activation('relu'),
    -            *conv_sequence(32, 'relu', True, strides=1, kernel_size=3),
    -            layers.Conv2DTranspose(
    -                filters=out_chan,
    -                kernel_size=2,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -        ])
    -
    -        self.min_size_box = 3
    -
    -        self.postprocessor = LinkNetPostProcessor()
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.bool)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            for box, box_size, is_ambiguous in zip(abs_boxes, boxes_size, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Fill polygon with 1
    -                seg_target[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = True
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts and masks from a list of boxes and a list of masks for each image
    -        Then, it computes the loss function with proba_map, gts and masks
    -
    -        Args:
    -            out_map: output feature map of the model of shape N x H x W x 1
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -        seg_target, seg_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute BCE loss
    -        return tf.math.reduce_mean(tf.keras.losses.binary_crossentropy(
    -            seg_target[seg_mask],
    -            tf.squeeze(out_map, axis=[-1])[seg_mask],
    -            from_logits=True
    -        ))
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        logits = self.stem(x)
    -        logits = self.fpn(logits)
    -        logits = self.classifier(logits)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            loss = self.compute_loss(logits, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _linknet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> LinkNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['out_chan'] = kwargs.get('out_chan', _cfg['out_chan'])
    -
    -    kwargs['out_chan'] = _cfg['out_chan']
    -    kwargs['input_shape'] = _cfg['input_shape']
    -    # Build the model
    -    model = LinkNet(cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def linknet(pretrained: bool = False, **kwargs: Any) -> LinkNet: - """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" - <https://arxiv.org/pdf/1707.03718.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import linknet - >>> model = linknet(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _linknet('linknet', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.0/_modules/doctr/models/detection/linknet/tensorflow.html b/v0.3.0/_modules/doctr/models/detection/linknet/tensorflow.html index cd4f446673..ce995f99d4 100644 --- a/v0.3.0/_modules/doctr/models/detection/linknet/tensorflow.html +++ b/v0.3.0/_modules/doctr/models/detection/linknet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.linknet.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.detection.linknet.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     # Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
     
     from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers, Sequential
    -from typing import Dict, Any, Tuple, Optional, List
    +from typing import Any, Dict, List, Optional, Tuple
     
    +import numpy as np
    +import tensorflow as tf
    +from tensorflow.keras import Model, Sequential, layers, losses
    +
    +from doctr.file_utils import CLASS_NAME
    +from doctr.models.classification import resnet18, resnet34, resnet50
    +from doctr.models.utils import (
    +    IntermediateLayerGetter,
    +    _bf16_to_float32,
    +    _build_model,
    +    conv_sequence,
    +    load_pretrained_params,
    +)
     from doctr.utils.repr import NestedObject
    -from doctr.models.backbones import ResnetStage
    -from doctr.models.utils import conv_sequence, load_pretrained_params
    -from .base import LinkNetPostProcessor, _LinkNet
     
    -__all__ = ['LinkNet', 'linknet16']
    +from .base import LinkNetPostProcessor, _LinkNet
     
    +__all__ = ["LinkNet", "linknet_resnet18", "linknet_resnet34", "linknet_resnet50"]
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'linknet16': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'num_classes': 1,
    -        'input_shape': (1024, 1024, 3),
    -        'rotated_bbox': False,
    -        'url': None,
    +    "linknet_resnet18": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/linknet_resnet18-615a82c5.weights.h5&src=0",
    +    },
    +    "linknet_resnet34": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/linknet_resnet34-9d772be5.weights.h5&src=0",
    +    },
    +    "linknet_resnet50": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/linknet_resnet50-6bf6c8b5.weights.h5&src=0",
         },
     }
     
     
    -def decoder_block(in_chan: int, out_chan: int) -> Sequential:
    +def decoder_block(in_chan: int, out_chan: int, stride: int, **kwargs: Any) -> Sequential:
         """Creates a LinkNet decoder block"""
    -
         return Sequential([
    -        *conv_sequence(in_chan // 4, 'relu', True, kernel_size=1),
    +        *conv_sequence(in_chan // 4, "relu", True, kernel_size=1, **kwargs),
             layers.Conv2DTranspose(
                 filters=in_chan // 4,
                 kernel_size=3,
    -            strides=2,
    +            strides=stride,
                 padding="same",
                 use_bias=False,
    -            kernel_initializer='he_normal'
    +            kernel_initializer="he_normal",
             ),
             layers.BatchNormalization(),
    -        layers.Activation('relu'),
    -        *conv_sequence(out_chan, 'relu', True, kernel_size=1),
    +        layers.Activation("relu"),
    +        *conv_sequence(out_chan, "relu", True, kernel_size=1),
         ])
     
     
    -class LinkNetFPN(layers.Layer, NestedObject):
    -    """LinkNet Encoder-Decoder module"""
    +class LinkNetFPN(Model, NestedObject):
    +    """LinkNet Decoder module"""
     
         def __init__(
             self,
    +        out_chans: int,
    +        in_shapes: List[Tuple[int, ...]],
         ) -> None:
    -
             super().__init__()
    -        self.encoder_1 = ResnetStage(num_blocks=2, output_channels=64, downsample=True)
    -        self.encoder_2 = ResnetStage(num_blocks=2, output_channels=128, downsample=True)
    -        self.encoder_3 = ResnetStage(num_blocks=2, output_channels=256, downsample=True)
    -        self.encoder_4 = ResnetStage(num_blocks=2, output_channels=512, downsample=True)
    -        self.decoder_1 = decoder_block(in_chan=64, out_chan=64)
    -        self.decoder_2 = decoder_block(in_chan=128, out_chan=64)
    -        self.decoder_3 = decoder_block(in_chan=256, out_chan=128)
    -        self.decoder_4 = decoder_block(in_chan=512, out_chan=256)
    +        self.out_chans = out_chans
    +        strides = [2] * (len(in_shapes) - 1) + [1]
    +        i_chans = [s[-1] for s in in_shapes[::-1]]
    +        o_chans = i_chans[1:] + [out_chans]
    +        self.decoders = [
    +            decoder_block(in_chan, out_chan, s, input_shape=in_shape)
    +            for in_chan, out_chan, s, in_shape in zip(i_chans, o_chans, strides, in_shapes[::-1])
    +        ]
    +
    +    def call(self, x: List[tf.Tensor], **kwargs: Any) -> tf.Tensor:
    +        out = 0
    +        for decoder, fmap in zip(self.decoders, x[::-1]):
    +            out = decoder(out + fmap, **kwargs)
    +        return out
     
    -    def call(
    -        self,
    -        x: tf.Tensor
    -    ) -> tf.Tensor:
    -        x_1 = self.encoder_1(x)
    -        x_2 = self.encoder_2(x_1)
    -        x_3 = self.encoder_3(x_2)
    -        x_4 = self.encoder_4(x_3)
    -        y_4 = self.decoder_4(x_4)
    -        y_3 = self.decoder_3(y_4 + x_3)
    -        y_2 = self.decoder_2(y_3 + x_2)
    -        y_1 = self.decoder_1(y_2 + x_1)
    -        return y_1
    -
    -
    -class LinkNet(_LinkNet, keras.Model):
    +    def extra_repr(self) -> str:
    +        return f"out_chans={self.out_chans}"
    +
    +
    +class LinkNet(_LinkNet, Model):
         """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation"
         <https://arxiv.org/pdf/1707.03718.pdf>`_.
     
         Args:
    -        num_classes: number of channels for the output
    +    ----
    +        feature extractor: the backbone serving as feature extractor
    +        fpn_channels: number of channels each extracted feature maps is mapped to
    +        bin_thresh: threshold for binarization of the output feature map
    +        box_thresh: minimal objectness score to consider a box
    +        assume_straight_pages: if True, fit straight bounding boxes only
    +        exportable: onnx exportable returns only logits
    +        cfg: the configuration dict of the model
    +        class_names: list of class names
         """
     
    -    _children_names: List[str] = ['stem', 'fpn', 'classifier', 'postprocessor']
    +    _children_names: List[str] = ["feat_extractor", "fpn", "classifier", "postprocessor"]
     
         def __init__(
             self,
    -        num_classes: int = 1,
    -        input_shape: Tuple[int, int, int] = (512, 512, 3),
    -        rotated_bbox: bool = False,
    +        feat_extractor: IntermediateLayerGetter,
    +        fpn_channels: int = 64,
    +        bin_thresh: float = 0.1,
    +        box_thresh: float = 0.1,
    +        assume_straight_pages: bool = True,
    +        exportable: bool = False,
             cfg: Optional[Dict[str, Any]] = None,
    +        class_names: List[str] = [CLASS_NAME],
         ) -> None:
             super().__init__(cfg=cfg)
     
    -        self.rotated_bbox = rotated_bbox
    +        self.class_names = class_names
    +        num_classes: int = len(self.class_names)
     
    -        self.stem = Sequential([
    -            *conv_sequence(64, 'relu', True, strides=2, kernel_size=7, input_shape=input_shape),
    -            layers.MaxPool2D(pool_size=(3, 3), strides=2, padding='same'),
    -        ])
    +        self.exportable = exportable
    +        self.assume_straight_pages = assume_straight_pages
    +
    +        self.feat_extractor = feat_extractor
     
    -        self.fpn = LinkNetFPN()
    +        self.fpn = LinkNetFPN(fpn_channels, [_shape[1:] for _shape in self.feat_extractor.output_shape])
    +        self.fpn.build(self.feat_extractor.output_shape)
     
             self.classifier = Sequential([
                 layers.Conv2DTranspose(
    @@ -393,154 +442,246 @@ 

    Source code for doctr.models.detection.linknet.tensorflow

    strides=2, padding="same", use_bias=False, - kernel_initializer='he_normal' + kernel_initializer="he_normal", + input_shape=self.fpn.decoders[-1].output_shape[1:], ), layers.BatchNormalization(), - layers.Activation('relu'), - *conv_sequence(32, 'relu', True, strides=1, kernel_size=3), + layers.Activation("relu"), + *conv_sequence(32, "relu", True, kernel_size=3, strides=1), layers.Conv2DTranspose( filters=num_classes, kernel_size=2, strides=2, padding="same", - use_bias=False, - kernel_initializer='he_normal' + use_bias=True, + kernel_initializer="he_normal", ), ]) - self.postprocessor = LinkNetPostProcessor(rotated_bbox=rotated_bbox) + self.postprocessor = LinkNetPostProcessor( + assume_straight_pages=assume_straight_pages, bin_thresh=bin_thresh, box_thresh=box_thresh + ) def compute_loss( self, out_map: tf.Tensor, - target: List[Dict[str, Any]], - focal_loss: bool = False, - alpha: float = .5, - gamma: float = 2., - edge_factor: float = 2., + target: List[Dict[str, np.ndarray]], + gamma: float = 2.0, + alpha: float = 0.5, + eps: float = 1e-8, ) -> tf.Tensor: """Compute linknet loss, BCE with boosted box edges or focal loss. Focal loss implementation based on <https://github.com/tensorflow/addons/>`_. Args: + ---- out_map: output feature map of the model of shape N x H x W x 1 target: list of dictionary where each dict has a `boxes` and a `flags` entry - focal_loss: if True, use focal loss instead of BCE - edge_factor: boost factor for box edges (in case of BCE) + gamma: modulating factor in the focal loss formula alpha: balancing factor in the focal loss formula - gammma: modulating factor in the focal loss formula + eps: epsilon factor in dice loss Returns: + ------- A loss tensor """ - seg_target, seg_mask, edge_mask = self.compute_target(target, out_map.shape[:3]) - seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32) - edge_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool) + seg_target, seg_mask = self.build_target(target, out_map.shape[1:], True) + seg_target = tf.convert_to_tensor(seg_target, dtype=out_map.dtype) seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool) - - # Get the cross_entropy for each entry - bce = tf.keras.losses.binary_crossentropy( - seg_target[seg_mask], - tf.squeeze(out_map, axis=[-1])[seg_mask], - from_logits=True) - - if focal_loss: - if gamma and gamma < 0: - raise ValueError("Value of gamma should be greater than or equal to zero.") - - # Convert logits to prob, compute gamma factor - pred_prob = tf.sigmoid(tf.squeeze(out_map, axis=[-1])[seg_mask]) - p_t = (seg_target[seg_mask] * pred_prob) + ((1 - seg_target[seg_mask]) * (1 - pred_prob)) - modulating_factor = tf.pow((1.0 - p_t), gamma) - - # Compute alpha factor - alpha_factor = seg_target[seg_mask] * alpha + (1 - seg_target[seg_mask]) * (1 - alpha) - - # compute the final loss - loss = tf.reduce_mean(alpha_factor * modulating_factor * bce) - - else: - # Compute BCE loss with highlighted edges - loss = tf.math.multiply( - 1 + (edge_factor - 1) * tf.cast(edge_mask, tf.float32), - bce - ) - loss = tf.reduce_mean(loss) - - return loss + seg_mask = tf.cast(seg_mask, tf.float32) + + bce_loss = losses.binary_crossentropy(seg_target[..., None], out_map[..., None], from_logits=True) + proba_map = tf.sigmoid(out_map) + + # Focal loss + if gamma < 0: + raise ValueError("Value of gamma should be greater than or equal to zero.") + # Convert logits to prob, compute gamma factor + p_t = (seg_target * proba_map) + ((1 - seg_target) * (1 - proba_map)) + alpha_t = seg_target * alpha + (1 - seg_target) * (1 - alpha) + # Unreduced loss + focal_loss = alpha_t * (1 - p_t) ** gamma * bce_loss + # Class reduced + focal_loss = tf.reduce_sum(seg_mask * focal_loss, (0, 1, 2, 3)) / tf.reduce_sum(seg_mask, (0, 1, 2, 3)) + + # Compute dice loss for each class + dice_map = tf.nn.softmax(out_map, axis=-1) if len(self.class_names) > 1 else proba_map + # Class-reduced dice loss + inter = tf.reduce_sum(seg_mask * dice_map * seg_target, axis=[0, 1, 2]) + cardinality = tf.reduce_sum(seg_mask * (dice_map + seg_target), axis=[0, 1, 2]) + dice_loss = tf.reduce_mean(1 - 2 * inter / (cardinality + eps)) + + return focal_loss + dice_loss def call( self, x: tf.Tensor, - target: Optional[List[Dict[str, Any]]] = None, + target: Optional[List[Dict[str, np.ndarray]]] = None, return_model_output: bool = False, - return_boxes: bool = False, - focal_loss: bool = True, + return_preds: bool = False, **kwargs: Any, ) -> Dict[str, Any]: - - logits = self.stem(x) - logits = self.fpn(logits) - logits = self.classifier(logits) + feat_maps = self.feat_extractor(x, **kwargs) + logits = self.fpn(feat_maps, **kwargs) + logits = self.classifier(logits, **kwargs) out: Dict[str, tf.Tensor] = {} - if return_model_output or target is None or return_boxes: - prob_map = tf.math.sigmoid(logits) + if self.exportable: + out["logits"] = logits + return out + + if return_model_output or target is None or return_preds: + prob_map = _bf16_to_float32(tf.math.sigmoid(logits)) + if return_model_output: out["out_map"] = prob_map - if target is None or return_boxes: + if target is None or return_preds: # Post-process boxes - out["preds"] = self.postprocessor(tf.squeeze(prob_map, axis=-1).numpy()) + out["preds"] = [dict(zip(self.class_names, preds)) for preds in self.postprocessor(prob_map.numpy())] if target is not None: - loss = self.compute_loss(logits, target, focal_loss) - out['loss'] = loss + loss = self.compute_loss(logits, target) + out["loss"] = loss return out -def _linknet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> LinkNet: +def _linknet( + arch: str, + pretrained: bool, + backbone_fn, + fpn_layers: List[str], + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> LinkNet: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['num_classes'] = kwargs.get('num_classes', _cfg['num_classes']) - _cfg['rotated_bbox'] = kwargs.get('rotated_bbox', _cfg['rotated_bbox']) + _cfg["input_shape"] = input_shape or default_cfgs[arch]["input_shape"] + if not kwargs.get("class_names", None): + kwargs["class_names"] = _cfg.get("class_names", [CLASS_NAME]) + else: + kwargs["class_names"] = sorted(kwargs["class_names"]) + + # Feature extractor + feat_extractor = IntermediateLayerGetter( + backbone_fn( + pretrained=pretrained_backbone, + include_top=False, + input_shape=_cfg["input_shape"], + ), + fpn_layers, + ) - kwargs['num_classes'] = _cfg['num_classes'] - kwargs['input_shape'] = _cfg['input_shape'] - kwargs['rotated_bbox'] = _cfg['rotated_bbox'] # Build the model - model = LinkNet(cfg=_cfg, **kwargs) + model = LinkNet(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) + # Load pretrained parameters if pretrained: - load_pretrained_params(model, _cfg['url']) + # The given class_names differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, + _cfg["url"], + skip_mismatch=kwargs["class_names"] != default_cfgs[arch].get("class_names", [CLASS_NAME]), + ) return model -
    -[docs] -def linknet16(pretrained: bool = False, **kwargs: Any) -> LinkNet: +
    +[docs] +def linknet_resnet18(pretrained: bool = False, **kwargs: Any) -> LinkNet: + """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" + <https://arxiv.org/pdf/1707.03718.pdf>`_. + + >>> import tensorflow as tf + >>> from doctr.models import linknet_resnet18 + >>> model = linknet_resnet18(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the LinkNet architecture + + Returns: + ------- + text detection architecture + """ + return _linknet( + "linknet_resnet18", + pretrained, + resnet18, + ["resnet_block_1", "resnet_block_3", "resnet_block_5", "resnet_block_7"], + **kwargs, + )
    + + + +
    +[docs] +def linknet_resnet34(pretrained: bool = False, **kwargs: Any) -> LinkNet: """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" <https://arxiv.org/pdf/1707.03718.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import linknet16 - >>> model = linknet16(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import linknet_resnet34 + >>> model = linknet_resnet34(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the LinkNet architecture Returns: + ------- text detection architecture """ + return _linknet( + "linknet_resnet34", + pretrained, + resnet34, + ["resnet_block_2", "resnet_block_6", "resnet_block_12", "resnet_block_15"], + **kwargs, + )
    + + + +
    +[docs] +def linknet_resnet50(pretrained: bool = False, **kwargs: Any) -> LinkNet: + """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" + <https://arxiv.org/pdf/1707.03718.pdf>`_. + + >>> import tensorflow as tf + >>> from doctr.models import linknet_resnet50 + >>> model = linknet_resnet50(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the LinkNet architecture - return _linknet('linknet16', pretrained, **kwargs)
    + Returns: + ------- + text detection architecture + """ + return _linknet( + "linknet_resnet50", + pretrained, + resnet50, + ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"], + **kwargs, + )
    @@ -574,8 +715,8 @@

    Source code for doctr.models.detection.linknet.tensorflow

    - +
    + diff --git a/v0.3.0/_modules/doctr/models/detection/zoo.html b/v0.3.0/_modules/doctr/models/detection/zoo.html index d3128b8d14..3651c4e2d3 100644 --- a/v0.3.0/_modules/doctr/models/detection/zoo.html +++ b/v0.3.0/_modules/doctr/models/detection/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.detection.zoo - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.detection.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -from typing import Any
    +from typing import Any, List
     
     from doctr.file_utils import is_tf_available, is_torch_available
    -from .core import DetectionPredictor
    -from ..preprocessor import PreProcessor
    -from .. import detection
     
    +from .. import detection
    +from ..detection.fast import reparameterize
    +from ..preprocessor import PreProcessor
    +from .predictor import DetectionPredictor
     
     __all__ = ["detection_predictor"]
     
    +ARCHS: List[str]
    +
     
     if is_tf_available():
    -    ARCHS = ['db_resnet50', 'linknet16']
    +    ARCHS = [
    +        "db_resnet50",
    +        "db_mobilenet_v3_large",
    +        "linknet_resnet18",
    +        "linknet_resnet34",
    +        "linknet_resnet50",
    +        "fast_tiny",
    +        "fast_small",
    +        "fast_base",
    +    ]
     elif is_torch_available():
    -    ARCHS = ['db_resnet34', 'db_resnet50', 'db_mobilenet_v3', 'linknet16']
    +    ARCHS = [
    +        "db_resnet34",
    +        "db_resnet50",
    +        "db_mobilenet_v3_large",
    +        "linknet_resnet18",
    +        "linknet_resnet34",
    +        "linknet_resnet50",
    +        "fast_tiny",
    +        "fast_small",
    +        "fast_base",
    +    ]
    +
     
    +def _predictor(arch: Any, pretrained: bool, assume_straight_pages: bool = True, **kwargs: Any) -> DetectionPredictor:
    +    if isinstance(arch, str):
    +        if arch not in ARCHS:
    +            raise ValueError(f"unknown architecture '{arch}'")
     
    -def _predictor(arch: str, pretrained: bool, **kwargs: Any) -> DetectionPredictor:
    +        _model = detection.__dict__[arch](
    +            pretrained=pretrained,
    +            pretrained_backbone=kwargs.get("pretrained_backbone", True),
    +            assume_straight_pages=assume_straight_pages,
    +        )
    +        # Reparameterize FAST models by default to lower inference latency and memory usage
    +        if isinstance(_model, detection.FAST):
    +            _model = reparameterize(_model)
    +    else:
    +        if not isinstance(arch, (detection.DBNet, detection.LinkNet, detection.FAST)):
    +            raise ValueError(f"unknown architecture: {type(arch)}")
     
    -    if arch not in ARCHS:
    -        raise ValueError(f"unknown architecture '{arch}'")
    +        _model = arch
    +        _model.assume_straight_pages = assume_straight_pages
    +        _model.postprocessor.assume_straight_pages = assume_straight_pages
     
    -    # Detection
    -    _model = detection.__dict__[arch](pretrained=pretrained)
    -    kwargs['mean'] = kwargs.get('mean', _model.cfg['mean'])
    -    kwargs['std'] = kwargs.get('std', _model.cfg['std'])
    -    kwargs['batch_size'] = kwargs.get('batch_size', 1)
    +    kwargs.pop("pretrained_backbone", None)
    +
    +    kwargs["mean"] = kwargs.get("mean", _model.cfg["mean"])
    +    kwargs["std"] = kwargs.get("std", _model.cfg["std"])
    +    kwargs["batch_size"] = kwargs.get("batch_size", 2)
         predictor = DetectionPredictor(
    -        PreProcessor(_model.cfg['input_shape'][:2], **kwargs),
    -        _model
    +        PreProcessor(_model.cfg["input_shape"][:-1] if is_tf_available() else _model.cfg["input_shape"][1:], **kwargs),
    +        _model,
         )
         return predictor
     
     
     
    -[docs] -def detection_predictor(arch: str = 'db_resnet50', pretrained: bool = False, **kwargs: Any) -> DetectionPredictor: +[docs] +def detection_predictor( + arch: Any = "fast_base", + pretrained: bool = False, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + batch_size: int = 2, + **kwargs: Any, +) -> DetectionPredictor: """Text detection architecture. - Example:: - >>> import numpy as np - >>> from doctr.models import detection_predictor - >>> model = detection_predictor(pretrained=True) - >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) - >>> out = model([input_page]) + >>> import numpy as np + >>> from doctr.models import detection_predictor + >>> model = detection_predictor(arch='db_resnet50', pretrained=True) + >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) Args: - arch: name of the architecture to use ('db_resnet50') + ---- + arch: name of the architecture or model itself to use (e.g. 'db_resnet50') pretrained: If True, returns a model pre-trained on our text detection dataset + assume_straight_pages: If True, fit straight boxes to the page + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right + batch_size: number of samples the model processes in parallel + **kwargs: optional keyword arguments passed to the architecture Returns: + ------- Detection predictor """ - - return _predictor(arch, pretrained, **kwargs)
    + return _predictor( + arch=arch, + pretrained=pretrained, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + batch_size=batch_size, + **kwargs, + )
    @@ -367,8 +449,8 @@

    Source code for doctr.models.detection.zoo

           
         
       
    - - + + diff --git a/v0.3.0/_modules/doctr/models/export.html b/v0.3.0/_modules/doctr/models/export.html deleted file mode 100644 index f25a81aa21..0000000000 --- a/v0.3.0/_modules/doctr/models/export.html +++ /dev/null @@ -1,411 +0,0 @@ - - - - - - - - - - - - doctr.models.export - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.export

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import logging
    -import numpy as np
    -import tensorflow as tf
    -from tensorflow.keras import Model
    -from typing import Tuple
    -
    -logging.getLogger("tensorflow").setLevel(logging.DEBUG)
    -
    -
    -__all__ = ['convert_to_tflite', 'convert_to_fp16', 'quantize_model']
    -
    -
    -
    -[docs] -def convert_to_tflite(tf_model: Model) -> bytes: - """Converts a model to TFLite format - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_tflite, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_tflite(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - return converter.convert()
    - - - -
    -[docs] -def convert_to_fp16(tf_model: Model) -> bytes: - """Converts a model to half precision - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_fp16, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_fp16(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the serialized FP16 model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - converter.target_spec.supported_types = [tf.float16] - return converter.convert()
    - - - -
    -[docs] -def quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) -> bytes: - """Quantize a Tensorflow model - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import quantize_model, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = quantize_model(model, (224, 224, 3)) - - Args: - tf_model: a keras model - input_shape: shape of the expected input tensor (excluding batch dimension) with channel last order - - Returns: - bytes: the serialized quantized model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - - # Float fallback for operators that do not have an integer implementation - def representative_dataset(): - for _ in range(100): - data = np.random.rand(1, *input_shape) - yield [data.astype(np.float32)] - - converter.representative_dataset = representative_dataset - converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] - converter.inference_input_type = tf.int8 - converter.inference_output_type = tf.int8 - - return converter.convert()
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.0/_modules/doctr/models/factory/hub.html b/v0.3.0/_modules/doctr/models/factory/hub.html index 8274a809f5..756b2c7a17 100644 --- a/v0.3.0/_modules/doctr/models/factory/hub.html +++ b/v0.3.0/_modules/doctr/models/factory/hub.html @@ -13,7 +13,7 @@ - + doctr.models.factory.hub - docTR documentation @@ -568,7 +568,7 @@

    Source code for doctr.models.factory.hub

         
       
    - + diff --git a/v0.3.0/_modules/doctr/models/recognition/crnn.html b/v0.3.0/_modules/doctr/models/recognition/crnn.html deleted file mode 100644 index daa2393439..0000000000 --- a/v0.3.0/_modules/doctr/models/recognition/crnn.html +++ /dev/null @@ -1,565 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.crnn - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.crnn

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import layers
    -from tensorflow.keras.models import Sequential
    -from typing import Tuple, Dict, Any, Optional, List
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel, RecognitionPostProcessor
    -
    -__all__ = ['CRNN', 'crnn_vgg16_bn', 'crnn_resnet31', 'CTCPostProcessor']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'crnn_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/crnn_vgg16_bn-748c855f.zip',
    -    },
    -    'crnn_resnet31': {
    -        'mean': (0.694, 0.695, 0.693),
    -        'std': (0.299, 0.296, 0.301),
    -        'backbone': 'resnet31', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.1/crnn_resnet31-69ab71db.zip',
    -    },
    -}
    -
    -
    -class CTCPostProcessor(RecognitionPostProcessor):
    -    """
    -    Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor
    -    ) -> List[Tuple[str, float]]:
    -        """
    -        Performs decoding of raw output with CTC and decoding of CTC predictions
    -        with label_to_idx mapping dictionnary
    -
    -        Args:
    -            logits: raw output of the model, shape BATCH_SIZE X SEQ_LEN X NUM_CLASSES + 1
    -
    -        Returns:
    -            A list of decoded words of length BATCH_SIZE
    -
    -        """
    -        # Decode CTC
    -        _decoded, _log_prob = tf.nn.ctc_beam_search_decoder(
    -            tf.transpose(logits, perm=[1, 0, 2]),
    -            tf.fill(logits.shape[0], logits.shape[1]),
    -            beam_width=1, top_paths=1,
    -        )
    -        out_idxs = tf.sparse.to_dense(_decoded[0], default_value=len(self.vocab))
    -        probs = tf.math.exp(tf.squeeze(_log_prob, axis=1))
    -
    -        # Map it to characters
    -        _decoded_strings_pred = tf.strings.reduce_join(
    -            inputs=tf.nn.embedding_lookup(self._embedding, out_idxs),
    -            axis=-1
    -        )
    -        _decoded_strings_pred = tf.strings.split(_decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -class CRNN(RecognitionModel):
    -    """Implements a CRNN architecture as described in `"An End-to-End Trainable Neural Network for Image-based
    -    Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of units in the LSTM layers
    -        cfg: configuration dictionary
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: tf.keras.Model,
    -        vocab: str,
    -        rnn_units: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(vocab=vocab, cfg=cfg)
    -        self.feat_extractor = feature_extractor
    -
    -        # Initialize kernels
    -        h, w, c = self.feat_extractor.output_shape[1:]
    -        self.max_length = w
    -
    -        self.decoder = Sequential(
    -            [
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Dense(units=len(vocab) + 1)
    -            ]
    -        )
    -        self.decoder.build(input_shape=(None, w, h * c))
    -
    -        self.postprocessor = CTCPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        target: List[str],
    -    ) -> tf.Tensor:
    -        """Compute CTC loss for the model.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        gt, seq_len = self.compute_target(target)
    -        batch_len = model_output.shape[0]
    -        input_length = model_output.shape[1] * tf.ones(shape=(batch_len))
    -        ctc_loss = tf.nn.ctc_loss(
    -            gt, model_output, seq_len, input_length, logits_time_major=False, blank_index=len(self.vocab)
    -        )
    -        return ctc_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        # B x H x W x C --> B x W x H x C
    -        transposed_feat = tf.transpose(features, perm=[0, 2, 1, 3])
    -        w, h, c = transposed_feat.get_shape().as_list()[1:]
    -        # B x W x H x C --> B x W x H * C
    -        features_seq = tf.reshape(transposed_feat, shape=(-1, w, h * c))
    -        logits = self.decoder(features_seq, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = logits
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(logits)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(logits, target)
    -
    -        return out
    -
    -
    -def _crnn(arch: str, pretrained: bool, input_shape: Optional[Tuple[int, int, int]] = None, **kwargs: Any) -> CRNN:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[_cfg['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -
    -    # Build the model
    -    model = CRNN(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a VGG-16 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_vgg16_bn - >>> model = crnn_vgg16_bn(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_vgg16_bn', pretrained, **kwargs)
    - - - -def crnn_resnet31(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a resnet31 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_resnet31 - >>> model = crnn_resnet31(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_resnet31', pretrained, **kwargs) -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.0/_modules/doctr/models/recognition/crnn/tensorflow.html b/v0.3.0/_modules/doctr/models/recognition/crnn/tensorflow.html index 41cc93dd23..bc64da9a1b 100644 --- a/v0.3.0/_modules/doctr/models/recognition/crnn/tensorflow.html +++ b/v0.3.0/_modules/doctr/models/recognition/crnn/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.crnn.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple, Union
    +
     import tensorflow as tf
     from tensorflow.keras import layers
    -from tensorflow.keras.models import Sequential, Model
    -from typing import Tuple, Dict, Any, Optional, List
    +from tensorflow.keras.models import Model, Sequential
    +
    +from doctr.datasets import VOCABS
     
    -from ... import backbones
    -from ...utils import load_pretrained_params
    +from ...classification import mobilenet_v3_large_r, mobilenet_v3_small_r, vgg16_bn_r
    +from ...utils.tensorflow import _bf16_to_float32, _build_model, load_pretrained_params
     from ..core import RecognitionModel, RecognitionPostProcessor
     
    -__all__ = ['CRNN', 'crnn_vgg16_bn', 'crnn_resnet31', 'CTCPostProcessor']
    +__all__ = ["CRNN", "crnn_vgg16_bn", "crnn_mobilenet_v3_small", "crnn_mobilenet_v3_large"]
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'crnn_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/crnn_vgg16_bn-748c855f.zip',
    +    "crnn_vgg16_bn": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["legacy_french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/crnn_vgg16_bn-9c188f45.weights.h5&src=0",
         },
    -    'crnn_resnet31': {
    -        'mean': (0.694, 0.695, 0.693),
    -        'std': (0.299, 0.296, 0.301),
    -        'backbone': 'resnet31', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.1/crnn_resnet31-69ab71db.zip',
    +    "crnn_mobilenet_v3_small": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/crnn_mobilenet_v3_small-54850265.weights.h5&src=0",
    +    },
    +    "crnn_mobilenet_v3_large": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/crnn_mobilenet_v3_large-c64045e5.weights.h5&src=0",
         },
     }
     
     
     class CTCPostProcessor(RecognitionPostProcessor):
    -    """
    -    Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
    +    """Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
     
         Args:
    +    ----
             vocab: string containing the ordered sequence of supported characters
             ignore_case: if True, ignore case of letters
             ignore_accents: if True, ignore accents of letters
    @@ -325,37 +353,57 @@ 

    Source code for doctr.models.recognition.crnn.tensorflow

    def __call__( self, - logits: tf.Tensor - ) -> List[Tuple[str, float]]: - """ - Performs decoding of raw output with CTC and decoding of CTC predictions + logits: tf.Tensor, + beam_width: int = 1, + top_paths: int = 1, + ) -> Union[List[Tuple[str, float]], List[Tuple[List[str], List[float]]]]: + """Performs decoding of raw output with CTC and decoding of CTC predictions with label_to_idx mapping dictionnary Args: + ---- logits: raw output of the model, shape BATCH_SIZE X SEQ_LEN X NUM_CLASSES + 1 + beam_width: An int scalar >= 0 (beam search beam width). + top_paths: An int scalar >= 0, <= beam_width (controls output size). Returns: + ------- A list of decoded words of length BATCH_SIZE + """ # Decode CTC _decoded, _log_prob = tf.nn.ctc_beam_search_decoder( tf.transpose(logits, perm=[1, 0, 2]), - tf.fill(logits.shape[0], logits.shape[1]), - beam_width=1, top_paths=1, + tf.fill(tf.shape(logits)[:1], tf.shape(logits)[1]), + beam_width=beam_width, + top_paths=top_paths, ) - out_idxs = tf.sparse.to_dense(_decoded[0], default_value=len(self.vocab)) - probs = tf.math.exp(tf.squeeze(_log_prob, axis=1)) + + _decoded = tf.sparse.concat( + 1, + [tf.sparse.expand_dims(dec, axis=1) for dec in _decoded], + expand_nonconcat_dims=True, + ) # dim : batchsize x beamwidth x actual_max_len_predictions + out_idxs = tf.sparse.to_dense(_decoded, default_value=len(self.vocab)) # Map it to characters _decoded_strings_pred = tf.strings.reduce_join( inputs=tf.nn.embedding_lookup(tf.constant(self._embedding, dtype=tf.string), out_idxs), - axis=-1 + axis=-1, ) _decoded_strings_pred = tf.strings.split(_decoded_strings_pred, "<eos>") - decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0] - word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] - + decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value="not valid")[ + :, :, 0 + ] # dim : batch_size x beam_width + + if top_paths == 1: + probs = tf.math.exp(tf.squeeze(_log_prob, axis=1)) # dim : batchsize + decoded_strings_pred = tf.squeeze(decoded_strings_pred, axis=1) + word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] + else: + probs = tf.math.exp(_log_prob) # dim : batchsize x beamwidth + word_values = [[word.decode() for word in words] for words in decoded_strings_pred.numpy().tolist()] return list(zip(word_values, probs.numpy().tolist())) @@ -364,19 +412,26 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. Args: + ---- feature_extractor: the backbone serving as feature extractor vocab: vocabulary used for encoding rnn_units: number of units in the LSTM layers + exportable: onnx exportable returns only logits + beam_width: beam width for beam search decoding + top_paths: number of top paths for beam search decoding cfg: configuration dictionary """ - _children_names: List[str] = ['feat_extractor', 'decoder', 'postprocessor'] + _children_names: List[str] = ["feat_extractor", "decoder", "postprocessor"] def __init__( self, - feature_extractor: tf.keras.Model, + feature_extractor: Model, vocab: str, rnn_units: int = 128, + exportable: bool = False, + beam_width: int = 1, + top_paths: int = 1, cfg: Optional[Dict[str, Any]] = None, ) -> None: # Initialize kernels @@ -386,19 +441,21 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    self.vocab = vocab self.max_length = w self.cfg = cfg + self.exportable = exportable self.feat_extractor = feature_extractor - self.decoder = Sequential( - [ - layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), - layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), - layers.Dense(units=len(vocab) + 1) - ] - ) + self.decoder = Sequential([ + layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), + layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), + layers.Dense(units=len(vocab) + 1), + ]) self.decoder.build(input_shape=(None, w, h * c)) self.postprocessor = CTCPostProcessor(vocab=vocab) + self.beam_width = beam_width + self.top_paths = top_paths + def compute_loss( self, model_output: tf.Tensor, @@ -407,16 +464,17 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    """Compute CTC loss for the model. Args: - gt: the encoded tensor with gt labels + ---- model_output: predicted logits of the model - seq_len: lengths of each gt word inside the batch + target: lengths of each gt word inside the batch Returns: + ------- The loss of the model on the batch """ - gt, seq_len = self.compute_target(target) + gt, seq_len = self.build_target(target) batch_len = model_output.shape[0] - input_length = model_output.shape[1] * tf.ones(shape=(batch_len)) + input_length = tf.fill((batch_len,), model_output.shape[1]) ctc_loss = tf.nn.ctc_loss( gt, model_output, seq_len, input_length, logits_time_major=False, blank_index=len(self.vocab) ) @@ -428,8 +486,12 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    target: Optional[List[str]] = None, return_model_output: bool = False, return_preds: bool = False, + beam_width: int = 1, + top_paths: int = 1, **kwargs: Any, ) -> Dict[str, Any]: + if kwargs.get("training", False) and target is None: + raise ValueError("Need to provide labels during training") features = self.feat_extractor(x, **kwargs) # B x H x W x C --> B x W x H x C @@ -437,91 +499,132 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    w, h, c = transposed_feat.get_shape().as_list()[1:] # B x W x H x C --> B x W x H * C features_seq = tf.reshape(transposed_feat, shape=(-1, w, h * c)) - logits = self.decoder(features_seq, **kwargs) + logits = _bf16_to_float32(self.decoder(features_seq, **kwargs)) out: Dict[str, tf.Tensor] = {} + if self.exportable: + out["logits"] = logits + return out + if return_model_output: out["out_map"] = logits if target is None or return_preds: # Post-process boxes - out["preds"] = self.postprocessor(logits) + out["preds"] = self.postprocessor(logits, beam_width=beam_width, top_paths=top_paths) if target is not None: - out['loss'] = self.compute_loss(logits, target) + out["loss"] = self.compute_loss(logits, target) return out -def _crnn(arch: str, pretrained: bool, input_shape: Optional[Tuple[int, int, int]] = None, **kwargs: Any) -> CRNN: +def _crnn( + arch: str, + pretrained: bool, + backbone_fn, + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> CRNN: + pretrained_backbone = pretrained_backbone and not pretrained + + kwargs["vocab"] = kwargs.get("vocab", default_cfgs[arch]["vocab"]) - # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab']) - _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units']) + _cfg["vocab"] = kwargs["vocab"] + _cfg["input_shape"] = input_shape or default_cfgs[arch]["input_shape"] - # Feature extractor - feat_extractor = backbones.__dict__[_cfg['backbone']]( - input_shape=_cfg['input_shape'], + feat_extractor = backbone_fn( + input_shape=_cfg["input_shape"], include_top=False, + pretrained=pretrained_backbone, ) - kwargs['vocab'] = _cfg['vocab'] - kwargs['rnn_units'] = _cfg['rnn_units'] - # Build the model model = CRNN(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) # Load pretrained parameters if pretrained: - load_pretrained_params(model, _cfg['url']) + # The given vocab differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params(model, _cfg["url"], skip_mismatch=kwargs["vocab"] != default_cfgs[arch]["vocab"]) return model
    -[docs] +[docs] def crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> CRNN: """CRNN with a VGG-16 backbone as described in `"An End-to-End Trainable Neural Network for Image-based Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_vgg16_bn - >>> model = crnn_vgg16_bn(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import crnn_vgg16_bn + >>> model = crnn_vgg16_bn(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the CRNN architecture Returns: + ------- text recognition architecture """ + return _crnn("crnn_vgg16_bn", pretrained, vgg16_bn_r, **kwargs)
    + + + +
    +[docs] +def crnn_mobilenet_v3_small(pretrained: bool = False, **kwargs: Any) -> CRNN: + """CRNN with a MobileNet V3 Small backbone as described in `"An End-to-End Trainable Neural Network for Image-based + Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. + + >>> import tensorflow as tf + >>> from doctr.models import crnn_mobilenet_v3_small + >>> model = crnn_mobilenet_v3_small(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the CRNN architecture - return _crnn('crnn_vgg16_bn', pretrained, **kwargs)
    + Returns: + ------- + text recognition architecture + """ + return _crnn("crnn_mobilenet_v3_small", pretrained, mobilenet_v3_small_r, **kwargs)
    -def crnn_resnet31(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a resnet31 backbone as described in `"An End-to-End Trainable Neural Network for Image-based +
    +[docs] +def crnn_mobilenet_v3_large(pretrained: bool = False, **kwargs: Any) -> CRNN: + """CRNN with a MobileNet V3 Large backbone as described in `"An End-to-End Trainable Neural Network for Image-based Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_resnet31 - >>> model = crnn_resnet31(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import crnn_mobilenet_v3_large + >>> model = crnn_mobilenet_v3_large(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the CRNN architecture Returns: + ------- text recognition architecture """ + return _crnn("crnn_mobilenet_v3_large", pretrained, mobilenet_v3_large_r, **kwargs)
    - return _crnn('crnn_resnet31', pretrained, **kwargs)
    @@ -554,8 +657,8 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    - +
    + diff --git a/v0.3.0/_modules/doctr/models/recognition/master/tensorflow.html b/v0.3.0/_modules/doctr/models/recognition/master/tensorflow.html index 2dc5a27717..aa6aa69325 100644 --- a/v0.3.0/_modules/doctr/models/recognition/master/tensorflow.html +++ b/v0.3.0/_modules/doctr/models/recognition/master/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.master.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.master.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import tensorflow as tf
    -from tensorflow.keras import layers, Sequential, Model
    -from typing import Tuple, List, Dict, Any, Optional
     from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple
     
    -from ..core import RecognitionPostProcessor
    -from ...backbones.resnet import ResnetStage
    -from ...utils import conv_sequence, load_pretrained_params
    -from ..transformer import Decoder, positional_encoding, create_look_ahead_mask, create_padding_mask
    -from ....datasets import VOCABS
    -from .base import _MASTER, _MASTERPostProcessor
    +import tensorflow as tf
    +from tensorflow.keras import Model, layers
    +
    +from doctr.datasets import VOCABS
    +from doctr.models.classification import magc_resnet31
    +from doctr.models.modules.transformer import Decoder, PositionalEncoding
     
    +from ...utils.tensorflow import _bf16_to_float32, _build_model, load_pretrained_params
    +from .base import _MASTER, _MASTERPostProcessor
     
    -__all__ = ['MASTER', 'master', 'MASTERPostProcessor']
    +__all__ = ["MASTER", "master"]
     
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'master': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'input_shape': (48, 160, 3),
    -        'vocab': VOCABS['french'],
    -        'url': None,
    +    "master": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/master-d7fdaeff.weights.h5&src=0",
         },
     }
     
     
    -class MAGC(layers.Layer):
    -
    -    """Implements the Multi-Aspect Global Context Attention, as described in
    -    <https://arxiv.org/pdf/1910.02562.pdf>`_.
    -
    -    Args:
    -        inplanes: input channels
    -        headers: number of headers to split channels
    -        att_scale: if True, re-scale attention to counteract the variance distibutions
    -        **kwargs
    -    """
    -
    -    def __init__(
    -        self,
    -        inplanes: int,
    -        headers: int = 1,
    -        att_scale: bool = False,
    -        **kwargs
    -    ) -> None:
    -        super().__init__(**kwargs)
    -
    -        self.headers = headers  # h
    -        self.inplanes = inplanes  # C
    -        self.att_scale = att_scale
    -
    -        self.single_header_inplanes = int(inplanes / headers)  # C / h
    -
    -        self.conv_mask = tf.keras.layers.Conv2D(
    -            filters=1,
    -            kernel_size=1,
    -            kernel_initializer=tf.initializers.he_normal()
    -        )
    -
    -        self.transform = tf.keras.Sequential(
    -            [
    -                tf.keras.layers.Conv2D(
    -                    filters=self.inplanes,
    -                    kernel_size=1,
    -                    kernel_initializer=tf.initializers.he_normal()
    -                ),
    -                tf.keras.layers.LayerNormalization([1, 2, 3]),
    -                tf.keras.layers.ReLU(),
    -                tf.keras.layers.Conv2D(
    -                    filters=self.inplanes,
    -                    kernel_size=1,
    -                    kernel_initializer=tf.initializers.he_normal()
    -                ),
    -            ],
    -            name='transform'
    -        )
    -
    -    @tf.function
    -    def context_modeling(self, inputs: tf.Tensor) -> tf.Tensor:
    -        b, h, w, c = (tf.shape(inputs)[i] for i in range(4))
    -
    -        # B, H, W, C -->> B*h, H, W, C/h
    -        x = tf.reshape(inputs, shape=(b, h, w, self.headers, self.single_header_inplanes))
    -        x = tf.transpose(x, perm=(0, 3, 1, 2, 4))
    -        x = tf.reshape(x, shape=(b * self.headers, h, w, self.single_header_inplanes))
    -
    -        # Compute shorcut
    -        shortcut = x
    -        # B*h, 1, H*W, C/h
    -        shortcut = tf.reshape(shortcut, shape=(b * self.headers, 1, h * w, self.single_header_inplanes))
    -        # B*h, 1, C/h, H*W
    -        shortcut = tf.transpose(shortcut, perm=[0, 1, 3, 2])
    -
    -        # Compute context mask
    -        # B*h, H, W, 1,
    -        context_mask = self.conv_mask(x)
    -        # B*h, 1, H*W, 1
    -        context_mask = tf.reshape(context_mask, shape=(b * self.headers, 1, h * w, 1))
    -        # scale variance
    -        if self.att_scale and self.headers > 1:
    -            context_mask = context_mask / tf.sqrt(self.single_header_inplanes)
    -        # B*h, 1, H*W, 1
    -        context_mask = tf.keras.activations.softmax(context_mask, axis=2)
    -
    -        # Compute context
    -        # B*h, 1, C/h, 1
    -        context = tf.matmul(shortcut, context_mask)
    -        context = tf.reshape(context, shape=(b, 1, c, 1))
    -        # B, 1, 1, C
    -        context = tf.transpose(context, perm=(0, 1, 3, 2))
    -        # Set shape to resolve shape when calling this module in the Sequential MAGCResnet
    -        batch, chan = inputs.get_shape().as_list()[0], inputs.get_shape().as_list()[-1]
    -        context.set_shape([batch, 1, 1, chan])
    -        return context
    -
    -    def call(self, inputs: tf.Tensor, **kwargs) -> tf.Tensor:
    -        # Context modeling: B, H, W, C  ->  B, 1, 1, C
    -        context = self.context_modeling(inputs)
    -        # Transform: B, 1, 1, C  ->  B, 1, 1, C
    -        transformed = self.transform(context)
    -        return inputs + transformed
    -
    -
    -class MAGCResnet(Sequential):
    -
    -    """Implements the modified resnet with MAGC layers, as described in paper.
    -
    -    Args:
    -        headers: number of header to split channels in MAGC layers
    -        input_shape: shape of the model input (without batch dim)
    -    """
    -
    -    def __init__(
    -        self,
    -        headers: int = 1,
    -        input_shape: Tuple[int, int, int] = (48, 160, 3),
    -    ) -> None:
    -        _layers = [
    -            # conv_1x
    -            *conv_sequence(out_channels=64, activation='relu', bn=True, kernel_size=3, input_shape=input_shape),
    -            *conv_sequence(out_channels=128, activation='relu', bn=True, kernel_size=3),
    -            layers.MaxPooling2D((2, 2), (2, 2)),
    -            # conv_2x
    -            ResnetStage(num_blocks=1, output_channels=256),
    -            MAGC(inplanes=256, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=256, activation='relu', bn=True, kernel_size=3),
    -            layers.MaxPooling2D((2, 2), (2, 2)),
    -            # conv_3x
    -            ResnetStage(num_blocks=2, output_channels=512),
    -            MAGC(inplanes=512, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=512, activation='relu', bn=True, kernel_size=3),
    -            layers.MaxPooling2D((2, 1), (2, 1)),
    -            # conv_4x
    -            ResnetStage(num_blocks=5, output_channels=512),
    -            MAGC(inplanes=512, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=512, activation='relu', bn=True, kernel_size=3),
    -            # conv_5x
    -            ResnetStage(num_blocks=3, output_channels=512),
    -            MAGC(inplanes=512, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=512, activation='relu', bn=True, kernel_size=3),
    -        ]
    -        super().__init__(_layers)
    -
    -
     class MASTER(_MASTER, Model):
    -
         """Implements MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_.
         Implementation based on the official TF implementation: <https://github.com/jiangxiluning/MASTER-TF>`_.
     
         Args:
    +    ----
    +        feature_extractor: the backbone serving as feature extractor
             vocab: vocabulary, (without EOS, SOS, PAD)
             d_model: d parameter for the transformer decoder
    -        headers: headers for the MAGC module
             dff: depth of the pointwise feed-forward layer
             num_heads: number of heads for the mutli-head attention module
             num_layers: number of decoder layers to stack
             max_length: maximum length of character sequence handled by the model
    -        input_size: size of the image inputs
    +        dropout: dropout probability of the decoder
    +        input_shape: size of the image inputs
    +        exportable: onnx exportable returns only logits
    +        cfg: dictionary containing information about the model
         """
     
         def __init__(
             self,
    +        feature_extractor: Model,
             vocab: str,
             d_model: int = 512,
    -        headers: int = 1,
             dff: int = 2048,
    -        num_heads: int = 8,
    +        num_heads: int = 8,  # number of heads in the transformer decoder
             num_layers: int = 3,
             max_length: int = 50,
    -        input_shape: Tuple[int, int, int] = (48, 160, 3),
    +        dropout: float = 0.2,
    +        input_shape: Tuple[int, int, int] = (32, 128, 3),  # different from the paper
    +        exportable: bool = False,
             cfg: Optional[Dict[str, Any]] = None,
         ) -> None:
             super().__init__()
     
    -        self.vocab = vocab
    +        self.exportable = exportable
             self.max_length = max_length
    +        self.d_model = d_model
    +        self.vocab = vocab
             self.cfg = cfg
             self.vocab_size = len(vocab)
     
    -        self.feature_extractor = MAGCResnet(headers=headers, input_shape=input_shape)
    -        self.seq_embedding = layers.Embedding(self.vocab_size + 3, d_model)  # 3 more classes: EOS/PAD/SOS
    +        self.feat_extractor = feature_extractor
    +        self.positional_encoding = PositionalEncoding(self.d_model, dropout, max_len=input_shape[0] * input_shape[1])
     
             self.decoder = Decoder(
                 num_layers=num_layers,
    -            d_model=d_model,
    +            d_model=self.d_model,
                 num_heads=num_heads,
    +            vocab_size=self.vocab_size + 3,  # EOS, SOS, PAD
                 dff=dff,
    -            vocab_size=self.vocab_size,
    -            maximum_position_encoding=max_length,
    +            dropout=dropout,
    +            maximum_position_encoding=self.max_length,
             )
    -        self.feature_pe = positional_encoding(input_shape[0] * input_shape[1], d_model)
    -        self.linear = layers.Dense(self.vocab_size + 3, kernel_initializer=tf.initializers.he_uniform())
     
    +        self.linear = layers.Dense(self.vocab_size + 3, kernel_initializer=tf.initializers.he_uniform())
             self.postprocessor = MASTERPostProcessor(vocab=self.vocab)
     
         @tf.function
    -    def make_mask(self, target: tf.Tensor) -> tf.Tensor:
    -        look_ahead_mask = create_look_ahead_mask(tf.shape(target)[1])
    -        target_padding_mask = create_padding_mask(target, self.vocab_size + 2)  # Pad symbol
    -        combined_mask = tf.maximum(target_padding_mask, look_ahead_mask)
    -        return combined_mask
    +    def make_source_and_target_mask(self, source: tf.Tensor, target: tf.Tensor) -> Tuple[tf.Tensor, tf.Tensor]:
    +        # [1, 1, 1, ..., 0, 0, 0] -> 0 is masked
    +        # (N, 1, 1, max_length)
    +        target_pad_mask = tf.cast(tf.math.not_equal(target, self.vocab_size + 2), dtype=tf.uint8)
    +        target_pad_mask = target_pad_mask[:, tf.newaxis, tf.newaxis, :]
    +        target_length = target.shape[1]
    +        # sub mask filled diagonal with 1 = see 0 = masked (max_length, max_length)
    +        target_sub_mask = tf.linalg.band_part(tf.ones((target_length, target_length)), -1, 0)
    +        # source mask filled with ones (max_length, positional_encoded_seq_len)
    +        source_mask = tf.ones((target_length, source.shape[1]))
    +        # combine the two masks into one boolean mask where False is masked (N, 1, max_length, max_length)
    +        target_mask = tf.math.logical_and(
    +            tf.cast(target_sub_mask, dtype=tf.bool), tf.cast(target_pad_mask, dtype=tf.bool)
    +        )
    +        return source_mask, target_mask
     
    +    @staticmethod
         def compute_loss(
    -        self,
             model_output: tf.Tensor,
             gt: tf.Tensor,
             seq_len: List[int],
    @@ -512,11 +413,13 @@ 

    Source code for doctr.models.recognition.master.tensorflow

    Sequences are masked after the EOS character. Args: + ---- gt: the encoded tensor with gt labels model_output: predicted logits of the model seq_len: lengths of each gt word inside the batch Returns: + ------- The loss of the model on the batch """ # Input length : number of timesteps @@ -532,7 +435,7 @@

    Source code for doctr.models.recognition.master.tensorflow

    mask_values = tf.zeros_like(cce) mask_2d = tf.sequence_mask(seq_len, input_len - 1) # delete the last mask timestep as well masked_loss = tf.where(mask_2d, cce, mask_values) - ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32)) + ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, model_output.dtype)) return tf.expand_dims(ce_loss, axis=1) @@ -547,94 +450,103 @@

    Source code for doctr.models.recognition.master.tensorflow

    """Call function for training Args: + ---- x: images target: list of str labels return_model_output: if True, return logits return_preds: if True, decode logits + **kwargs: keyword arguments passed to the decoder - Return: + Returns: + ------- A dictionnary containing eventually loss, logits and predictions. """ - # Encode - feature = self.feature_extractor(x, **kwargs) - b, h, w, c = (tf.shape(feature)[i] for i in range(4)) + feature = self.feat_extractor(x, **kwargs) + b, h, w, c = feature.get_shape() + # (N, H, W, C) --> (N, H * W, C) feature = tf.reshape(feature, shape=(b, h * w, c)) - encoded = feature + self.feature_pe[:, :h * w, :] + # add positional encoding to features + encoded = self.positional_encoding(feature, **kwargs) out: Dict[str, tf.Tensor] = {} + if kwargs.get("training", False) and target is None: + raise ValueError("Need to provide labels during training") + if target is not None: # Compute target: tensor of gts and sequence lengths - gt, seq_len = self.compute_target(target) - - if kwargs.get('training', False): - if target is None: - raise AssertionError("In training mode, you need to pass a value to 'target'") - tgt_mask = self.make_mask(gt) + gt, seq_len = self.build_target(target) + # Compute decoder masks + source_mask, target_mask = self.make_source_and_target_mask(encoded, gt) # Compute logits - output = self.decoder(gt, encoded, tgt_mask, None, **kwargs) + output = self.decoder(gt, encoded, source_mask, target_mask, **kwargs) logits = self.linear(output, **kwargs) - else: - # When not training, we want to compute logits in with the decoder, although - # we have access to gts (we need gts to compute the loss, but not in the decoder) logits = self.decode(encoded, **kwargs) + logits = _bf16_to_float32(logits) + + if self.exportable: + out["logits"] = logits + return out + if target is not None: - out['loss'] = self.compute_loss(logits, gt, seq_len) + out["loss"] = self.compute_loss(logits, gt, seq_len) if return_model_output: - out['out_map'] = logits + out["out_map"] = logits if return_preds: - predictions = self.postprocessor(logits) - out['preds'] = predictions + out["preds"] = self.postprocessor(logits) return out + @tf.function def decode(self, encoded: tf.Tensor, **kwargs: Any) -> tf.Tensor: """Decode function for prediction Args: + ---- encoded: encoded features + **kwargs: keyword arguments passed to the decoder - Return: + Returns: + ------- A Tuple of tf.Tensor: predictions, logits """ - b = tf.shape(encoded)[0] - max_len = tf.constant(self.max_length, dtype=tf.int32) + b = encoded.shape[0] + start_symbol = tf.constant(self.vocab_size + 1, dtype=tf.int32) # SOS padding_symbol = tf.constant(self.vocab_size + 2, dtype=tf.int32) # PAD - ys = tf.fill(dims=(b, max_len - 1), value=padding_symbol) + ys = tf.fill(dims=(b, self.max_length - 1), value=padding_symbol) start_vector = tf.fill(dims=(b, 1), value=start_symbol) ys = tf.concat([start_vector, ys], axis=-1) - logits = tf.zeros(shape=(b, max_len - 1, self.vocab_size + 3), dtype=tf.float32) # 3 symbols - # max_len = len + 2 (sos + eos) + # Final dimension include EOS/SOS/PAD for i in range(self.max_length - 1): - ys_mask = self.make_mask(ys) - output = self.decoder(ys, encoded, ys_mask, None, **kwargs) + source_mask, target_mask = self.make_source_and_target_mask(encoded, ys) + output = self.decoder(ys, encoded, source_mask, target_mask, **kwargs) logits = self.linear(output, **kwargs) prob = tf.nn.softmax(logits, axis=-1) - next_word = tf.argmax(prob, axis=-1, output_type=ys.dtype) - # ys.shape = B, T - i_mesh, j_mesh = tf.meshgrid(tf.range(b), tf.range(max_len), indexing='ij') + next_token = tf.argmax(prob, axis=-1, output_type=ys.dtype) + # update ys with the next token and ignore the first token (SOS) + i_mesh, j_mesh = tf.meshgrid(tf.range(b), tf.range(self.max_length), indexing="ij") indices = tf.stack([i_mesh[:, i + 1], j_mesh[:, i + 1]], axis=1) - ys = tf.tensor_scatter_nd_update(ys, indices, next_word[:, i + 1]) + ys = tf.tensor_scatter_nd_update(ys, indices, next_token[:, i]) - # final_logits of shape (N, max_length - 1, vocab_size + 1) (whithout sos) + # Shape (N, max_length, vocab_size + 1) return logits class MASTERPostProcessor(_MASTERPostProcessor): """Post processor for MASTER architectures + Args: + ---- vocab: string containing the ordered sequence of supported characters - ignore_case: if True, ignore case of letters - ignore_accents: if True, ignore accents of letters """ def __call__( @@ -649,51 +561,66 @@

    Source code for doctr.models.recognition.master.tensorflow

    probs = tf.math.reduce_min(probs, axis=1) # decode raw output of the model with tf_label_to_idx - out_idxs = tf.cast(out_idxs, dtype='int32') + out_idxs = tf.cast(out_idxs, dtype="int32") embedding = tf.constant(self._embedding, dtype=tf.string) decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(embedding, out_idxs), axis=-1) decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>") - decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0] + decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value="not valid")[:, 0] word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] - return list(zip(word_values, probs.numpy().tolist())) + return list(zip(word_values, probs.numpy().clip(0, 1).tolist())) -def _master(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> MASTER: +def _master(arch: str, pretrained: bool, backbone_fn, pretrained_backbone: bool = True, **kwargs: Any) -> MASTER: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab']) + _cfg["input_shape"] = kwargs.get("input_shape", _cfg["input_shape"]) + _cfg["vocab"] = kwargs.get("vocab", _cfg["vocab"]) - kwargs['vocab'] = _cfg['vocab'] + kwargs["vocab"] = _cfg["vocab"] + kwargs["input_shape"] = _cfg["input_shape"] # Build the model - model = MASTER(cfg=_cfg, **kwargs) + model = MASTER( + backbone_fn(pretrained=pretrained_backbone, input_shape=_cfg["input_shape"], include_top=False), + cfg=_cfg, + **kwargs, + ) + _build_model(model) + # Load pretrained parameters if pretrained: - load_pretrained_params(model, default_cfgs[arch]['url']) + # The given vocab differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, default_cfgs[arch]["url"], skip_mismatch=kwargs["vocab"] != default_cfgs[arch]["vocab"] + ) return model
    -[docs] +[docs] def master(pretrained: bool = False, **kwargs: Any) -> MASTER: """MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import master - >>> model = master(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 48, 160, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + + >>> import tensorflow as tf + >>> from doctr.models import master + >>> model = master(pretrained=False) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keywoard arguments passed to the MASTER architecture + Returns: + ------- text recognition architecture """ - - return _master('master', pretrained, **kwargs)
    + return _master("master", pretrained, magc_resnet31, **kwargs)
    @@ -727,8 +654,8 @@

    Source code for doctr.models.recognition.master.tensorflow

    - +
    + diff --git a/v0.3.0/_modules/doctr/models/recognition/parseq/tensorflow.html b/v0.3.0/_modules/doctr/models/recognition/parseq/tensorflow.html index 0819737dfc..b181acef53 100644 --- a/v0.3.0/_modules/doctr/models/recognition/parseq/tensorflow.html +++ b/v0.3.0/_modules/doctr/models/recognition/parseq/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.parseq.tensorflow - docTR documentation @@ -845,7 +845,7 @@

    Source code for doctr.models.recognition.parseq.tensorflow

    - + diff --git a/v0.3.0/_modules/doctr/models/recognition/sar.html b/v0.3.0/_modules/doctr/models/recognition/sar.html deleted file mode 100644 index 2482e9f156..0000000000 --- a/v0.3.0/_modules/doctr/models/recognition/sar.html +++ /dev/null @@ -1,712 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.sar - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.sar

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import Sequential, layers
    -from typing import Tuple, Dict, List, Any, Optional
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel
    -from .core import RecognitionPostProcessor
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['SAR', 'SARPostProcessor', 'sar_vgg16_bn', 'sar_resnet31']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'sar_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1-models/sar_vgg16bn-0d7e2c26.zip',
    -    },
    -    'sar_resnet31': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'resnet31', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/sar_resnet31-ea202587.zip',
    -    },
    -}
    -
    -
    -class AttentionModule(layers.Layer, NestedObject):
    -    """Implements attention module of the SAR model
    -
    -    Args:
    -        attention_units: number of hidden attention units
    -
    -    """
    -    def __init__(
    -        self,
    -        attention_units: int
    -    ) -> None:
    -
    -        super().__init__()
    -        self.hidden_state_projector = layers.Conv2D(
    -            attention_units, 1, strides=1, use_bias=False, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.features_projector = layers.Conv2D(
    -            attention_units, 3, strides=1, use_bias=True, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.attention_projector = layers.Conv2D(
    -            1, 1, strides=1, use_bias=False, padding="same", kernel_initializer='he_normal',
    -        )
    -        self.flatten = layers.Flatten()
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        hidden_state: tf.Tensor,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        [H, W] = features.get_shape().as_list()[1:3]
    -        # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units)
    -        hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs)
    -        # shape (N, H, W, vgg_units) -> (N, H, W, attention_units)
    -        features_projection = self.features_projector(features, **kwargs)
    -        projection = tf.math.tanh(hidden_state_projection + features_projection)
    -        # shape (N, H, W, attention_units) -> (N, H, W, 1)
    -        attention = self.attention_projector(projection, **kwargs)
    -        # shape (N, H, W, 1) -> (N, H * W)
    -        attention = self.flatten(attention)
    -        attention = tf.nn.softmax(attention)
    -        # shape (N, H * W) -> (N, H, W, 1)
    -        attention_map = tf.reshape(attention, [-1, H, W, 1])
    -        glimpse = tf.math.multiply(features, attention_map)
    -        # shape (N, H * W) -> (N, 1)
    -        glimpse = tf.reduce_sum(glimpse, axis=[1, 2])
    -        return glimpse
    -
    -
    -class SARDecoder(layers.Layer, NestedObject):
    -    """Implements decoder module of the SAR model
    -
    -    Args:
    -        rnn_units: number of hidden units in recurrent cells
    -        max_length: maximum length of a sequence
    -        vocab_size: number of classes in the model alphabet
    -        embedding_units: number of hidden embedding units
    -        attention_units: number of hidden attention units
    -        num_decoder_layers: number of LSTM layers to stack
    -
    -    """
    -    def __init__(
    -        self,
    -        rnn_units: int,
    -        max_length: int,
    -        vocab_size: int,
    -        embedding_units: int,
    -        attention_units: int,
    -        num_decoder_layers: int = 2,
    -        input_shape: Optional[List[Tuple[Optional[int]]]] = None,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.vocab_size = vocab_size
    -        self.lstm_decoder = layers.StackedRNNCells(
    -            [layers.LSTMCell(rnn_units, dtype=tf.float32, implementation=1) for _ in range(num_decoder_layers)]
    -        )
    -        self.embed = layers.Dense(embedding_units, use_bias=False, input_shape=(None, self.vocab_size + 1))
    -        self.attention_module = AttentionModule(attention_units)
    -        self.output_dense = layers.Dense(vocab_size + 1, use_bias=True, input_shape=(None, 2 * rnn_units))
    -        self.max_length = max_length
    -
    -        # Initialize kernels
    -        if input_shape is not None:
    -            self.attention_module.call(layers.Input(input_shape[0][1:]), layers.Input((1, 1, rnn_units)))
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        holistic: tf.Tensor,
    -        gt: Optional[tf.Tensor] = None,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # initialize states (each of shape (N, rnn_units))
    -        states = self.lstm_decoder.get_initial_state(
    -            inputs=None, batch_size=features.shape[0], dtype=tf.float32
    -        )
    -        # run first step of lstm
    -        # holistic: shape (N, rnn_units)
    -        _, states = self.lstm_decoder(holistic, states, **kwargs)
    -        # Initialize with the index of virtual START symbol (placed after <eos>)
    -        symbol = tf.fill(features.shape[0], self.vocab_size + 1)
    -        logits_list = []
    -        if kwargs.get('training') and gt is None:
    -            raise ValueError('Need to provide labels during training for teacher forcing')
    -        for t in range(self.max_length + 1):  # keep 1 step for <eos>
    -            # one-hot symbol with depth vocab_size + 1
    -            # embeded_symbol: shape (N, embedding_units)
    -            embeded_symbol = self.embed(tf.one_hot(symbol, depth=self.vocab_size + 1), **kwargs)
    -            logits, states = self.lstm_decoder(embeded_symbol, states, **kwargs)
    -            glimpse = self.attention_module(
    -                features, tf.expand_dims(tf.expand_dims(logits, axis=1), axis=1), **kwargs,
    -            )
    -            # logits: shape (N, rnn_units), glimpse: shape (N, 1)
    -            logits = tf.concat([logits, glimpse], axis=-1)
    -            # shape (N, rnn_units + 1) -> (N, vocab_size + 1)
    -            logits = self.output_dense(logits, **kwargs)
    -            # update symbol with predicted logits for t+1 step
    -            if kwargs.get('training'):
    -                symbol = gt[:, t]
    -            else:
    -                symbol = tf.argmax(logits, axis=-1)
    -            logits_list.append(logits)
    -        outputs = tf.stack(logits_list, axis=1)  # shape (N, max_length + 1, vocab_size + 1)
    -
    -        return outputs
    -
    -
    -class SAR(RecognitionModel):
    -    """Implements a SAR architecture as described in `"Show, Attend and Read:A Simple and Strong Baseline for
    -    Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of hidden units in both encoder and decoder LSTM
    -        embedding_units: number of embedding units
    -        attention_units: number of hidden units in attention module
    -        max_length: maximum word length handled by the model
    -        num_decoders: number of LSTM to stack in decoder layer
    -
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'encoder', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor,
    -        vocab: str,
    -        rnn_units: int = 512,
    -        embedding_units: int = 512,
    -        attention_units: int = 512,
    -        max_length: int = 30,
    -        num_decoders: int = 2,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(vocab=vocab, cfg=cfg)
    -
    -        self.max_length = max_length + 1  # Add 1 timestep for EOS after the longest word
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.encoder = Sequential(
    -            [
    -                layers.LSTM(units=rnn_units, return_sequences=True),
    -                layers.LSTM(units=rnn_units, return_sequences=False)
    -            ]
    -        )
    -        # Initialize the kernels (watch out for reduce_max)
    -        self.encoder.build(input_shape=(None,) + self.feat_extractor.output_shape[2:])
    -
    -        self.decoder = SARDecoder(
    -            rnn_units, max_length, len(vocab), embedding_units, attention_units, num_decoders,
    -            input_shape=[self.feat_extractor.output_shape, self.encoder.output_shape]
    -        )
    -
    -        self.postprocessor = SARPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        gt: tf.Tensor,
    -        seq_len: tf.Tensor,
    -    ) -> tf.Tensor:
    -        """Compute categorical cross-entropy loss for the model.
    -        Sequences are masked after the EOS character.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        # Input length : number of timesteps
    -        input_len = tf.shape(model_output)[1]
    -        # Add one for additional <eos> token
    -        seq_len = seq_len + 1
    -        # One-hot gt labels
    -        oh_gt = tf.one_hot(gt, depth=model_output.shape[2])
    -        # Compute loss
    -        cce = tf.nn.softmax_cross_entropy_with_logits(oh_gt, model_output)
    -        # Compute mask
    -        mask_values = tf.zeros_like(cce)
    -        mask_2d = tf.sequence_mask(seq_len, input_len)
    -        masked_loss = tf.where(mask_2d, cce, mask_values)
    -        ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32))
    -        return tf.expand_dims(ce_loss, axis=1)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        pooled_features = tf.reduce_max(features, axis=1)  # vertical max pooling
    -        encoded = self.encoder(pooled_features, **kwargs)
    -        if target is not None:
    -            gt, seq_len = self.compute_target(target)
    -        decoded_features = self.decoder(features, encoded, gt=None if target is None else gt, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = decoded_features
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(decoded_features)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(decoded_features, gt, seq_len)
    -
    -        return out
    -
    -
    -class SARPostProcessor(RecognitionPostProcessor):
    -    """Post processor for SAR architectures
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor,
    -    ) -> List[Tuple[str, float]]:
    -        # compute pred with argmax for attention models
    -        out_idxs = tf.math.argmax(logits, axis=2)
    -        # N x L
    -        probs = tf.gather(tf.nn.softmax(logits, axis=-1), out_idxs, axis=-1, batch_dims=2)
    -        # Take the minimum confidence of the sequence
    -        probs = tf.math.reduce_min(probs, axis=1)
    -
    -        # decode raw output of the model with tf_label_to_idx
    -        out_idxs = tf.cast(out_idxs, dtype='int32')
    -        decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(self._embedding, out_idxs), axis=-1)
    -        decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -def _sar(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> SAR:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -    _cfg['embedding_units'] = kwargs.get('embedding_units', _cfg['rnn_units'])
    -    _cfg['attention_units'] = kwargs.get('attention_units', _cfg['rnn_units'])
    -    _cfg['max_length'] = kwargs.get('max_length', _cfg['max_length'])
    -    _cfg['num_decoders'] = kwargs.get('num_decoders', _cfg['num_decoders'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[default_cfgs[arch]['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -    kwargs['embedding_units'] = _cfg['embedding_units']
    -    kwargs['attention_units'] = _cfg['attention_units']
    -    kwargs['max_length'] = _cfg['max_length']
    -    kwargs['num_decoders'] = _cfg['num_decoders']
    -
    -    # Build the model
    -    model = SAR(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, default_cfgs[arch]['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a VGG16 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import sar_vgg16_bn - >>> model = sar_vgg16_bn(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_vgg16_bn', pretrained, **kwargs)
    - - - -
    -[docs] -def sar_resnet31(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a resnet-31 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example: - >>> import tensorflow as tf - >>> from doctr.models import sar_resnet31 - >>> model = sar_resnet31(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_resnet31', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.0/_modules/doctr/models/recognition/sar/tensorflow.html b/v0.3.0/_modules/doctr/models/recognition/sar/tensorflow.html index e514e4f0c4..4a591e6451 100644 --- a/v0.3.0/_modules/doctr/models/recognition/sar/tensorflow.html +++ b/v0.3.0/_modules/doctr/models/recognition/sar/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.sar.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.sar.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple
    +
     import tensorflow as tf
    -from tensorflow.keras import Sequential, layers, Model
    -from typing import Tuple, Dict, List, Any, Optional
    +from tensorflow.keras import Model, Sequential, layers
     
    -from ... import backbones
    -from ...utils import load_pretrained_params
    -from ..core import RecognitionModel, RecognitionPostProcessor
    +from doctr.datasets import VOCABS
     from doctr.utils.repr import NestedObject
     
    -__all__ = ['SAR', 'SARPostProcessor', 'sar_vgg16_bn', 'sar_resnet31']
    +from ...classification import resnet31
    +from ...utils.tensorflow import _bf16_to_float32, _build_model, load_pretrained_params
    +from ..core import RecognitionModel, RecognitionPostProcessor
    +
    +__all__ = ["SAR", "sar_resnet31"]
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'sar_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1-models/sar_vgg16bn-0d7e2c26.zip',
    -    },
    -    'sar_resnet31': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'resnet31', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/sar_resnet31-ea202587.zip',
    +    "sar_resnet31": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/sar_resnet31-5a58806c.weights.h5&src=0",
         },
     }
     
     
    +class SAREncoder(layers.Layer, NestedObject):
    +    """Implements encoder module of the SAR model
    +
    +    Args:
    +    ----
    +        rnn_units: number of hidden rnn units
    +        dropout_prob: dropout probability
    +    """
    +
    +    def __init__(self, rnn_units: int, dropout_prob: float = 0.0) -> None:
    +        super().__init__()
    +        self.rnn = Sequential([
    +            layers.LSTM(units=rnn_units, return_sequences=True, recurrent_dropout=dropout_prob),
    +            layers.LSTM(units=rnn_units, return_sequences=False, recurrent_dropout=dropout_prob),
    +        ])
    +
    +    def call(
    +        self,
    +        x: tf.Tensor,
    +        **kwargs: Any,
    +    ) -> tf.Tensor:
    +        # (N, C)
    +        return self.rnn(x, **kwargs)
    +
    +
     class AttentionModule(layers.Layer, NestedObject):
         """Implements attention module of the SAR model
     
         Args:
    +    ----
             attention_units: number of hidden attention units
     
         """
    -    def __init__(
    -        self,
    -        attention_units: int
    -    ) -> None:
     
    +    def __init__(self, attention_units: int) -> None:
             super().__init__()
             self.hidden_state_projector = layers.Conv2D(
    -            attention_units, 1, strides=1, use_bias=False, padding='same', kernel_initializer='he_normal',
    +            attention_units,
    +            1,
    +            strides=1,
    +            use_bias=False,
    +            padding="same",
    +            kernel_initializer="he_normal",
             )
             self.features_projector = layers.Conv2D(
    -            attention_units, 3, strides=1, use_bias=True, padding='same', kernel_initializer='he_normal',
    +            attention_units,
    +            3,
    +            strides=1,
    +            use_bias=True,
    +            padding="same",
    +            kernel_initializer="he_normal",
             )
             self.attention_projector = layers.Conv2D(
    -            1, 1, strides=1, use_bias=False, padding="same", kernel_initializer='he_normal',
    +            1,
    +            1,
    +            strides=1,
    +            use_bias=False,
    +            padding="same",
    +            kernel_initializer="he_normal",
             )
             self.flatten = layers.Flatten()
     
    @@ -343,12 +395,12 @@ 

    Source code for doctr.models.recognition.sar.tensorflow

    hidden_state: tf.Tensor, **kwargs: Any, ) -> tf.Tensor: - [H, W] = features.get_shape().as_list()[1:3] - # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units) - hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs) # shape (N, H, W, vgg_units) -> (N, H, W, attention_units) features_projection = self.features_projector(features, **kwargs) + # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units) + hidden_state = tf.expand_dims(tf.expand_dims(hidden_state, axis=1), axis=1) + hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs) projection = tf.math.tanh(hidden_state_projection + features_projection) # shape (N, H, W, attention_units) -> (N, H, W, 1) attention = self.attention_projector(projection, **kwargs) @@ -358,23 +410,25 @@

    Source code for doctr.models.recognition.sar.tensorflow

    # shape (N, H * W) -> (N, H, W, 1) attention_map = tf.reshape(attention, [-1, H, W, 1]) glimpse = tf.math.multiply(features, attention_map) - # shape (N, H * W) -> (N, 1) - glimpse = tf.reduce_sum(glimpse, axis=[1, 2]) - return glimpse + # shape (N, H * W) -> (N, C) + return tf.reduce_sum(glimpse, axis=[1, 2]) class SARDecoder(layers.Layer, NestedObject): """Implements decoder module of the SAR model Args: + ---- rnn_units: number of hidden units in recurrent cells max_length: maximum length of a sequence vocab_size: number of classes in the model alphabet embedding_units: number of hidden embedding units attention_units: number of hidden attention units - num_decoder_layers: number of LSTM layers to stack + num_decoder_cells: number of LSTMCell layers to stack + dropout_prob: dropout probability """ + def __init__( self, rnn_units: int, @@ -382,23 +436,22 @@

    Source code for doctr.models.recognition.sar.tensorflow

    vocab_size: int, embedding_units: int, attention_units: int, - num_decoder_layers: int = 2, - input_shape: Optional[List[Tuple[Optional[int]]]] = None, + num_decoder_cells: int = 2, + dropout_prob: float = 0.0, ) -> None: - super().__init__() self.vocab_size = vocab_size - self.lstm_decoder = layers.StackedRNNCells( - [layers.LSTMCell(rnn_units, dtype=tf.float32, implementation=1) for _ in range(num_decoder_layers)] - ) - self.embed = layers.Dense(embedding_units, use_bias=False, input_shape=(None, self.vocab_size + 1)) - self.attention_module = AttentionModule(attention_units) - self.output_dense = layers.Dense(vocab_size + 1, use_bias=True, input_shape=(None, 2 * rnn_units)) self.max_length = max_length - # Initialize kernels - if input_shape is not None: - self.attention_module.call(layers.Input(input_shape[0][1:]), layers.Input((1, 1, rnn_units))) + self.embed = layers.Dense(embedding_units, use_bias=False) + self.embed_tgt = layers.Embedding(embedding_units, self.vocab_size + 1) + + self.lstm_cells = layers.StackedRNNCells([ + layers.LSTMCell(rnn_units, implementation=1) for _ in range(num_decoder_cells) + ]) + self.attention_module = AttentionModule(attention_units) + self.output_dense = layers.Dense(self.vocab_size + 1, use_bias=True) + self.dropout = layers.Dropout(dropout_prob) def call( self, @@ -407,40 +460,47 @@

    Source code for doctr.models.recognition.sar.tensorflow

    gt: Optional[tf.Tensor] = None, **kwargs: Any, ) -> tf.Tensor: - - # initialize states (each of shape (N, rnn_units)) - states = self.lstm_decoder.get_initial_state( - inputs=None, batch_size=features.shape[0], dtype=tf.float32 - ) - # run first step of lstm - # holistic: shape (N, rnn_units) - _, states = self.lstm_decoder(holistic, states, **kwargs) - # Initialize with the index of virtual START symbol (placed after <eos>) - symbol = tf.fill(features.shape[0], self.vocab_size + 1) - logits_list = [] - if kwargs.get('training') and gt is None: - raise ValueError('Need to provide labels during training for teacher forcing') - for t in range(self.max_length + 1): # keep 1 step for <eos> - # one-hot symbol with depth vocab_size + 1 - # embeded_symbol: shape (N, embedding_units) - embeded_symbol = self.embed(tf.one_hot(symbol, depth=self.vocab_size + 1), **kwargs) - logits, states = self.lstm_decoder(embeded_symbol, states, **kwargs) - glimpse = self.attention_module( - features, tf.expand_dims(tf.expand_dims(logits, axis=1), axis=1), **kwargs, - ) - # logits: shape (N, rnn_units), glimpse: shape (N, 1) - logits = tf.concat([logits, glimpse], axis=-1) - # shape (N, rnn_units + 1) -> (N, vocab_size + 1) - logits = self.output_dense(logits, **kwargs) - # update symbol with predicted logits for t+1 step - if kwargs.get('training'): - symbol = gt[:, t] # type: ignore[index] + if gt is not None: + gt_embedding = self.embed_tgt(gt, **kwargs) + + logits_list: List[tf.Tensor] = [] + + for t in range(self.max_length + 1): # 32 + if t == 0: + # step to init the first states of the LSTMCell + states = self.lstm_cells.get_initial_state( + inputs=None, batch_size=features.shape[0], dtype=features.dtype + ) + prev_symbol = holistic + elif t == 1: + # step to init a 'blank' sequence of length vocab_size + 1 filled with zeros + # (N, vocab_size + 1) --> (N, embedding_units) + prev_symbol = tf.zeros([features.shape[0], self.vocab_size + 1], dtype=features.dtype) + prev_symbol = self.embed(prev_symbol, **kwargs) else: - symbol = tf.argmax(logits, axis=-1) - logits_list.append(logits) - outputs = tf.stack(logits_list, axis=1) # shape (N, max_length + 1, vocab_size + 1) - - return outputs + if gt is not None and kwargs.get("training", False): + # (N, embedding_units) -2 because of <bos> and <eos> (same) + prev_symbol = self.embed(gt_embedding[:, t - 2], **kwargs) + else: + # -1 to start at timestep where prev_symbol was initialized + index = tf.argmax(logits_list[t - 1], axis=-1) + # update prev_symbol with ones at the index of the previous logit vector + prev_symbol = self.embed(self.embed_tgt(index, **kwargs), **kwargs) + + # (N, C), (N, C) take the last hidden state and cell state from current timestep + _, states = self.lstm_cells(prev_symbol, states, **kwargs) + # states = (hidden_state, cell_state) + hidden_state = states[0][0] + # (N, H, W, C), (N, C) --> (N, C) + glimpse = self.attention_module(features, hidden_state, **kwargs) + # (N, C), (N, C) --> (N, 2 * C) + logits = tf.concat([hidden_state, glimpse], axis=1) + logits = self.dropout(logits, **kwargs) + # (N, vocab_size + 1) + logits_list.append(self.output_dense(logits, **kwargs)) + + # (max_length + 1, N, vocab_size + 1) --> (N, max_length + 1, vocab_size + 1) + return tf.transpose(tf.stack(logits_list[1:]), (1, 0, 2)) class SAR(Model, RecognitionModel): @@ -448,17 +508,20 @@

    Source code for doctr.models.recognition.sar.tensorflow

    Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. Args: + ---- feature_extractor: the backbone serving as feature extractor vocab: vocabulary used for encoding rnn_units: number of hidden units in both encoder and decoder LSTM embedding_units: number of embedding units attention_units: number of hidden units in attention module max_length: maximum word length handled by the model - num_decoders: number of LSTM to stack in decoder layer - + num_decoder_cells: number of LSTMCell layers to stack + dropout_prob: dropout probability for the encoder and decoder + exportable: onnx exportable returns only logits + cfg: dictionary containing information about the model """ - _children_names: List[str] = ['feat_extractor', 'encoder', 'decoder', 'postprocessor'] + _children_names: List[str] = ["feat_extractor", "encoder", "decoder", "postprocessor"] def __init__( self, @@ -468,36 +531,34 @@

    Source code for doctr.models.recognition.sar.tensorflow

    embedding_units: int = 512, attention_units: int = 512, max_length: int = 30, - num_decoders: int = 2, + num_decoder_cells: int = 2, + dropout_prob: float = 0.0, + exportable: bool = False, cfg: Optional[Dict[str, Any]] = None, ) -> None: - super().__init__() self.vocab = vocab + self.exportable = exportable self.cfg = cfg - self.max_length = max_length + 1 # Add 1 timestep for EOS after the longest word self.feat_extractor = feature_extractor - self.encoder = Sequential( - [ - layers.LSTM(units=rnn_units, return_sequences=True), - layers.LSTM(units=rnn_units, return_sequences=False) - ] - ) - # Initialize the kernels (watch out for reduce_max) - self.encoder.build(input_shape=(None,) + self.feat_extractor.output_shape[2:]) - + self.encoder = SAREncoder(rnn_units, dropout_prob) self.decoder = SARDecoder( - rnn_units, max_length, len(vocab), embedding_units, attention_units, num_decoders, - input_shape=[self.feat_extractor.output_shape, self.encoder.output_shape] + rnn_units, + self.max_length, + len(vocab), + embedding_units, + attention_units, + num_decoder_cells, + dropout_prob, ) self.postprocessor = SARPostProcessor(vocab=vocab) + @staticmethod def compute_loss( - self, model_output: tf.Tensor, gt: tf.Tensor, seq_len: tf.Tensor, @@ -506,11 +567,13 @@

    Source code for doctr.models.recognition.sar.tensorflow

    Sequences are masked after the EOS character. Args: + ---- gt: the encoded tensor with gt labels model_output: predicted logits of the model seq_len: lengths of each gt word inside the batch Returns: + ------- The loss of the model on the batch """ # Input length : number of timesteps @@ -525,7 +588,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    mask_values = tf.zeros_like(cce) mask_2d = tf.sequence_mask(seq_len, input_len) masked_loss = tf.where(mask_2d, cce, mask_values) - ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32)) + ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, model_output.dtype)) return tf.expand_dims(ce_loss, axis=1) def call( @@ -536,16 +599,28 @@

    Source code for doctr.models.recognition.sar.tensorflow

    return_preds: bool = False, **kwargs: Any, ) -> Dict[str, Any]: - features = self.feat_extractor(x, **kwargs) - pooled_features = tf.reduce_max(features, axis=1) # vertical max pooling + # vertical max pooling --> (N, C, W) + pooled_features = tf.reduce_max(features, axis=1) + # holistic (N, C) encoded = self.encoder(pooled_features, **kwargs) + if target is not None: - gt, seq_len = self.compute_target(target) + gt, seq_len = self.build_target(target) seq_len = tf.cast(seq_len, tf.int32) - decoded_features = self.decoder(features, encoded, gt=None if target is None else gt, **kwargs) + + if kwargs.get("training", False) and target is None: + raise ValueError("Need to provide labels during training for teacher forcing") + + decoded_features = _bf16_to_float32( + self.decoder(features, encoded, gt=None if target is None else gt, **kwargs) + ) out: Dict[str, tf.Tensor] = {} + if self.exportable: + out["logits"] = decoded_features + return out + if return_model_output: out["out_map"] = decoded_features @@ -554,7 +629,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    out["preds"] = self.postprocessor(decoded_features) if target is not None: - out['loss'] = self.compute_loss(decoded_features, gt, seq_len) + out["loss"] = self.compute_loss(decoded_features, gt, seq_len) return out @@ -563,9 +638,8 @@

    Source code for doctr.models.recognition.sar.tensorflow

    """Post processor for SAR architectures Args: + ---- vocab: string containing the ordered sequence of supported characters - ignore_case: if True, ignore case of letters - ignore_accents: if True, ignore accents of letters """ def __call__( @@ -580,95 +654,75 @@

    Source code for doctr.models.recognition.sar.tensorflow

    probs = tf.math.reduce_min(probs, axis=1) # decode raw output of the model with tf_label_to_idx - out_idxs = tf.cast(out_idxs, dtype='int32') + out_idxs = tf.cast(out_idxs, dtype="int32") embedding = tf.constant(self._embedding, dtype=tf.string) decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(embedding, out_idxs), axis=-1) decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>") - decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0] + decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value="not valid")[:, 0] word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] - return list(zip(word_values, probs.numpy().tolist())) + return list(zip(word_values, probs.numpy().clip(0, 1).tolist())) -def _sar(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> SAR: +def _sar( + arch: str, + pretrained: bool, + backbone_fn, + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> SAR: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab']) - _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units']) - _cfg['embedding_units'] = kwargs.get('embedding_units', _cfg['rnn_units']) - _cfg['attention_units'] = kwargs.get('attention_units', _cfg['rnn_units']) - _cfg['max_length'] = kwargs.get('max_length', _cfg['max_length']) - _cfg['num_decoders'] = kwargs.get('num_decoders', _cfg['num_decoders']) + _cfg["input_shape"] = input_shape or _cfg["input_shape"] + _cfg["vocab"] = kwargs.get("vocab", _cfg["vocab"]) # Feature extractor - feat_extractor = backbones.__dict__[default_cfgs[arch]['backbone']]( - input_shape=_cfg['input_shape'], + feat_extractor = backbone_fn( + pretrained=pretrained_backbone, + input_shape=_cfg["input_shape"], include_top=False, ) - kwargs['vocab'] = _cfg['vocab'] - kwargs['rnn_units'] = _cfg['rnn_units'] - kwargs['embedding_units'] = _cfg['embedding_units'] - kwargs['attention_units'] = _cfg['attention_units'] - kwargs['max_length'] = _cfg['max_length'] - kwargs['num_decoders'] = _cfg['num_decoders'] + kwargs["vocab"] = _cfg["vocab"] # Build the model model = SAR(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) # Load pretrained parameters if pretrained: - load_pretrained_params(model, default_cfgs[arch]['url']) + # The given vocab differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, default_cfgs[arch]["url"], skip_mismatch=kwargs["vocab"] != default_cfgs[arch]["vocab"] + ) return model -
    -[docs] -def sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a VGG16 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import sar_vgg16_bn - >>> model = sar_vgg16_bn(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_vgg16_bn', pretrained, **kwargs)
    - - -
    -[docs] +[docs] def sar_resnet31(pretrained: bool = False, **kwargs: Any) -> SAR: """SAR with a resnet-31 feature extractor as described in `"Show, Attend and Read:A Simple and Strong Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - Example: - >>> import tensorflow as tf - >>> from doctr.models import sar_resnet31 - >>> model = sar_resnet31(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import sar_resnet31 + >>> model = sar_resnet31(pretrained=False) + >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the SAR architecture Returns: + ------- text recognition architecture """ - - return _sar('sar_resnet31', pretrained, **kwargs)
    + return _sar("sar_resnet31", pretrained, resnet31, **kwargs)
    @@ -702,8 +756,8 @@

    Source code for doctr.models.recognition.sar.tensorflow

    - +
    + diff --git a/v0.3.0/_modules/doctr/models/recognition/vitstr/tensorflow.html b/v0.3.0/_modules/doctr/models/recognition/vitstr/tensorflow.html index 6e101893bf..c594d40a56 100644 --- a/v0.3.0/_modules/doctr/models/recognition/vitstr/tensorflow.html +++ b/v0.3.0/_modules/doctr/models/recognition/vitstr/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.vitstr.tensorflow - docTR documentation @@ -621,7 +621,7 @@

    Source code for doctr.models.recognition.vitstr.tensorflow

    - + diff --git a/v0.3.0/_modules/doctr/models/recognition/zoo.html b/v0.3.0/_modules/doctr/models/recognition/zoo.html index bf0ae6af6e..f664304019 100644 --- a/v0.3.0/_modules/doctr/models/recognition/zoo.html +++ b/v0.3.0/_modules/doctr/models/recognition/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.zoo - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -from typing import Any
    +from typing import Any, List
     
    -from doctr.file_utils import is_tf_available, is_torch_available
    -from .core import RecognitionPredictor
    -from ..preprocessor import PreProcessor
    -from .. import recognition
    +from doctr.file_utils import is_tf_available
    +from doctr.models.preprocessor import PreProcessor
     
    +from .. import recognition
    +from .predictor import RecognitionPredictor
     
     __all__ = ["recognition_predictor"]
     
     
    -if is_tf_available():
    -    ARCHS = ['crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31', 'master']
    -elif is_torch_available():
    -    ARCHS = ['crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31']
    +ARCHS: List[str] = [
    +    "crnn_vgg16_bn",
    +    "crnn_mobilenet_v3_small",
    +    "crnn_mobilenet_v3_large",
    +    "sar_resnet31",
    +    "master",
    +    "vitstr_small",
    +    "vitstr_base",
    +    "parseq",
    +]
    +
     
    +def _predictor(arch: Any, pretrained: bool, **kwargs: Any) -> RecognitionPredictor:
    +    if isinstance(arch, str):
    +        if arch not in ARCHS:
    +            raise ValueError(f"unknown architecture '{arch}'")
     
    -def _predictor(arch: str, pretrained: bool, **kwargs: Any) -> RecognitionPredictor:
    +        _model = recognition.__dict__[arch](
    +            pretrained=pretrained, pretrained_backbone=kwargs.get("pretrained_backbone", True)
    +        )
    +    else:
    +        if not isinstance(
    +            arch, (recognition.CRNN, recognition.SAR, recognition.MASTER, recognition.ViTSTR, recognition.PARSeq)
    +        ):
    +            raise ValueError(f"unknown architecture: {type(arch)}")
    +        _model = arch
     
    -    if arch not in ARCHS:
    -        raise ValueError(f"unknown architecture '{arch}'")
    +    kwargs.pop("pretrained_backbone", None)
     
    -    _model = recognition.__dict__[arch](pretrained=pretrained)
    -    kwargs['mean'] = kwargs.get('mean', _model.cfg['mean'])
    -    kwargs['std'] = kwargs.get('std', _model.cfg['std'])
    -    kwargs['batch_size'] = kwargs.get('batch_size', 32)
    -    predictor = RecognitionPredictor(
    -        PreProcessor(_model.cfg['input_shape'][:2], preserve_aspect_ratio=True, **kwargs),
    -        _model
    -    )
    +    kwargs["mean"] = kwargs.get("mean", _model.cfg["mean"])
    +    kwargs["std"] = kwargs.get("std", _model.cfg["std"])
    +    kwargs["batch_size"] = kwargs.get("batch_size", 128)
    +    input_shape = _model.cfg["input_shape"][:2] if is_tf_available() else _model.cfg["input_shape"][-2:]
    +    predictor = RecognitionPredictor(PreProcessor(input_shape, preserve_aspect_ratio=True, **kwargs), _model)
     
         return predictor
     
     
     
    -[docs] -def recognition_predictor(arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) -> RecognitionPredictor: +[docs] +def recognition_predictor( + arch: Any = "crnn_vgg16_bn", + pretrained: bool = False, + symmetric_pad: bool = False, + batch_size: int = 128, + **kwargs: Any, +) -> RecognitionPredictor: """Text recognition architecture. Example:: @@ -326,14 +369,18 @@

    Source code for doctr.models.recognition.zoo

            >>> out = model([input_page])
     
         Args:
    -        arch: name of the architecture to use ('crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31')
    +    ----
    +        arch: name of the architecture or model itself to use (e.g. 'crnn_vgg16_bn')
             pretrained: If True, returns a model pre-trained on our text recognition dataset
    +        symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right
    +        batch_size: number of samples the model processes in parallel
    +        **kwargs: optional parameters to be passed to the architecture
     
         Returns:
    +    -------
             Recognition predictor
         """
    -
    -    return _predictor(arch, pretrained, **kwargs)
    + return _predictor(arch=arch, pretrained=pretrained, symmetric_pad=symmetric_pad, batch_size=batch_size, **kwargs)
    @@ -367,8 +414,8 @@

    Source code for doctr.models.recognition.zoo

       
    -
    - +
    + diff --git a/v0.3.0/_modules/doctr/models/zoo.html b/v0.3.0/_modules/doctr/models/zoo.html index dec6857019..d459671648 100644 --- a/v0.3.0/_modules/doctr/models/zoo.html +++ b/v0.3.0/_modules/doctr/models/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.zoo - docTR documentation @@ -225,15 +225,42 @@

    Source code for doctr.models.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     from typing import Any
    -from .core import OCRPredictor
    +
     from .detection.zoo import detection_predictor
    +from .kie_predictor import KIEPredictor
    +from .predictor import OCRPredictor
     from .recognition.zoo import recognition_predictor
     
    +__all__ = ["ocr_predictor", "kie_predictor"]
     
    -__all__ = ["ocr_predictor"]
    -
    -
    -def _predictor(det_arch: str, reco_arch: str, pretrained: bool, det_bs=2, reco_bs=128) -> OCRPredictor:
     
    +def _predictor(
    +    det_arch: Any,
    +    reco_arch: Any,
    +    pretrained: bool,
    +    pretrained_backbone: bool = True,
    +    assume_straight_pages: bool = True,
    +    preserve_aspect_ratio: bool = True,
    +    symmetric_pad: bool = True,
    +    det_bs: int = 2,
    +    reco_bs: int = 128,
    +    detect_orientation: bool = False,
    +    straighten_pages: bool = False,
    +    detect_language: bool = False,
    +    **kwargs,
    +) -> OCRPredictor:
         # Detection
    -    det_predictor = detection_predictor(det_arch, pretrained=pretrained, batch_size=det_bs)
    +    det_predictor = detection_predictor(
    +        det_arch,
    +        pretrained=pretrained,
    +        pretrained_backbone=pretrained_backbone,
    +        batch_size=det_bs,
    +        assume_straight_pages=assume_straight_pages,
    +        preserve_aspect_ratio=preserve_aspect_ratio,
    +        symmetric_pad=symmetric_pad,
    +    )
     
         # Recognition
    -    reco_predictor = recognition_predictor(reco_arch, pretrained=pretrained, batch_size=reco_bs)
    +    reco_predictor = recognition_predictor(
    +        reco_arch,
    +        pretrained=pretrained,
    +        pretrained_backbone=pretrained_backbone,
    +        batch_size=reco_bs,
    +    )
     
    -    return OCRPredictor(det_predictor, reco_predictor)
    +    return OCRPredictor(
    +        det_predictor,
    +        reco_predictor,
    +        assume_straight_pages=assume_straight_pages,
    +        preserve_aspect_ratio=preserve_aspect_ratio,
    +        symmetric_pad=symmetric_pad,
    +        detect_orientation=detect_orientation,
    +        straighten_pages=straighten_pages,
    +        detect_language=detect_language,
    +        **kwargs,
    +    )
     
     
     
    -[docs] +[docs] def ocr_predictor( - det_arch: str = 'db_resnet50', - reco_arch: str = 'crnn_vgg16_bn', + det_arch: Any = "fast_base", + reco_arch: Any = "crnn_vgg16_bn", pretrained: bool = False, - **kwargs: Any + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + export_as_straight_boxes: bool = False, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs: Any, ) -> OCRPredictor: """End-to-end OCR architecture using one model for localization, and another for text recognition. - Example:: - >>> import numpy as np - >>> from doctr.models import ocr_predictor - >>> model = ocr_predictor(pretrained=True) - >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) - >>> out = model([input_page]) + >>> 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(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) Args: - arch: name of the architecture to use ('db_sar_vgg', 'db_sar_resnet', 'db_crnn_vgg', 'db_crnn_resnet') + ---- + det_arch: name of the detection architecture or the model itself to use + (e.g. 'db_resnet50', 'db_mobilenet_v3_large') + reco_arch: name of the recognition architecture or the model itself to use + (e.g. 'crnn_vgg16_bn', 'sar_resnet31') pretrained: If True, returns a model pre-trained on our OCR dataset + pretrained_backbone: If True, returns a model with a pretrained backbone + assume_straight_pages: if True, speeds up the inference by assuming you only pass straight pages + without rotated textual elements. + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it. + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right. + export_as_straight_boxes: when assume_straight_pages is set to False, export final predictions + (potentially rotated) as straight bounding boxes. + detect_orientation: if True, the estimated general page orientation will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + straighten_pages: if True, estimates the page general orientation + based on the segmentation map median line orientation. + Then, rotates page before passing it again to the deep learning detection module. + Doing so will improve performances for documents with page-uniform rotations. + detect_language: if True, the language prediction will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + kwargs: keyword args of `OCRPredictor` Returns: + ------- OCR predictor """ + return _predictor( + det_arch, + reco_arch, + pretrained, + pretrained_backbone=pretrained_backbone, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + export_as_straight_boxes=export_as_straight_boxes, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + )
    + + - return _predictor(det_arch, reco_arch, pretrained, **kwargs)
    +def _kie_predictor( + det_arch: Any, + reco_arch: Any, + pretrained: bool, + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + det_bs: int = 2, + reco_bs: int = 128, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs, +) -> KIEPredictor: + # Detection + det_predictor = detection_predictor( + det_arch, + pretrained=pretrained, + pretrained_backbone=pretrained_backbone, + batch_size=det_bs, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + ) + + # Recognition + reco_predictor = recognition_predictor( + reco_arch, + pretrained=pretrained, + pretrained_backbone=pretrained_backbone, + batch_size=reco_bs, + ) + + return KIEPredictor( + det_predictor, + reco_predictor, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + ) + + +
    +[docs] +def kie_predictor( + det_arch: Any = "fast_base", + reco_arch: Any = "crnn_vgg16_bn", + pretrained: bool = False, + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + export_as_straight_boxes: bool = False, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs: Any, +) -> KIEPredictor: + """End-to-end KIE architecture using one model for localization, and another for text recognition. + + >>> 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(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) + + Args: + ---- + det_arch: name of the detection architecture or the model itself to use + (e.g. 'db_resnet50', 'db_mobilenet_v3_large') + reco_arch: name of the recognition architecture or the model itself to use + (e.g. 'crnn_vgg16_bn', 'sar_resnet31') + pretrained: If True, returns a model pre-trained on our OCR dataset + pretrained_backbone: If True, returns a model with a pretrained backbone + assume_straight_pages: if True, speeds up the inference by assuming you only pass straight pages + without rotated textual elements. + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it. + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right. + export_as_straight_boxes: when assume_straight_pages is set to False, export final predictions + (potentially rotated) as straight bounding boxes. + detect_orientation: if True, the estimated general page orientation will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + straighten_pages: if True, estimates the page general orientation + based on the segmentation map median line orientation. + Then, rotates page before passing it again to the deep learning detection module. + Doing so will improve performances for documents with page-uniform rotations. + detect_language: if True, the language prediction will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + kwargs: keyword args of `OCRPredictor` + + Returns: + ------- + KIE predictor + """ + return _kie_predictor( + det_arch, + reco_arch, + pretrained, + pretrained_backbone=pretrained_backbone, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + export_as_straight_boxes=export_as_straight_boxes, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + )
    @@ -353,8 +575,8 @@

    Source code for doctr.models.zoo

           
         
       
    - - + + diff --git a/v0.3.0/_modules/doctr/transforms/modules.html b/v0.3.0/_modules/doctr/transforms/modules.html deleted file mode 100644 index ba8269e7ef..0000000000 --- a/v0.3.0/_modules/doctr/transforms/modules.html +++ /dev/null @@ -1,734 +0,0 @@ - - - - - - - - - - - - doctr.transforms.modules - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.transforms.modules

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import random
    -import tensorflow as tf
    -from typing import List, Any, Tuple, Callable
    -
    -from doctr.utils.repr import NestedObject
    -from . import functional as F
    -
    -
    -__all__ = ['Compose', 'Resize', 'Normalize', 'LambdaTransformation', 'ToGray', 'ColorInversion',
    -           'RandomBrightness', 'RandomContrast', 'RandomSaturation', 'RandomHue', 'RandomGamma', 'RandomJpegQuality',
    -           'OneOf', 'RandomApply']
    -
    -
    -
    -[docs] -class Compose(NestedObject): - """Implements a wrapper that will apply transformations sequentially - - Example:: - >>> from doctr.transforms import Compose, Resize - >>> import tensorflow as tf - >>> transfos = Compose([Resize((32, 32))]) - >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformation modules - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, x: Any) -> Any: - for t in self.transforms: - x = t(x) - - return x
    - - - -
    -[docs] -class Resize(NestedObject): - """Resizes a tensor to a target size - - Example:: - >>> from doctr.transforms import Resize - >>> import tensorflow as tf - >>> transfo = Resize((32, 32)) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - output_size: expected output size - method: interpolation method - preserve_aspect_ratio: if `True`, preserve aspect ratio and pad the rest with zeros - symmetric_pad: if `True` while preserving aspect ratio, the padding will be done symmetrically - """ - def __init__( - self, - output_size: Tuple[int, int], - method: str = 'bilinear', - preserve_aspect_ratio: bool = False, - symmetric_pad: bool = False, - ) -> None: - self.output_size = output_size - self.method = method - self.preserve_aspect_ratio = preserve_aspect_ratio - self.symmetric_pad = symmetric_pad - - def extra_repr(self) -> str: - _repr = f"output_size={self.output_size}, method='{self.method}'" - if self.preserve_aspect_ratio: - _repr += f", preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}" - return _repr - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img = tf.image.resize(img, self.output_size, self.method, self.preserve_aspect_ratio) - if self.preserve_aspect_ratio: - # pad width - if not self.symmetric_pad: - offset = (0, 0) - elif self.output_size[0] == img.shape[0]: - offset = (0, int((self.output_size[1] - img.shape[1]) / 2)) - else: - offset = (int((self.output_size[0] - img.shape[0]) / 2), 0) - img = tf.image.pad_to_bounding_box(img, *offset, *self.output_size) - return img
    - - - -
    -[docs] -class Normalize(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - mean: average value per channel - std: standard deviation per channel - """ - def __init__(self, mean: Tuple[float, float, float], std: Tuple[float, float, float]) -> None: - self.mean = tf.constant(mean, dtype=tf.float32) - self.std = tf.constant(std, dtype=tf.float32) - - def extra_repr(self) -> str: - return f"mean={self.mean.numpy().tolist()}, std={self.std.numpy().tolist()}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img -= self.mean - img /= self.std - return img
    - - - -
    -[docs] -class LambdaTransformation(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import LambdaTransformation - >>> import tensorflow as tf - >>> transfo = LambdaTransformation(lambda x: x/ 255.) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - fn: the function to be applied to the input tensor - """ - def __init__(self, fn: Callable[[tf.Tensor], tf.Tensor]) -> None: - self.fn = fn - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return self.fn(img)
    - - - -
    -[docs] -class ToGray(NestedObject): - """Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ToGray() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - """ - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.rgb_to_grayscale(img)
    - - - -
    -[docs] -class ColorInversion(NestedObject): - """Applies the following tranformation to a tensor (image or batch of images): - convert to grayscale, colorize (shift 0-values randomly), and then invert colors - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ColorInversion(min_val=0.6) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_val: range [min_val, 1] to colorize RGB pixels - """ - def __init__(self, min_val: float = 0.6) -> None: - self.min_val = min_val - - def extra_repr(self) -> str: - return f"min_val={self.min_val}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return F.invert_colors(img, self.min_val)
    - - - -
    -[docs] -class RandomBrightness(NestedObject): - """Randomly adjust brightness of a tensor (batch of images or image) by adding a delta - to all pixels - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Brightness() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - p: probability to apply transformation - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_brightness(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomContrast(NestedObject): - """Randomly adjust contrast of a tensor (batch of images or image) by adjusting - each pixel: (img - mean) * contrast_factor + mean. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Contrast() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1) - """ - def __init__(self, delta: float = .3) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_contrast(img, lower=1 - self.delta, upper=1 / (1 - self.delta))
    - - - -
    -[docs] -class RandomSaturation(NestedObject): - """Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and - increasing saturation by a factor. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Saturation() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1) - """ - def __init__(self, delta: float = .5) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_saturation(img, lower=1 - self.delta, upper=1 + self.delta)
    - - - -
    -[docs] -class RandomHue(NestedObject): - """Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Hue() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_hue(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomGamma(NestedObject): - """randomly performs gamma correction for a tensor (batch of images or image) - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Gamma() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_gamma: non-negative real number, lower bound for gamma param - max_gamma: non-negative real number, upper bound for gamma - min_gain: lower bound for constant multiplier - max_gain: upper bound for constant multiplier - """ - def __init__( - self, - min_gamma: float = 0.5, - max_gamma: float = 1.5, - min_gain: float = 0.8, - max_gain: float = 1.2, - ) -> None: - self.min_gamma = min_gamma - self.max_gamma = max_gamma - self.min_gain = min_gain - self.max_gain = max_gain - - def extra_repr(self) -> str: - return f"""gamma_range=({self.min_gamma}, {self.max_gamma}), - gain_range=({self.min_gain}, {self.max_gain})""" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - gamma = random.uniform(self.min_gamma, self.max_gamma) - gain = random.uniform(self.min_gain, self.max_gain) - return tf.image.adjust_gamma(img, gamma=gamma, gain=gain)
    - - - -
    -[docs] -class RandomJpegQuality(NestedObject): - """Randomly adjust jpeg quality of a 3 dimensional RGB image - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = JpegQuality() - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - min_quality: int between [0, 100] - max_quality: int between [0, 100] - """ - def __init__(self, min_quality: int = 60, max_quality: int = 100) -> None: - self.min_quality = min_quality - self.max_quality = max_quality - - def extra_repr(self) -> str: - return f"min_quality={self.min_quality}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_jpeg_quality( - img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality - )
    - - - -
    -[docs] -class OneOf(NestedObject): - """Randomly apply one of the input transformations - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = OneOf([JpegQuality(), Gamma()]) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformations, one only will be picked - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - # Pick transformation - transfo = self.transforms[int(random.random() * len(self.transforms))] - # Apply - return transfo(img)
    - - - -
    -[docs] -class RandomApply(NestedObject): - """Apply with a probability p the input transformation - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = RandomApply(Gamma(), p=.5) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transform: transformation to apply - p: probability to apply - """ - def __init__(self, transform: NestedObject, p: float = .5) -> None: - self.transform = transform - self.p = p - - def extra_repr(self) -> str: - return f"transform={self.transform}, p={self.p}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - if random.random() < self.p: - return self.transform(img) - return img
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.0/_modules/doctr/transforms/modules/base.html b/v0.3.0/_modules/doctr/transforms/modules/base.html index c42079a8fd..4596df3848 100644 --- a/v0.3.0/_modules/doctr/transforms/modules/base.html +++ b/v0.3.0/_modules/doctr/transforms/modules/base.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.base - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.transforms.modules.base

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    +import math
     import random
    -from typing import List, Any, Callable
    +from typing import Any, Callable, List, Optional, Tuple, Union
    +
    +import numpy as np
     
     from doctr.utils.repr import NestedObject
    +
     from .. import functional as F
     
    +__all__ = ["SampleCompose", "ImageTransform", "ColorInversion", "OneOf", "RandomApply", "RandomRotate", "RandomCrop"]
    +
    +
    +class SampleCompose(NestedObject):
    +    """Implements a wrapper that will apply transformations sequentially on both image and target
    +
    +    .. tabs::
    +
    +        .. tab:: TensorFlow
    +
    +            .. code:: python
    +
    +                >>> import numpy as np
    +                >>> import tensorflow as tf
    +                >>> from doctr.transforms import SampleCompose, ImageTransform, ColorInversion, RandomRotate
    +                >>> transfo = SampleCompose([ImageTransform(ColorInversion((32, 32))), RandomRotate(30)])
    +                >>> out, out_boxes = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1), np.zeros((2, 4)))
    +
    +        .. tab:: PyTorch
    +
    +            .. code:: python
    +
    +                >>> import numpy as np
    +                >>> import torch
    +                >>> from doctr.transforms import SampleCompose, ImageTransform, ColorInversion, RandomRotate
    +                >>> transfos = SampleCompose([ImageTransform(ColorInversion((32, 32))), RandomRotate(30)])
    +                >>> out, out_boxes = transfos(torch.rand(8, 64, 64, 3), np.zeros((2, 4)))
    +
    +    Args:
    +    ----
    +        transforms: list of transformation modules
    +    """
    +
    +    _children_names: List[str] = ["sample_transforms"]
    +
    +    def __init__(self, transforms: List[Callable[[Any, Any], Tuple[Any, Any]]]) -> None:
    +        self.sample_transforms = transforms
    +
    +    def __call__(self, x: Any, target: Any) -> Tuple[Any, Any]:
    +        for t in self.sample_transforms:
    +            x, target = t(x, target)
    +
    +        return x, target
    +
    +
    +class ImageTransform(NestedObject):
    +    """Implements a transform wrapper to turn an image-only transformation into an image+target transform
    +
    +    .. tabs::
    +
    +        .. tab:: TensorFlow
    +
    +            .. code:: python
    +
    +                >>> import tensorflow as tf
    +                >>> from doctr.transforms import ImageTransform, ColorInversion
    +                >>> transfo = ImageTransform(ColorInversion((32, 32)))
    +                >>> out, _ = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1), None)
    +
    +        .. tab:: PyTorch
    +
    +            .. code:: python
    +
    +                >>> import torch
    +                >>> from doctr.transforms import ImageTransform, ColorInversion
    +                >>> transfo = ImageTransform(ColorInversion((32, 32)))
    +                >>> out, _ = transfo(torch.rand(8, 64, 64, 3), None)
    +
    +    Args:
    +    ----
    +        transform: the image transformation module to wrap
    +    """
    +
    +    _children_names: List[str] = ["img_transform"]
    +
    +    def __init__(self, transform: Callable[[Any], Any]) -> None:
    +        self.img_transform = transform
     
    -__all__ = ['ColorInversion', 'OneOf', 'RandomApply']
    +    def __call__(self, img: Any, target: Any) -> Tuple[Any, Any]:
    +        img = self.img_transform(img)
    +        return img, target
     
     
     
    -[docs] +[docs] class ColorInversion(NestedObject): """Applies the following tranformation to a tensor (image or batch of images): convert to grayscale, colorize (shift 0-values randomly), and then invert colors - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ColorInversion(min_val=0.6) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + .. tabs:: + + .. tab:: TensorFlow + + .. code:: python + + >>> import tensorflow as tf + >>> from doctr.transforms import ColorInversion + >>> transfo = ColorInversion(min_val=0.6) + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + + .. tab:: PyTorch + + .. code:: python + + >>> import torch + >>> from doctr.transforms import ColorInversion + >>> transfo = ColorInversion(min_val=0.6) + >>> out = transfo(torch.rand(8, 64, 64, 3)) Args: + ---- min_val: range [min_val, 1] to colorize RGB pixels """ + def __init__(self, min_val: float = 0.5) -> None: self.min_val = min_val @@ -316,59 +437,178 @@

    Source code for doctr.transforms.modules.base

    -[docs] +[docs] class OneOf(NestedObject): """Randomly apply one of the input transformations - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = OneOf([JpegQuality(), Gamma()]) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + .. tabs:: + + .. tab:: TensorFlow + + .. code:: python + + >>> import tensorflow as tf + >>> from doctr.transforms import OneOf + >>> transfo = OneOf([JpegQuality(), Gamma()]) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + .. tab:: PyTorch + + .. code:: python + + >>> import torch + >>> from doctr.transforms import OneOf + >>> transfo = OneOf([JpegQuality(), Gamma()]) + >>> out = transfo(torch.rand(1, 64, 64, 3)) Args: + ---- transforms: list of transformations, one only will be picked """ - _children_names: List[str] = ['transforms'] + _children_names: List[str] = ["transforms"] def __init__(self, transforms: List[Callable[[Any], Any]]) -> None: self.transforms = transforms - def __call__(self, img: Any) -> Any: + def __call__(self, img: Any, target: Optional[np.ndarray] = None) -> Union[Any, Tuple[Any, np.ndarray]]: # Pick transformation transfo = self.transforms[int(random.random() * len(self.transforms))] # Apply - return transfo(img)
    + return transfo(img) if target is None else transfo(img, target) # type: ignore[call-arg]
    -[docs] +[docs] class RandomApply(NestedObject): """Apply with a probability p the input transformation - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = RandomApply(Gamma(), p=.5) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + .. tabs:: + + .. tab:: TensorFlow + + .. code:: python + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomApply + >>> transfo = RandomApply(Gamma(), p=.5) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + .. tab:: PyTorch + + .. code:: python + + >>> import torch + >>> from doctr.transforms import RandomApply + >>> transfo = RandomApply(Gamma(), p=.5) + >>> out = transfo(torch.rand(1, 64, 64, 3)) Args: + ---- transform: transformation to apply p: probability to apply """ - def __init__(self, transform: Callable[[Any], Any], p: float = .5) -> None: + + def __init__(self, transform: Callable[[Any], Any], p: float = 0.5) -> None: self.transform = transform self.p = p def extra_repr(self) -> str: return f"transform={self.transform}, p={self.p}" - def __call__(self, img: Any) -> Any: + def __call__(self, img: Any, target: Optional[np.ndarray] = None) -> Union[Any, Tuple[Any, np.ndarray]]: if random.random() < self.p: - return self.transform(img) - return img
    + return self.transform(img) if target is None else self.transform(img, target) # type: ignore[call-arg] + return img if target is None else (img, target)
    + + + +
    +[docs] +class RandomRotate(NestedObject): + """Randomly rotate a tensor image and its boxes + + .. image:: https://doctr-static.mindee.com/models?id=v0.4.0/rotation_illustration.png&src=0 + :align: center + + Args: + ---- + max_angle: maximum angle for rotation, in degrees. Angles will be uniformly picked in + [-max_angle, max_angle] + expand: whether the image should be padded before the rotation + """ + + def __init__(self, max_angle: float = 5.0, expand: bool = False) -> None: + self.max_angle = max_angle + self.expand = expand + + def extra_repr(self) -> str: + return f"max_angle={self.max_angle}, expand={self.expand}" + + def __call__(self, img: Any, target: np.ndarray) -> Tuple[Any, np.ndarray]: + angle = random.uniform(-self.max_angle, self.max_angle) + r_img, r_polys = F.rotate_sample(img, target, angle, self.expand) + # Removes deleted boxes + is_kept = (r_polys.max(1) > r_polys.min(1)).sum(1) == 2 + return r_img, r_polys[is_kept]
    + + + +
    +[docs] +class RandomCrop(NestedObject): + """Randomly crop a tensor image and its boxes + + Args: + ---- + scale: tuple of floats, relative (min_area, max_area) of the crop + ratio: tuple of float, relative (min_ratio, max_ratio) where ratio = h/w + """ + + def __init__(self, scale: Tuple[float, float] = (0.08, 1.0), ratio: Tuple[float, float] = (0.75, 1.33)) -> None: + self.scale = scale + self.ratio = ratio + + def extra_repr(self) -> str: + return f"scale={self.scale}, ratio={self.ratio}" + + def __call__(self, img: Any, target: np.ndarray) -> Tuple[Any, np.ndarray]: + scale = random.uniform(self.scale[0], self.scale[1]) + ratio = random.uniform(self.ratio[0], self.ratio[1]) + + height, width = img.shape[:2] + + # Calculate crop size + crop_area = scale * width * height + aspect_ratio = ratio * (width / height) + crop_width = int(round(math.sqrt(crop_area * aspect_ratio))) + crop_height = int(round(math.sqrt(crop_area / aspect_ratio))) + + # Ensure crop size does not exceed image dimensions + crop_width = min(crop_width, width) + crop_height = min(crop_height, height) + + # Randomly select crop position + x = random.randint(0, width - crop_width) + y = random.randint(0, height - crop_height) + + # relative crop box + crop_box = (x / width, y / height, (x + crop_width) / width, (y + crop_height) / height) + if target.shape[1:] == (4, 2): + min_xy = np.min(target, axis=1) + max_xy = np.max(target, axis=1) + _target = np.concatenate((min_xy, max_xy), axis=1) + else: + _target = target + + # Crop image and targets + croped_img, crop_boxes = F.crop_detection(img, _target, crop_box) + # hard fallback if no box is kept + if crop_boxes.shape[0] == 0: + return img, target + # clip boxes + return croped_img, np.clip(crop_boxes, 0, 1)
    @@ -402,8 +642,8 @@

    Source code for doctr.transforms.modules.base

    - - + + diff --git a/v0.3.0/_modules/doctr/transforms/modules/tensorflow.html b/v0.3.0/_modules/doctr/transforms/modules/tensorflow.html index 1d192a876b..acbbe96225 100644 --- a/v0.3.0/_modules/doctr/transforms/modules/tensorflow.html +++ b/v0.3.0/_modules/doctr/transforms/modules/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.transforms.modules.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import random
    +from typing import Any, Callable, Iterable, List, Optional, Tuple, Union
    +
    +import numpy as np
     import tensorflow as tf
    -from typing import List, Any, Tuple, Callable
     
     from doctr.utils.repr import NestedObject
     
    -
    -__all__ = ['Compose', 'Resize', 'Normalize', 'LambdaTransformation', 'ToGray', 'RandomBrightness',
    -           'RandomContrast', 'RandomSaturation', 'RandomHue', 'RandomGamma', 'RandomJpegQuality']
    +from ..functional.tensorflow import _gaussian_filter, random_shadow
    +
    +__all__ = [
    +    "Compose",
    +    "Resize",
    +    "Normalize",
    +    "LambdaTransformation",
    +    "ToGray",
    +    "RandomBrightness",
    +    "RandomContrast",
    +    "RandomSaturation",
    +    "RandomHue",
    +    "RandomGamma",
    +    "RandomJpegQuality",
    +    "GaussianBlur",
    +    "ChannelShuffle",
    +    "GaussianNoise",
    +    "RandomHorizontalFlip",
    +    "RandomShadow",
    +    "RandomResize",
    +]
     
     
     
    -[docs] +[docs] class Compose(NestedObject): """Implements a wrapper that will apply transformations sequentially - Example:: - >>> from doctr.transforms import Compose, Resize - >>> import tensorflow as tf - >>> transfos = Compose([Resize((32, 32))]) - >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import Compose, Resize + >>> transfos = Compose([Resize((32, 32))]) + >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) Args: + ---- transforms: list of transformation modules """ - _children_names: List[str] = ['transforms'] + _children_names: List[str] = ["transforms"] def __init__(self, transforms: List[Callable[[Any], Any]]) -> None: self.transforms = transforms @@ -319,26 +361,27 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class Resize(NestedObject): """Resizes a tensor to a target size - Example:: - >>> from doctr.transforms import Resize - >>> import tensorflow as tf - >>> transfo = Resize((32, 32)) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import Resize + >>> transfo = Resize((32, 32)) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) Args: + ---- output_size: expected output size method: interpolation method preserve_aspect_ratio: if `True`, preserve aspect ratio and pad the rest with zeros symmetric_pad: if `True` while preserving aspect ratio, the padding will be done symmetrically """ + def __init__( self, - output_size: Tuple[int, int], - method: str = 'bilinear', + output_size: Union[int, Tuple[int, int]], + method: str = "bilinear", preserve_aspect_ratio: bool = False, symmetric_pad: bool = False, ) -> None: @@ -346,6 +389,14 @@

    Source code for doctr.transforms.modules.tensorflow

    self.method = method self.preserve_aspect_ratio = preserve_aspect_ratio self.symmetric_pad = symmetric_pad + self.antialias = True + + if isinstance(self.output_size, int): + self.wanted_size = (self.output_size, self.output_size) + elif isinstance(self.output_size, (tuple, list)): + self.wanted_size = self.output_size + else: + raise AssertionError("Output size should be either a list, a tuple or an int") def extra_repr(self) -> str: _repr = f"output_size={self.output_size}, method='{self.method}'" @@ -353,64 +404,106 @@

    Source code for doctr.transforms.modules.tensorflow

    _repr += f", preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}" return _repr - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img = tf.image.resize(img, self.output_size, self.method, self.preserve_aspect_ratio) + def __call__( + self, + img: tf.Tensor, + target: Optional[np.ndarray] = None, + ) -> Union[tf.Tensor, Tuple[tf.Tensor, np.ndarray]]: + input_dtype = img.dtype + self.output_size = ( + (self.output_size, self.output_size) if isinstance(self.output_size, int) else self.output_size + ) + + img = tf.image.resize(img, self.wanted_size, self.method, self.preserve_aspect_ratio, self.antialias) + # It will produce an un-padded resized image, with a side shorter than wanted if we preserve aspect ratio + raw_shape = img.shape[:2] + if self.symmetric_pad: + half_pad = (int((self.output_size[0] - img.shape[0]) / 2), 0) if self.preserve_aspect_ratio: - # pad width - if not self.symmetric_pad: - offset = (0, 0) - elif self.output_size[0] == img.shape[0]: - offset = (0, int((self.output_size[1] - img.shape[1]) / 2)) - else: - offset = (int((self.output_size[0] - img.shape[0]) / 2), 0) - img = tf.image.pad_to_bounding_box(img, *offset, *self.output_size) - return img
    + if isinstance(self.output_size, (tuple, list)): + # In that case we need to pad because we want to enforce both width and height + if not self.symmetric_pad: + half_pad = (0, 0) + elif self.output_size[0] == img.shape[0]: + half_pad = (0, int((self.output_size[1] - img.shape[1]) / 2)) + # Pad image + img = tf.image.pad_to_bounding_box(img, *half_pad, *self.output_size) + + # In case boxes are provided, resize boxes if needed (for detection task if preserve aspect ratio) + if target is not None: + if self.symmetric_pad: + offset = half_pad[0] / img.shape[0], half_pad[1] / img.shape[1] + + if self.preserve_aspect_ratio: + # Get absolute coords + if target.shape[1:] == (4,): + if isinstance(self.output_size, (tuple, list)) and self.symmetric_pad: + target[:, [0, 2]] = offset[1] + target[:, [0, 2]] * raw_shape[1] / img.shape[1] + target[:, [1, 3]] = offset[0] + target[:, [1, 3]] * raw_shape[0] / img.shape[0] + else: + target[:, [0, 2]] *= raw_shape[1] / img.shape[1] + target[:, [1, 3]] *= raw_shape[0] / img.shape[0] + elif target.shape[1:] == (4, 2): + if isinstance(self.output_size, (tuple, list)) and self.symmetric_pad: + target[..., 0] = offset[1] + target[..., 0] * raw_shape[1] / img.shape[1] + target[..., 1] = offset[0] + target[..., 1] * raw_shape[0] / img.shape[0] + else: + target[..., 0] *= raw_shape[1] / img.shape[1] + target[..., 1] *= raw_shape[0] / img.shape[0] + else: + raise AssertionError("Boxes should be in the format (n_boxes, 4, 2) or (n_boxes, 4)") + + return tf.cast(img, dtype=input_dtype), np.clip(target, 0, 1) + + return tf.cast(img, dtype=input_dtype)
    -[docs] +[docs] class Normalize(NestedObject): """Normalize a tensor to a Gaussian distribution for each channel - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import Normalize + >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- mean: average value per channel std: standard deviation per channel """ + def __init__(self, mean: Tuple[float, float, float], std: Tuple[float, float, float]) -> None: - self.mean = tf.constant(mean, dtype=tf.float32) - self.std = tf.constant(std, dtype=tf.float32) + self.mean = tf.constant(mean) + self.std = tf.constant(std) def extra_repr(self) -> str: return f"mean={self.mean.numpy().tolist()}, std={self.std.numpy().tolist()}" def __call__(self, img: tf.Tensor) -> tf.Tensor: - img -= self.mean - img /= self.std + img -= tf.cast(self.mean, dtype=img.dtype) + img /= tf.cast(self.std, dtype=img.dtype) return img
    -[docs] +[docs] class LambdaTransformation(NestedObject): """Normalize a tensor to a Gaussian distribution for each channel - Example:: - >>> from doctr.transforms import LambdaTransformation - >>> import tensorflow as tf - >>> transfo = LambdaTransformation(lambda x: x/ 255.) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import LambdaTransformation + >>> transfo = LambdaTransformation(lambda x: x/ 255.) + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- fn: the function to be applied to the input tensor """ + def __init__(self, fn: Callable[[tf.Tensor], tf.Tensor]) -> None: self.fn = fn @@ -420,37 +513,42 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class ToGray(NestedObject): """Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ToGray() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import ToGray + >>> transfo = ToGray() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) """ + + def __init__(self, num_output_channels: int = 1): + self.num_output_channels = num_output_channels + def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.rgb_to_grayscale(img)
    + img = tf.image.rgb_to_grayscale(img) + return img if self.num_output_channels == 1 else tf.repeat(img, self.num_output_channels, axis=-1)
    -[docs] +[docs] class RandomBrightness(NestedObject): """Randomly adjust brightness of a tensor (batch of images or image) by adding a delta to all pixels - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Brightness() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomBrightness + >>> transfo = RandomBrightness() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] p: probability to apply transformation """ + def __init__(self, max_delta: float = 0.3) -> None: self.max_delta = max_delta @@ -463,21 +561,22 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomContrast(NestedObject): """Randomly adjust contrast of a tensor (batch of images or image) by adjusting each pixel: (img - mean) * contrast_factor + mean. - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Contrast() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomContrast + >>> transfo = RandomContrast() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1) """ - def __init__(self, delta: float = .3) -> None: + + def __init__(self, delta: float = 0.3) -> None: self.delta = delta def extra_repr(self) -> str: @@ -489,21 +588,22 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomSaturation(NestedObject): """Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and increasing saturation by a factor. - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Saturation() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomSaturation + >>> transfo = RandomSaturation() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1) """ - def __init__(self, delta: float = .5) -> None: + + def __init__(self, delta: float = 0.5) -> None: self.delta = delta def extra_repr(self) -> str: @@ -515,19 +615,20 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomHue(NestedObject): """Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Hue() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomHue + >>> transfo = RandomHue() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] """ + def __init__(self, max_delta: float = 0.3) -> None: self.max_delta = max_delta @@ -540,22 +641,23 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomGamma(NestedObject): """randomly performs gamma correction for a tensor (batch of images or image) - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Gamma() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomGamma + >>> transfo = RandomGamma() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- min_gamma: non-negative real number, lower bound for gamma param max_gamma: non-negative real number, upper bound for gamma min_gain: lower bound for constant multiplier max_gain: upper bound for constant multiplier """ + def __init__( self, min_gamma: float = 0.5, @@ -580,20 +682,21 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomJpegQuality(NestedObject): """Randomly adjust jpeg quality of a 3 dimensional RGB image - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = JpegQuality() - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomJpegQuality + >>> transfo = RandomJpegQuality() + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) Args: + ---- min_quality: int between [0, 100] max_quality: int between [0, 100] """ + def __init__(self, min_quality: int = 60, max_quality: int = 100) -> None: self.min_quality = min_quality self.max_quality = max_quality @@ -602,10 +705,224 @@

    Source code for doctr.transforms.modules.tensorflow

    return f"min_quality={self.min_quality}" def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_jpeg_quality( - img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality + return tf.image.random_jpeg_quality(img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality)
    + + + +
    +[docs] +class GaussianBlur(NestedObject): + """Randomly adjust jpeg quality of a 3 dimensional RGB image + + >>> import tensorflow as tf + >>> from doctr.transforms import GaussianBlur + >>> transfo = GaussianBlur(3, (.1, 5)) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + kernel_shape: size of the blurring kernel + std: min and max value of the standard deviation + """ + + def __init__(self, kernel_shape: Union[int, Iterable[int]], std: Tuple[float, float]) -> None: + self.kernel_shape = kernel_shape + self.std = std + + def extra_repr(self) -> str: + return f"kernel_shape={self.kernel_shape}, std={self.std}" + + def __call__(self, img: tf.Tensor) -> tf.Tensor: + return tf.squeeze( + _gaussian_filter( + img[tf.newaxis, ...], + kernel_size=self.kernel_shape, + sigma=random.uniform(self.std[0], self.std[1]), + mode="REFLECT", + ), + axis=0, )
    + + +
    +[docs] +class ChannelShuffle(NestedObject): + """Randomly shuffle channel order of a given image""" + + def __init__(self): + pass + + def __call__(self, img: tf.Tensor) -> tf.Tensor: + return tf.transpose(tf.random.shuffle(tf.transpose(img, perm=[2, 0, 1])), perm=[1, 2, 0])
    + + + +
    +[docs] +class GaussianNoise(NestedObject): + """Adds Gaussian Noise to the input tensor + + >>> import tensorflow as tf + >>> from doctr.transforms import GaussianNoise + >>> transfo = GaussianNoise(0., 1.) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + mean : mean of the gaussian distribution + std : std of the gaussian distribution + """ + + def __init__(self, mean: float = 0.0, std: float = 1.0) -> None: + super().__init__() + self.std = std + self.mean = mean + + def __call__(self, x: tf.Tensor) -> tf.Tensor: + # Reshape the distribution + noise = self.mean + 2 * self.std * tf.random.uniform(x.shape) - self.std + if x.dtype == tf.uint8: + return tf.cast( + tf.clip_by_value(tf.math.round(tf.cast(x, dtype=tf.float32) + 255 * noise), 0, 255), dtype=tf.uint8 + ) + else: + return tf.cast(tf.clip_by_value(x + noise, 0, 1), dtype=x.dtype) + + def extra_repr(self) -> str: + return f"mean={self.mean}, std={self.std}"
    + + + +
    +[docs] +class RandomHorizontalFlip(NestedObject): + """Adds random horizontal flip to the input tensor/np.ndarray + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomHorizontalFlip + >>> transfo = RandomHorizontalFlip(p=0.5) + >>> image = tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1) + >>> target = np.array([[0.1, 0.1, 0.4, 0.5] ], dtype= np.float32) + >>> out = transfo(image, target) + + Args: + ---- + p : probability of Horizontal Flip + """ + + def __init__(self, p: float) -> None: + super().__init__() + self.p = p + + def __call__(self, img: Union[tf.Tensor, np.ndarray], target: np.ndarray) -> Tuple[tf.Tensor, np.ndarray]: + if np.random.rand(1) <= self.p: + _img = tf.image.flip_left_right(img) + _target = target.copy() + # Changing the relative bbox coordinates + if target.shape[1:] == (4,): + _target[:, ::2] = 1 - target[:, [2, 0]] + else: + _target[..., 0] = 1 - target[..., 0] + return _img, _target + return img, target
    + + + +
    +[docs] +class RandomShadow(NestedObject): + """Adds random shade to the input image + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomShadow + >>> transfo = RandomShadow(0., 1.) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + opacity_range : minimum and maximum opacity of the shade + """ + + def __init__(self, opacity_range: Optional[Tuple[float, float]] = None) -> None: + super().__init__() + self.opacity_range = opacity_range if isinstance(opacity_range, tuple) else (0.2, 0.8) + + def __call__(self, x: tf.Tensor) -> tf.Tensor: + # Reshape the distribution + if x.dtype == tf.uint8: + return tf.cast( + tf.clip_by_value( + tf.math.round(255 * random_shadow(tf.cast(x, dtype=tf.float32) / 255, self.opacity_range)), + 0, + 255, + ), + dtype=tf.uint8, + ) + else: + return tf.clip_by_value(random_shadow(x, self.opacity_range), 0, 1) + + def extra_repr(self) -> str: + return f"opacity_range={self.opacity_range}"
    + + + +
    +[docs] +class RandomResize(NestedObject): + """Randomly resize the input image and align corresponding targets + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomResize + >>> transfo = RandomResize((0.3, 0.9), preserve_aspect_ratio=True, symmetric_pad=True, p=0.5) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + scale_range: range of the resizing factor for width and height (independently) + preserve_aspect_ratio: whether to preserve the aspect ratio of the image, + given a float value, the aspect ratio will be preserved with this probability + symmetric_pad: whether to symmetrically pad the image, + given a float value, the symmetric padding will be applied with this probability + p: probability to apply the transformation + """ + + def __init__( + self, + scale_range: Tuple[float, float] = (0.3, 0.9), + preserve_aspect_ratio: Union[bool, float] = False, + symmetric_pad: Union[bool, float] = False, + p: float = 0.5, + ): + super().__init__() + self.scale_range = scale_range + self.preserve_aspect_ratio = preserve_aspect_ratio + self.symmetric_pad = symmetric_pad + self.p = p + self._resize = Resize + + def __call__(self, img: tf.Tensor, target: np.ndarray) -> Tuple[tf.Tensor, np.ndarray]: + if np.random.rand(1) <= self.p: + scale_h = random.uniform(*self.scale_range) + scale_w = random.uniform(*self.scale_range) + new_size = (int(img.shape[-3] * scale_h), int(img.shape[-2] * scale_w)) + + _img, _target = self._resize( + new_size, + preserve_aspect_ratio=self.preserve_aspect_ratio + if isinstance(self.preserve_aspect_ratio, bool) + else bool(np.random.rand(1) <= self.symmetric_pad), + symmetric_pad=self.symmetric_pad + if isinstance(self.symmetric_pad, bool) + else bool(np.random.rand(1) <= self.symmetric_pad), + )(img, target) + + return _img, _target + return img, target + + def extra_repr(self) -> str: + return f"scale_range={self.scale_range}, preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}, p={self.p}" # noqa: E501
    +
    @@ -638,8 +955,8 @@

    Source code for doctr.transforms.modules.tensorflow

    - +
    + diff --git a/v0.3.0/_modules/doctr/utils/metrics.html b/v0.3.0/_modules/doctr/utils/metrics.html index 460c64a385..8a37d5949a 100644 --- a/v0.3.0/_modules/doctr/utils/metrics.html +++ b/v0.3.0/_modules/doctr/utils/metrics.html @@ -13,7 +13,7 @@ - + doctr.utils.metrics - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.utils.metrics

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
    +
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +from typing import Dict, List, Optional, Tuple
     
     import numpy as np
    -import cv2
    -from typing import List, Tuple, Dict, Optional
    -from unidecode import unidecode
    +from anyascii import anyascii
     from scipy.optimize import linear_sum_assignment
    -from doctr.utils.geometry import rbbox_to_polygon
    +from shapely.geometry import Polygon
     
    -__all__ = ['TextMatch', 'box_iou', 'box_ioa', 'mask_iou', 'rbox_to_mask',
    -           'nms', 'LocalizationConfusion', 'OCRMetric']
    +__all__ = [
    +    "TextMatch",
    +    "box_iou",
    +    "polygon_iou",
    +    "nms",
    +    "LocalizationConfusion",
    +    "OCRMetric",
    +    "DetectionMetric",
    +]
     
     
     def string_match(word1: str, word2: str) -> Tuple[bool, bool, bool, bool]:
    -    """Perform string comparison with multiple levels of tolerance
    +    """Performs string comparison with multiple levels of tolerance
     
         Args:
    +    ----
             word1: a string
             word2: another string
     
         Returns:
    +    -------
             a tuple with booleans specifying respectively whether the raw strings, their lower-case counterparts, their
    -            unidecode counterparts and their lower-case unidecode counterparts match
    +            anyascii counterparts and their lower-case anyascii counterparts match
         """
    -    raw_match = (word1 == word2)
    -    caseless_match = (word1.lower() == word2.lower())
    -    unidecode_match = (unidecode(word1) == unidecode(word2))
    +    raw_match = word1 == word2
    +    caseless_match = word1.lower() == word2.lower()
    +    anyascii_match = anyascii(word1) == anyascii(word2)
     
         # Warning: the order is important here otherwise the pair ("EUR", "€") cannot be matched
    -    unicase_match = (unidecode(word1).lower() == unidecode(word2).lower())
    +    unicase_match = anyascii(word1).lower() == anyascii(word2).lower()
     
    -    return raw_match, caseless_match, unidecode_match, unicase_match
    +    return raw_match, caseless_match, anyascii_match, unicase_match
     
     
     
    -[docs] +[docs] class TextMatch: - """Implements text match metric (word-level accuracy) for recognition task. + r"""Implements text match metric (word-level accuracy) for recognition task. The raw aggregated metric is computed as follows: .. math:: - \\forall X, Y \\in \\mathcal{W}^N, - TextMatch(X, Y) = \\frac{1}{N} \\sum\\limits_{i=1}^N f_{Y_i}(X_i) + \forall X, Y \in \mathcal{W}^N, + TextMatch(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N f_{Y_i}(X_i) with the indicator function :math:`f_{a}` defined as: .. math:: - \\forall a, x \\in \\mathcal{W}, - f_a(x) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } x = a \\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{W}` is the set of all possible character sequences, + \forall a, x \in \mathcal{W}, + f_a(x) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } x = a \\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{W}` is the set of all possible character sequences, :math:`N` is a strictly positive integer. - Example:: - >>> from doctr.utils import TextMatch - >>> metric = TextMatch() - >>> metric.update(['Hello', 'world'], ['hello', 'world']) - >>> metric.summary() + >>> from doctr.utils import TextMatch + >>> metric = TextMatch() + >>> metric.update(['Hello', 'world'], ['hello', 'world']) + >>> metric.summary() """ def __init__(self) -> None: self.reset() +
    +[docs] def update( self, gt: List[str], @@ -354,29 +386,32 @@

    Source code for doctr.utils.metrics

             """Update the state of the metric with new predictions
     
             Args:
    +        ----
                 gt: list of groung-truth character sequences
    -            pred: list of predicted character sequences"""
    -
    +            pred: list of predicted character sequences
    +        """
             if len(gt) != len(pred):
                 raise AssertionError("prediction size does not match with ground-truth labels size")
     
             for gt_word, pred_word in zip(gt, pred):
    -            _raw, _caseless, _unidecode, _unicase = string_match(gt_word, pred_word)
    +            _raw, _caseless, _anyascii, _unicase = string_match(gt_word, pred_word)
                 self.raw += int(_raw)
                 self.caseless += int(_caseless)
    -            self.unidecode += int(_unidecode)
    +            self.anyascii += int(_anyascii)
                 self.unicase += int(_unicase)
     
    -        self.total += len(gt)
    +        self.total += len(gt)
    +
    -[docs] +[docs] def summary(self) -> Dict[str, float]: """Computes the aggregated metrics - Returns: - a dictionary with the exact match score for the raw data, its lower-case counterpart, its unidecode - counterpart and its lower-case unidecode counterpart + Returns + ------- + a dictionary with the exact match score for the raw data, its lower-case counterpart, its anyascii + counterpart and its lower-case anyascii counterpart """ if self.total == 0: raise AssertionError("you need to update the metric before getting the summary") @@ -384,7 +419,7 @@

    Source code for doctr.utils.metrics

             return dict(
                 raw=self.raw / self.total,
                 caseless=self.caseless / self.total,
    -            unidecode=self.unidecode / self.total,
    +            anyascii=self.anyascii / self.total,
                 unicase=self.unicase / self.total,
             )
    @@ -392,23 +427,25 @@

    Source code for doctr.utils.metrics

         def reset(self) -> None:
             self.raw = 0
             self.caseless = 0
    -        self.unidecode = 0
    +        self.anyascii = 0
             self.unicase = 0
             self.total = 0
    def box_iou(boxes_1: np.ndarray, boxes_2: np.ndarray) -> np.ndarray: - """Compute the IoU between two sets of bounding boxes + """Computes the IoU between two sets of bounding boxes Args: + ---- boxes_1: bounding boxes of shape (N, 4) in format (xmin, ymin, xmax, ymax) boxes_2: bounding boxes of shape (M, 4) in format (xmin, ymin, xmax, ymax) + Returns: + ------- the IoU matrix of shape (N, M) """ - - iou_mat = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32) + iou_mat: np.ndarray = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32) if boxes_1.shape[0] > 0 and boxes_2.shape[0] > 0: l1, t1, r1, b1 = np.split(boxes_1, 4, axis=1) @@ -419,107 +456,54 @@

    Source code for doctr.utils.metrics

             right = np.minimum(r1, r2.T)
             bot = np.minimum(b1, b2.T)
     
    -        intersection = np.clip(right - left, 0, np.Inf) * np.clip(bot - top, 0, np.Inf)
    +        intersection = np.clip(right - left, 0, np.inf) * np.clip(bot - top, 0, np.inf)
             union = (r1 - l1) * (b1 - t1) + ((r2 - l2) * (b2 - t2)).T - intersection
             iou_mat = intersection / union
     
         return iou_mat
     
     
    -def box_ioa(boxes_1: np.ndarray, boxes_2: np.ndarray) -> np.ndarray:
    -    """Compute the IoA (intersection over area) between two sets of bounding boxes:
    -    ioa(i, j) = inter(i, j) / area(i)
    -
    -    Args:
    -        boxes_1: bounding boxes of shape (N, 4) in format (xmin, ymin, xmax, ymax)
    -        boxes_2: bounding boxes of shape (M, 4) in format (xmin, ymin, xmax, ymax)
    -    Returns:
    -        the IoA matrix of shape (N, M)
    -    """
    -
    -    ioa_mat = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32)
    -
    -    if boxes_1.shape[0] > 0 and boxes_2.shape[0] > 0:
    -        l1, t1, r1, b1 = np.split(boxes_1, 4, axis=1)
    -        l2, t2, r2, b2 = np.split(boxes_2, 4, axis=1)
    -
    -        left = np.maximum(l1, l2.T)
    -        top = np.maximum(t1, t2.T)
    -        right = np.minimum(r1, r2.T)
    -        bot = np.minimum(b1, b2.T)
    -
    -        intersection = np.clip(right - left, 0, np.Inf) * np.clip(bot - top, 0, np.Inf)
    -        area = (r1 - l1) * (b1 - t1)
    -        ioa_mat = intersection / area
    -
    -    return ioa_mat
    -
    -
    -def mask_iou(masks_1: np.ndarray, masks_2: np.ndarray) -> np.ndarray:
    -    """Compute the IoU between two sets of boolean masks
    +def polygon_iou(polys_1: np.ndarray, polys_2: np.ndarray) -> np.ndarray:
    +    """Computes the IoU between two sets of rotated bounding boxes
     
         Args:
    -        masks_1: boolean masks of shape (N, H, W)
    -        masks_2: boolean masks of shape (M, H, W)
    +    ----
    +        polys_1: rotated bounding boxes of shape (N, 4, 2)
    +        polys_2: rotated bounding boxes of shape (M, 4, 2)
    +        mask_shape: spatial shape of the intermediate masks
    +        use_broadcasting: if set to True, leverage broadcasting speedup by consuming more memory
     
         Returns:
    +    -------
             the IoU matrix of shape (N, M)
         """
    +    if polys_1.ndim != 3 or polys_2.ndim != 3:
    +        raise AssertionError("expects boxes to be in format (N, 4, 2)")
     
    -    if masks_1.shape[1:] != masks_2.shape[1:]:
    -        raise AssertionError("both boolean masks should have the same spatial shape")
    +    iou_mat = np.zeros((polys_1.shape[0], polys_2.shape[0]), dtype=np.float32)
     
    -    iou_mat = np.zeros((masks_1.shape[0], masks_2.shape[0]), dtype=np.float32)
    +    shapely_polys_1 = [Polygon(poly) for poly in polys_1]
    +    shapely_polys_2 = [Polygon(poly) for poly in polys_2]
     
    -    if masks_1.shape[0] > 0 and masks_2.shape[0] > 0:
    -        intersection = np.logical_and(masks_1[:, None, ...], masks_2[None, ...])
    -        union = np.logical_or(masks_1[:, None, ...], masks_2[None, ...])
    -        axes = tuple(range(2, masks_1.ndim + 1))
    -        iou_mat = intersection.sum(axis=axes) / union.sum(axis=axes)
    +    for i, poly1 in enumerate(shapely_polys_1):
    +        for j, poly2 in enumerate(shapely_polys_2):
    +            intersection_area = poly1.intersection(poly2).area
    +            union_area = poly1.area + poly2.area - intersection_area
    +            iou_mat[i, j] = intersection_area / union_area
     
         return iou_mat
     
     
    -def rbox_to_mask(boxes: np.ndarray, shape: Tuple[int, int]) -> np.ndarray:
    -    """Convert boxes to masks
    -
    -    Args:
    -        boxes: rotated bounding boxes of shape (N, 5) in format (x, y, w, h, alpha)
    -        shape: spatial shapes of the output masks
    -
    -    Returns:
    -        the boolean masks of shape (N, H, W)
    -    """
    -
    -    masks = np.zeros((boxes.shape[0], *shape), dtype=np.uint8)
    -
    -    if boxes.shape[0] > 0:
    -        # Get absolute coordinates
    -        if boxes.dtype != np.int:
    -            abs_boxes = boxes.copy()
    -            abs_boxes[:, [0, 2]] = abs_boxes[:, [0, 2]] * shape[1]
    -            abs_boxes[:, [1, 3]] = abs_boxes[:, [1, 3]] * shape[0]
    -            abs_boxes = abs_boxes.round().astype(np.int)
    -        else:
    -            abs_boxes = boxes
    -            abs_boxes[:, 2:] = abs_boxes[:, 2:] + 1
    -
    -        # TODO: optimize slicing to improve vectorization
    -        for idx, _box in enumerate(abs_boxes):
    -            box = rbbox_to_polygon(_box)
    -            cv2.fillPoly(masks[idx], [np.array(box, np.int32)], 1)
    -
    -    return masks.astype(bool)
    -
    -
    -def nms(boxes: np.ndarray, thresh: float = .5) -> List[int]:
    +def nms(boxes: np.ndarray, thresh: float = 0.5) -> List[int]:
         """Perform non-max suppression, borrowed from <https://github.com/rbgirshick/fast-rcnn>`_.
     
         Args:
    +    ----
             boxes: np array of straight boxes: (*, 5), (xmin, ymin, xmax, ymax, score)
             thresh: iou threshold to perform box suppression.
     
         Returns:
    +    -------
             A list of box indexes to keep
         """
         x1 = boxes[:, 0]
    @@ -551,66 +535,71 @@ 

    Source code for doctr.utils.metrics

     
     
     
    -[docs] +[docs] class LocalizationConfusion: - """Implements common confusion metrics and mean IoU for localization evaluation. + r"""Implements common confusion metrics and mean IoU for localization evaluation. The aggregated metrics are computed as follows: .. math:: - \\forall Y \\in \\mathcal{B}^N, \\forall X \\in \\mathcal{B}^M, \\\\ - Recall(X, Y) = \\frac{1}{N} \\sum\\limits_{i=1}^N g_{X}(Y_i) \\\\ - Precision(X, Y) = \\frac{1}{M} \\sum\\limits_{i=1}^N g_{X}(Y_i) \\\\ - meanIoU(X, Y) = \\frac{1}{M} \\sum\\limits_{i=1}^M \\max\\limits_{j \\in [1, N]} IoU(X_i, Y_j) + \forall Y \in \mathcal{B}^N, \forall X \in \mathcal{B}^M, \\ + Recall(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N g_{X}(Y_i) \\ + Precision(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M g_{X}(Y_i) \\ + meanIoU(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(X_i, Y_j) with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and :math:`y`, and the function :math:`g_{X}` defined as: .. math:: - \\forall y \\in \\mathcal{B}, - g_X(y) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } y\\mbox{ has been assigned to any }(X_i)_i\\mbox{ with an }IoU \\geq 0.5 \\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{B}` is the set of possible bounding boxes, + \forall y \in \mathcal{B}, + g_X(y) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } y\mbox{ has been assigned to any }(X_i)_i\mbox{ with an }IoU \geq 0.5 \\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. - Example:: - >>> import numpy as np - >>> from doctr.utils import LocalizationConfusion - >>> metric = LocalizationConfusion(iou_thresh=0.5) - >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]])) - >>> metric.summary() + >>> import numpy as np + >>> from doctr.utils import LocalizationConfusion + >>> metric = LocalizationConfusion(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]])) + >>> metric.summary() Args: + ---- iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format """ def __init__( self, iou_thresh: float = 0.5, - rotated_bbox: bool = False, - mask_shape: Tuple[int, int] = (1024, 1024), + use_polygons: bool = False, ) -> None: self.iou_thresh = iou_thresh - self.rotated_bbox = rotated_bbox - self.mask_shape = mask_shape + self.use_polygons = use_polygons self.reset() +
    +[docs] def update(self, gts: np.ndarray, preds: np.ndarray) -> None: + """Updates the metric + Args: + ---- + gts: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones + preds: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones + """ if preds.shape[0] > 0: # Compute IoU - if self.rotated_bbox: - mask_gts = rbox_to_mask(gts, shape=self.mask_shape) - mask_preds = rbox_to_mask(preds, shape=self.mask_shape) - iou_mat = mask_iou(mask_gts, mask_preds) + if self.use_polygons: + iou_mat = polygon_iou(gts, preds) else: iou_mat = box_iou(gts, preds) - self.tot_iou += float(iou_mat.max(axis=1).sum()) + self.tot_iou += float(iou_mat.max(axis=0).sum()) # Assign pairs gt_indices, pred_indices = linear_sum_assignment(-iou_mat) @@ -618,17 +607,18 @@

    Source code for doctr.utils.metrics

     
             # Update counts
             self.num_gts += gts.shape[0]
    -        self.num_preds += preds.shape[0]
    +        self.num_preds += preds.shape[0]
    +
    -[docs] +[docs] def summary(self) -> Tuple[Optional[float], Optional[float], Optional[float]]: """Computes the aggregated metrics - Returns: + Returns + ------- a tuple with the recall, precision and meanIoU scores """ - # Recall recall = self.matches / self.num_gts if self.num_gts > 0 else None @@ -636,7 +626,7 @@

    Source code for doctr.utils.metrics

             precision = self.matches / self.num_preds if self.num_preds > 0 else None
     
             # mean IoU
    -        mean_iou = self.tot_iou / self.num_preds if self.num_preds > 0 else None
    +        mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None
     
             return recall, precision, mean_iou
    @@ -645,64 +635,65 @@

    Source code for doctr.utils.metrics

             self.num_gts = 0
             self.num_preds = 0
             self.matches = 0
    -        self.tot_iou = 0.
    + self.tot_iou = 0.0
    -[docs] +[docs] class OCRMetric: - """Implements end-to-end OCR metric. + r"""Implements an end-to-end OCR metric. The aggregated metrics are computed as follows: .. math:: - \\forall (B, L) \\in \\mathcal{B}^N \\times \\mathcal{L}^N, - \\forall (\\hat{B}, \\hat{L}) \\in \\mathcal{B}^M \\times \\mathcal{L}^M, \\\\ - Recall(B, \\hat{B}, L, \\hat{L}) = \\frac{1}{N} \\sum\\limits_{i=1}^N h_{B,L}(\\hat{B}_i, \\hat{L}_i) \\\\ - Precision(B, \\hat{B}, L, \\hat{L}) = \\frac{1}{M} \\sum\\limits_{i=1}^N h_{B,L}(\\hat{B}_i, \\hat{L}_i) \\\\ - meanIoU(B, \\hat{B}) = \\frac{1}{M} \\sum\\limits_{i=1}^M \\max\\limits_{j \\in [1, N]} IoU(\\hat{B}_i, B_j) + \forall (B, L) \in \mathcal{B}^N \times \mathcal{L}^N, + \forall (\hat{B}, \hat{L}) \in \mathcal{B}^M \times \mathcal{L}^M, \\ + Recall(B, \hat{B}, L, \hat{L}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ + Precision(B, \hat{B}, L, \hat{L}) = \frac{1}{M} \sum\limits_{i=1}^M h_{B,L}(\hat{B}_i, \hat{L}_i) \\ + meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j) with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and :math:`y`, and the function :math:`h_{B, L}` defined as: .. math:: - \\forall (b, l) \\in \\mathcal{B} \\times \\mathcal{L}, - h_{B,L}(b, l) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } b\\mbox{ has been assigned to a given }B_j\\mbox{ with an } \\\\ - & IoU \\geq 0.5 \\mbox{ and that for this assignment, } l = L_j\\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{B}` is the set of possible bounding boxes, - :math:`\\mathcal{L}` is the set of possible character sequences, + \forall (b, l) \in \mathcal{B} \times \mathcal{L}, + h_{B,L}(b, l) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ + & IoU \geq 0.5 \mbox{ and that for this assignment, } l = L_j\\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, + :math:`\mathcal{L}` is the set of possible character sequences, :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. - Example:: - >>> import numpy as np - >>> from doctr.utils import OCRMetric - >>> metric = OCRMetric(iou_thresh=0.5) - >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), - ['hello'], ['hello', 'world']) - >>> metric.summary() + >>> import numpy as np + >>> from doctr.utils import OCRMetric + >>> metric = OCRMetric(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), + >>> ['hello'], ['hello', 'world']) + >>> metric.summary() Args: + ---- iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format """ def __init__( self, iou_thresh: float = 0.5, - rotated_bbox: bool = False, - mask_shape: Tuple[int, int] = (1024, 1024), + use_polygons: bool = False, ) -> None: self.iou_thresh = iou_thresh - self.rotated_bbox = rotated_bbox - self.mask_shape = mask_shape + self.use_polygons = use_polygons self.reset() +
    +[docs] def update( self, gt_boxes: np.ndarray, @@ -710,50 +701,58 @@

    Source code for doctr.utils.metrics

             gt_labels: List[str],
             pred_labels: List[str],
         ) -> None:
    +        """Updates the metric
     
    +        Args:
    +        ----
    +            gt_boxes: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones
    +            pred_boxes: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones
    +            gt_labels: a list of N string labels
    +            pred_labels: a list of M string labels
    +        """
             if gt_boxes.shape[0] != len(gt_labels) or pred_boxes.shape[0] != len(pred_labels):
    -            raise AssertionError("there should be the same number of boxes and string both for the ground truth "
    -                                 "and the predictions")
    +            raise AssertionError(
    +                "there should be the same number of boxes and string both for the ground truth and the predictions"
    +            )
     
             # Compute IoU
             if pred_boxes.shape[0] > 0:
    -            if self.rotated_bbox:
    -                mask_gts = rbox_to_mask(gt_boxes, shape=self.mask_shape)
    -                mask_preds = rbox_to_mask(pred_boxes, shape=self.mask_shape)
    -                iou_mat = mask_iou(mask_gts, mask_preds)
    +            if self.use_polygons:
    +                iou_mat = polygon_iou(gt_boxes, pred_boxes)
                 else:
                     iou_mat = box_iou(gt_boxes, pred_boxes)
     
    -            self.tot_iou += float(iou_mat.max(axis=1).sum())
    +            self.tot_iou += float(iou_mat.max(axis=0).sum())
     
                 # Assign pairs
                 gt_indices, pred_indices = linear_sum_assignment(-iou_mat)
                 is_kept = iou_mat[gt_indices, pred_indices] >= self.iou_thresh
                 # String comparison
                 for gt_idx, pred_idx in zip(gt_indices[is_kept], pred_indices[is_kept]):
    -                _raw, _caseless, _unidecode, _unicase = string_match(gt_labels[gt_idx], pred_labels[pred_idx])
    +                _raw, _caseless, _anyascii, _unicase = string_match(gt_labels[gt_idx], pred_labels[pred_idx])
                     self.raw_matches += int(_raw)
                     self.caseless_matches += int(_caseless)
    -                self.unidecode_matches += int(_unidecode)
    +                self.anyascii_matches += int(_anyascii)
                     self.unicase_matches += int(_unicase)
     
             self.num_gts += gt_boxes.shape[0]
    -        self.num_preds += pred_boxes.shape[0]
    +        self.num_preds += pred_boxes.shape[0]
    +
    -[docs] +[docs] def summary(self) -> Tuple[Dict[str, Optional[float]], Dict[str, Optional[float]], Optional[float]]: """Computes the aggregated metrics - Returns: - a tuple with the recall & precision for each string comparison flexibility and the mean IoU + Returns + ------- + a tuple with the recall & precision for each string comparison and the mean IoU """ - # Recall recall = dict( raw=self.raw_matches / self.num_gts if self.num_gts > 0 else None, caseless=self.caseless_matches / self.num_gts if self.num_gts > 0 else None, - unidecode=self.unidecode_matches / self.num_gts if self.num_gts > 0 else None, + anyascii=self.anyascii_matches / self.num_gts if self.num_gts > 0 else None, unicase=self.unicase_matches / self.num_gts if self.num_gts > 0 else None, ) @@ -761,12 +760,12 @@

    Source code for doctr.utils.metrics

             precision = dict(
                 raw=self.raw_matches / self.num_preds if self.num_preds > 0 else None,
                 caseless=self.caseless_matches / self.num_preds if self.num_preds > 0 else None,
    -            unidecode=self.unidecode_matches / self.num_preds if self.num_preds > 0 else None,
    +            anyascii=self.anyascii_matches / self.num_preds if self.num_preds > 0 else None,
                 unicase=self.unicase_matches / self.num_preds if self.num_preds > 0 else None,
             )
     
             # mean IoU (overall detected boxes)
    -        mean_iou = self.tot_iou / self.num_preds if self.num_preds > 0 else None
    +        mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None
     
             return recall, precision, mean_iou
    @@ -774,12 +773,136 @@

    Source code for doctr.utils.metrics

         def reset(self) -> None:
             self.num_gts = 0
             self.num_preds = 0
    -        self.tot_iou = 0.
    +        self.tot_iou = 0.0
             self.raw_matches = 0
             self.caseless_matches = 0
    -        self.unidecode_matches = 0
    +        self.anyascii_matches = 0
             self.unicase_matches = 0
    + + +
    +[docs] +class DetectionMetric: + r"""Implements an object detection metric. + + The aggregated metrics are computed as follows: + + .. math:: + \forall (B, C) \in \mathcal{B}^N \times \mathcal{C}^N, + \forall (\hat{B}, \hat{C}) \in \mathcal{B}^M \times \mathcal{C}^M, \\ + Recall(B, \hat{B}, C, \hat{C}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,C}(\hat{B}_i, \hat{C}_i) \\ + Precision(B, \hat{B}, C, \hat{C}) = \frac{1}{M} \sum\limits_{i=1}^M h_{B,C}(\hat{B}_i, \hat{C}_i) \\ + meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j) + + with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and + :math:`y`, and the function :math:`h_{B, C}` defined as: + + .. math:: + \forall (b, c) \in \mathcal{B} \times \mathcal{C}, + h_{B,C}(b, c) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ + & IoU \geq 0.5 \mbox{ and that for this assignment, } c = C_j\\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, + :math:`\mathcal{C}` is the set of possible class indices, + :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. + + >>> import numpy as np + >>> from doctr.utils import DetectionMetric + >>> metric = DetectionMetric(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), + >>> np.zeros(1, dtype=np.int64), np.array([0, 1], dtype=np.int64)) + >>> metric.summary() + + Args: + ---- + iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format + """ + + def __init__( + self, + iou_thresh: float = 0.5, + use_polygons: bool = False, + ) -> None: + self.iou_thresh = iou_thresh + self.use_polygons = use_polygons + self.reset() + +
    +[docs] + def update( + self, + gt_boxes: np.ndarray, + pred_boxes: np.ndarray, + gt_labels: np.ndarray, + pred_labels: np.ndarray, + ) -> None: + """Updates the metric + + Args: + ---- + gt_boxes: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones + pred_boxes: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones + gt_labels: an array of class indices of shape (N,) + pred_labels: an array of class indices of shape (M,) + """ + if gt_boxes.shape[0] != gt_labels.shape[0] or pred_boxes.shape[0] != pred_labels.shape[0]: + raise AssertionError( + "there should be the same number of boxes and string both for the ground truth and the predictions" + ) + + # Compute IoU + if pred_boxes.shape[0] > 0: + if self.use_polygons: + iou_mat = polygon_iou(gt_boxes, pred_boxes) + else: + iou_mat = box_iou(gt_boxes, pred_boxes) + + self.tot_iou += float(iou_mat.max(axis=0).sum()) + + # Assign pairs + gt_indices, pred_indices = linear_sum_assignment(-iou_mat) + is_kept = iou_mat[gt_indices, pred_indices] >= self.iou_thresh + # Category comparison + self.num_matches += int((gt_labels[gt_indices[is_kept]] == pred_labels[pred_indices[is_kept]]).sum()) + + self.num_gts += gt_boxes.shape[0] + self.num_preds += pred_boxes.shape[0]
    + + +
    +[docs] + def summary(self) -> Tuple[Optional[float], Optional[float], Optional[float]]: + """Computes the aggregated metrics + + Returns + ------- + a tuple with the recall & precision for each class prediction and the mean IoU + """ + # Recall + recall = self.num_matches / self.num_gts if self.num_gts > 0 else None + + # Precision + precision = self.num_matches / self.num_preds if self.num_preds > 0 else None + + # mean IoU (overall detected boxes) + mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None + + return recall, precision, mean_iou
    + + + def reset(self) -> None: + self.num_gts = 0 + self.num_preds = 0 + self.tot_iou = 0.0 + self.num_matches = 0
    +
    @@ -812,8 +935,8 @@

    Source code for doctr.utils.metrics

           
         
       
    - - + + diff --git a/v0.3.0/_modules/doctr/utils/visualization.html b/v0.3.0/_modules/doctr/utils/visualization.html index 8e7dcca811..c818be6d7b 100644 --- a/v0.3.0/_modules/doctr/utils/visualization.html +++ b/v0.3.0/_modules/doctr/utils/visualization.html @@ -13,7 +13,7 @@ - + doctr.utils.visualization - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.utils.visualization

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
    +import colorsys
    +from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple, Union
     
    -import matplotlib.pyplot as plt
    -from matplotlib.figure import Figure
    +import cv2
     import matplotlib.patches as patches
    -import mplcursors
    -from PIL import ImageFont, ImageDraw, Image
    +import matplotlib.pyplot as plt
     import numpy as np
    -import cv2
    -from typing import Tuple, List, Dict, Any, Union
    +from matplotlib.figure import Figure
     
    -from .common_types import BoundingBox, RotatedBbox
    +from .common_types import BoundingBox, Polygon4P
     
    -__all__ = ['visualize_page', 'synthetize_page']
    +__all__ = ["visualize_page", "visualize_kie_page", "draw_boxes"]
     
     
    -def create_rect_patch(
    -    geometry: Union[BoundingBox, RotatedBbox],
    -    label: str,
    +def rect_patch(
    +    geometry: BoundingBox,
         page_dimensions: Tuple[int, int],
    -    color: Tuple[int, int, int],
    +    label: Optional[str] = None,
    +    color: Tuple[float, float, float] = (0, 0, 0),
         alpha: float = 0.3,
         linewidth: int = 2,
         fill: bool = True,
    -) -> patches.Patch:
    -    """Create a matplotlib patch (rectangle) bounding the element
    +    preserve_aspect_ratio: bool = False,
    +) -> patches.Rectangle:
    +    """Create a matplotlib rectangular patch for the element
     
         Args:
    +    ----
             geometry: bounding box of the element
    +        page_dimensions: dimensions of the Page in format (height, width)
             label: label to display when hovered
    -        page_dimensions: dimensions of the Page
             color: color to draw box
             alpha: opacity parameter to fill the boxes, 0 = transparent
             linewidth: line width
    +        fill: whether the patch should be filled
    +        preserve_aspect_ratio: pass True if you passed True to the predictor
     
         Returns:
    +    -------
             a rectangular Patch
         """
    +    if len(geometry) != 2 or any(not isinstance(elt, tuple) or len(elt) != 2 for elt in geometry):
    +        raise ValueError("invalid geometry format")
    +
    +    # Unpack
         height, width = page_dimensions
    -    if len(geometry) == 5:
    -        x, y, w, h, a = geometry  # type: ignore[misc]
    -        x, w = x * width, w * width
    -        y, h = y * height, h * height
    -        points = cv2.boxPoints(((x, y), (w, h), a))
    -        return patches.Polygon(
    -            points,
    -            fill=fill,
    -            linewidth=linewidth,
    -            edgecolor=(*color, alpha),
    -            facecolor=(*color, alpha),
    -            label=label
    -        )
    -    else:
    -        (xmin, ymin), (xmax, ymax) = geometry  # type: ignore[misc]
    -        xmin, xmax = xmin * width, xmax * width
    -        ymin, ymax = ymin * height, ymax * height
    -        return patches.Rectangle(
    -            (xmin, ymin),
    -            xmax - xmin,
    -            ymax - ymin,
    -            fill=fill,
    -            linewidth=linewidth,
    -            edgecolor=(*color, alpha),
    -            facecolor=(*color, alpha),
    -            label=label
    -        )
    +    (xmin, ymin), (xmax, ymax) = geometry
    +    # Switch to absolute coords
    +    if preserve_aspect_ratio:
    +        width = height = max(height, width)
    +    xmin, w = xmin * width, (xmax - xmin) * width
    +    ymin, h = ymin * height, (ymax - ymin) * height
    +
    +    return patches.Rectangle(
    +        (xmin, ymin),
    +        w,
    +        h,
    +        fill=fill,
    +        linewidth=linewidth,
    +        edgecolor=(*color, alpha),
    +        facecolor=(*color, alpha),
    +        label=label,
    +    )
    +
    +
    +def polygon_patch(
    +    geometry: np.ndarray,
    +    page_dimensions: Tuple[int, int],
    +    label: Optional[str] = None,
    +    color: Tuple[float, float, float] = (0, 0, 0),
    +    alpha: float = 0.3,
    +    linewidth: int = 2,
    +    fill: bool = True,
    +    preserve_aspect_ratio: bool = False,
    +) -> patches.Polygon:
    +    """Create a matplotlib polygon patch for the element
    +
    +    Args:
    +    ----
    +        geometry: bounding box of the element
    +        page_dimensions: dimensions of the Page in format (height, width)
    +        label: label to display when hovered
    +        color: color to draw box
    +        alpha: opacity parameter to fill the boxes, 0 = transparent
    +        linewidth: line width
    +        fill: whether the patch should be filled
    +        preserve_aspect_ratio: pass True if you passed True to the predictor
    +
    +    Returns:
    +    -------
    +        a polygon Patch
    +    """
    +    if not geometry.shape == (4, 2):
    +        raise ValueError("invalid geometry format")
    +
    +    # Unpack
    +    height, width = page_dimensions
    +    geometry[:, 0] = geometry[:, 0] * (max(width, height) if preserve_aspect_ratio else width)
    +    geometry[:, 1] = geometry[:, 1] * (max(width, height) if preserve_aspect_ratio else height)
    +
    +    return patches.Polygon(
    +        geometry,
    +        fill=fill,
    +        linewidth=linewidth,
    +        edgecolor=(*color, alpha),
    +        facecolor=(*color, alpha),
    +        label=label,
    +    )
    +
    +
    +def create_obj_patch(
    +    geometry: Union[BoundingBox, Polygon4P, np.ndarray],
    +    page_dimensions: Tuple[int, int],
    +    **kwargs: Any,
    +) -> patches.Patch:
    +    """Create a matplotlib patch for the element
    +
    +    Args:
    +    ----
    +        geometry: bounding box (straight or rotated) of the element
    +        page_dimensions: dimensions of the page in format (height, width)
    +        **kwargs: keyword arguments for the patch
    +
    +    Returns:
    +    -------
    +        a matplotlib Patch
    +    """
    +    if isinstance(geometry, tuple):
    +        if len(geometry) == 2:  # straight word BB (2 pts)
    +            return rect_patch(geometry, page_dimensions, **kwargs)
    +        elif len(geometry) == 4:  # rotated word BB (4 pts)
    +            return polygon_patch(np.asarray(geometry), page_dimensions, **kwargs)
    +    elif isinstance(geometry, np.ndarray) and geometry.shape == (4, 2):  # rotated line
    +        return polygon_patch(geometry, page_dimensions, **kwargs)
    +    raise ValueError("invalid geometry format")
    +
    +
    +def get_colors(num_colors: int) -> List[Tuple[float, float, float]]:
    +    """Generate num_colors color for matplotlib
    +
    +    Args:
    +    ----
    +        num_colors: number of colors to generate
    +
    +    Returns:
    +    -------
    +        colors: list of generated colors
    +    """
    +    colors = []
    +    for i in np.arange(0.0, 360.0, 360.0 / num_colors):
    +        hue = i / 360.0
    +        lightness = (50 + np.random.rand() * 10) / 100.0
    +        saturation = (90 + np.random.rand() * 10) / 100.0
    +        colors.append(colorsys.hls_to_rgb(hue, lightness, saturation))
    +    return colors
     
     
     
    -[docs] +[docs] def visualize_page( page: Dict[str, Any], image: np.ndarray, @@ -359,18 +472,18 @@

    Source code for doctr.utils.visualization

     ) -> Figure:
         """Visualize a full page with predicted blocks, lines and words
     
    -    Example::
    -        >>> import numpy as np
    -        >>> import matplotlib.pyplot as plt
    -        >>> from doctr.utils.visualization import visualize_page
    -        >>> from doctr.models import ocr_db_crnn
    -        >>> model = ocr_db_crnn(pretrained=True)
    -        >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    -        >>> out = model([[input_page]])
    -        >>> visualize_page(out[0].pages[0].export(), input_page)
    -        >>> plt.show()
    +    >>> import numpy as np
    +    >>> import matplotlib.pyplot as plt
    +    >>> from doctr.utils.visualization import visualize_page
    +    >>> from doctr.models import ocr_db_crnn
    +    >>> model = ocr_db_crnn(pretrained=True)
    +    >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    +    >>> out = model([[input_page]])
    +    >>> visualize_page(out[0].pages[0].export(), input_page)
    +    >>> plt.show()
     
         Args:
    +    ----
             page: the exported Page of a Document
             image: np array of the page, needs to have the same shape than page['dimensions']
             words_only: whether only words should be displayed
    @@ -378,6 +491,11 @@ 

    Source code for doctr.utils.visualization

             scale: figsize of the largest windows side
             interactive: whether the plot should be interactive
             add_labels: for static plot, adds text labels on top of bounding box
    +        **kwargs: keyword arguments for the polygon patch
    +
    +    Returns:
    +    -------
    +        the matplotlib figure
         """
         # Get proper scale and aspect ratio
         h, w = image.shape[:2]
    @@ -386,128 +504,189 @@ 

    Source code for doctr.utils.visualization

         # Display the image
         ax.imshow(image)
         # hide both axis
    -    ax.axis('off')
    +    ax.axis("off")
     
         if interactive:
             artists: List[patches.Patch] = []  # instantiate an empty list of patches (to be drawn on the page)
     
    -    for block in page['blocks']:
    +    for block in page["blocks"]:
             if not words_only:
    -            rect = create_rect_patch(block['geometry'], 'block', page['dimensions'], (0, 1, 0), linewidth=1, **kwargs)
    +            rect = create_obj_patch(
    +                block["geometry"], page["dimensions"], label="block", color=(0, 1, 0), linewidth=1, **kwargs
    +            )
                 # add patch on figure
                 ax.add_patch(rect)
                 if interactive:
                     # add patch to cursor's artists
                     artists.append(rect)
     
    -        for line in block['lines']:
    +        for line in block["lines"]:
                 if not words_only:
    -                rect = create_rect_patch(line['geometry'], 'line', page['dimensions'], (1, 0, 0), linewidth=1, **kwargs)
    +                rect = create_obj_patch(
    +                    line["geometry"], page["dimensions"], label="line", color=(1, 0, 0), linewidth=1, **kwargs
    +                )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
     
    -            for word in line['words']:
    -                rect = create_rect_patch(word['geometry'], f"{word['value']} (confidence: {word['confidence']:.2%})",
    -                                         page['dimensions'], (0, 0, 1), **kwargs)
    +            for word in line["words"]:
    +                rect = create_obj_patch(
    +                    word["geometry"],
    +                    page["dimensions"],
    +                    label=f"{word['value']} (confidence: {word['confidence']:.2%})",
    +                    color=(0, 0, 1),
    +                    **kwargs,
    +                )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
                     elif add_labels:
    -                    if len(word['geometry']) == 5:
    +                    if len(word["geometry"]) == 5:
                             text_loc = (
    -                            int(page['dimensions'][1] * (word['geometry'][0] - word['geometry'][2] / 2)),
    -                            int(page['dimensions'][0] * (word['geometry'][1] - word['geometry'][3] / 2))
    +                            int(page["dimensions"][1] * (word["geometry"][0] - word["geometry"][2] / 2)),
    +                            int(page["dimensions"][0] * (word["geometry"][1] - word["geometry"][3] / 2)),
                             )
                         else:
                             text_loc = (
    -                            int(page['dimensions'][1] * word['geometry'][0][0]),
    -                            int(page['dimensions'][0] * word['geometry'][0][1])
    +                            int(page["dimensions"][1] * word["geometry"][0][0]),
    +                            int(page["dimensions"][0] * word["geometry"][0][1]),
    +                        )
    +
    +                    if len(word["geometry"]) == 2:
    +                        # We draw only if boxes are in straight format
    +                        ax.text(
    +                            *text_loc,
    +                            word["value"],
    +                            size=10,
    +                            alpha=0.5,
    +                            color=(0, 0, 1),
                             )
    -                    ax.text(
    -                        *text_loc,
    -                        word['value'],
    -                        size=10,
    -                        alpha=0.5,
    -                        color=(0, 0, 1),
    -                    )
     
             if display_artefacts:
    -            for artefact in block['artefacts']:
    -                rect = create_rect_patch(
    -                    artefact['geometry'],
    -                    'artefact',
    -                    page['dimensions'],
    -                    (0.5, 0.5, 0.5),  # type: ignore[arg-type]
    +            for artefact in block["artefacts"]:
    +                rect = create_obj_patch(
    +                    artefact["geometry"],
    +                    page["dimensions"],
    +                    label="artefact",
    +                    color=(0.5, 0.5, 0.5),
                         linewidth=1,
    -                    **kwargs
    +                    **kwargs,
                     )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
     
         if interactive:
    +        import mplcursors
    +
             # Create mlp Cursor to hover patches in artists
             mplcursors.Cursor(artists, hover=2).connect("add", lambda sel: sel.annotation.set_text(sel.artist.get_label()))
    -    fig.tight_layout(pad=0.)
    +    fig.tight_layout(pad=0.0)
     
         return fig
    -def synthetize_page( +def visualize_kie_page( page: Dict[str, Any], - draw_proba: bool = False, - font_size: int = 13, -) -> np.ndarray: - """Draw a the content of the element page (OCR response) on a blank page. + image: np.ndarray, + words_only: bool = False, + display_artefacts: bool = True, + scale: float = 10, + interactive: bool = True, + add_labels: bool = True, + **kwargs: Any, +) -> Figure: + """Visualize a full page with predicted blocks, lines and words + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from doctr.utils.visualization import visualize_page + >>> from doctr.models import ocr_db_crnn + >>> model = ocr_db_crnn(pretrained=True) + >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) + >>> out = model([[input_page]]) + >>> visualize_kie_page(out[0].pages[0].export(), input_page) + >>> plt.show() Args: - page: exported Page object to represent - draw_proba: if True, draw words in colors to represent confidence. Blue: p=1, red: p=0 - font_size: size of the font, default font = 13 + ---- + page: the exported Page of a Document + image: np array of the page, needs to have the same shape than page['dimensions'] + words_only: whether only words should be displayed + display_artefacts: whether artefacts should be displayed + scale: figsize of the largest windows side + interactive: whether the plot should be interactive + add_labels: for static plot, adds text labels on top of bounding box + **kwargs: keyword arguments for the polygon patch - Return: - A np array (drawn page) + Returns: + ------- + the matplotlib figure """ - # Draw template - h, w = page["dimensions"] - response = 255 * np.ones((h, w, 3), dtype=np.int32) + # Get proper scale and aspect ratio + h, w = image.shape[:2] + size = (scale * w / h, scale) if h > w else (scale, h / w * scale) + fig, ax = plt.subplots(figsize=size) + # Display the image + ax.imshow(image) + # hide both axis + ax.axis("off") - # Draw each word - for block in page["blocks"]: - for line in block["lines"]: - for word in line["words"]: - # Get aboslute word geometry - (xmin, ymin), (xmax, ymax) = word["geometry"] - xmin, xmax = int(w * xmin), int(w * xmax) - ymin, ymax = int(h * ymin), int(h * ymax) - - # White drawing context adapted to font size, 0.75 factor to convert pts --> pix - h_box, w_box = ymax - ymin, xmax - xmin - h_font, w_font = font_size, int(font_size * w_box / (h_box * 0.75)) - img = Image.new('RGB', (w_font, h_font), color=(255, 255, 255)) - d = ImageDraw.Draw(img) - - # Draw in black the value of the word - d.text((0, 0), word["value"], font=ImageFont.load_default(), fill=(0, 0, 0)) - - # Resize back to box size - img = img.resize((w_box, h_box), Image.NEAREST) - - # Colorize if draw_proba - if draw_proba: - p = int(255 * word["confidence"]) - mask = np.where(np.array(img) == 0, 1, 0) - proba = np.array([255 - p, 0, p]) - color = mask * proba[np.newaxis, np.newaxis, :] - white_mask = 255 * (1 - mask) - img = color + white_mask - - # Write to response page - response[ymin:ymax, xmin:xmax, :] = np.array(img) - - return response + if interactive: + artists: List[patches.Patch] = [] # instantiate an empty list of patches (to be drawn on the page) + + colors = {k: color for color, k in zip(get_colors(len(page["predictions"])), page["predictions"])} + for key, value in page["predictions"].items(): + for prediction in value: + if not words_only: + rect = create_obj_patch( + prediction["geometry"], + page["dimensions"], + label=f"{key} \n {prediction['value']} (confidence: {prediction['confidence']:.2%}", + color=colors[key], + linewidth=1, + **kwargs, + ) + # add patch on figure + ax.add_patch(rect) + if interactive: + # add patch to cursor's artists + artists.append(rect) + + if interactive: + import mplcursors + + # Create mlp Cursor to hover patches in artists + mplcursors.Cursor(artists, hover=2).connect("add", lambda sel: sel.annotation.set_text(sel.artist.get_label())) + fig.tight_layout(pad=0.0) + + return fig + + +def draw_boxes(boxes: np.ndarray, image: np.ndarray, color: Optional[Tuple[int, int, int]] = None, **kwargs) -> None: + """Draw an array of relative straight boxes on an image + + Args: + ---- + boxes: array of relative boxes, of shape (*, 4) + image: np array, float32 or uint8 + color: color to use for bounding box edges + **kwargs: keyword arguments from `matplotlib.pyplot.plot` + """ + h, w = image.shape[:2] + # Convert boxes to absolute coords + _boxes = deepcopy(boxes) + _boxes[:, [0, 2]] *= w + _boxes[:, [1, 3]] *= h + _boxes = _boxes.astype(np.int32) + for box in _boxes.tolist(): + xmin, ymin, xmax, ymax = box + image = cv2.rectangle( + image, (xmin, ymin), (xmax, ymax), color=color if isinstance(color, tuple) else (0, 0, 255), thickness=2 + ) + plt.imshow(image) + plt.plot(**kwargs)
    @@ -540,8 +719,8 @@

    Source code for doctr.utils.visualization

           
         
       
    - - + + diff --git a/v0.3.0/_modules/index.html b/v0.3.0/_modules/index.html index e86abcd4d4..5793c44f20 100644 --- a/v0.3.0/_modules/index.html +++ b/v0.3.0/_modules/index.html @@ -13,7 +13,7 @@ - + Overview: module code - docTR documentation @@ -225,20 +225,42 @@ - - + + diff --git a/v0.3.0/_sources/changelog.rst.txt b/v0.3.0/_sources/changelog.rst.txt index 430097d6c8..35befe7b96 100644 --- a/v0.3.0/_sources/changelog.rst.txt +++ b/v0.3.0/_sources/changelog.rst.txt @@ -1,6 +1,54 @@ Changelog ========= +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 `_ diff --git a/v0.3.0/_sources/datasets.rst.txt b/v0.3.0/_sources/datasets.rst.txt deleted file mode 100644 index 354122f1e5..0000000000 --- a/v0.3.0/_sources/datasets.rst.txt +++ /dev/null @@ -1,68 +0,0 @@ -doctr.datasets -============== - -.. currentmodule:: doctr.datasets - -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. - - -.. _datasets: - -Available Datasets ------------------- -The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL. - -.. autoclass:: doctr.datasets.datasets.VisionDataset - - -Here are all datasets that are available through DocTR: - -.. autoclass:: FUNSD -.. autoclass:: SROIE -.. autoclass:: CORD -.. autoclass:: OCRDataset - - -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. - -.. autoclass:: doctr.datasets.loader.DataLoader - - -.. _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 - * - digits - - 10 - - 0123456789 - * - ascii_letters - - 52 - - abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ - * - punctuation - - 32 - - !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ - * - currency - - 5 - - £€¥¢฿ - * - latin - - 96 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~° - * - french - - 154 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿ - -.. autofunction:: encode_sequences diff --git a/v0.3.0/_sources/documents.rst.txt b/v0.3.0/_sources/documents.rst.txt deleted file mode 100644 index 655730073e..0000000000 --- a/v0.3.0/_sources/documents.rst.txt +++ /dev/null @@ -1,87 +0,0 @@ -doctr.documents -=============== - - -.. currentmodule:: doctr.documents - -The documents module enables users to easily access content from documents and export analysis -results to structured formats. - - -Document structure ------------------- - -Structural organization of the documents. - -Word -^^^^ -A Word is an uninterrupted sequence of characters. - -.. autoclass:: Word - -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 - -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 - - -Document -^^^^^^^^ - -A Document is a collection of Pages. - -.. autoclass:: Document - - .. automethod:: show - - -File reading ------------- - -High-performance file reading and conversion to processable structured data. - -.. autofunction:: read_pdf - -.. autofunction:: read_img - -.. autofunction:: read_html - - -.. autoclass:: DocumentFile - - .. automethod:: from_pdf - - .. automethod:: from_url - - .. automethod:: from_images - -.. autoclass:: PDF - - .. automethod:: as_images - - .. automethod:: get_words - - .. automethod:: get_artefacts diff --git a/v0.3.0/_sources/getting_started/installing.rst.txt b/v0.3.0/_sources/getting_started/installing.rst.txt index e764e734a7..39e79aa3dd 100644 --- a/v0.3.0/_sources/getting_started/installing.rst.txt +++ b/v0.3.0/_sources/getting_started/installing.rst.txt @@ -3,7 +3,7 @@ Installation ************ -This library requires `Python `_ 3.9 or higher. +This library requires `Python `_ 3.10 or higher. Prerequisites diff --git a/v0.3.0/_sources/index.rst.txt b/v0.3.0/_sources/index.rst.txt index fc3ff89fdf..53251db142 100644 --- a/v0.3.0/_sources/index.rst.txt +++ b/v0.3.0/_sources/index.rst.txt @@ -1,7 +1,8 @@ -DocTR: Document Text Recognition -================================ +******************************** +docTR: Document Text Recognition +******************************** -State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 (PyTorch now in beta) +State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 & PyTorch .. image:: https://github.com/mindee/doctr/releases/download/v0.2.0/ocr.png :align: center @@ -9,38 +10,29 @@ State-of-the-art Optical Character Recognition made seamless & accessible to any DocTR provides an easy and powerful way to extract valuable information from your documents: -* |:receipt:| **for automation**: seemlessly process documents for Natural Language Understanding tasks: we provide OCR predictors to parse textual information (localize and identify each word) 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. -Welcome to the documentation of `DocTR `_! - - Main Features ------------- * |:robot:| Robust 2-stage (detection + recognition) OCR predictors with pretrained parameters * |:zap:| User-friendly, 3 lines of code to load a document and extract text with a predictor -* |:rocket:| State-of-the-art performances on public document datasets, comparable with GoogleVision/AWS Textract +* |: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, small dependencies -* |:tools:| Daily maintained -* |:factory:| Easy integration - +* |:bird:| Light package, minimal dependencies +* |:tools:| Actively maintained by Mindee +* |:factory:| Easy integration (available templates for browser demo & API deployment) -Getting Started ---------------- .. toctree:: :maxdepth: 2 + :caption: Getting started + :hidden: - installing - - -Build & train your predictor -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* Compose your own end-to-end OCR predictor: mix and match detection & recognition predictors (all-pretrained) -* Fine-tune or train from scratch any detection or recognition model to specialize on your data + getting_started/installing + notebooks Model zoo @@ -48,36 +40,83 @@ Model zoo Text detection models """"""""""""""""""""" - * `DBNet `_ (Differentiable Binarization) - * `LinkNet `_ +* 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 `_ (Show, Attend and Read) - * `CRNN `_ (Convolutional Recurrent Neural Network) - * `MASTER `_ (Multi-Aspect Non-local Network for Scene Text Recognition) +* 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" `_ 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 `_. +* 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" `_. .. toctree:: :maxdepth: 2 - :caption: Notes + :caption: Using docTR + :hidden: - changelog + 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 + + +.. toctree:: + :maxdepth: 2 + :caption: Community + :hidden: + + community/resources .. toctree:: :maxdepth: 2 :caption: Package Reference + :hidden: - datasets - documents - models - transforms - utils + 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/v0.3.0/_sources/installing.rst.txt b/v0.3.0/_sources/installing.rst.txt deleted file mode 100644 index 5c8779dc1c..0000000000 --- a/v0.3.0/_sources/installing.rst.txt +++ /dev/null @@ -1,46 +0,0 @@ - -************ -Installation -************ - -This library requires Python 3.6 or higher. - - -Prerequisites -============= - -Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so: - -* TensorFlow: `installation page `_. -* PyTorch: `installation page `_. - -If you are running another OS than Linux, you will need a few extra dependencies. - -For MacOS users, you can install them as follows: - -.. code:: shell - - brew install cairo pango gdk-pixbuf libffi - -For Windows users, those dependencies are included in GTK. You can find the latest installer over `here `_. - - -Via Python Package -================== - -Install the last stable release of the package using pip: - -.. code:: bash - - pip install python-doctr - - -Via Git -======= - -Install the library in developper mode: - -.. code:: bash - - git clone https://github.com/mindee/doctr.git - pip install -e doctr/. diff --git a/v0.3.0/_sources/models.rst.txt b/v0.3.0/_sources/models.rst.txt deleted file mode 100644 index 9830c6c153..0000000000 --- a/v0.3.0/_sources/models.rst.txt +++ /dev/null @@ -1,215 +0,0 @@ -doctr.models -============ - -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. - -.. currentmodule:: doctr.models - -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 TensorFlow model. -* Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable. - - -Text Detection --------------- -Localizing text elements in images - -+---------------------------------------------------+----------------------------+----------------------------+---------+ -| | FUNSD | CORD | | -+==================+=================+==============+============+===============+============+===============+=========+ -| **Architecture** | **Input shape** | **# params** | **Recall** | **Precision** | **Recall** | **Precision** | **FPS** | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ -| db_resnet50 | (1024, 1024, 3) | 25.2 M | 82.14 | 87.64 | 92.49 | 89.66 | 2.1 | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ - -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 combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for detection -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for detection is the following: - -1. resize each input image to the target size (bilinear interpolation by default) with potential deformation. -2. batch images together -3. normalize the batch using the training data statistics - - -Detection models -^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - -.. autofunction:: doctr.models.detection.db_resnet50 -.. autofunction:: doctr.models.detection.linknet16 - -Detection predictors -^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information. - -.. autofunction:: doctr.models.detection.detection_predictor - - -Text Recognition ----------------- -Identifying strings in images - -.. list-table:: Text recognition model zoo - :widths: 20 20 15 10 10 10 - :header-rows: 1 - - * - Architecture - - Input shape - - # params - - FUNSD - - CORD - - FPS - * - crnn_vgg16_bn - - (32, 128, 3) - - 15.8M - - 86.02 - - 91.3 - - 12.8 - * - sar_vgg16_bn - - (32, 128, 3) - - 21.5M - - 86.2 - - 91.7 - - 3.3 - * - sar_resnet31 - - (32, 128, 3) - - 53.1M - - **86.3** - - **92.1** - - 2.7 - -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 metrics being used are available in :ref:`metrics`. - -All these recognition models are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for recognition -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for recognition is the following: - -1. resize each input image to the target size (bilinear interpolation by default) without deformation. -2. pad the image to the target size (with zeros by default) -3. batch images together -4. normalize the batch using the training data statistics - -Recognition models -^^^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - - -.. autofunction:: doctr.models.recognition.crnn_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_resnet31 -.. autofunction:: doctr.models.recognition.master - - -Recognition predictors -^^^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage. - -.. autofunction:: doctr.models.recognition.recognition_predictor - - -End-to-End OCR --------------- -Predictors that localize and identify text elements in images - -+-----------------------------+--------------------------------------+--------------------------------------+ -| | FUNSD | CORD | -+=============================+============+===============+=========+============+===============+=========+ -| **Architecture** | **Recall** | **Precision** | **FPS** | **Recall** | **Precision** | **FPS** | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + crnn_vgg16_bn | 70.08 | 74.77 | 0.85 | 82.19 | **79.67** | 1.6 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_vgg16_bn | N/A | N/A | 0.49 | N/A | N/A | 1.0 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_resnet31 | N/A | N/A | 0.27 | N/A | N/A | 0.83 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision text detection | 59.50 | 62.50 | | 75.30 | 70.00 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision doc. text detection | 64.00 | 53.30 | | 68.90 | 61.10 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| AWS textract | **78.10** | **83.00** | | **87.50** | 66.00 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ - -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`. - -All recognition models of predictors are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Results on private ocr datasets - -+------------------------------------+----------------------------+----------------------------+----------------------------+ -| | Receipts | Invoices | IDs | -+====================================+============+===============+============+===============+============+===============+ -| **Architecture** | **Recall** | **Precision** | **Recall** | **Precision** | **Recall** | **Precision** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| db_resnet50 + crnn_vgg16_bn (ours) | **78.90** | **81.01** | 65.68 | **69.86** | **49.48** | **50.46** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| Gvision doc. text detection | 68.91 | 59.89 | 63.20 | 52.85 | 43.70 | 29.21 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| AWS textract | 75.77 | 77.70 | **70.47** | 69.13 | 46.39 | 43.32 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ - - -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. - -.. autofunction:: doctr.models.zoo.ocr_predictor - - -Model export ------------- -Utility functions to make the most of document analysis models. - -.. currentmodule:: doctr.models.export - -Model compression -^^^^^^^^^^^^^^^^^ - -.. autofunction:: convert_to_tflite - -.. autofunction:: convert_to_fp16 - -.. autofunction:: quantize_model - -Using SavedModel -^^^^^^^^^^^^^^^^ - -Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -`SavedModel `_ format as follows: - - - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> _ = model(input_t, training=False) - >>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/') - -And loaded just as easily: - - - >>> import tensorflow as tf - >>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/') diff --git a/v0.3.0/_sources/transforms.rst.txt b/v0.3.0/_sources/transforms.rst.txt deleted file mode 100644 index 0230fe75f5..0000000000 --- a/v0.3.0/_sources/transforms.rst.txt +++ /dev/null @@ -1,32 +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: - -.. autoclass:: Resize -.. autoclass:: Normalize -.. autoclass:: LambdaTransformation -.. autoclass:: ToGray -.. autoclass:: ColorInversion -.. autoclass:: RandomBrightness -.. autoclass:: RandomContrast -.. autoclass:: RandomSaturation -.. autoclass:: RandomHue -.. autoclass:: RandomGamma -.. autoclass:: RandomJpegQuality - - -Composing transformations ---------------------------------------------- -It is common to require several transformations to be performed consecutively. - -.. autoclass:: Compose -.. autoclass:: OneOf -.. autoclass:: RandomApply diff --git a/v0.3.0/_sources/utils.rst.txt b/v0.3.0/_sources/utils.rst.txt deleted file mode 100644 index 69c1abe0eb..0000000000 --- a/v0.3.0/_sources/utils.rst.txt +++ /dev/null @@ -1,36 +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 - - -.. _metrics: - -Task evaluation ---------------- -Implementations of task-specific metrics to easily assess your model performances. - -.. currentmodule:: doctr.utils.metrics - -.. autoclass:: TextMatch - - .. automethod:: summary - -.. autoclass:: LocalizationConfusion - - .. automethod:: summary - -.. autoclass:: OCRMetric - - .. automethod:: summary diff --git a/v0.3.0/_static/basic.css b/v0.3.0/_static/basic.css index f316efcb47..7ebbd6d07b 100644 --- a/v0.3.0/_static/basic.css +++ b/v0.3.0/_static/basic.css @@ -1,12 +1,5 @@ /* - * basic.css - * ~~~~~~~~~ - * * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ /* -- main layout ----------------------------------------------------------- */ @@ -115,15 +108,11 @@ img { /* -- search page ----------------------------------------------------------- */ ul.search { - margin: 10px 0 0 20px; - padding: 0; + margin-top: 10px; } ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; + padding: 5px 0; } ul.search li a { diff --git a/v0.3.0/_static/doctools.js b/v0.3.0/_static/doctools.js index 4d67807d17..0398ebb9f0 100644 --- a/v0.3.0/_static/doctools.js +++ b/v0.3.0/_static/doctools.js @@ -1,12 +1,5 @@ /* - * doctools.js - * ~~~~~~~~~~~ - * * Base JavaScript utilities for all Sphinx HTML documentation. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; diff --git a/v0.3.0/_static/documentation_options.js b/v0.3.0/_static/documentation_options.js index a7b5cbe04a..4f656fdbea 100644 --- a/v0.3.0/_static/documentation_options.js +++ b/v0.3.0/_static/documentation_options.js @@ -1,5 +1,5 @@ const DOCUMENTATION_OPTIONS = { - VERSION: '0.3.0a0-git', + VERSION: '0.10.1a0-git', LANGUAGE: 'en', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/v0.3.0/_static/language_data.js b/v0.3.0/_static/language_data.js index 367b8ed81b..c7fe6c6faf 100644 --- a/v0.3.0/_static/language_data.js +++ b/v0.3.0/_static/language_data.js @@ -1,13 +1,6 @@ /* - * language_data.js - * ~~~~~~~~~~~~~~~~ - * * This script contains the language-specific data used by searchtools.js, * namely the list of stopwords, stemmer, scorer and splitter. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; diff --git a/v0.3.0/_static/searchtools.js b/v0.3.0/_static/searchtools.js index b08d58c9b9..2c774d17af 100644 --- a/v0.3.0/_static/searchtools.js +++ b/v0.3.0/_static/searchtools.js @@ -1,12 +1,5 @@ /* - * searchtools.js - * ~~~~~~~~~~~~~~~~ - * * Sphinx JavaScript utilities for the full-text search. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; @@ -20,7 +13,7 @@ if (typeof Scorer === "undefined") { // and returns the new score. /* score: result => { - const [docname, title, anchor, descr, score, filename] = result + const [docname, title, anchor, descr, score, filename, kind] = result return score }, */ @@ -47,6 +40,14 @@ if (typeof Scorer === "undefined") { }; } +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + const _removeChildren = (element) => { while (element && element.lastChild) element.removeChild(element.lastChild); }; @@ -64,9 +65,13 @@ const _displayItem = (item, searchTerms, highlightTerms) => { const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; const contentRoot = document.documentElement.dataset.content_root; - const [docName, title, anchor, descr, score, _filename] = item; + const [docName, title, anchor, descr, score, _filename, kind] = item; let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); let requestUrl; let linkUrl; if (docBuilder === "dirhtml") { @@ -115,8 +120,10 @@ const _finishSearch = (resultCount) => { "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." ); else - Search.status.innerText = _( - "Search finished, found ${resultCount} page(s) matching the search query." + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, ).replace('${resultCount}', resultCount); }; const _displayNextItem = ( @@ -138,7 +145,7 @@ const _displayNextItem = ( else _finishSearch(resultCount); }; // Helper function used by query() to order search results. -// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. // Order the results by score (in opposite order of appearance, since the // `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. const _orderResultsByScoreThenName = (a, b) => { @@ -248,6 +255,7 @@ const Search = { searchSummary.classList.add("search-summary"); searchSummary.innerText = ""; const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); searchList.classList.add("search"); const out = document.getElementById("search-results"); @@ -318,7 +326,7 @@ const Search = { const indexEntries = Search._index.indexentries; // Collect multiple result groups to be sorted separately and then ordered. - // Each is an array of [docname, title, anchor, descr, score, filename]. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. const normalResults = []; const nonMainIndexResults = []; @@ -337,6 +345,7 @@ const Search = { null, score + boost, filenames[file], + SearchResultKind.title, ]); } } @@ -354,6 +363,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.index, ]; if (isMain) { normalResults.push(result); @@ -475,6 +485,7 @@ const Search = { descr, score, filenames[match[0]], + SearchResultKind.object, ]); }; Object.keys(objects).forEach((prefix) => @@ -585,6 +596,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.text, ]); } return results; diff --git a/v0.3.0/changelog.html b/v0.3.0/changelog.html index eafac3a877..fc45a50384 100644 --- a/v0.3.0/changelog.html +++ b/v0.3.0/changelog.html @@ -12,9 +12,9 @@ gtag('js', new Date()); gtag('config', 'G-40DVRMX8T4'); - + - + Changelog - docTR documentation @@ -226,20 +226,42 @@ + diff --git a/v0.3.0/community/resources.html b/v0.3.0/community/resources.html index 2564037893..9a1988258c 100644 --- a/v0.3.0/community/resources.html +++ b/v0.3.0/community/resources.html @@ -14,7 +14,7 @@ - + Community resources - docTR documentation @@ -389,7 +389,7 @@

    Community resources - + diff --git a/v0.3.0/contributing/code_of_conduct.html b/v0.3.0/contributing/code_of_conduct.html index 5ea4a1f99d..03422dbb4d 100644 --- a/v0.3.0/contributing/code_of_conduct.html +++ b/v0.3.0/contributing/code_of_conduct.html @@ -14,7 +14,7 @@ - + Contributor Covenant Code of Conduct - docTR documentation @@ -504,7 +504,7 @@

    Attribution - + diff --git a/v0.3.0/contributing/contributing.html b/v0.3.0/contributing/contributing.html index e5a85682c6..05e2b3641b 100644 --- a/v0.3.0/contributing/contributing.html +++ b/v0.3.0/contributing/contributing.html @@ -14,7 +14,7 @@ - + Contributing to docTR - docTR documentation @@ -481,7 +481,7 @@

    Let’s connect - + diff --git a/v0.3.0/datasets.html b/v0.3.0/datasets.html deleted file mode 100644 index 193e576c57..0000000000 --- a/v0.3.0/datasets.html +++ /dev/null @@ -1,578 +0,0 @@ - - - - - - - - - - - - - doctr.datasets - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.datasets

    -

    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.

    -
    -

    Available Datasets

    -

    The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL.

    -
    -
    -class doctr.datasets.datasets.VisionDataset(url: str, file_name: str | None = None, file_hash: str | None = None, extract_archive: bool = False, download: bool = False, overwrite: bool = False)[source]
    -
    - -

    Here are all datasets that are available through DocTR:

    -
    -
    -class doctr.datasets.FUNSD(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    FUNSD dataset from “FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents”.

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD
    ->>> train_set = FUNSD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.SROIE(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    SROIE dataset from “ICDAR2019 Competition on Scanned Receipt OCR and Information Extraction”.

    -
    -
    Example::
    >>> from doctr.datasets import SROIE
    ->>> train_set = SROIE(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.CORD(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    CORD dataset from “CORD: A Consolidated Receipt Dataset forPost-OCR Parsing”.

    -
    -
    Example::
    >>> from doctr.datasets import CORD
    ->>> train_set = CORD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.OCRDataset(img_folder: str, label_file: str, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    Implements an OCR dataset

    -
    -
    Parameters:
    -
      -
    • img_folder – local path to image folder (all jpg at the root)

    • -
    • label_file – local path to the label file

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -

    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.

    -
    -
    -class doctr.datasets.loader.DataLoader(dataset, shuffle: bool = True, batch_size: int = 1, drop_last: bool = False, workers: int | None = None)[source]
    -

    Implements a dataset wrapper for fast data loading

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD, 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)
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • dataset – the dataset

    • -
    • shuffle – whether the samples should be shuffled before passing it to the iterator

    • -
    • batch_size – number of elements in each batch

    • -
    • drop_last – if True, drops the last batch if it isn’t full

    • -
    • workers – number of workers to use for data loading

    • -
    -
    -
    -
    - -
    -
    -

    Supported Vocabs

    -

    Since textual content has to be encoded properly for models to interpret them efficiently, DocTR supports multiple sets -of vocabs.

    -
    - - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    DocTR Vocabs

    Name

    size

    characters

    digits

    10

    0123456789

    ascii_letters

    52

    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

    punctuation

    32

    !”#$%&'()*+,-./:;<=>?@[\]^_`{|}~

    currency

    5

    £€¥¢฿

    latin

    96

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°

    french

    154

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿

    -
    -
    -
    -doctr.datasets.encode_sequences(sequences: List[str], vocab: str, target_size: int | None = None, eos: int = -1, sos: int | None = None, pad: int | None = None, **kwargs: Any) ndarray[source]
    -

    Encode character sequences using a given vocab as mapping

    -
    -
    Parameters:
    -
      -
    • sequences – the list of character sequences of size N

    • -
    • vocab – the ordered vocab to use for encoding

    • -
    • target_size – maximum length of the encoded data

    • -
    • eos – encoding of End Of String

    • -
    • sos – optional encoding of Start Of String

    • -
    • pad – optional encoding for padding. In case of padding, all sequences are followed by 1 EOS then PAD

    • -
    -
    -
    Returns:
    -

    the padded encoded data as a tensor

    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.0/documents.html b/v0.3.0/documents.html deleted file mode 100644 index 98cbb2c5ef..0000000000 --- a/v0.3.0/documents.html +++ /dev/null @@ -1,772 +0,0 @@ - - - - - - - - - - - - - doctr.documents - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.documents

    -

    The documents module enables users to easily access content from documents and export analysis -results to structured formats.

    -
    -

    Document structure

    -

    Structural organization of the documents.

    -
    -

    Word

    -

    A Word is an uninterrupted sequence of characters.

    -
    -
    -class doctr.documents.Word(value: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float])[source]
    -

    Implements a word element

    -
    -
    Parameters:
    -
      -
    • value – the text string of the word

    • -
    • confidence – the confidence associated with the text prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to

    • -
    • size (the page's)

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Line(words: List[Word], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float] | None = None)[source]
    -

    Implements a line element as a collection of words

    -
    -
    Parameters:
    -
      -
    • words – list of word elements

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all words in it.

    • -
    -
    -
    -
    - -
    -
    -

    Artefact

    -

    An Artefact is a non-textual element (e.g. QR code, picture, chart, signature, logo, etc.).

    -
    -
    -class doctr.documents.Artefact(artefact_type: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]])[source]
    -

    Implements a non-textual element

    -
    -
    Parameters:
    -
      -
    • artefact_type – the type of artefact

    • -
    • confidence – the confidence of the type prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size.

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Block(lines: List[Line] = [], artefacts: List[Artefact] = [], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float] | None = None)[source]
    -

    Implements a block element as a collection of lines and artefacts

    -
    -
    Parameters:
    -
      -
    • lines – list of line elements

    • -
    • artefacts – list of artefacts

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all lines and artefacts in it.

    • -
    -
    -
    -
    - -
    -
    -

    Page

    -

    A Page is a collection of Blocks that were on the same physical page.

    -
    -
    -class doctr.documents.Page(blocks: List[Block], page_idx: int, dimensions: Tuple[int, int], orientation: Dict[str, Any] | None = None, language: Dict[str, Any] | None = None)[source]
    -

    Implements a page element as a collection of blocks

    -
    -
    Parameters:
    -
      -
    • blocks – list of block elements

    • -
    • page_idx – the index of the page in the input raw document

    • -
    • dimensions – the page size in pixels in format (width, height)

    • -
    • orientation – a dictionary with the value of the rotation angle in degress and confidence of the prediction

    • -
    • language – a dictionary with the language value and confidence of the prediction

    • -
    -
    -
    -
    -
    -show(page: ndarray, interactive: bool = True, **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -
      -
    • page – image encoded as a numpy array in uint8

    • -
    • interactive – whether the display should be interactive

    • -
    -
    -
    -
    - -
    - -
    -
    -

    Document

    -

    A Document is a collection of Pages.

    -
    -
    -class doctr.documents.Document(pages: List[Page])[source]
    -

    Implements a document element as a collection of pages

    -
    -
    Parameters:
    -

    pages – list of page elements

    -
    -
    -
    -
    -show(pages: List[ndarray], **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -

    pages – list of images encoded as numpy arrays in uint8

    -
    -
    -
    - -
    - -
    -
    -
    -

    File reading

    -

    High-performance file reading and conversion to processable structured data.

    -
    -
    -doctr.documents.read_pdf(file: str | Path | bytes, **kwargs: Any) Document[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_pdf
    ->>> doc = read_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_img(file: str | Path | bytes, output_size: Tuple[int, int] | None = None, rgb_output: bool = True) ndarray[source]
    -

    Read an image file into numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_img
    ->>> page = read_img("path/to/your/doc.jpg")
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • file – the path to the image file

    • -
    • output_size – the expected output size of each page in format H x W

    • -
    • rgb_output – whether the output ndarray channel order should be RGB instead of BGR.

    • -
    -
    -
    Returns:
    -

    the page decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_html(url: str, **kwargs: Any) bytes[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_html
    ->>> doc = read_html("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – URL of the target web page

    -
    -
    Returns:
    -

    decoded PDF file as a bytes stream

    -
    -
    -
    - -
    -
    -class doctr.documents.DocumentFile[source]
    -

    Read a document from multiple extensions

    -
    -
    -classmethod from_pdf(file: str | Path | bytes, **kwargs) PDF[source]
    -

    Read a PDF file

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file or a binary stream

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_url(url: str, **kwargs) PDF[source]
    -

    Interpret a web page as a PDF document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_url("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – the URL of the target web page

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_images(files: Sequence[str | Path | bytes] | str | Path | bytes, **kwargs) List[ndarray][source]
    -

    Read an image file (or a collection of image files) and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"])
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    files – the path to the image file or a binary stream, or a collection of those

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    - -
    -
    -class doctr.documents.PDF(doc: Document)[source]
    -

    PDF document template

    -
    -
    Parameters:
    -

    doc – input PDF document

    -
    -
    -
    -
    -as_images(**kwargs) List[ndarray][source]
    -

    Convert all document pages to images

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of convert_page_to_numpy

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -get_words(**kwargs) List[List[Tuple[Tuple[float, float, float, float], str]]][source]
    -

    Get the annotations for all words in the document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of fitz.Page.getTextWords

    -
    -
    Returns:
    -

    the list of pages annotations, represented as a list of tuple (bounding box, value)

    -
    -
    -
    - -
    -
    -get_artefacts() List[List[Tuple[float, float, float, float]]][source]
    -

    Get the artefacts for the entire document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts()
    -
    -
    -
    -
    -
    -
    Returns:
    -

    the list of pages artefacts, represented as a list of bounding boxes

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.0/genindex.html b/v0.3.0/genindex.html index a19b433943..21520455b4 100644 --- a/v0.3.0/genindex.html +++ b/v0.3.0/genindex.html @@ -13,7 +13,7 @@ - Index - docTR documentation + Index - docTR documentation @@ -224,20 +224,42 @@

    +
    +

    U

    + + +
    +
    +

    V

    @@ -561,7 +711,13 @@

    V

    W

    +
    @@ -599,8 +755,8 @@

    W

    - - + + diff --git a/v0.3.0/getting_started/installing.html b/v0.3.0/getting_started/installing.html index a488e9a030..af3b58193e 100644 --- a/v0.3.0/getting_started/installing.html +++ b/v0.3.0/getting_started/installing.html @@ -14,7 +14,7 @@ - + Installation - docTR documentation @@ -305,7 +305,7 @@

    Installation

    -

    This library requires Python 3.9 or higher.

    +

    This library requires Python 3.10 or higher.

    Prerequisites

    Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so:

    @@ -435,7 +435,7 @@

    Via Git - + diff --git a/v0.3.0/index.html b/v0.3.0/index.html index 4c6a28c66a..3a06afc6d9 100644 --- a/v0.3.0/index.html +++ b/v0.3.0/index.html @@ -12,9 +12,9 @@ gtag('js', new Date()); gtag('config', 'G-40DVRMX8T4'); - + - + docTR documentation @@ -226,20 +226,42 @@
    -

    DocTR: Document Text Recognition

    -

    State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 (PyTorch now in beta)

    +

    docTR: Document Text Recognition

    +

    State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 & PyTorch

    https://github.com/mindee/doctr/releases/download/v0.2.0/ocr.png

    DocTR provides an easy and powerful way to extract valuable information from your documents:

      -
    • 🧾 for automation: seemlessly process documents for Natural Language Understanding tasks: we provide OCR predictors to parse textual information (localize and identify each word) from your documents.

    • +
    • 🧾 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.

    • 👩‍🔬 for research: quickly compare your own architectures speed & performances with state-of-art models on public datasets.

    -

    Welcome to the documentation of DocTR!

    Main Features

    • 🤖 Robust 2-stage (detection + recognition) OCR predictors with pretrained parameters

    • ⚡ User-friendly, 3 lines of code to load a document and extract text with a predictor

    • -
    • 🚀 State-of-the-art performances on public document datasets, comparable with GoogleVision/AWS Textract

    • +
    • 🚀 State-of-the-art performance on public document datasets, comparable with GoogleVision/AWS Textract

    • ⚡ Optimized for inference speed on both CPU & GPU

    • -
    • 🐦 Light package, small dependencies

    • -
    • 🛠️ Daily maintained

    • -
    • 🏭 Easy integration

    • +
    • 🐦 Light package, minimal dependencies

    • +
    • 🛠️ Actively maintained by Mindee

    • +
    • 🏭 Easy integration (available templates for browser demo & API deployment)

    -
    -
    -

    Getting Started

    -
    -

    Build & train your predictor

    -
      -
    • Compose your own end-to-end OCR predictor: mix and match detection & recognition predictors (all-pretrained)

    • -
    • Fine-tune or train from scratch any detection or recognition model to specialize on your data

    • -
    -

    Model zoo

    Text detection models

    -
    -

    Text recognition models

    -
    -

    Supported datasets

    -
    -
    +
    +
    +
    +
    +
    @@ -406,7 +381,7 @@

    Supported datasets - +
    Next @@ -446,10 +421,8 @@

    Supported datasets + diff --git a/v0.3.0/installing.html b/v0.3.0/installing.html deleted file mode 100644 index b61c60134b..0000000000 --- a/v0.3.0/installing.html +++ /dev/null @@ -1,395 +0,0 @@ - - - - - - - - - - - - - Installation - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    Installation

    -

    This library requires Python 3.6 or higher.

    -
    -

    Prerequisites

    -

    Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so:

    - -

    If you are running another OS than Linux, you will need a few extra dependencies.

    -

    For MacOS users, you can install them as follows:

    -
    brew install cairo pango gdk-pixbuf libffi
    -
    -
    -

    For Windows users, those dependencies are included in GTK. You can find the latest installer over here.

    -
    -
    -

    Via Python Package

    -

    Install the last stable release of the package using pip:

    -
    pip install python-doctr
    -
    -
    -
    -
    -

    Via Git

    -

    Install the library in developper mode:

    -
    git clone https://github.com/mindee/doctr.git
    -pip install -e doctr/.
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.0/models.html b/v0.3.0/models.html deleted file mode 100644 index b5cd44c9fa..0000000000 --- a/v0.3.0/models.html +++ /dev/null @@ -1,1002 +0,0 @@ - - - - - - - - - - - - - doctr.models - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.models

    -

    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 TensorFlow model.

    • -
    • Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable.

    • -
    -
    -

    Text Detection

    -

    Localizing text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Input shape

    # params

    Recall

    Precision

    Recall

    Precision

    FPS

    db_resnet50

    (1024, 1024, 3)

    25.2 M

    82.14

    87.64

    92.49

    89.66

    2.1

    -
    -

    All text detection models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for detection

    -

    In DocTR, the pre-processing scheme for detection is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) with potential deformation.

    2. -
    3. batch images together

    4. -
    5. normalize the batch using the training data statistics

    6. -
    -
    -
    -

    Detection models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.detection.db_resnet50(pretrained: bool = False, **kwargs: Any) DBNet[source]
    -

    DBNet as described in “Real-time Scene Text Detection with Differentiable Binarization”, using a ResNet-50 backbone.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -doctr.models.detection.linknet16(pretrained: bool = False, **kwargs: Any) LinkNet[source]
    -

    LinkNet as described in “LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import linknet16
    ->>> model = linknet16(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -

    Detection predictors

    -

    Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information.

    -
    -
    -doctr.models.detection.detection_predictor(arch: str = 'db_resnet50', pretrained: bool = False, **kwargs: Any) DetectionPredictor[source]
    -

    Text detection architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import detection_predictor
    ->>> model = detection_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_resnet50’)

    • -
    • pretrained – If True, returns a model pre-trained on our text detection dataset

    • -
    -
    -
    Returns:
    -

    Detection predictor

    -
    -
    -
    - -
    -
    -
    -

    Text Recognition

    -

    Identifying strings in images

    -
    - - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Text recognition model zoo

    Architecture

    Input shape

    # params

    FUNSD

    CORD

    FPS

    crnn_vgg16_bn

    (32, 128, 3)

    15.8M

    86.02

    91.3

    12.8

    sar_vgg16_bn

    (32, 128, 3)

    21.5M

    86.2

    91.7

    3.3

    sar_resnet31

    (32, 128, 3)

    53.1M

    86.3

    92.1

    2.7

    -
    -

    All text recognition models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All these recognition models are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for recognition

    -

    In DocTR, the pre-processing scheme for recognition is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) without deformation.

    2. -
    3. pad the image to the target size (with zeros by default)

    4. -
    5. batch images together

    6. -
    7. normalize the batch using the training data statistics

    8. -
    -
    -
    -

    Recognition models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.recognition.crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) CRNN[source]
    -

    CRNN with a VGG-16 backbone as described in “An End-to-End Trainable Neural Network for Image-based -Sequence Recognition and Its Application to Scene Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import crnn_vgg16_bn
    ->>> model = crnn_vgg16_bn(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a VGG16 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_vgg16_bn
    ->>> model = sar_vgg16_bn(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_resnet31(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a resnet-31 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -

    Example

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_resnet31
    ->>> model = sar_resnet31(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.master(pretrained: bool = False, **kwargs: Any) MASTER[source]
    -

    MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_. -Example:

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import master
    ->>> model = master(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 48, 160, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -

    Recognition predictors

    -

    Combining the right components around a given architecture for easier usage.

    -
    -
    -doctr.models.recognition.recognition_predictor(arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) RecognitionPredictor[source]
    -

    Text recognition architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import recognition_predictor
    ->>> model = recognition_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(32, 128, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘crnn_vgg16_bn’, ‘crnn_resnet31’, ‘sar_vgg16_bn’, ‘sar_resnet31’)

    • -
    • pretrained – If True, returns a model pre-trained on our text recognition dataset

    • -
    -
    -
    Returns:
    -

    Recognition predictor

    -
    -
    -
    - -
    -
    -
    -

    End-to-End OCR

    -

    Predictors that localize and identify text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Recall

    Precision

    FPS

    Recall

    Precision

    FPS

    db_resnet50 + crnn_vgg16_bn

    70.08

    74.77

    0.85

    82.19

    79.67

    1.6

    db_resnet50 + sar_vgg16_bn

    N/A

    N/A

    0.49

    N/A

    N/A

    1.0

    db_resnet50 + sar_resnet31

    N/A

    N/A

    0.27

    N/A

    N/A

    0.83

    Gvision text detection

    59.50

    62.50

    75.30

    70.00

    Gvision doc. text detection

    64.00

    53.30

    68.90

    61.10

    AWS textract

    78.10

    83.00

    87.50

    66.00

    -
    -

    All OCR models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All recognition models of predictors are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -

    Results on private ocr datasets

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Receipts

    Invoices

    IDs

    Architecture

    Recall

    Precision

    Recall

    Precision

    Recall

    Precision

    db_resnet50 + crnn_vgg16_bn (ours)

    78.90

    81.01

    65.68

    69.86

    49.48

    50.46

    Gvision doc. text detection

    68.91

    59.89

    63.20

    52.85

    43.70

    29.21

    AWS textract

    75.77

    77.70

    70.47

    69.13

    46.39

    43.32

    -
    -
    -

    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.

    -
    -
    -doctr.models.zoo.ocr_predictor(det_arch: str = 'db_resnet50', reco_arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) OCRPredictor[source]
    -

    End-to-end OCR architecture using one model for localization, and another for text recognition.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import ocr_predictor
    ->>> model = ocr_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_sar_vgg’, ‘db_sar_resnet’, ‘db_crnn_vgg’, ‘db_crnn_resnet’)

    • -
    • pretrained – If True, returns a model pre-trained on our OCR dataset

    • -
    -
    -
    Returns:
    -

    OCR predictor

    -
    -
    -
    - -
    -
    -
    -

    Model export

    -

    Utility functions to make the most of document analysis models.

    -
    -

    Model compression

    -
    -
    -doctr.models.export.convert_to_tflite(tf_model: Model) bytes[source]
    -

    Converts a model to TFLite format

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_tflite, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_tflite(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.convert_to_fp16(tf_model: Model) bytes[source]
    -

    Converts a model to half precision

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_fp16, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_fp16(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the serialized FP16 model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) bytes[source]
    -

    Quantize a Tensorflow model

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import quantize_model, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = quantize_model(model, (224, 224, 3))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • tf_model – a keras model

    • -
    • input_shape – shape of the expected input tensor (excluding batch dimension) with channel last order

    • -
    -
    -
    Returns:
    -

    the serialized quantized model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -

    Using SavedModel

    -

    Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -SavedModel format as follows:

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> _ = model(input_t, training=False)
    ->>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/')
    -
    -
    -

    And loaded just as easily:

    -
    >>> import tensorflow as tf
    ->>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/')
    -
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.0/modules/contrib.html b/v0.3.0/modules/contrib.html index e99f6b3f74..7fb86b8b38 100644 --- a/v0.3.0/modules/contrib.html +++ b/v0.3.0/modules/contrib.html @@ -14,7 +14,7 @@ - + doctr.contrib - docTR documentation @@ -380,7 +380,7 @@

    Supported contribution modules - + diff --git a/v0.3.0/modules/datasets.html b/v0.3.0/modules/datasets.html index 456e10b172..380a986793 100644 --- a/v0.3.0/modules/datasets.html +++ b/v0.3.0/modules/datasets.html @@ -14,7 +14,7 @@ - + doctr.datasets - docTR documentation @@ -1081,7 +1081,7 @@

    Returns:

    - + diff --git a/v0.3.0/modules/io.html b/v0.3.0/modules/io.html index 01eadaa4b8..24c41954be 100644 --- a/v0.3.0/modules/io.html +++ b/v0.3.0/modules/io.html @@ -14,7 +14,7 @@ - + doctr.io - docTR documentation @@ -760,7 +760,7 @@

    Returns: - + diff --git a/v0.3.0/modules/models.html b/v0.3.0/modules/models.html index c465cc0586..91b8810a6a 100644 --- a/v0.3.0/modules/models.html +++ b/v0.3.0/modules/models.html @@ -14,7 +14,7 @@ - + doctr.models - docTR documentation @@ -1612,7 +1612,7 @@

    Args: - + diff --git a/v0.3.0/modules/transforms.html b/v0.3.0/modules/transforms.html index 30f7a2631a..c5ead3f3ce 100644 --- a/v0.3.0/modules/transforms.html +++ b/v0.3.0/modules/transforms.html @@ -14,7 +14,7 @@ - + doctr.transforms - docTR documentation @@ -835,7 +835,7 @@

    Args:< - + diff --git a/v0.3.0/modules/utils.html b/v0.3.0/modules/utils.html index 888a32c321..b7f6fc570b 100644 --- a/v0.3.0/modules/utils.html +++ b/v0.3.0/modules/utils.html @@ -14,7 +14,7 @@ - + doctr.utils - docTR documentation @@ -715,7 +715,7 @@

    Args: - + diff --git a/v0.3.0/notebooks.html b/v0.3.0/notebooks.html index f97771aebb..d36539f59e 100644 --- a/v0.3.0/notebooks.html +++ b/v0.3.0/notebooks.html @@ -14,7 +14,7 @@ - + docTR Notebooks - docTR documentation @@ -391,7 +391,7 @@

    docTR Notebooks - + diff --git a/v0.3.0/objects.inv b/v0.3.0/objects.inv index a22d2ce821026792e7b2f75cbac9aecf06cc0d89..c1700f291b7b6bc7252a625eb1acde3a63b67eca 100644 GIT binary patch delta 4090 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 delta 1868 zcmV-S2ebIcAgT|LI|DN=Fp)nzf2CSYZ`(K!z4KQH*kG}Hpb@8NQRLRfO*V_9$=YuA zXwVYv2$4jUq@1R|zWk6VlKNIpp``cTkeuOgNGBp8r;7-2#4u-ztB3U}$lor*1ThvY z1M}gTr^z2@0R)jtxc>HaGmY_ZwO;A=B-&>EaQkHvBP2BP1_XUt)H3{re~@WB#VjV- zoZg!T#~CB^kdW6dwV^(C^rm4FXCaC3j^XcxXksQU9EvRDf;6Vf0?Q)bzeAnV@P<}G zP=x71_VrFRCrus+X=~IBb;jZ}G#Mo^_Je9jP{WND35yhG;{7Me@d1TyNSLqwu*`?g z2?<{&a#m$)CT+o<$*bh1f0#d$Xd3xCPVh{-lDarhlJ4RZ9d$6y?Sj_Hqr>lu6f~JC zau;V)C*g5*J)N;YZ01}^@)7eLDx-3?z^h20)5)UCQ%5T(vjX#f(ZPwfaDuzmENlpL zuJayUZ&446YC?};d}I4?+)uJe|%GqVifHp!QTLf zgGEu}^f;4Qrl{#mxmC92-0_ZAyiRo|BaQHsfLB_nC@Ki`fZNzT;$f$bVOXACj2k85-XU zl=1i{-l^A91Sk&4e=&>IJcKmMcBkWU`C+%u=8B|x+F}gRGQ{33e@A1^ zl>8(_)}Ipx!70kvfzVVODM&)-tqcuWt=qem1?r=xIbnD*?+&H=2yLquh|e+pRWcJ1 zkCT}X8GblSX01^ck@QoZCvP*kpJ{x2<{4&eroa`+#5=}kf6{MInxdK9e+oR4`EQPo z{}sYheD1t$5HIMwAX#HJCqlO5hN9`+73-}?Fk9@!iL7&N!KT>Ix*Rg)1@ssTnldEZ z8uMf1ZDNlR>%yUtOUBKUCXF-EpLWLBYgHI$yd_ikkw(3wJj#^jj5FY;?=c2S zPGz9rv8bHHd7s9iSob89i%<c7_oe3s#6wq;JmU!U(a4tM$EVPV9u? z%{9EmKlnZ3;qyephBwvlDQ1P4H}iydd3m6>%3V*Tf4X7{7>_O!v=?U<*mBCP?o9wX zp;*Ag(X_f#^_b4>t3gA9{$vEon_UL>>i(p^1L}?i?29;wf$tPIhqf5m4zZj>hZeY3^uXK( zlqWOSqJ3I3NLV6Q9@Ww^ZXKm(p;s*u-cJ#|Gb)jF^f&Hvdc(dX8?1Y>?HP#Tm8j-! z&>vr#Y@wZ0<8rNJqG=jaGA_+{0xmLJV4dbWe;2DeWGH#i=B-G$U0(*~c2uU!U|j=% zG1;F_Dghf$i%@LRc*rHXXEl7)PynubtEhLB0xu8%&P0+TQ2Yro~2f=5J$*@C6wV@5Y_sON4zSSI3 zDpB_qrR4u4q^D-rh<7Fe0B^oM0Ff3UNkgtmlf{syZLnscuz-!foTJ=E_%`I`n1 zvmo{W63V#{;UV&1P;Vujli{+UV8NR8*dy6yuQOQ=ShRVMy|U6O#ovpV#k?7%{?d80 z!N8j4f%$)Y6Xk2>y20+_&|YR?s~v6KpgW3Vwt8FD!mnFp0K*8d_ue*|J2o z?K;3K0?Qoh#ZKTCjDWuqxp|A^e~EIDP*C`cdlr{LLkqoakpP9tOAMWGr1y(XO@)LO z@|&V=r!b_=gb9mIRfOCz}Q~EsFI53>6&YH - - - - - - - - - - Python Module Index - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    - -
    -

    Python Module Index

    - -
    - - - - - - - - - - - -
     
    d
    - doctr -
    - -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.3.0/search.html b/v0.3.0/search.html index 73772822d2..d050f5eac7 100644 --- a/v0.3.0/search.html +++ b/v0.3.0/search.html @@ -14,7 +14,7 @@ - + Search - docTR documentation @@ -226,20 +226,42 @@ - - + + diff --git a/v0.3.0/searchindex.js b/v0.3.0/searchindex.js index 803f4f4bcf..6f154115ab 100644 --- a/v0.3.0/searchindex.js +++ b/v0.3.0/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles": {"Artefact": [[2, "artefact"]], "Available Datasets": [[1, "available-datasets"]], "Block": [[2, "block"]], "Build & train your predictor": [[3, "build-train-your-predictor"]], "Changelog": [[0, null]], "Composing transformations": [[6, "composing-transformations"]], "Data Loading": [[1, "data-loading"]], "Detection models": [[5, "detection-models"]], "Detection predictors": [[5, "detection-predictors"]], "DocTR Vocabs": [[1, "id1"]], "DocTR: Document Text Recognition": [[3, null]], "Document": [[2, "document"]], "Document structure": [[2, "document-structure"]], "End-to-End OCR": [[5, "end-to-end-ocr"]], "File reading": [[2, "file-reading"]], "Getting Started": [[3, "getting-started"]], "Installation": [[4, null]], "Line": [[2, "line"]], "Main Features": [[3, "main-features"]], "Model compression": [[5, "model-compression"]], "Model export": [[5, "model-export"]], "Model zoo": [[3, "model-zoo"]], "Notes": [[3, null]], "Package Reference": [[3, null]], "Page": [[2, "page"]], "Pre-processing for detection": [[5, "pre-processing-for-detection"]], "Pre-processing for recognition": [[5, "pre-processing-for-recognition"]], "Prerequisites": [[4, "prerequisites"]], "Recognition models": [[5, "recognition-models"]], "Recognition predictors": [[5, "recognition-predictors"]], "Supported Vocabs": [[1, "supported-vocabs"]], "Supported datasets": [[3, "supported-datasets"]], "Supported transformations": [[6, "supported-transformations"]], "Task evaluation": [[7, "task-evaluation"]], "Text Detection": [[5, "text-detection"]], "Text Recognition": [[5, "text-recognition"]], "Text detection models": [[3, "text-detection-models"]], "Text recognition model zoo": [[5, "id2"]], "Text recognition models": [[3, "text-recognition-models"]], "Two-stage approaches": [[5, "two-stage-approaches"]], "Using SavedModel": [[5, "using-savedmodel"]], "Via Git": [[4, "via-git"]], "Via Python Package": [[4, "via-python-package"]], "Visualization": [[7, "visualization"]], "Word": [[2, "word"]], "doctr.datasets": [[1, null]], "doctr.documents": [[2, null]], "doctr.models": [[5, null]], "doctr.transforms": [[6, null]], "doctr.utils": [[7, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]]}, "docnames": ["changelog", "datasets", "documents", "index", "installing", "models", "transforms", "utils"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "datasets.rst", "documents.rst", "index.rst", "installing.rst", "models.rst", "transforms.rst", "utils.rst"], "indexentries": {"artefact (class in doctr.documents)": [[2, "doctr.documents.Artefact", false]], "as_images() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.as_images", false]], "block (class in doctr.documents)": [[2, "doctr.documents.Block", false]], "colorinversion (class in doctr.transforms)": [[6, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[6, "doctr.transforms.Compose", false]], "convert_to_fp16() (in module doctr.models.export)": [[5, "doctr.models.export.convert_to_fp16", false]], "convert_to_tflite() (in module doctr.models.export)": [[5, "doctr.models.export.convert_to_tflite", false]], "cord (class in doctr.datasets)": [[1, "doctr.datasets.CORD", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.crnn_vgg16_bn", false]], "dataloader (class in doctr.datasets.loader)": [[1, "doctr.datasets.loader.DataLoader", false]], "db_resnet50() (in module doctr.models.detection)": [[5, "doctr.models.detection.db_resnet50", false]], "detection_predictor() (in module doctr.models.detection)": [[5, "doctr.models.detection.detection_predictor", false]], "document (class in doctr.documents)": [[2, "doctr.documents.Document", false]], "documentfile (class in doctr.documents)": [[2, "doctr.documents.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[1, "doctr.datasets.encode_sequences", false]], "from_images() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_images", false]], "from_pdf() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_pdf", false]], "from_url() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[1, "doctr.datasets.FUNSD", false]], "get_artefacts() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.get_artefacts", false]], "get_words() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.get_words", false]], "lambdatransformation (class in doctr.transforms)": [[6, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.documents)": [[2, "doctr.documents.Line", false]], "linknet16() (in module doctr.models.detection)": [[5, "doctr.models.detection.linknet16", false]], "localizationconfusion (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.LocalizationConfusion", false]], "master() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.master", false]], "normalize (class in doctr.transforms)": [[6, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models.zoo)": [[5, "doctr.models.zoo.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[1, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[6, "doctr.transforms.OneOf", false]], "page (class in doctr.documents)": [[2, "doctr.documents.Page", false]], "pdf (class in doctr.documents)": [[2, "doctr.documents.PDF", false]], "quantize_model() (in module doctr.models.export)": [[5, "doctr.models.export.quantize_model", false]], "randomapply (class in doctr.transforms)": [[6, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[6, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[6, "doctr.transforms.RandomContrast", false]], "randomgamma (class in doctr.transforms)": [[6, "doctr.transforms.RandomGamma", false]], "randomhue (class in doctr.transforms)": [[6, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[6, "doctr.transforms.RandomJpegQuality", false]], "randomsaturation (class in doctr.transforms)": [[6, "doctr.transforms.RandomSaturation", false]], "read_html() (in module doctr.documents)": [[2, "doctr.documents.read_html", false]], "read_img() (in module doctr.documents)": [[2, "doctr.documents.read_img", false]], "read_pdf() (in module doctr.documents)": [[2, "doctr.documents.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.recognition_predictor", false]], "resize (class in doctr.transforms)": [[6, "doctr.transforms.Resize", false]], "sar_resnet31() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.sar_resnet31", false]], "sar_vgg16_bn() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.sar_vgg16_bn", false]], "show() (doctr.documents.document method)": [[2, "doctr.documents.Document.show", false]], "show() (doctr.documents.page method)": [[2, "doctr.documents.Page.show", false]], "sroie (class in doctr.datasets)": [[1, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[7, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[7, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[7, "doctr.utils.metrics.TextMatch.summary", false]], "textmatch (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.TextMatch", false]], "togray (class in doctr.transforms)": [[6, "doctr.transforms.ToGray", false]], "visiondataset (class in doctr.datasets.datasets)": [[1, "doctr.datasets.datasets.VisionDataset", false]], "visualize_page() (in module doctr.utils.visualization)": [[7, "doctr.utils.visualization.visualize_page", false]], "word (class in doctr.documents)": [[2, "doctr.documents.Word", false]]}, "objects": {"doctr.datasets": [[1, 0, 1, "", "CORD"], [1, 0, 1, "", "FUNSD"], [1, 0, 1, "", "OCRDataset"], [1, 0, 1, "", "SROIE"], [1, 1, 1, "", "encode_sequences"]], "doctr.datasets.datasets": [[1, 0, 1, "", "VisionDataset"]], "doctr.datasets.loader": [[1, 0, 1, "", "DataLoader"]], "doctr.documents": [[2, 0, 1, "", "Artefact"], [2, 0, 1, "", "Block"], [2, 0, 1, "", "Document"], [2, 0, 1, "", "DocumentFile"], [2, 0, 1, "", "Line"], [2, 0, 1, "", "PDF"], [2, 0, 1, "", "Page"], [2, 0, 1, "", "Word"], [2, 1, 1, "", "read_html"], [2, 1, 1, "", "read_img"], [2, 1, 1, "", "read_pdf"]], "doctr.documents.Document": [[2, 2, 1, "", "show"]], "doctr.documents.DocumentFile": [[2, 2, 1, "", "from_images"], [2, 2, 1, "", "from_pdf"], [2, 2, 1, "", "from_url"]], "doctr.documents.PDF": [[2, 2, 1, "", "as_images"], [2, 2, 1, "", "get_artefacts"], [2, 2, 1, "", "get_words"]], "doctr.documents.Page": [[2, 2, 1, "", "show"]], "doctr.models.detection": [[5, 1, 1, "", "db_resnet50"], [5, 1, 1, "", "detection_predictor"], [5, 1, 1, "", "linknet16"]], "doctr.models.export": [[5, 1, 1, "", "convert_to_fp16"], [5, 1, 1, "", "convert_to_tflite"], [5, 1, 1, "", "quantize_model"]], "doctr.models.recognition": [[5, 1, 1, "", "crnn_vgg16_bn"], [5, 1, 1, "", "master"], [5, 1, 1, "", "recognition_predictor"], [5, 1, 1, "", "sar_resnet31"], [5, 1, 1, "", "sar_vgg16_bn"]], "doctr.models.zoo": [[5, 1, 1, "", "ocr_predictor"]], "doctr.transforms": [[6, 0, 1, "", "ColorInversion"], [6, 0, 1, "", "Compose"], [6, 0, 1, "", "LambdaTransformation"], [6, 0, 1, "", "Normalize"], [6, 0, 1, "", "OneOf"], [6, 0, 1, "", "RandomApply"], [6, 0, 1, "", "RandomBrightness"], [6, 0, 1, "", "RandomContrast"], [6, 0, 1, "", "RandomGamma"], [6, 0, 1, "", "RandomHue"], [6, 0, 1, "", "RandomJpegQuality"], [6, 0, 1, "", "RandomSaturation"], [6, 0, 1, "", "Resize"], [6, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[7, 0, 1, "", "LocalizationConfusion"], [7, 0, 1, "", "OCRMetric"], [7, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.LocalizationConfusion": [[7, 2, 1, "", "summary"]], "doctr.utils.metrics.OCRMetric": [[7, 2, 1, "", "summary"]], "doctr.utils.metrics.TextMatch": [[7, 2, 1, "", "summary"]], "doctr.utils.visualization": [[7, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [2, 7], "0": [1, 3, 5, 6, 7], "00": 5, "01": 5, "0123456789": 1, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 1, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": [], "02": 5, "02562": 5, "03": 3, "035": [], "0361328125": [], "04": [], "05": 3, "06": [], "06640625": [], "07": [], "08": 5, "09": [], "0966796875": [], "1": [1, 3, 5, 6, 7], "10": [1, 5, 7], "100": [5, 6, 7], "1000": 5, "101": [], "1024": [5, 7], "104": [], "106": [], "108": [], "1095": [], "11": 3, "110": 7, "1107": [], "114": [], "115": [], "1156": [], "116": [], "118": [], "11800h": [], "11th": [], "12": 5, "120": [], "123": [], "126": [], "1268": [], "128": 5, "13": 5, "130": [], "13068": [], "131": [], "1337891": [], "1357421875": [], "1396484375": [], "14": 5, "1420": [], "14470v1": [], "149": [], "15": 5, "150": 7, "154": 1, "1552": [], "16": 5, "160": 5, "1630859375": [], "1684": [], "16x16": [], "17": [], "1778": [], "1782": [], "18": 3, "185546875": [], "19": 5, "1900": [], "1910": 5, "19342": [], "19370": [], "195": [], "19598": [], "199": 5, "1999": [], "1m": 5, "2": [3, 5, 6], "20": 5, "200": 7, "2000": [], "2003": [], "2012": [], "2013": [], "2015": [], "2019": 3, "2021": 3, "2023": [], "207901": [], "21": 5, "2103": [], "2186": [], "21888": [], "22": [], "224": [5, 6], "225": 6, "22672": [], "229": 6, "23": [], "233": [], "236": [], "24": [], "246": [], "249": [], "25": 5, "2504": [], "255": [5, 6, 7], "256": 5, "257": [], "26": [], "26032": [], "264": [], "27": 5, "2700": [], "2710": [], "2749": [], "28": 3, "287": [], "29": 5, "296": [], "299": [], "2d": [], "3": [2, 3, 4, 5, 6, 7], "30": 5, "300": [], "3000": [], "301": [], "30595": 5, "30ghz": [], "31": 5, "32": [1, 5, 6], "3232421875": [], "33": [], "33402": [], "33608": [], "34": [], "340": [], "3456": [], "3515625": [], "36": [], "360": [], "37": [], "38": [], "39": 5, "4": [], "40": [], "406": 6, "41": [], "42": [], "43": 5, "44": [], "45": [], "456": 6, "46": 5, "47": 5, "472": [], "48": 5, "485": 6, "49": 5, "49377": [], "5": [1, 6, 7], "50": 5, "51": [], "51171875": [], "512": [], "52": [1, 5], "529": [], "53": 5, "533": [], "54": [], "540": [], "5478515625": [], "55": [], "56": [], "57": [], "58": [], "580": [], "5810546875": [], "583": [], "59": 5, "595": [], "597": [], "5k": [], "5m": 5, "6": [4, 5, 6], "60": 6, "600": [5, 7], "61": 5, "611": [], "62": 5, "625": [], "626": [], "629": [], "63": 5, "630": [], "64": [5, 6], "640": [], "641": [], "647": [], "65": 5, "66": 5, "660": [], "664": [], "666": [], "67": 5, "672": [], "68": 5, "689": [], "69": 5, "693": [], "694": [], "695": [], "6m": [], "7": 5, "70": [5, 7], "700": [], "701": [], "702": [], "707470": [], "71": [], "7100000": [], "713": [], "7141797": [], "7149": [], "72": [], "72dpi": [], "73": [], "73257": [], "733": [], "74": 5, "745": [], "75": 5, "753": [], "7581382": [], "76": [], "77": 5, "772": [], "772875": [], "78": 5, "780": [], "781": [], "783": [], "785": [], "789": [], "79": 5, "793533": [], "796": [], "798": [], "7m": [], "8": [5, 6], "80": [], "800": [5, 7], "81": 5, "817": [], "82": 5, "8275l": 5, "83": 5, "830": [], "84": [], "849": [], "85": 5, "8564453125": [], "857": [], "85875": [], "86": 5, "860": [], "8603515625": [], "862": [], "863": [], "87": 5, "8707": [], "875": [], "88": [], "89": 5, "8m": 5, "9": [], "90": 5, "90k": [], "90kdict32px": [], "91": 5, "913": [], "914085328578949": [], "917": [], "92": 5, "921": [], "93": [], "94": [], "95": 7, "9578408598899841": [], "96": 1, "97": [], "98": [], "99": [], "9949972033500671": [], "A": [1, 2, 3, 5], "And": 5, "As": [], "Be": [], "Being": [], "By": [], "For": [4, 5], "If": [2, 4, 5], "In": [1, 5], "It": 6, "Its": 5, "No": [], "Of": 1, "Or": [], "The": [1, 2, 5, 7], "Then": 5, "To": [], "_": [1, 5], "__call__": [], "_build": [], "_i": 7, "ab": [], "abc": [], "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 1, "abdef": [], "abl": [], "about": 5, "abov": 5, "abstract": 1, "abstractdataset": [], "abus": [], "accent": [], "accept": [], "access": [1, 2, 3], "account": [], "accur": [], "accuraci": 7, "achiev": [], "act": [], "action": [], "activ": [], "ad": 6, "adapt": [], "add": [6, 7], "add_hook": [], "add_label": 7, "addit": [], "addition": 5, "address": 2, "adjust": 6, "advanc": [], "advantag": [], "advis": [], "aesthet": [], "affect": [], "after": [], "ag": [], "again": [], "aggreg": [1, 7], "aggress": [], "align": 2, "all": [1, 2, 3, 5, 6, 7], "allow": [], "along": 5, "alreadi": [], "also": [], "alwai": [], "an": [1, 2, 3, 5, 7], "analysi": [2, 5], "ancient_greek": [], "andrej": [], "angl": 2, "ani": [1, 2, 3, 5, 6, 7], "annot": 2, "anot": [], "anoth": [1, 4, 5], "answer": [], "anyascii": [], "anyon": 3, "anyth": [], "api": [], "apolog": [], "apologi": [], "app": [], "appear": [], "appli": [1, 6], "applic": 5, "appoint": [], "appreci": [], "appropri": [], "ar": [1, 2, 4, 5, 6, 7], "arab": [], "arabic_diacrit": [], "arabic_lett": [], "arabic_punctu": [], "arbitrarili": [], "arch": 5, "architectur": [3, 5], "archiv": [], "area": [], "argument": [1, 2], "around": 5, "arrai": [2, 7], "art": 3, "artefact": 7, "artefact_typ": 2, "articl": [], "artifici": [], "arxiv": 5, "as_imag": 2, "asarrai": 7, "ascii_lett": 1, "aspect": [3, 6], "assess": 7, "assign": 7, "associ": 2, "assum": [], "assume_straight_pag": [], "astyp": [5, 7], "attack": [], "attend": [3, 5], "attent": [], "autoclass": [], "autom": 3, "automat": [], "autoregress": [], "avail": [3, 5, 6], "averag": [5, 6], "avoid": [], "aw": [3, 5], "awar": [], "azur": [], "b": 7, "b_j": 7, "back": [], "backbon": 5, "backend": 5, "background": [], "bangla": [], "bar": [], "bar_cod": [], "baranovskij": [], "base": 5, "baselin": 5, "batch": [1, 5, 6], "batch_siz": 1, "bblanchon": [], "bbox": [], "becaus": [], "been": [5, 7], "befor": 1, "begin": 7, "behavior": [], "being": [5, 7], "belong": [], "benchmark": [], "best": [], "beta": 3, "better": [], "between": [6, 7], "bgr": 2, "bilinear": [5, 6], "bin_thresh": [], "binar": [3, 5], "binari": 2, "bit": [], "block": [5, 7], "block_1_1": [], "blur": [], "bmvc": [], "bn": [], "bodi": [], "bool": [1, 2, 5, 6, 7], "boolean": [], "both": [3, 5, 6], "bottom": [], "bound": [1, 2, 6, 7], "box": [1, 2, 7], "box_thresh": [], "brew": 4, "bright": 6, "browser": [], "build": [], "built": [], "byte": [2, 5], "c": [], "c5": 5, "c_j": [], "cach": [], "cache_sampl": [], "cairo": 4, "call": [], "callabl": [1, 6], "can": [1, 4, 5], "capabl": 5, "case": [1, 7], "cf": 5, "cfg": [], "challeng": [], "challenge2_test_task12_imag": [], "challenge2_test_task1_gt": [], "challenge2_training_task12_imag": [], "challenge2_training_task1_gt": [], "chang": [], "changelog": 3, "channel": [2, 5, 6], "channel_prior": [], "channelshuffl": [], "charact": [1, 2, 3, 5, 7], "charactergener": [], "characterist": [], "charg": 5, "charset": [], "chart": 2, "check": [], "checkpoint": [], "chip": [], "christian": [], "ci": [], "clarifi": [], "clariti": [], "class": [1, 2, 6, 7], "class_nam": [], "classif": [], "classmethod": 2, "clear": [], "clone": 4, "close": [], "co": [], "code": [2, 3], "codecov": [], "colab": [], "collate_fn": [], "collect": 2, "color": 6, "colorinvers": 6, "column": 2, "com": [2, 4], "combin": 5, "command": [], "comment": [], "commit": [], "common": [6, 7], "commun": [], "compar": 3, "comparison": 7, "competit": 1, "compil": [], "complaint": [], "complementari": 7, "complet": [], "compon": 5, "compos": [1, 3, 5], "comprehens": [], "comput": [5, 7], "conf_threshold": [], "confid": 2, "config": [], "configur": [], "confus": 7, "consecut": [5, 6], "consequ": [], "consid": [1, 2, 7], "consist": [], "consolid": [1, 3], "constant": 6, "construct": [], "contact": [], "contain": [], "content": [1, 2], "context": [], "contib": [], "continu": [], "contrast": 6, "contrast_factor": 6, "contrib": [], "contribut": [], "contributor": [], "conv_sequ": 5, "convers": 2, "convert": [2, 5, 6], "convert_page_to_numpi": 2, "convert_to_fp16": 5, "convert_to_tflit": 5, "convolut": 3, "cool": [], "coordin": 2, "cord": [1, 3, 5], "core": 7, "corner": [], "correct": 6, "correspond": [4, 5], "could": [], "counterpart": 7, "cover": [], "coverag": [], "cpu": [3, 5], "creat": [], "crnn": [3, 5], "crnn_mobilenet_v3_larg": [], "crnn_mobilenet_v3_smal": [], "crnn_resnet31": 5, "crnn_vgg16_bn": 5, "crop": 5, "crop_orient": [], "crop_orientation_predictor": [], "crop_param": [], "cuda": [], "currenc": 1, "current": [], "custom": [], "custom_crop_orientation_model": [], "custom_page_orientation_model": [], "customhook": [], "cvit": [], "czczup": [], "czech": [], "d": [], "daili": 3, "danish": [], "data": [2, 3, 5, 6, 7], "dataload": 1, "dataset": 5, "dataset_info": [], "date": [], "db": [], "db_crnn_resnet": 5, "db_crnn_vgg": 5, "db_mobilenet_v3_larg": [], "db_resnet34": [], "db_resnet50": 5, "db_sar_resnet": 5, "db_sar_vgg": 5, "dbnet": [3, 5], "deal": [], "decis": [], "decod": 2, "decode_img_as_tensor": [], "dedic": [], "deem": [], "deep": 5, "def": [], "default": [2, 5], "defer": 1, "defin": 7, "deform": 5, "degre": [], "degress": 2, "delet": [], "delimit": [], "delta": 6, "demo": [], "demonstr": [], "depend": [3, 4], "deploi": [], "deploy": [], "derogatori": [], "describ": 5, "descript": [], "design": 6, "desir": [], "det_arch": 5, "det_b": [], "det_model": [], "det_param": [], "det_predictor": [], "detail": [], "detect": [], "detect_languag": [], "detect_orient": [], "detection_predictor": 5, "detection_task": [], "detectiondataset": [], "detectionmetr": [], "detectionpredictor": 5, "detector": [], "deterior": [], "determin": [], "dev": [], "develop": [], "developp": 4, "deviat": 6, "devic": [], "dict": [2, 7], "dictionari": [2, 7], "differ": [], "differenti": [3, 5], "digit": 1, "dimens": [2, 5, 7], "dimension": 6, "direct": [], "directli": 5, "directori": [], "disabl": [], "disable_crop_orient": [], "disable_page_orient": [], "disclaim": 5, "discuss": [], "disk": [], "disparag": [], "displai": [2, 7], "display_artefact": 7, "distanc": [], "distribut": 6, "div": [], "divers": [], "divid": [], "do": 4, "doc": [2, 5], "docartefact": [], "docstr": [], "doctr": 4, "doctr_cache_dir": [], "doctr_multiprocessing_dis": [], "document": [1, 5, 7], "documentbuild": [], "documentfil": 2, "doesn": [], "don": [], "done": 6, "download": 1, "downsiz": [], "draw": 6, "drop": 1, "drop_last": 1, "dtype": 5, "dual": [], "dummi": [], "dummy_img": [], "dummy_input": [], "dure": [], "dutch": [], "dynam": [], "dynamic_seq_length": [], "e": [2, 4], "each": [1, 2, 3, 5, 6, 7], "eas": [], "easi": [3, 7], "easier": 5, "easili": [2, 5, 7], "econom": [], "edit": [], "educ": [], "effect": [], "effici": [1, 5], "either": 5, "element": [1, 2, 5], "els": [], "email": [], "empathi": [], "en": [], "enabl": 2, "enclos": 2, "encod": [1, 2, 5], "encode_sequ": 1, "encount": [], "encrypt": [], "end": [1, 3, 7], "english": [], "enough": 5, "ensur": [], "entir": 2, "entri": [], "environ": [], "eo": 1, "equiv": [], "error": [], "estim": [], "etc": 2, "ethnic": [], "evalu": [1, 3, 5], "event": [], "everyon": [], "everyth": [], "exact": 7, "exactmatch": [], "exampl": [1, 2, 5, 6, 7], "exchang": [], "exclud": 5, "execut": [], "exist": [], "expand": [], "expect": [2, 5, 6], "experi": 5, "explan": 5, "explicit": [], "exploit": 5, "export": [2, 3, 7], "export_as_straight_box": [], "export_as_xml": [], "export_model_to_onnx": [], "express": 6, "extens": 2, "extern": [], "extra": 4, "extract": [1, 3], "extract_arch": 1, "extractor": 5, "f_": 7, "f_a": 7, "factor": 6, "fair": [], "fairli": [], "fals": [1, 5, 6, 7], "faq": [], "fascan": [], "fast": 1, "fast_bas": [], "fast_smal": [], "fast_tini": [], "faster": [], "fasterrcnn_mobilenet_v3_large_fpn": [], "favorit": [], "featur": [5, 7], "feed": 5, "feedback": [], "feel": [], "felix92": [], "few": 4, "figsiz": 7, "figur": 7, "file": [1, 3], "file_hash": 1, "file_nam": 1, "final": [], "find": 4, "fine": 3, "finnish": [], "first": [], "firsthand": [], "fit": [], "fitz": 2, "flag": [], "flexibl": 7, "flip": [], "float": [2, 6, 7], "float32": 5, "fn": 6, "focu": [], "focus": [], "folder": [1, 5], "follow": [1, 4, 5, 6, 7], "font": [], "font_famili": [], "foral": 7, "forc": [], "forg": [], "form": [1, 3], "format": [2, 5], "forpost": [1, 3], "forum": [], "found": [], "fp": 5, "fp16": 5, "frac": 7, "frame": 5, "framework": 1, "free": [], "french": [1, 5], "friendli": 3, "from": [1, 2, 3, 5, 6, 7], "from_hub": [], "from_imag": 2, "from_pdf": 2, "from_url": 2, "full": [1, 5, 7], "fulli": [], "function": [5, 6, 7], "funsd": [1, 3, 5], "further": [], "futur": [], "g": 2, "g_": 7, "g_x": 7, "gallagh": [], "gamma": 6, "gaussian": 6, "gaussianblur": [], "gaussiannois": [], "gdk": 4, "gen": [], "gender": [], "gener": [], "generic_cyrillic_lett": [], "geometri": 2, "geq": 7, "german": [], "get": 2, "get_artefact": 2, "get_word": 2, "gettextword": 2, "git": 3, "github": 4, "give": [], "given": [1, 2, 5, 7], "global": [], "go": [], "good": [], "googl": [], "googlevis": 3, "gpu": 3, "gracefulli": [], "graph": 2, "grayscal": 6, "ground": 7, "groung": [], "group": [], "gt": [], "gt_box": [], "gt_label": [], "gtk": 4, "guid": [], "guidanc": [], "gvision": 5, "h": 2, "h_": 7, "ha": [1, 7], "half": 5, "handl": 1, "handwrit": [], "handwritten": [], "harass": [], "hardwar": [], "harm": [], "hat": 7, "have": [1, 5, 7], "head": [], "healthi": [], "hebrew": [], "height": 2, "hello": 7, "help": [], "here": [1, 4, 6], "hf": [], "hf_hub_download": [], "high": 2, "higher": 4, "hindi": [], "hindi_digit": [], "hocr": [], "hook": [], "horizont": 2, "hous": [], "how": [], "howev": [], "hsv": 6, "html": [], "http": [2, 4, 5], "hub": [], "hue": 6, "huggingfac": [], "hw": [], "i": [1, 2, 5, 6, 7], "i7": [], "ibrahimov": [], "ic03": [], "ic13": [], "icdar": 3, "icdar2019": 1, "id": 5, "ident": [], "identifi": [3, 5], "ignor": [], "ignore_acc": [], "ignore_cas": [], "iiit": [], "iiit5k": [], "iiithw": [], "imag": [1, 2, 5, 6, 7], "imagenet": [], "imageri": [], "images_90k_norm": [], "img": [1, 6], "img_cont": [], "img_fold": 1, "img_path": [], "img_transform": [], "imgur5k": [], "imgur5k_annot": [], "imlist": [], "impact": [], "implement": [1, 2, 5, 6, 7], "import": [1, 2, 5, 6, 7], "improv": [], "inappropri": [], "incid": [], "includ": [4, 5], "inclus": [], "increas": 6, "independ": [], "index": 2, "indic": 7, "individu": [], "infer": [3, 6], "inform": [1, 3, 5], "inherit": [1, 5], "input": [2, 5, 6], "input_crop": [], "input_pag": [5, 7], "input_shap": 5, "input_t": 5, "input_tensor": 5, "inspir": 6, "instal": 3, "instanc": 5, "instanti": 5, "instead": [1, 2], "insult": [], "int": [1, 2, 5, 6, 7], "int64": [], "integ": 7, "integr": 3, "intel": [], "interact": [2, 7], "interfac": [], "interoper": [], "interpol": [5, 6], "interpret": [1, 2], "intersect": 7, "invert": 6, "investig": [], "invis": [], "invoic": 5, "involv": 5, "io": [], "iou": 7, "iou_thresh": 7, "iou_threshold": [], "irregular": 5, "isn": 1, "issu": [], "italian": [], "iter": 1, "its": [1, 2, 5, 7], "itself": [], "j": 7, "jame": [], "job": [], "join": [], "jpeg": 6, "jpegqual": 6, "jpg": [1, 2], "json": [], "json_output": [], "jump": [], "just": 5, "kei": [], "kera": 5, "kernel": [], "kernel_s": 5, "kernel_shap": [], "keywoard": [], "keyword": [1, 2], "kie": [], "kie_predictor": [], "kiepredictor": [], "kind": [], "know": [], "kwarg": [1, 2, 5, 7], "l": 7, "l_j": 7, "label": [1, 7], "label_fil": 1, "label_fold": [], "label_path": [], "labels_path": [], "ladder": [], "lambda": 6, "lambdatransform": 6, "lang": [], "languag": [2, 3], "larg": [], "largest": 7, "last": [1, 4, 5], "latenc": [], "later": [], "latest": 4, "latin": 1, "layer": [], "layout": [], "lead": [], "leader": [], "learn": 5, "least": 4, "left": 7, "legacy_french": [], "length": 1, "less": [], "let": 5, "letter": [], "level": [5, 7], "levenshtein": [], "leverag": [], "lf": [], "libffi": 4, "librari": 4, "light": 3, "lightweight": [], "like": [], "limits_": 7, "line": [3, 7], "line_1_1": [], "link": [], "linknet": [3, 5], "linknet16": 5, "linknet_resnet18": [], "linknet_resnet34": [], "linknet_resnet50": [], "linux": 4, "list": [1, 2, 6], "ll": 7, "load": [3, 5], "load_state_dict": [], "load_weight": [], "loader": 1, "loc_pr": [], "local": [1, 3, 5, 7], "localis": [], "localizationconfus": 7, "locat": [], "login": [], "login_to_hub": [], "logo": 2, "love": [], "lower": [6, 7], "m": [5, 7], "m1": [], "macbook": [], "machin": [], "maco": 4, "made": 3, "magc_resnet31": [], "mai": [], "mail": [], "main": [], "maintain": 3, "mainten": [], "make": [5, 7], "mani": [], "manipul": [], "map": 1, "map_loc": [], "mask_shap": 7, "master": [3, 5], "match": [3, 7], "mathcal": 7, "matplotlib": 7, "max": 7, "max_angl": [], "max_area": [], "max_char": [], "max_delta": 6, "max_dist": [], "max_gain": 6, "max_gamma": 6, "max_qual": 6, "max_ratio": [], "maximum": 1, "maxval": [5, 6], "mbox": 7, "mean": [6, 7], "meaniou": 7, "meant": 2, "measur": 5, "media": [], "median": [], "meet": [], "member": [], "memori": [], "mention": [], "merg": [], "messag": [], "meta": [], "metadata": [], "metal": [], "method": 6, "metric": [5, 7], "middl": [], "might": 5, "min": [], "min_area": [], "min_char": [], "min_gain": 6, "min_gamma": 6, "min_qual": 6, "min_ratio": [], "min_val": 6, "minde": 4, "minim": [], "minimalist": [], "minimum": 7, "minval": 6, "miss": [], "mistak": [], "mix": 3, "mixed_float16": [], "mixed_precis": [], "mjsynth": [], "mnt": [], "mobilenet": [], "mobilenet_v3_larg": [], "mobilenet_v3_large_r": [], "mobilenet_v3_smal": [], "mobilenet_v3_small_crop_orient": [], "mobilenet_v3_small_page_orient": [], "mobilenet_v3_small_r": [], "mobilenetv3": [], "modal": [], "mode": 4, "model": [1, 7], "model_nam": [], "model_path": [], "moder": [], "modif": [], "modifi": [], "modul": [2, 5, 6, 7], "more": [], "moscardi": [], "most": 5, "mozilla": [], "multi": 3, "multilingu": [], "multipl": [1, 2, 6], "multipli": 6, "multiprocess": [], "my": [], "my_awesome_model": [], "my_hook": [], "n": [1, 5, 7], "na": [], "name": [1, 5], "nation": [], "natur": 3, "ndarrai": [1, 2, 7], "necessari": [], "need": [4, 7], "neg": 6, "nest": [], "nestedobject": [], "netraj": [], "network": [3, 5], "neural": [3, 5], "new": [], "newer": [], "next": 1, "nois": [], "noisi": [1, 3], "non": [2, 3, 6, 7], "none": [1, 2, 7], "normal": [5, 6], "norwegian": [], "note": 0, "now": 3, "np": [5, 7], "num_output_channel": [], "num_sampl": [], "number": [1, 6, 7], "numpi": [2, 5, 7], "o": 4, "obb": [], "obj_detect": [], "object": 1, "objectness_scor": [], "oblig": [], "obtain": [], "occupi": [], "ocr": [1, 3, 7], "ocr_carea": [], "ocr_db_crnn": 7, "ocr_lin": [], "ocr_pag": [], "ocr_par": [], "ocr_predictor": 5, "ocrdataset": 1, "ocrmetr": 7, "ocrpredictor": 5, "ocrx_word": [], "offens": [], "offici": [], "offlin": [], "offset": 6, "onc": 5, "one": [1, 5, 6], "oneof": 6, "ones": 1, "onli": [6, 7], "onlin": [], "onnx": [], "onnxruntim": [], "onnxtr": [], "opac": [], "opacity_rang": [], "open": [], "opinion": [], "optic": [3, 5], "optim": 3, "option": 1, "order": [1, 2, 5], "org": 5, "organ": 2, "orient": 2, "orientationpredictor": [], "other": [], "otherwis": 7, "our": 5, "out": [5, 6, 7], "outpout": [], "output": [2, 5, 6], "output_s": [2, 6], "outsid": [], "over": [4, 7], "overal": [], "overlai": 2, "overview": [], "overwrit": 1, "overwritten": [], "own": 3, "p": 6, "packag": 7, "pad": [1, 5, 6], "page": [4, 5, 7], "page1": 2, "page2": 2, "page_1": [], "page_idx": 2, "page_orientation_predictor": [], "page_param": [], "pair": 7, "pango": 4, "paper": 5, "par_1_1": [], "paragraph": [], "paragraph_break": [], "parallel": [], "param": [5, 6], "paramet": [1, 2, 3, 5, 6, 7], "pars": [1, 3], "parseq": [], "part": 6, "parti": [], "partial": [], "particip": [], "pass": [1, 5], "password": [], "patch": [], "path": [1, 2, 5], "path_to_checkpoint": [], "path_to_custom_model": [], "path_to_pt": [], "patil": [], "pattern": [], "pdf": [2, 5], "pdfpage": [], "peopl": [], "per": [5, 6], "perform": [2, 3, 5, 6, 7], "period": [], "permiss": [], "permut": [], "persian_lett": [], "person": [], "phase": [], "photo": [], "physic": 2, "pick": 6, "pictur": 2, "pip": 4, "pipelin": [], "pixbuf": 4, "pixel": [2, 6], "platinum": 5, "pleas": [], "plot": 7, "plt": 7, "plug": [], "plugin": [], "png": 2, "point": [], "polici": [], "polish": [], "polit": [], "polygon": 1, "pool": [], "portugues": [], "posit": 7, "possibl": 7, "post": 5, "postprocessor": [], "potenti": 5, "power": 3, "ppageno": [], "pre": [], "precis": [5, 7], "pred": [], "pred_box": [], "pred_label": [], "predefin": 1, "predict": [2, 7], "predictor": [], "prefer": 1, "preinstal": [], "preprocessor": 5, "prerequisit": 3, "present": [], "preserv": 6, "preserve_aspect_ratio": 6, "pretrain": [3, 5, 7], "pretrained_backbon": [], "print": [], "prior": [], "privaci": [], "privat": 5, "probabl": 6, "problem": [], "procedur": 6, "process": [2, 3], "processor": 5, "produc": 5, "product": [], "profession": [], "project": [], "promptli": [], "proper": [], "properli": 1, "properti": 5, "provid": [3, 5], "public": 3, "publicli": [], "publish": [], "pull": [], "punctuat": 1, "pure": [], "purpos": [], "push_to_hf_hub": [], "py": [], "pypdfium2": [], "pyplot": 7, "python": 3, "python3": [], "pytorch": [3, 4], "q": [], "qr": 2, "qr_code": [], "qualiti": 6, "quantiz": 5, "quantize_model": 5, "question": [], "quickli": 3, "quicktour": [], "r": [], "race": [], "ramdisk": [], "rand": [5, 7], "random": [5, 6, 7], "randomappli": 6, "randombright": 6, "randomcontrast": 6, "randomcrop": [], "randomgamma": 6, "randomhorizontalflip": [], "randomhu": 6, "randomjpegqu": 6, "randomli": 6, "randomres": [], "randomrot": [], "randomsatur": 6, "randomshadow": [], "rang": 6, "rassi": [], "ratio": 6, "raw": [2, 7], "re": [], "read": [3, 5], "read_html": 2, "read_img": 2, "read_img_as_numpi": [], "read_img_as_tensor": [], "read_pdf": 2, "readi": [], "real": [5, 6], "realli": [], "reason": [], "rebuild": [], "rebuilt": [], "recal": [5, 7], "receipt": [1, 3, 5], "reco_arch": 5, "reco_b": [], "reco_model": [], "reco_param": [], "reco_predictor": [], "recogn": [], "recognit": 7, "recognition_predictor": 5, "recognition_task": [], "recognitiondataset": [], "recognitionpredictor": 5, "rectangular": [], "recurr": 3, "reduc": 6, "refer": 4, "regardless": [], "region": [], "regroup": 7, "regular": [], "reject": [], "rel": 2, "relat": [], "releas": [0, 4], "relev": [], "religion": [], "relu": 5, "remov": [], "render": [], "repo": [], "repo_id": [], "report": [], "repositori": [], "repres": [2, 5], "represent": 5, "request": [], "requir": [4, 6], "research": 3, "residu": [], "resiz": [5, 6], "resnet": 5, "resnet18": [], "resnet31": [], "resnet34": [], "resnet50": [], "resolv": 2, "resolve_block": [], "resolve_lin": [], "resourc": [], "respect": [], "rest": [6, 7], "restrict": [], "result": [2, 5], "return": [1, 2, 5, 7], "reusabl": 5, "review": [], "rgb": [2, 6], "rgb_mode": [], "rgb_output": 2, "right": [5, 7], "roboflow": [], "robust": 3, "root": 1, "rotat": [1, 2], "rotated_bbox": [1, 7], "run": 4, "same": [2, 7], "sampl": 1, "sample_transform": 1, "sanjin": [], "sar": [3, 5], "sar_resnet31": 5, "sar_vgg16_bn": 5, "satur": 6, "save": [1, 5], "saved_model": 5, "scale": 7, "scale_rang": [], "scan": [1, 3], "scene": [3, 5], "scheme": 5, "score": 7, "scratch": 3, "script": [], "seamless": 3, "seamlessli": [], "search": [], "searchabl": [], "sec": [], "second": 5, "section": [], "secur": [], "see": [], "seemlessli": 3, "seen": 5, "segment": 5, "self": [], "semant": 5, "send": [], "sens": 7, "sensit": [], "separ": 5, "sequenc": [1, 2, 5, 7], "sequenti": [5, 6], "seri": [], "serial": 5, "serialized_model": 5, "seriou": [], "set": [1, 5, 7], "set_global_polici": [], "sever": [2, 6], "sex": [], "sexual": [], "sha256": [], "shade": [], "shape": [2, 5, 6, 7], "share": [], "shift": 6, "shm": [], "should": [1, 2, 7], "show": [2, 3, 5, 7], "showcas": [], "shuffl": 1, "side": 7, "signatur": 2, "signific": 1, "simpl": 5, "simpler": [], "sinc": 1, "singl": [], "single_img_doc": [], "size": [1, 2, 5, 6], "skew": [], "slack": [], "slightli": [], "small": 3, "smallest": 2, "snapshot_download": [], "snippet": [], "so": [1, 4], "social": [], "socio": [], "some": [], "someth": [], "somewher": [], "sort": [], "sourc": [1, 2, 5, 6, 7], "space": [], "span": [], "spanish": [], "spatial": 2, "special": 3, "specif": [1, 5, 7], "specifi": 2, "speed": [3, 5], "sphinx": [], "sroie": [1, 3], "stabl": 4, "stackoverflow": [], "stage": 3, "standalon": [], "standard": 6, "start": 1, "state": 3, "static": 7, "statist": 5, "statu": [], "std": 6, "step": [], "still": [], "str": [1, 2, 5, 6, 7], "straight": 1, "straighten": [], "straighten_pag": [], "straigten_pag": [], "stream": 2, "street": [], "strict": [], "strictli": 7, "string": [1, 2, 5, 7], "strive": [], "strong": 5, "structur": [3, 5], "subset": [1, 5], "suggest": [], "sum": 7, "summari": 7, "support": 5, "sustain": [], "svhn": [], "svt": [], "swedish": [], "symbol": [], "symmetr": 6, "symmetric_pad": 6, "synthet": [], "synthtext": [], "system": [], "t": 1, "tabl": [], "take": [], "target": [1, 2, 5, 6], "target_s": 1, "task": [1, 3, 5], "task2": [], "team": [], "techminde": [], "templat": 2, "tensor": [1, 5, 6], "tensorflow": [3, 4, 5, 6], "tensorspec": [], "term": [], "test": [], "test_set": [], "text": [2, 7], "text_output": [], "textmatch": 7, "textnet": [], "textnet_bas": [], "textnet_smal": [], "textnet_tini": [], "textract": [3, 5], "textstylebrush": [], "textual": [1, 2, 3], "tf": [5, 6], "tf_model": 5, "tflite": 5, "than": [4, 7], "thank": [], "thei": [], "them": [1, 4], "thi": [4, 5, 7], "thing": [], "third": [], "those": [2, 4, 5], "threaten": [], "threshold": [], "through": [1, 6], "tilman": [], "time": [1, 5, 7], "tini": [], "titl": 2, "tm": [], "tmp": [], "togeth": [2, 5], "tograi": 6, "tool": [], "top": 7, "topic": [], "torch": [], "torchvis": 6, "total": [], "toward": [], "train": [1, 5, 6], "train_it": 1, "train_load": 1, "train_pytorch": [], "train_set": 1, "train_tensorflow": [], "trainabl": 5, "tranform": 6, "transcrib": [], "transfer": [], "transfo": 6, "transform": [1, 3], "translat": [], "troll": [], "true": [1, 2, 5, 6, 7], "truth": 7, "tune": 3, "tupl": [2, 5, 6, 7], "turn": [], "two": 2, "txt": [], "type": [2, 5], "typic": [], "u": [], "ucsd": [], "udac": [], "uint8": [2, 5, 7], "ukrainian": [], "unaccept": [], "underli": 1, "underneath": 2, "understand": [1, 3], "unidecod": 7, "uniform": [5, 6], "uniformli": [], "uninterrupt": 2, "union": 7, "unit": [], "unittest": [], "unlock": [], "unoffici": [], "unprofession": [], "unsolicit": [], "unsupervis": [], "unwelcom": [], "up": 5, "updat": 7, "upgrad": [], "upper": 6, "uppercas": [], "url": [1, 2], "us": [1, 4, 7], "usabl": 5, "usag": 5, "use_polygon": [], "useabl": [], "user": [2, 3, 4], "utf": [], "util": [3, 5], "v0": 3, "v1": [], "v3": [], "valid": [], "valu": [2, 6], "valuabl": 3, "variabl": [], "varieti": [], "veri": [], "verifi": 1, "verma": [], "version": 5, "vgg": 5, "vgg16": 5, "vgg16_bn_r": [], "via": 3, "video": [], "vietnames": [], "view": [], "viewpoint": [], "violat": [], "visibl": [], "vision": [], "visiondataset": 1, "visiontransform": [], "visual": 3, "visualize_pag": 7, "vit_": [], "vit_b": [], "vitstr": [], "vitstr_bas": [], "vitstr_smal": [], "viz": [], "vocab": [3, 5], "vocabulari": [], "w": [2, 7], "w3": [], "wa": [], "wai": [1, 3, 5], "want": [], "warm": 5, "warmup": [], "wasn": [], "we": [2, 3, 5, 6], "weasyprint": [], "web": 2, "websit": [], "welcom": 3, "well": [], "were": 2, "what": [], "when": [], "whenev": [], "where": [2, 7], "whether": [1, 2, 7], "which": 5, "whichev": 4, "while": 6, "why": [], "width": 2, "wiki": [], "wildreceipt": [], "window": [4, 7], "wish": [], "within": [], "without": 5, "wonder": [], "word": [3, 5, 7], "word_1_1": [], "word_1_2": [], "word_1_3": [], "wordgener": [], "words_onli": 7, "work": [], "worker": 1, "workflow": [], "worklow": [], "world": 7, "worth": [], "wrap": [], "wrapper": [1, 6], "write": [], "written": 2, "www": 2, "x": [2, 6, 7], "x12larg": 5, "x_ascend": [], "x_descend": [], "x_i": 7, "x_size": [], "x_wconf": [], "xeon": 5, "xhtml": [], "xmax": 2, "xmin": 2, "xml": [], "xml_bytes_str": [], "xml_element": [], "xml_output": [], "xmln": [], "y": 7, "y_i": 7, "y_j": 7, "yet": [], "ymax": 2, "ymin": 2, "yolov8": [], "you": [4, 5], "your": [1, 2, 5, 7], "yoursit": 2, "yugesh": [], "zero": [5, 6], "zoo": [], "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 1, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": [], "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": [], "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": [], "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": [], "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": [], "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": [], "\u00e4\u00f6\u00e4\u00f6": [], "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": [], "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": [], "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": [], "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": [], "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": [], "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": [], "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": [], "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": [], "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": [], "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": [], "\u067e\u0686\u06a2\u06a4\u06af": [], "\u0905": [], "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": [], "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": [], "\u0950": [], "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": [], "\u09bd": [], "\u09ce": [], "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": []}, "titles": ["Changelog", "doctr.datasets", "doctr.documents", "DocTR: Document Text Recognition", "Installation", "doctr.models", "doctr.transforms", "doctr.utils"], "titleterms": {"": [], "0": 0, "01": [], "02": [], "03": 0, "04": [], "05": 0, "07": [], "08": [], "09": [], "1": 0, "10": [], "11": 0, "12": [], "18": 0, "2": 0, "2021": 0, "2022": [], "2023": [], "2024": [], "21": [], "22": [], "27": [], "28": 0, "29": [], "3": [], "31": [], "4": [], "5": [], "6": [], "7": [], "8": [], "9": [], "advanc": [], "approach": 5, "architectur": [], "arg": [], "artefact": 2, "artefactdetect": [], "attribut": [], "avail": 1, "aw": [], "ban": [], "block": 2, "bug": [], "build": 3, "changelog": 0, "choos": [], "classif": [], "code": [], "codebas": [], "commit": [], "commun": [], "compos": 6, "compress": 5, "conda": [], "conduct": [], "connect": [], "content": [], "continu": [], "contrib": [], "contribut": [], "contributor": [], "convent": [], "correct": [], "coven": [], "custom": [], "data": 1, "dataload": [], "dataset": [1, 3], "detect": [3, 5], "develop": [], "do": [], "doctr": [1, 2, 3, 5, 6, 7], "document": [2, 3], "end": 5, "enforc": [], "evalu": 7, "export": 5, "factori": [], "featur": 3, "feedback": [], "file": 2, "from": [], "gener": [], "get": 3, "git": 4, "guidelin": [], "half": [], "hub": [], "huggingfac": [], "i": [], "implement": [], "infer": [], "instal": 4, "integr": [], "io": [], "lambda": [], "let": [], "line": 2, "linux": [], "load": 1, "loader": [], "main": 3, "mode": [], "model": [3, 5], "modifi": [], "modul": [], "name": [], "note": 3, "notebook": [], "object": [], "ocr": 5, "onli": [], "onnx": [], "optim": [], "option": [], "orient": [], "our": [], "output": [], "own": [], "packag": [3, 4], "page": 2, "perman": [], "pipelin": [], "pledg": [], "post": [], "pre": 5, "precis": [], "predictor": [3, 5], "prepar": [], "prerequisit": 4, "pretrain": [], "process": 5, "push": [], "python": 4, "qualiti": [], "question": [], "read": 2, "readi": [], "recognit": [3, 5], "refer": 3, "report": [], "request": [], "resourc": [], "respons": [], "return": [], "right": [], "savedmodel": 5, "scope": [], "share": [], "should": [], "stage": 5, "standard": [], "start": 3, "structur": 2, "style": [], "support": [1, 3, 6], "synthet": [], "task": 7, "temporari": [], "test": [], "text": [3, 5], "train": 3, "transform": 6, "two": 5, "unit": [], "us": 5, "util": 7, "v0": 0, "verif": [], "via": 4, "visual": 7, "vocab": 1, "warn": [], "what": [], "word": 2, "your": 3, "zoo": [3, 5]}}) \ No newline at end of file +Search.setIndex({"alltitles": {"1. Correction": [[2, "correction"]], "2. Warning": [[2, "warning"]], "3. Temporary Ban": [[2, "temporary-ban"]], "4. Permanent Ban": [[2, "permanent-ban"]], "AWS Lambda": [[14, null]], "Advanced options": [[19, "advanced-options"]], "Args:": [[7, "args"], [7, "id4"], [7, "id7"], [7, "id10"], [7, "id13"], [7, "id16"], [7, "id19"], [7, "id22"], [7, "id25"], [7, "id29"], [7, "id32"], [7, "id37"], [7, "id40"], [7, "id46"], [7, "id49"], [7, "id50"], [7, "id51"], [7, "id54"], [7, "id57"], [7, "id60"], [7, "id61"], [8, "args"], [8, "id2"], [8, "id3"], [8, "id4"], [8, "id5"], [8, "id6"], [8, "id7"], [8, "id10"], [8, "id12"], [8, "id14"], [8, "id16"], [8, "id20"], [8, "id24"], [8, "id28"], [9, "args"], [9, "id3"], [9, "id8"], [9, "id13"], [9, "id17"], [9, "id21"], [9, "id26"], [9, "id31"], [9, "id36"], [9, "id41"], [9, "id46"], [9, "id50"], [9, "id54"], [9, "id59"], [9, "id63"], [9, "id68"], [9, "id73"], [9, "id77"], [9, "id81"], [9, "id85"], [9, "id90"], [9, "id95"], [9, "id99"], [9, "id104"], [9, "id109"], [9, "id114"], [9, "id119"], [9, "id123"], [9, "id127"], [9, "id132"], [9, "id137"], [9, "id142"], [9, "id146"], [9, "id150"], [9, "id155"], [9, "id159"], [9, "id163"], [9, "id167"], [9, "id169"], [9, "id171"], [9, "id173"], [10, "args"], [10, "id1"], [10, "id2"], [10, "id3"], [10, "id4"], [10, "id5"], [10, "id6"], [10, "id7"], [10, "id8"], [10, "id9"], [10, "id10"], [10, "id11"], [10, "id12"], [10, "id13"], [10, "id14"], [10, "id15"], [10, "id16"], [10, "id17"], [10, "id18"], [10, "id19"], [11, "args"], [11, "id3"], [11, "id4"], [11, "id5"], [11, "id6"], [11, "id7"], [11, "id8"], [11, "id9"]], "Artefact": [[8, "artefact"]], "ArtefactDetection": [[16, "artefactdetection"]], "Attribution": [[2, "attribution"]], "Available Datasets": [[17, "available-datasets"]], "Available architectures": [[19, "available-architectures"], [19, "id1"], [19, "id2"]], "Available contribution modules": [[16, "available-contribution-modules"]], "Block": [[8, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[17, null]], "Choosing the right model": [[19, null]], "Classification": [[15, "classification"]], "Code quality": [[3, "code-quality"]], "Code style verification": [[3, "code-style-verification"]], "Codebase structure": [[3, "codebase-structure"]], "Commits": [[3, "commits"]], "Community resources": [[1, null]], "Composing transformations": [[10, "composing-transformations"]], "Continuous Integration": [[3, "continuous-integration"]], "Contributing to docTR": [[3, null]], "Contributor Covenant Code of Conduct": [[2, null]], "Custom dataset loader": [[7, "custom-dataset-loader"]], "Custom orientation classification models": [[13, "custom-orientation-classification-models"]], "Data Loading": [[17, "data-loading"]], "Dataloader": [[7, "dataloader"]], "Detection": [[15, "detection"], [17, "detection"]], "Detection predictors": [[19, "detection-predictors"]], "Developer mode installation": [[3, "developer-mode-installation"]], "Developing docTR": [[3, "developing-doctr"]], "Document": [[8, "document"]], "Document structure": [[8, "document-structure"]], "End-to-End OCR": [[19, "end-to-end-ocr"]], "Enforcement": [[2, "enforcement"]], "Enforcement Guidelines": [[2, "enforcement-guidelines"]], "Enforcement Responsibilities": [[2, "enforcement-responsibilities"]], "Export to ONNX": [[18, "export-to-onnx"]], "Feature requests & bug report": [[3, "feature-requests-bug-report"]], "Feedback": [[3, "feedback"]], "File reading": [[8, "file-reading"]], "Half-precision": [[18, "half-precision"]], "Installation": [[4, null]], "Integrate contributions into your pipeline": [[16, null]], "Let\u2019s connect": [[3, "let-s-connect"]], "Line": [[8, "line"]], "Loading from Huggingface Hub": [[15, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[13, "loading-your-custom-trained-model"]], "Loading your custom trained orientation classification model": [[13, "loading-your-custom-trained-orientation-classification-model"]], "Main Features": [[5, "main-features"]], "Model optimization": [[18, "model-optimization"]], "Model zoo": [[5, "model-zoo"]], "Modifying the documentation": [[3, "modifying-the-documentation"]], "Naming conventions": [[15, "naming-conventions"]], "OCR": [[17, "ocr"]], "Object Detection": [[17, "object-detection"]], "Our Pledge": [[2, "our-pledge"]], "Our Standards": [[2, "our-standards"]], "Page": [[8, "page"]], "Preparing your model for inference": [[18, null]], "Prerequisites": [[4, "prerequisites"]], "Pretrained community models": [[15, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[15, "pushing-to-the-huggingface-hub"]], "Questions": [[3, "questions"]], "Recognition": [[15, "recognition"], [17, "recognition"]], "Recognition predictors": [[19, "recognition-predictors"]], "Returns:": [[7, "returns"], [8, "returns"], [8, "id11"], [8, "id13"], [8, "id15"], [8, "id19"], [8, "id23"], [8, "id27"], [8, "id31"], [9, "returns"], [9, "id6"], [9, "id11"], [9, "id16"], [9, "id20"], [9, "id24"], [9, "id29"], [9, "id34"], [9, "id39"], [9, "id44"], [9, "id49"], [9, "id53"], [9, "id57"], [9, "id62"], [9, "id66"], [9, "id71"], [9, "id76"], [9, "id80"], [9, "id84"], [9, "id88"], [9, "id93"], [9, "id98"], [9, "id102"], [9, "id107"], [9, "id112"], [9, "id117"], [9, "id122"], [9, "id126"], [9, "id130"], [9, "id135"], [9, "id140"], [9, "id145"], [9, "id149"], [9, "id153"], [9, "id158"], [9, "id162"], [9, "id166"], [9, "id168"], [9, "id170"], [9, "id172"], [11, "returns"]], "Scope": [[2, "scope"]], "Share your model with the community": [[15, null]], "Supported Vocabs": [[7, "supported-vocabs"]], "Supported contribution modules": [[6, "supported-contribution-modules"]], "Supported datasets": [[5, "supported-datasets"]], "Supported transformations": [[10, "supported-transformations"]], "Synthetic dataset generator": [[7, "synthetic-dataset-generator"], [17, "synthetic-dataset-generator"]], "Task evaluation": [[11, "task-evaluation"]], "Text Detection": [[19, "text-detection"]], "Text Recognition": [[19, "text-recognition"]], "Text detection models": [[5, "text-detection-models"]], "Text recognition models": [[5, "text-recognition-models"]], "Train your own model": [[13, null]], "Two-stage approaches": [[19, "two-stage-approaches"]], "Unit tests": [[3, "unit-tests"]], "Use your own datasets": [[17, "use-your-own-datasets"]], "Using your ONNX exported model": [[18, "using-your-onnx-exported-model"]], "Via Conda (Only for Linux)": [[4, "via-conda-only-for-linux"]], "Via Git": [[4, "via-git"]], "Via Python Package": [[4, "via-python-package"]], "Visualization": [[11, "visualization"]], "What should I do with the output?": [[19, "what-should-i-do-with-the-output"]], "Word": [[8, "word"]], "docTR Notebooks": [[12, null]], "docTR Vocabs": [[7, "id62"]], "docTR: Document Text Recognition": [[5, null]], "doctr.contrib": [[6, null]], "doctr.datasets": [[7, null], [7, "datasets"]], "doctr.io": [[8, null]], "doctr.models": [[9, null]], "doctr.models.classification": [[9, "doctr-models-classification"]], "doctr.models.detection": [[9, "doctr-models-detection"]], "doctr.models.factory": [[9, "doctr-models-factory"]], "doctr.models.recognition": [[9, "doctr-models-recognition"]], "doctr.models.zoo": [[9, "doctr-models-zoo"]], "doctr.transforms": [[10, null]], "doctr.utils": [[11, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.10.0 (2024-10-21)": [[0, "v0-10-0-2024-10-21"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]], "v0.7.0 (2023-09-09)": [[0, "v0-7-0-2023-09-09"]], "v0.8.0 (2024-02-28)": [[0, "v0-8-0-2024-02-28"]], "v0.8.1 (2024-03-04)": [[0, "v0-8-1-2024-03-04"]], "v0.9.0 (2024-08-08)": [[0, "v0-9-0-2024-08-08"]]}, "docnames": ["changelog", "community/resources", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/contrib", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_contrib_modules", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "community/resources.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/contrib.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_contrib_modules.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[8, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[8, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[10, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[7, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[10, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[10, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[7, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[7, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[9, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[7, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[7, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[8, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[8, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[7, "doctr.datasets.encode_sequences", false]], "fast_base() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_base", false]], "fast_small() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_small", false]], "fast_tiny() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_tiny", false]], "from_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[7, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[10, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[10, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[7, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[7, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[7, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[7, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[7, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[9, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[10, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[8, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet34() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[7, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_crop_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_crop_orientation", false]], "mobilenet_v3_small_page_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_page_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[10, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[9, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[7, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[10, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[8, "doctr.io.Page", false]], "page_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.page_orientation_predictor", false]], "parseq() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[10, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[10, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[10, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[10, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[10, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[10, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[10, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[10, "doctr.transforms.RandomJpegQuality", false]], "randomresize (class in doctr.transforms)": [[10, "doctr.transforms.RandomResize", false]], "randomrotate (class in doctr.transforms)": [[10, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[10, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[10, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[8, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[8, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[8, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[7, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[10, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[8, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[8, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[7, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[7, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[7, "doctr.datasets.SVT", false]], "synthtext (class in doctr.datasets)": [[7, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.TextMatch", false]], "textnet_base() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_base", false]], "textnet_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_small", false]], "textnet_tiny() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_tiny", false]], "togray (class in doctr.transforms)": [[10, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[11, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_small", false]], "wildreceipt (class in doctr.datasets)": [[7, "doctr.datasets.WILDRECEIPT", false]], "word (class in doctr.io)": [[8, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[7, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[7, 0, 1, "", "CORD"], [7, 0, 1, "", "CharacterGenerator"], [7, 0, 1, "", "DetectionDataset"], [7, 0, 1, "", "DocArtefacts"], [7, 0, 1, "", "FUNSD"], [7, 0, 1, "", "IC03"], [7, 0, 1, "", "IC13"], [7, 0, 1, "", "IIIT5K"], [7, 0, 1, "", "IIITHWS"], [7, 0, 1, "", "IMGUR5K"], [7, 0, 1, "", "MJSynth"], [7, 0, 1, "", "OCRDataset"], [7, 0, 1, "", "RecognitionDataset"], [7, 0, 1, "", "SROIE"], [7, 0, 1, "", "SVHN"], [7, 0, 1, "", "SVT"], [7, 0, 1, "", "SynthText"], [7, 0, 1, "", "WILDRECEIPT"], [7, 0, 1, "", "WordGenerator"], [7, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[7, 0, 1, "", "DataLoader"]], "doctr.io": [[8, 0, 1, "", "Artefact"], [8, 0, 1, "", "Block"], [8, 0, 1, "", "Document"], [8, 0, 1, "", "DocumentFile"], [8, 0, 1, "", "Line"], [8, 0, 1, "", "Page"], [8, 0, 1, "", "Word"], [8, 1, 1, "", "decode_img_as_tensor"], [8, 1, 1, "", "read_html"], [8, 1, 1, "", "read_img_as_numpy"], [8, 1, 1, "", "read_img_as_tensor"], [8, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[8, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[8, 2, 1, "", "from_images"], [8, 2, 1, "", "from_pdf"], [8, 2, 1, "", "from_url"]], "doctr.io.Page": [[8, 2, 1, "", "show"]], "doctr.models": [[9, 1, 1, "", "kie_predictor"], [9, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[9, 1, 1, "", "crop_orientation_predictor"], [9, 1, 1, "", "magc_resnet31"], [9, 1, 1, "", "mobilenet_v3_large"], [9, 1, 1, "", "mobilenet_v3_large_r"], [9, 1, 1, "", "mobilenet_v3_small"], [9, 1, 1, "", "mobilenet_v3_small_crop_orientation"], [9, 1, 1, "", "mobilenet_v3_small_page_orientation"], [9, 1, 1, "", "mobilenet_v3_small_r"], [9, 1, 1, "", "page_orientation_predictor"], [9, 1, 1, "", "resnet18"], [9, 1, 1, "", "resnet31"], [9, 1, 1, "", "resnet34"], [9, 1, 1, "", "resnet50"], [9, 1, 1, "", "textnet_base"], [9, 1, 1, "", "textnet_small"], [9, 1, 1, "", "textnet_tiny"], [9, 1, 1, "", "vgg16_bn_r"], [9, 1, 1, "", "vit_b"], [9, 1, 1, "", "vit_s"]], "doctr.models.detection": [[9, 1, 1, "", "db_mobilenet_v3_large"], [9, 1, 1, "", "db_resnet50"], [9, 1, 1, "", "detection_predictor"], [9, 1, 1, "", "fast_base"], [9, 1, 1, "", "fast_small"], [9, 1, 1, "", "fast_tiny"], [9, 1, 1, "", "linknet_resnet18"], [9, 1, 1, "", "linknet_resnet34"], [9, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[9, 1, 1, "", "from_hub"], [9, 1, 1, "", "login_to_hub"], [9, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[9, 1, 1, "", "crnn_mobilenet_v3_large"], [9, 1, 1, "", "crnn_mobilenet_v3_small"], [9, 1, 1, "", "crnn_vgg16_bn"], [9, 1, 1, "", "master"], [9, 1, 1, "", "parseq"], [9, 1, 1, "", "recognition_predictor"], [9, 1, 1, "", "sar_resnet31"], [9, 1, 1, "", "vitstr_base"], [9, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[10, 0, 1, "", "ChannelShuffle"], [10, 0, 1, "", "ColorInversion"], [10, 0, 1, "", "Compose"], [10, 0, 1, "", "GaussianBlur"], [10, 0, 1, "", "GaussianNoise"], [10, 0, 1, "", "LambdaTransformation"], [10, 0, 1, "", "Normalize"], [10, 0, 1, "", "OneOf"], [10, 0, 1, "", "RandomApply"], [10, 0, 1, "", "RandomBrightness"], [10, 0, 1, "", "RandomContrast"], [10, 0, 1, "", "RandomCrop"], [10, 0, 1, "", "RandomGamma"], [10, 0, 1, "", "RandomHorizontalFlip"], [10, 0, 1, "", "RandomHue"], [10, 0, 1, "", "RandomJpegQuality"], [10, 0, 1, "", "RandomResize"], [10, 0, 1, "", "RandomRotate"], [10, 0, 1, "", "RandomSaturation"], [10, 0, 1, "", "RandomShadow"], [10, 0, 1, "", "Resize"], [10, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[11, 0, 1, "", "DetectionMetric"], [11, 0, 1, "", "LocalizationConfusion"], [11, 0, 1, "", "OCRMetric"], [11, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.visualization": [[11, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [2, 8, 9, 11, 15, 18], "0": [2, 4, 7, 10, 11, 13, 16, 17, 19], "00": 19, "01": 19, "0123456789": 7, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "02562": 9, "03": 19, "035": 19, "0361328125": 19, "04": 19, "05": 19, "06": 19, "06640625": 19, "07": 19, "08": [10, 19], "09": 19, "0966796875": 19, "1": [7, 8, 9, 10, 11, 13, 17, 19], "10": [4, 7, 11, 19], "100": [7, 10, 11, 17, 19], "1000": 19, "101": 7, "1024": [9, 13, 19], "104": 7, "106": 7, "108": 7, "1095": 17, "11": 19, "110": 11, "1107": 17, "114": 7, "115": 7, "1156": 17, "116": 7, "118": 7, "11800h": 19, "11th": 19, "12": 19, "120": 7, "123": 7, "126": 7, "1268": 17, "128": [9, 13, 18, 19], "13": 19, "130": 7, "13068": 17, "131": 7, "1337891": 17, "1357421875": 19, "1396484375": 19, "14": 19, "1420": 19, "14470v1": 7, "149": 17, "15": 19, "150": [11, 19], "1552": 19, "16": [9, 18, 19], "1630859375": 19, "1684": 19, "16x16": 9, "17": 19, "1778": 19, "1782": 19, "18": [9, 19], "185546875": 19, "1900": 19, "1910": 9, "19342": 17, "19370": 17, "195": 7, "19598": 17, "199": 19, "1999": 19, "2": [4, 5, 7, 8, 9, 10, 16, 19], "20": 19, "200": 11, "2000": 17, "2003": [5, 7], "2012": 7, "2013": [5, 7], "2015": 7, "2019": 5, "2023": 1, "207901": 17, "21": 19, "2103": 7, "2186": 17, "21888": 17, "22": 19, "224": [9, 10], "225": 10, "22672": 17, "229": [10, 17], "23": 19, "233": 17, "236": 7, "24": 19, "246": 17, "249": 17, "25": 19, "2504": 19, "255": [8, 9, 10, 11, 19], "256": 9, "257": 17, "26": 19, "26032": 17, "264": 13, "27": 19, "2700": 17, "2710": 19, "2749": 13, "28": 19, "287": 13, "29": 19, "296": 13, "299": 13, "2d": 19, "3": [4, 5, 8, 9, 10, 11, 18, 19], "30": 19, "300": 17, "3000": 17, "301": 13, "30595": 19, "30ghz": 19, "31": 9, "32": [7, 9, 10, 13, 17, 18, 19], "3232421875": 19, "33": [10, 19], "33402": 17, "33608": 17, "34": [9, 19], "340": 19, "3456": 19, "3515625": 19, "36": 19, "360": 17, "37": [7, 19], "38": 19, "39": 19, "4": [9, 10, 11, 19], "40": 19, "406": 10, "41": 19, "42": 19, "43": 19, "44": 19, "45": 19, "456": 10, "46": 19, "47": 19, "472": 17, "48": [7, 19], "485": 10, "49": 19, "49377": 17, "5": [7, 10, 11, 16, 19], "50": [9, 17, 19], "51": 19, "51171875": 19, "512": 9, "52": [7, 19], "529": 19, "53": 19, "54": 19, "540": 19, "5478515625": 19, "55": 19, "56": 19, "57": 19, "58": [7, 19], "580": 19, "5810546875": 19, "583": 19, "59": 19, "597": 19, "5k": [5, 7], "5m": 19, "6": [10, 19], "60": 10, "600": [9, 11, 19], "61": 19, "62": 19, "626": 17, "63": 19, "64": [9, 10, 19], "641": 19, "647": 17, "65": 19, "66": 19, "67": 19, "68": 19, "69": 19, "693": 13, "694": 13, "695": 13, "6m": 19, "7": 19, "70": [7, 11, 19], "707470": 17, "71": [7, 19], "7100000": 17, "7141797": 17, "7149": 17, "72": 19, "72dpi": 8, "73": 19, "73257": 17, "74": 19, "75": [10, 19], "7581382": 17, "76": 19, "77": 19, "772": 13, "772875": 17, "78": 19, "785": 13, "79": 19, "793533": 17, "796": 17, "798": 13, "7m": 19, "8": [9, 10, 19], "80": 19, "800": [9, 11, 17, 19], "81": 19, "82": 19, "83": 19, "84": 19, "849": 17, "85": 19, "8564453125": 19, "857": 19, "85875": 17, "86": 19, "8603515625": 19, "87": 19, "8707": 17, "88": 19, "89": 19, "9": [10, 19], "90": 19, "90k": 7, "90kdict32px": 7, "91": 19, "914085328578949": 19, "92": 19, "93": 19, "94": [7, 19], "95": [11, 19], "9578408598899841": 19, "96": 19, "97": 19, "98": 19, "99": 19, "9949972033500671": 19, "A": [2, 3, 5, 7, 8, 9, 12, 18], "As": 3, "Be": 19, "Being": 2, "By": 14, "For": [2, 3, 4, 13, 19], "If": [3, 8, 9, 13, 19], "In": [3, 7, 17], "It": [10, 15, 16, 18], "Its": [5, 9], "No": [2, 19], "Of": 7, "Or": [16, 18], "The": [2, 3, 7, 8, 11, 14, 16, 17, 18, 19], "Then": 9, "To": [3, 4, 14, 15, 16, 18, 19], "_": [2, 7, 9], "__call__": 19, "_build": 3, "_i": 11, "ab": 7, "abc": 18, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "abdef": [7, 17], "abl": [17, 19], "about": [2, 17, 19], "abov": 19, "abstract": 1, "abstractdataset": 7, "abus": 2, "accept": 2, "access": [5, 8, 17, 19], "account": [2, 15], "accur": 19, "accuraci": 11, "achiev": 18, "act": 2, "action": 2, "activ": 5, "ad": [3, 9, 10], "adapt": 2, "add": [10, 11, 15, 19], "add_hook": 19, "add_label": 11, "addit": [3, 4, 8, 16, 19], "addition": [3, 19], "address": [2, 8], "adjust": 10, "advanc": 2, "advantag": 18, "advis": 3, "aesthet": [5, 7], "affect": 2, "after": [15, 19], "ag": 2, "again": 9, "aggreg": [11, 17], "aggress": 2, "align": [2, 8, 10], "all": [2, 3, 6, 7, 8, 10, 11, 16, 17, 19], "allow": [2, 18], "along": 19, "alreadi": [3, 18], "also": [2, 9, 15, 16, 17, 19], "alwai": 17, "an": [2, 3, 5, 7, 8, 9, 11, 16, 18, 19], "analysi": [8, 16], "ancient_greek": 7, "andrej": 1, "angl": [8, 10], "ani": [2, 7, 8, 9, 10, 11, 18, 19], "annot": 7, "anot": 17, "anoth": [9, 13, 17], "answer": 2, "anyascii": 11, "anyon": 5, "anyth": 16, "api": [3, 5], "apolog": 2, "apologi": 2, "app": 3, "appear": 2, "appli": [2, 7, 10], "applic": [5, 9], "appoint": 2, "appreci": 15, "appropri": [2, 3, 19], "ar": [2, 3, 4, 6, 7, 8, 10, 11, 12, 16, 17, 19], "arab": 7, "arabic_diacrit": 7, "arabic_lett": 7, "arabic_punctu": 7, "arbitrarili": [5, 9], "arch": [9, 15], "architectur": [5, 9, 15, 16], "area": 19, "argument": [7, 8, 9, 11, 13, 19], "around": 2, "arrai": [8, 10, 11], "art": [5, 16], "artefact": [11, 16, 19], "artefact_typ": 8, "articl": 1, "artifici": [5, 7], "arxiv": [7, 9], "asarrai": 11, "ascii_lett": 7, "aspect": [5, 9, 10, 19], "assess": 11, "assign": 11, "associ": 8, "assum": 9, "assume_straight_pag": [9, 13, 19], "astyp": [9, 11, 19], "attack": 2, "attend": [5, 9], "attent": [2, 9], "autom": 5, "automat": 19, "autoregress": [5, 9], "avail": [2, 5, 6, 10], "averag": [10, 19], "avoid": [2, 4], "aw": [5, 19], "awar": 19, "azur": 19, "b": [9, 11, 19], "b_j": 11, "back": 3, "backbon": 9, "backend": 19, "background": 17, "bangla": 7, "bar": 16, "bar_cod": 17, "baranovskij": 1, "base": [5, 9, 16], "baselin": [5, 9, 19], "batch": [7, 9, 10, 16, 17, 19], "batch_siz": [7, 9, 13, 16, 17, 18], "bblanchon": 4, "bbox": 19, "becaus": 14, "been": [3, 11, 17, 19], "befor": [7, 9, 10, 19], "begin": 11, "behavior": [2, 19], "being": [11, 19], "belong": 19, "benchmark": 19, "best": [1, 2], "better": [12, 19], "between": [10, 11, 19], "bgr": 8, "bilinear": 10, "bin_thresh": 19, "binar": [5, 9, 19], "binari": [8, 18, 19], "bit": 18, "block": [11, 19], "block_1_1": 19, "blur": 10, "bmvc": 7, "bn": 15, "bodi": [2, 19], "bool": [7, 8, 9, 10, 11], "boolean": [9, 19], "both": [5, 7, 10, 17, 19], "bottom": [9, 19], "bound": [7, 8, 9, 10, 11, 16, 17, 19], "box": [7, 8, 9, 10, 11, 16, 17, 19], "box_thresh": 19, "bright": 10, "browser": [3, 5], "build": [3, 4, 18], "built": 3, "byte": [8, 19], "c": [4, 8, 11], "c_j": 11, "cach": [3, 7, 14], "cache_sampl": 7, "call": 18, "callabl": [7, 10], "can": [3, 4, 13, 14, 15, 16, 17, 19], "capabl": [3, 12, 19], "case": [7, 11], "cf": 19, "cfg": 19, "challeng": 7, "challenge2_test_task12_imag": 7, "challenge2_test_task1_gt": 7, "challenge2_training_task12_imag": 7, "challenge2_training_task1_gt": 7, "chang": [14, 19], "channel": [2, 3, 8, 10], "channel_prior": 4, "channelshuffl": 10, "charact": [5, 7, 8, 11, 17, 19], "charactergener": [7, 17], "characterist": 2, "charg": 19, "charset": 19, "chart": 8, "check": [3, 15, 19], "checkpoint": 9, "chip": 4, "christian": 1, "ci": 3, "clarifi": 2, "clariti": 2, "class": [2, 7, 8, 10, 11, 19], "class_nam": 13, "classif": [17, 19], "classmethod": 8, "clear": 3, "clone": 4, "close": 3, "co": 15, "code": [5, 8, 16], "codecov": 3, "colab": 12, "collate_fn": 7, "collect": [8, 16], "color": 10, "colorinvers": 10, "column": 8, "com": [2, 4, 8, 9, 15], "combin": 19, "command": [3, 16], "comment": 2, "commit": 2, "common": [2, 10, 11, 18], "commun": 2, "compar": 5, "comparison": [11, 19], "competit": 7, "compil": [12, 19], "complaint": 2, "complementari": 11, "complet": 3, "compon": 19, "compos": [7, 19], "comprehens": 19, "comput": [7, 11, 18, 19], "conf_threshold": 16, "confid": [8, 19], "config": [4, 9], "configur": 9, "confus": 11, "consecut": [10, 19], "consequ": 2, "consid": [2, 3, 7, 8, 11, 19], "consist": 19, "consolid": [5, 7], "constant": 10, "construct": 2, "contact": 2, "contain": [1, 6, 7, 12, 17, 19], "content": [7, 8, 19], "context": 9, "contib": 4, "continu": 2, "contrast": 10, "contrast_factor": 10, "contrib": [4, 16], "contribut": 2, "contributor": 3, "convers": 8, "convert": [8, 10], "convolut": 9, "cool": 1, "coordin": [8, 19], "cord": [5, 7, 17, 19], "core": [11, 19], "corner": 19, "correct": 10, "correspond": [4, 8, 10, 19], "could": [2, 16], "counterpart": 11, "cover": 3, "coverag": 3, "cpu": [5, 13, 18], "creat": [1, 15], "crnn": [5, 9, 15], "crnn_mobilenet_v3_larg": [9, 15, 19], "crnn_mobilenet_v3_smal": [9, 18, 19], "crnn_vgg16_bn": [9, 13, 15, 19], "crop": [8, 9, 10, 13, 17, 19], "crop_orient": [8, 19], "crop_orientation_predictor": [9, 13], "crop_param": 13, "cuda": 18, "currenc": 7, "current": [3, 13, 19], "custom": [15, 16, 18, 19], "custom_crop_orientation_model": 13, "custom_page_orientation_model": 13, "customhook": 19, "cvit": 5, "czczup": 9, "czech": 7, "d": [7, 17], "danish": 7, "data": [5, 7, 8, 10, 11, 13, 15], "dataload": 17, "dataset": [9, 13, 19], "dataset_info": 7, "date": [13, 19], "db": 15, "db_mobilenet_v3_larg": [9, 15, 19], "db_resnet34": 19, "db_resnet50": [9, 13, 15, 19], "dbnet": [5, 9], "deal": [12, 19], "decis": 2, "decod": 8, "decode_img_as_tensor": 8, "dedic": 18, "deem": 2, "deep": [9, 19], "def": 19, "default": [4, 8, 13, 14, 19], "defer": 17, "defin": [11, 18], "degre": [8, 10, 19], "degress": 8, "delet": 3, "delimit": 19, "delta": 10, "demo": [3, 5], "demonstr": 2, "depend": [3, 4, 5, 19], "deploi": 3, "deploy": 5, "derogatori": 2, "describ": 9, "descript": 12, "design": 10, "desir": 8, "det_arch": [9, 13, 15, 18], "det_b": 19, "det_model": [13, 15, 18], "det_param": 13, "det_predictor": [13, 19], "detail": [13, 19], "detect": [1, 7, 8, 11, 12, 13, 16], "detect_languag": 9, "detect_orient": [9, 13, 19], "detection_predictor": [9, 19], "detection_task": [7, 17], "detectiondataset": [7, 17], "detectionmetr": 11, "detectionpredictor": [9, 13], "detector": [5, 9, 16], "deterior": 9, "determin": 2, "dev": [3, 14], "develop": 4, "deviat": 10, "devic": 18, "dict": [8, 11, 19], "dictionari": [8, 11], "differ": 2, "differenti": [5, 9], "digit": [5, 7, 17], "dimens": [8, 11, 19], "dimension": 10, "direct": 7, "directli": [15, 19], "directori": [3, 14], "disabl": [2, 14, 19], "disable_crop_orient": 19, "disable_page_orient": 19, "disclaim": 19, "discuss": 3, "disparag": 2, "displai": [8, 11], "display_artefact": 11, "distribut": 10, "div": 19, "divers": 2, "divid": 8, "do": [3, 4, 9], "doc": [3, 8, 16, 18, 19], "docartefact": [7, 17], "docstr": 3, "doctr": [1, 4, 13, 14, 15, 16, 17, 18, 19], "doctr_cache_dir": 14, "doctr_multiprocessing_dis": 14, "document": [1, 7, 9, 11, 12, 13, 16, 17, 18, 19], "documentbuild": 19, "documentfil": [8, 13, 15, 16, 18], "doesn": 18, "don": [13, 19], "done": 10, "download": [7, 17], "downsiz": 9, "draw": 10, "drop": 7, "drop_last": 7, "dtype": [8, 9, 10, 11, 18], "dual": [5, 7], "dummi": 15, "dummy_img": 19, "dummy_input": 18, "dure": 2, "dutch": 7, "dynam": [7, 16], "dynamic_seq_length": 7, "e": [2, 3, 4, 8, 9], "each": [5, 7, 8, 9, 10, 11, 17, 19], "eas": 3, "easi": [5, 11, 15, 18], "easili": [8, 11, 13, 15, 17, 19], "econom": 2, "edit": 2, "educ": 2, "effect": 19, "effici": [3, 5, 7, 9], "either": [11, 19], "element": [7, 8, 9, 19], "els": [3, 16], "email": 2, "empathi": 2, "en": 19, "enabl": [7, 8], "enclos": 8, "encod": [5, 7, 8, 9, 19], "encode_sequ": 7, "encount": 3, "encrypt": 8, "end": [5, 7, 9, 11], "english": [7, 17], "enough": [3, 19], "ensur": 3, "entri": 7, "environ": [2, 14], "eo": 7, "equiv": 19, "estim": 9, "etc": [8, 16], "ethnic": 2, "evalu": [17, 19], "event": 2, "everyon": 2, "everyth": [3, 19], "exact": [11, 19], "exampl": [2, 3, 5, 7, 9, 15, 19], "exchang": 18, "execut": 19, "exist": 15, "expand": 10, "expect": [8, 10, 11], "experi": 2, "explan": [2, 19], "explicit": 2, "exploit": [5, 9], "export": [8, 9, 11, 12, 16, 19], "export_as_straight_box": [9, 19], "export_as_xml": 19, "export_model_to_onnx": 18, "express": [2, 10], "extens": 8, "extern": [2, 17], "extract": [1, 5, 7], "extractor": 9, "f_": 11, "f_a": 11, "factor": 10, "fair": 2, "fairli": 2, "fals": [7, 8, 9, 10, 11, 13, 19], "faq": 2, "fascan": 15, "fast": [5, 7, 9], "fast_bas": [9, 19], "fast_smal": [9, 19], "fast_tini": [9, 19], "faster": [5, 9, 18], "fasterrcnn_mobilenet_v3_large_fpn": 9, "favorit": 19, "featur": [4, 9, 11, 12, 13, 16], "feedback": 2, "feel": [3, 15], "felix92": 15, "few": [18, 19], "figsiz": 11, "figur": [11, 16], "file": [3, 7], "final": 9, "find": [3, 17], "fine": 1, "finnish": 7, "first": [3, 7], "firsthand": 7, "fit": [9, 19], "flag": 19, "flip": 10, "float": [8, 10, 11, 18], "float32": [8, 9, 10, 18], "fn": 10, "focu": 15, "focus": [2, 7], "folder": 7, "follow": [2, 3, 4, 7, 10, 11, 13, 14, 15, 16, 19], "font": 7, "font_famili": 7, "foral": 11, "forc": 3, "forg": 4, "form": [5, 7, 19], "format": [8, 11, 13, 17, 18, 19], "forpost": [5, 7], "forum": 3, "found": 1, "fp16": 18, "frac": 11, "framework": [4, 15, 17, 19], "free": [2, 3, 15], "french": [7, 13, 15, 19], "friendli": 5, "from": [1, 2, 5, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19], "from_hub": [9, 15], "from_imag": [8, 15, 16, 18], "from_pdf": 8, "from_url": 8, "full": [7, 11, 19], "function": [7, 10, 11, 16], "funsd": [5, 7, 17, 19], "further": 17, "futur": 7, "g": [8, 9], "g_": 11, "g_x": 11, "gallagh": 1, "gamma": 10, "gaussian": 10, "gaussianblur": 10, "gaussiannois": 10, "gen": 19, "gender": 2, "gener": [3, 5, 8, 9], "generic_cyrillic_lett": 7, "geometri": [5, 8, 19], "geq": 11, "german": [7, 13, 15], "get": [18, 19], "git": 15, "github": [3, 4, 9, 15], "give": [2, 16], "given": [7, 8, 10, 11, 19], "global": 9, "go": 19, "good": 18, "googl": 3, "googlevis": 5, "gpu": [5, 16, 18], "gracefulli": 2, "graph": [5, 7, 8], "grayscal": 10, "ground": 11, "groung": 11, "group": [5, 19], "gt": 11, "gt_box": 11, "gt_label": 11, "guid": 3, "guidanc": 17, "gvision": 19, "h": [8, 9, 10], "h_": 11, "ha": [3, 7, 11, 17], "handl": [12, 17, 19], "handwrit": 7, "handwritten": 17, "harass": 2, "hardwar": 19, "harm": 2, "hat": 11, "have": [2, 3, 11, 13, 15, 17, 18, 19], "head": [9, 19], "healthi": 2, "hebrew": 7, "height": [8, 10], "hello": [11, 19], "help": 18, "here": [6, 10, 12, 16, 17, 19], "hf": 9, "hf_hub_download": 9, "high": 8, "higher": [4, 7, 19], "hindi": 7, "hindi_digit": 7, "hocr": 19, "hook": 19, "horizont": [8, 10, 19], "hous": 7, "how": [1, 3, 12, 13, 15, 17], "howev": 17, "hsv": 10, "html": [2, 3, 4, 8, 19], "http": [2, 4, 7, 8, 9, 15, 19], "hub": 9, "hue": 10, "huggingfac": 9, "hw": 7, "i": [2, 3, 7, 8, 9, 10, 11, 14, 15, 16, 17, 18], "i7": 19, "ibrahimov": 1, "ic03": [5, 7, 17], "ic13": [5, 7, 17], "icdar": [5, 7], "icdar2019": 7, "id": 19, "ident": 2, "identifi": 5, "iiit": [5, 7], "iiit5k": [7, 17], "iiithw": [5, 7, 17], "imag": [1, 5, 7, 8, 9, 10, 11, 15, 16, 17, 19], "imagenet": 9, "imageri": 2, "images_90k_norm": 7, "img": [7, 10, 17, 18], "img_cont": 8, "img_fold": [7, 17], "img_path": 8, "img_transform": 7, "imgur5k": [5, 7, 17], "imgur5k_annot": 7, "imlist": 7, "impact": 2, "implement": [7, 8, 9, 10, 11, 19], "import": [7, 8, 9, 10, 11, 13, 15, 16, 17, 18, 19], "improv": 9, "inappropri": 2, "incid": 2, "includ": [2, 7, 17, 18], "inclus": 2, "increas": 10, "independ": 10, "index": [3, 8], "indic": 11, "individu": 2, "infer": [5, 9, 10, 16, 19], "inform": [1, 2, 3, 5, 7, 17], "input": [3, 8, 9, 10, 18, 19], "input_crop": 9, "input_pag": [9, 11, 19], "input_shap": 18, "input_tensor": 9, "inspir": [2, 10], "instal": [15, 16, 18], "instanc": [2, 19], "instanti": [9, 19], "instead": [7, 8, 9], "insult": 2, "int": [7, 8, 9, 10], "int64": 11, "integ": 11, "integr": [1, 5, 15, 17], "intel": 19, "interact": [2, 8, 11], "interfac": [15, 18], "interoper": 18, "interpol": 10, "interpret": [7, 8], "intersect": 11, "invert": 10, "investig": 2, "invis": 2, "involv": [2, 19], "io": [13, 15, 16, 18], "iou": 11, "iou_thresh": 11, "iou_threshold": 16, "irregular": [5, 9, 17], "isn": 7, "issu": [2, 3, 15], "italian": 7, "iter": [7, 10, 17, 19], "its": [8, 9, 10, 11, 17, 19], "itself": [9, 15], "j": 11, "jame": 1, "job": 3, "join": 3, "jpeg": 10, "jpegqual": 10, "jpg": [7, 8, 15, 18], "json": [7, 17, 19], "json_output": 19, "jump": 3, "just": 2, "kei": [5, 7], "kera": [9, 18], "kernel": [5, 9, 10], "kernel_shap": 10, "keywoard": 9, "keyword": [7, 8, 9, 11], "kie": [9, 13], "kie_predictor": [9, 13], "kiepredictor": 9, "kind": 2, "know": [3, 18], "kwarg": [7, 8, 9, 11], "l": 11, "l_j": 11, "label": [7, 11, 16, 17], "label_fil": [7, 17], "label_fold": 7, "label_path": [7, 17], "labels_path": [7, 17], "ladder": 2, "lambda": 10, "lambdatransform": 10, "lang": 19, "languag": [2, 5, 7, 8, 9, 15, 19], "larg": [9, 15], "largest": 11, "last": [4, 7], "latenc": 9, "later": 3, "latest": 19, "latin": 7, "layer": 18, "layout": 19, "lead": 2, "leader": 2, "learn": [2, 5, 9, 18, 19], "least": 4, "left": [11, 19], "legacy_french": 7, "length": [7, 19], "less": [18, 19], "level": [2, 7, 11, 19], "leverag": 12, "lf": 15, "librari": [3, 4, 12, 13], "light": 5, "lightweight": 18, "like": 2, "limits_": 11, "line": [5, 9, 11, 19], "line_1_1": 19, "link": 13, "linknet": [5, 9], "linknet_resnet18": [9, 13, 18, 19], "linknet_resnet34": [9, 18, 19], "linknet_resnet50": [9, 19], "list": [7, 8, 10, 11, 15], "ll": 11, "load": [5, 7, 9, 16, 18], "load_state_dict": 13, "load_weight": 13, "loc_pr": 19, "local": [3, 5, 7, 9, 11, 17, 19], "localis": 7, "localizationconfus": 11, "locat": [3, 8, 19], "login": 9, "login_to_hub": [9, 15], "logo": [8, 16, 17], "love": 15, "lower": [10, 11, 19], "m": [3, 11, 19], "m1": 4, "macbook": 4, "machin": 18, "made": 5, "magc_resnet31": 9, "mai": [2, 3], "mail": 2, "main": 12, "maintain": 5, "mainten": 3, "make": [2, 3, 11, 13, 14, 15, 18, 19], "mani": [17, 19], "manipul": 19, "map": [7, 9], "map_loc": 13, "master": [5, 9, 19], "match": [11, 19], "mathcal": 11, "matplotlib": [8, 11], "max": [7, 10, 11], "max_angl": 10, "max_area": 10, "max_char": [7, 17], "max_delta": 10, "max_gain": 10, "max_gamma": 10, "max_qual": 10, "max_ratio": 10, "maximum": [7, 10], "maxval": [9, 10], "mbox": 11, "mean": [10, 11, 13], "meaniou": 11, "meant": [8, 18], "measur": 19, "media": 2, "median": 9, "meet": 13, "member": 2, "memori": [14, 18], "mention": 19, "merg": 7, "messag": 3, "meta": 19, "metadata": 18, "metal": 4, "method": [8, 10, 19], "metric": [11, 19], "middl": 19, "might": [18, 19], "min": 10, "min_area": 10, "min_char": [7, 17], "min_gain": 10, "min_gamma": 10, "min_qual": 10, "min_ratio": 10, "min_val": 10, "minde": [1, 2, 4, 5, 9], "minim": [3, 5], "minimalist": [5, 9], "minimum": [4, 7, 10, 11, 19], "minval": 10, "miss": 4, "mistak": 2, "mixed_float16": 18, "mixed_precis": 18, "mjsynth": [5, 7, 17], "mnt": 7, "mobilenet": [9, 15], "mobilenet_v3_larg": 9, "mobilenet_v3_large_r": 9, "mobilenet_v3_smal": [9, 13], "mobilenet_v3_small_crop_orient": [9, 13], "mobilenet_v3_small_page_orient": [9, 13], "mobilenet_v3_small_r": 9, "mobilenetv3": 9, "modal": [5, 7], "mode": 4, "model": [7, 11, 14, 16, 17], "model_nam": [9, 15, 18], "model_path": [16, 18], "moder": 2, "modif": 3, "modifi": [9, 14, 19], "modul": [4, 8, 9, 10, 11, 19], "more": [3, 17, 19], "moscardi": 1, "most": 19, "mozilla": 2, "multi": [5, 9], "multilingu": [7, 15], "multipl": [7, 8, 10, 19], "multipli": 10, "multiprocess": 14, "my": 9, "my_awesome_model": 15, "my_hook": 19, "n": [7, 11], "name": [7, 9, 18, 19], "nation": 2, "natur": [2, 5, 7], "ndarrai": [7, 8, 10, 11], "necessari": [4, 13, 14], "need": [3, 4, 7, 11, 13, 14, 15, 16, 19], "neg": 10, "nest": 19, "netraj": 1, "network": [5, 7, 9, 18], "neural": [5, 7, 9, 18], "new": [3, 11], "next": [7, 17], "nois": 10, "noisi": [5, 7], "non": [5, 7, 8, 9, 10, 11], "none": [7, 8, 9, 10, 11, 19], "normal": [9, 10], "norwegian": 7, "note": [0, 3, 7, 9, 13, 15, 16, 18], "now": 3, "np": [9, 10, 11, 19], "num_output_channel": 10, "num_sampl": [7, 17], "number": [7, 9, 10, 11, 19], "numpi": [8, 9, 11, 19], "o": 4, "obb": 16, "obj_detect": 15, "object": [7, 8, 11, 16, 19], "objectness_scor": [8, 19], "oblig": 2, "obtain": 19, "occupi": 18, "ocr": [1, 5, 7, 9, 11, 15], "ocr_carea": 19, "ocr_db_crnn": 11, "ocr_lin": 19, "ocr_pag": 19, "ocr_par": 19, "ocr_predictor": [9, 13, 15, 18, 19], "ocrdataset": [7, 17], "ocrmetr": 11, "ocrpredictor": [9, 13], "ocrx_word": 19, "offens": 2, "offici": [2, 9], "offlin": 2, "offset": 10, "onc": 19, "one": [3, 7, 9, 10, 13, 15, 19], "oneof": 10, "ones": [7, 11], "onli": [3, 9, 10, 11, 13, 15, 17, 18, 19], "onlin": 2, "onnx": 16, "onnxruntim": [16, 18], "onnxtr": 18, "opac": 10, "opacity_rang": 10, "open": [1, 2, 3, 15, 18], "opinion": 2, "optic": [5, 19], "optim": [5, 19], "option": [7, 9, 13], "order": [3, 7, 8, 10], "org": [2, 7, 9, 19], "organ": 8, "orient": [2, 8, 9, 12, 16, 19], "orientationpredictor": 9, "other": [2, 3], "otherwis": [2, 8, 11], "our": [1, 3, 9, 19], "out": [3, 9, 10, 11, 19], "outpout": 19, "output": [8, 10, 18], "output_s": [8, 10], "outsid": 14, "over": [7, 11, 19], "overal": [2, 9], "overlai": 8, "overview": 16, "overwrit": 13, "overwritten": 15, "own": 5, "p": [10, 19], "packag": [3, 5, 11, 14, 16, 17, 18], "pad": [7, 9, 10, 19], "page": [4, 7, 9, 11, 13, 19], "page1": 8, "page2": 8, "page_1": 19, "page_idx": [8, 19], "page_orientation_predictor": [9, 13], "page_param": 13, "pair": 11, "paper": 9, "par_1_1": 19, "paragraph": 19, "paragraph_break": 19, "parallel": 9, "param": [10, 19], "paramet": [5, 8, 9, 18], "pars": [5, 7], "parseq": [5, 9, 15, 18, 19], "part": [7, 10, 19], "parti": 4, "partial": 19, "particip": 2, "pass": [7, 8, 9, 13, 19], "password": 8, "patch": [9, 11], "path": [7, 8, 16, 17, 18], "path_to_checkpoint": 13, "path_to_custom_model": 18, "path_to_pt": 13, "patil": 1, "pattern": 2, "pdf": [8, 9, 12], "pdfpage": 8, "peopl": 2, "per": [10, 19], "perform": [5, 8, 9, 10, 11, 14, 18, 19], "period": 2, "permiss": 2, "permut": [5, 9], "persian_lett": 7, "person": [2, 17], "phase": 19, "photo": 17, "physic": [2, 8], "pick": 10, "pictur": 8, "pip": [3, 4, 16, 18], "pipelin": 19, "pixel": [8, 10, 19], "pleas": 3, "plot": 11, "plt": 11, "plug": 15, "plugin": 4, "png": 8, "point": 18, "polici": 14, "polish": 7, "polit": 2, "polygon": [7, 11, 19], "pool": 9, "portugues": 7, "posit": [2, 11], "possibl": [3, 11, 15, 19], "post": [2, 19], "postprocessor": 19, "potenti": 9, "power": 5, "ppageno": 19, "pre": [3, 9, 18], "precis": [11, 19], "pred": 11, "pred_box": 11, "pred_label": 11, "predefin": 17, "predict": [8, 9, 11, 19], "predictor": [5, 8, 9, 12, 13, 15, 18], "prefer": 17, "preinstal": 4, "preprocessor": [13, 19], "prerequisit": 15, "present": 12, "preserv": [9, 10, 19], "preserve_aspect_ratio": [8, 9, 10, 13, 19], "pretrain": [5, 9, 11, 13, 18, 19], "pretrained_backbon": [9, 13], "print": 19, "prior": 7, "privaci": 2, "privat": 2, "probabl": [1, 10], "problem": 3, "procedur": 10, "process": [3, 5, 8, 9, 13, 19], "processor": 19, "produc": [12, 19], "product": 18, "profession": 2, "project": [3, 17], "promptli": 2, "proper": 3, "properli": 7, "provid": [2, 3, 5, 15, 16, 17, 19], "public": [2, 5], "publicli": 19, "publish": 2, "pull": 15, "punctuat": 7, "pure": 7, "purpos": 3, "push_to_hf_hub": [9, 15], "py": 15, "pypdfium2": [4, 8], "pyplot": [8, 11], "python": [1, 3, 16], "python3": 15, "pytorch": [4, 5, 9, 10, 13, 15, 18, 19], "q": 3, "qr": [8, 16], "qr_code": 17, "qualiti": 10, "question": 2, "quickli": 5, "quicktour": 12, "r": 19, "race": 2, "ramdisk": 7, "rand": [9, 10, 11, 18, 19], "random": [9, 10, 11, 19], "randomappli": 10, "randombright": 10, "randomcontrast": 10, "randomcrop": 10, "randomgamma": 10, "randomhorizontalflip": 10, "randomhu": 10, "randomjpegqu": 10, "randomli": 10, "randomres": 10, "randomrot": 10, "randomsatur": 10, "randomshadow": 10, "rang": 10, "rassi": 15, "ratio": [9, 10, 19], "raw": [8, 11], "re": 18, "read": [5, 7, 9], "read_html": 8, "read_img_as_numpi": 8, "read_img_as_tensor": 8, "read_pdf": 8, "readi": 18, "real": [1, 5, 9, 10], "realli": 1, "reason": [2, 5, 7], "rebuild": 3, "rebuilt": 3, "recal": [11, 19], "receipt": [5, 7, 19], "reco_arch": [9, 13, 15, 18], "reco_b": 19, "reco_model": [13, 15, 18], "reco_param": 13, "reco_predictor": 13, "recogn": 19, "recognit": [7, 11, 12, 13], "recognition_predictor": [9, 19], "recognition_task": [7, 17], "recognitiondataset": [7, 17], "recognitionpredictor": [9, 13], "rectangular": 9, "reduc": [4, 10], "refer": [3, 4, 13, 15, 16, 17, 19], "regardless": 2, "region": 19, "regroup": 11, "regular": 17, "reject": 2, "rel": [8, 10, 11, 19], "relat": 8, "releas": [0, 4], "relev": 16, "religion": 2, "remov": 2, "render": [8, 19], "repo": 9, "repo_id": [9, 15], "report": 2, "repositori": [7, 9, 15], "repres": [2, 18, 19], "represent": [5, 9], "request": [2, 15], "requir": [4, 10, 18], "research": 5, "residu": 9, "resiz": [10, 19], "resnet": 9, "resnet18": [9, 15], "resnet31": 9, "resnet34": 9, "resnet50": [9, 15], "resolv": 8, "resolve_block": 19, "resolve_lin": 19, "resourc": 17, "respect": 2, "rest": [3, 10, 11], "restrict": 14, "result": [3, 7, 8, 12, 15, 18, 19], "return": 19, "reusabl": 19, "review": 2, "rgb": [8, 10], "rgb_mode": 8, "rgb_output": 8, "right": [2, 9, 11], "roboflow": 1, "robust": [5, 7], "root": 7, "rotat": [7, 8, 9, 10, 11, 12, 13, 17, 19], "run": [3, 4, 9], "same": [3, 8, 11, 17, 18, 19], "sampl": [7, 9, 17, 19], "sample_transform": 7, "sanjin": 1, "sar": [5, 9], "sar_resnet31": [9, 19], "satur": 10, "save": [9, 17], "scale": [8, 9, 10, 11], "scale_rang": 10, "scan": [5, 7], "scene": [5, 7, 9], "score": [8, 11], "script": [3, 17], "seamless": 5, "seamlessli": [5, 19], "search": [1, 9], "searchabl": 12, "sec": 19, "second": 19, "section": [1, 13, 15, 16, 18, 19], "secur": [2, 14], "see": [2, 3], "seen": 19, "segment": [5, 9, 19], "self": 19, "semant": [5, 9], "send": 19, "sens": 11, "sensit": 17, "separ": 19, "sequenc": [5, 7, 8, 9, 11, 19], "sequenti": [10, 19], "seri": 2, "seriou": 2, "set": [2, 4, 7, 9, 11, 14, 16, 19], "set_global_polici": 18, "sever": [8, 10, 19], "sex": 2, "sexual": 2, "shade": 10, "shape": [5, 8, 9, 10, 11, 19], "share": [14, 17], "shift": 10, "shm": 14, "should": [3, 7, 8, 10, 11], "show": [5, 8, 9, 11, 13, 15, 16], "showcas": [3, 12], "shuffl": [7, 10], "side": 11, "signatur": 8, "signific": 17, "simpl": [5, 9, 18], "simpler": 9, "sinc": [7, 17], "singl": [2, 3, 5, 7], "single_img_doc": 18, "size": [2, 7, 8, 10, 16, 19], "skew": 19, "slack": 3, "slightli": 9, "small": [3, 9, 19], "smallest": 8, "snapshot_download": 9, "snippet": 19, "so": [3, 4, 7, 9, 15, 17], "social": 2, "socio": 2, "some": [1, 4, 12, 15, 17], "someth": 3, "somewher": 3, "sort": 2, "sourc": [1, 7, 8, 9, 10, 11, 15], "space": [2, 19], "span": 19, "spanish": 7, "spatial": [5, 7, 8], "specif": [3, 4, 11, 13, 17, 19], "specifi": [2, 7, 8], "speed": [5, 9, 19], "sphinx": 3, "sroie": [5, 7, 17], "stabl": 4, "stackoverflow": 3, "stage": 5, "standalon": 12, "standard": 10, "start": 7, "state": [1, 5, 11, 16], "static": 11, "statist": 1, "statu": 2, "std": [10, 13], "step": 14, "still": 19, "str": [7, 8, 9, 10, 11], "straight": [7, 9, 17, 19], "straighten": 19, "straighten_pag": [9, 13, 19], "straigten_pag": 13, "stream": 8, "street": [5, 7], "strict": 4, "strictli": 11, "string": [7, 8, 11, 19], "strive": 4, "strong": [5, 9], "structur": [18, 19], "subset": [7, 19], "suggest": [3, 15], "sum": 11, "summari": 11, "support": [4, 13, 16, 18, 19], "sustain": 2, "svhn": [5, 7, 17], "svt": [7, 17], "swedish": 7, "symmetr": [9, 10, 19], "symmetric_pad": [9, 10, 19], "synthet": 5, "synthtext": [5, 7, 17], "system": 19, "t": [3, 7, 13, 18, 19], "tabl": [15, 16, 17], "take": [2, 7, 19], "target": [7, 8, 10, 11, 17], "target_s": 7, "task": [5, 7, 9, 15, 17, 19], "task2": 7, "team": 4, "techminde": 4, "templat": [3, 5], "tensor": [7, 8, 10, 19], "tensorflow": [4, 5, 8, 9, 10, 13, 15, 18, 19], "tensorspec": 18, "term": 2, "test": [7, 17], "test_set": 7, "text": [1, 7, 8, 9, 11, 17], "text_output": 19, "textmatch": 11, "textnet": 9, "textnet_bas": 9, "textnet_smal": 9, "textnet_tini": 9, "textract": [5, 19], "textstylebrush": [5, 7], "textual": [5, 7, 8, 9, 19], "tf": [4, 8, 9, 10, 15, 18], "than": [3, 11, 15], "thank": 3, "thei": [2, 11], "them": [7, 19], "thi": [1, 2, 3, 4, 6, 7, 10, 11, 13, 14, 15, 17, 18, 19], "thing": [18, 19], "third": 4, "those": [2, 8, 19], "threaten": 2, "threshold": 19, "through": [2, 10, 16, 17], "tilman": 15, "time": [1, 2, 5, 9, 11, 17], "tini": 9, "titl": [8, 19], "tm": 19, "tmp": 14, "togeth": [3, 8], "tograi": 10, "tool": [1, 17], "top": [11, 18, 19], "topic": 3, "torch": [4, 10, 13, 15, 18], "torchvis": 10, "total": 13, "toward": [2, 4], "train": [3, 7, 9, 10, 15, 16, 17, 18, 19], "train_it": [7, 17], "train_load": [7, 17], "train_pytorch": 15, "train_set": [7, 17], "train_tensorflow": 15, "trainabl": [5, 9], "tranform": 10, "transcrib": 19, "transfer": [5, 7], "transfo": 10, "transform": [5, 7, 9], "translat": 2, "troll": 2, "true": [7, 8, 9, 10, 11, 13, 14, 15, 17, 18, 19], "truth": 11, "tune": [1, 18], "tupl": [7, 8, 10, 11], "two": [8, 14], "txt": 7, "type": [8, 11, 15, 18, 19], "typic": 19, "u": [2, 3], "ucsd": 7, "udac": 3, "uint8": [8, 9, 11, 19], "ukrainian": 7, "unaccept": 2, "underli": [17, 19], "underneath": 8, "understand": [5, 7, 19], "uniform": [9, 10], "uniformli": 10, "uninterrupt": [8, 19], "union": 11, "unit": 1, "unittest": 3, "unlock": 8, "unoffici": 9, "unprofession": 2, "unsolicit": 2, "unsupervis": 5, "unwelcom": 2, "up": [9, 19], "updat": 11, "upgrad": 3, "upper": [7, 10], "uppercas": 17, "url": 8, "us": [2, 3, 4, 7, 9, 11, 12, 13, 14, 15, 16, 19], "usabl": 19, "usag": [14, 18], "use_polygon": [7, 11, 17], "useabl": 19, "user": [5, 8, 12], "utf": 19, "util": 18, "v1": 15, "v3": [9, 15, 19], "valid": 17, "valu": [3, 8, 10, 19], "valuabl": 5, "variabl": 14, "varieti": 7, "veri": 9, "verma": 1, "version": [2, 3, 4, 18, 19], "vgg": 9, "vgg16": 15, "vgg16_bn_r": 9, "via": 2, "video": 1, "vietnames": 7, "view": [5, 7], "viewpoint": 2, "violat": 2, "visibl": 2, "vision": [5, 7, 9], "visiondataset": 7, "visiontransform": 9, "visual": [4, 5, 16], "visualize_pag": 11, "vit_": 9, "vit_b": 9, "vitstr": [5, 9, 18], "vitstr_bas": [9, 19], "vitstr_smal": [9, 13, 18, 19], "viz": 4, "vocab": [13, 15, 17, 18, 19], "vocabulari": [7, 13, 15], "w": [8, 9, 10, 11], "w3": 19, "wa": 2, "wai": [2, 5, 17], "want": [3, 18, 19], "warmup": 19, "wasn": 3, "we": [1, 2, 3, 4, 5, 8, 10, 13, 15, 17, 18, 19], "weasyprint": 8, "web": [3, 8], "websit": 7, "welcom": 2, "well": [1, 2, 18], "were": [2, 8, 19], "what": [1, 2], "when": [2, 3, 9], "whenev": 3, "where": [3, 8, 10, 11], "whether": [3, 7, 8, 10, 11, 17, 19], "which": [2, 9, 14, 16, 17, 19], "whichev": 4, "while": [10, 19], "why": 2, "width": [8, 10], "wiki": 2, "wildreceipt": [5, 7, 17], "window": [9, 11], "wish": 3, "within": 2, "without": [2, 7, 9], "wonder": 3, "word": [5, 7, 9, 11, 19], "word_1_1": 19, "word_1_2": 19, "word_1_3": 19, "wordgener": [7, 17], "words_onli": 11, "work": [1, 13, 14, 19], "workflow": 3, "worklow": 3, "world": [11, 19], "worth": 9, "wrap": 19, "wrapper": [7, 10], "write": 14, "written": [2, 8], "www": [2, 8, 19], "x": [8, 10, 11], "x_ascend": 19, "x_descend": 19, "x_i": 11, "x_size": 19, "x_wconf": 19, "xhtml": 19, "xmax": 8, "xmin": 8, "xml": 19, "xml_bytes_str": 19, "xml_element": 19, "xml_output": 19, "xmln": 19, "y": 11, "y_i": 11, "y_j": 11, "yet": 16, "ymax": 8, "ymin": 8, "yolov8": 16, "you": [3, 4, 7, 8, 9, 13, 14, 15, 16, 17, 18, 19], "your": [3, 5, 8, 11, 19], "yoursit": 8, "yugesh": 1, "zero": [10, 11], "zoo": 13, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 7, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 7, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": 7, "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 7, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 7, "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 7, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 7, "\u00e4\u00f6\u00e4\u00f6": 7, "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 7, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": 7, "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": 7, "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": 7, "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": 7, "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": 7, "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0905": 7, "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": 7, "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": 7, "\u0950": 7, "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": 7, "\u09bd": 7, "\u09ce": 7, "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": 7}, "titles": ["Changelog", "Community resources", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.contrib", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Integrate contributions into your pipeline", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 3, "0": 0, "01": 0, "02": 0, "03": 0, "04": 0, "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 2], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 2], "2021": 0, "2022": 0, "2023": 0, "2024": 0, "21": 0, "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 2], "31": 0, "4": [0, 2], "5": 0, "6": 0, "7": 0, "8": 0, "9": 0, "advanc": 19, "approach": 19, "architectur": 19, "arg": [7, 8, 9, 10, 11], "artefact": 8, "artefactdetect": 16, "attribut": 2, "avail": [16, 17, 19], "aw": 14, "ban": 2, "block": 8, "bug": 3, "changelog": 0, "choos": [17, 19], "classif": [9, 13, 15], "code": [2, 3], "codebas": 3, "commit": 3, "commun": [1, 15], "compos": 10, "conda": 4, "conduct": 2, "connect": 3, "continu": 3, "contrib": 6, "contribut": [3, 6, 16], "contributor": 2, "convent": 15, "correct": 2, "coven": 2, "custom": [7, 13], "data": 17, "dataload": 7, "dataset": [5, 7, 17], "detect": [5, 9, 15, 17, 19], "develop": 3, "do": 19, "doctr": [3, 5, 6, 7, 8, 9, 10, 11, 12], "document": [3, 5, 8], "end": 19, "enforc": 2, "evalu": 11, "export": 18, "factori": 9, "featur": [3, 5], "feedback": 3, "file": 8, "from": 15, "gener": [7, 17], "git": 4, "guidelin": 2, "half": 18, "hub": 15, "huggingfac": 15, "i": 19, "infer": 18, "instal": [3, 4], "integr": [3, 16], "io": 8, "lambda": 14, "let": 3, "line": 8, "linux": 4, "load": [13, 15, 17], "loader": 7, "main": 5, "mode": 3, "model": [5, 9, 13, 15, 18, 19], "modifi": 3, "modul": [6, 16], "name": 15, "notebook": 12, "object": 17, "ocr": [17, 19], "onli": 4, "onnx": 18, "optim": 18, "option": 19, "orient": 13, "our": 2, "output": 19, "own": [13, 17], "packag": 4, "page": 8, "perman": 2, "pipelin": 16, "pledg": 2, "precis": 18, "predictor": 19, "prepar": 18, "prerequisit": 4, "pretrain": 15, "push": 15, "python": 4, "qualiti": 3, "question": 3, "read": 8, "readi": 17, "recognit": [5, 9, 15, 17, 19], "report": 3, "request": 3, "resourc": 1, "respons": 2, "return": [7, 8, 9, 11], "right": 19, "scope": 2, "share": 15, "should": 19, "stage": 19, "standard": 2, "structur": [3, 8], "style": 3, "support": [5, 6, 7, 10], "synthet": [7, 17], "task": 11, "temporari": 2, "test": 3, "text": [5, 19], "train": 13, "transform": 10, "two": 19, "unit": 3, "us": [17, 18], "util": 11, "v0": 0, "verif": 3, "via": 4, "visual": 11, "vocab": 7, "warn": 2, "what": 19, "word": 8, "your": [13, 15, 16, 17, 18], "zoo": [5, 9]}}) \ No newline at end of file diff --git a/v0.3.0/transforms.html b/v0.3.0/transforms.html deleted file mode 100644 index 85e94d8a76..0000000000 --- a/v0.3.0/transforms.html +++ /dev/null @@ -1,684 +0,0 @@ - - - - - - - - - - - - - doctr.transforms - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    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:

    -
    -
    -class doctr.transforms.Resize(output_size: Tuple[int, int], method: str = 'bilinear', preserve_aspect_ratio: bool = False, symmetric_pad: bool = False)[source]
    -

    Resizes a tensor to a target size

    -
    -
    Example::
    >>> from doctr.transforms import Resize
    ->>> import tensorflow as tf
    ->>> transfo = Resize((32, 32))
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • output_size – expected output size

    • -
    • method – interpolation method

    • -
    • preserve_aspect_ratio – if True, preserve aspect ratio and pad the rest with zeros

    • -
    • symmetric_pad – if True while preserving aspect ratio, the padding will be done symmetrically

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.Normalize(mean: Tuple[float, float, float], std: Tuple[float, float, float])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • mean – average value per channel

    • -
    • std – standard deviation per channel

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.LambdaTransformation(fn: Callable[[Tensor], Tensor])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import LambdaTransformation
    ->>> import tensorflow as tf
    ->>> transfo = LambdaTransformation(lambda x: x/ 255.)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    fn – the function to be applied to the input tensor

    -
    -
    -
    - -
    -
    -class doctr.transforms.ToGray[source]
    -

    Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ToGray()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    - -
    -
    -class doctr.transforms.ColorInversion(min_val: float = 0.5)[source]
    -

    Applies the following tranformation to a tensor (image or batch of images): -convert to grayscale, colorize (shift 0-values randomly), and then invert colors

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ColorInversion(min_val=0.6)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    min_val – range [min_val, 1] to colorize RGB pixels

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomBrightness(max_delta: float = 0.3)[source]
    -

    Randomly adjust brightness of a tensor (batch of images or image) by adding a delta -to all pixels

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Brightness()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    • -
    • p – probability to apply transformation

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomContrast(delta: float = 0.3)[source]
    -

    Randomly adjust contrast of a tensor (batch of images or image) by adjusting -each pixel: (img - mean) * contrast_factor + mean.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Contrast()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomSaturation(delta: float = 0.5)[source]
    -

    Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and -increasing saturation by a factor.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Saturation()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomHue(max_delta: float = 0.3)[source]
    -

    Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Hue()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomGamma(min_gamma: float = 0.5, max_gamma: float = 1.5, min_gain: float = 0.8, max_gain: float = 1.2)[source]
    -

    randomly performs gamma correction for a tensor (batch of images or image)

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Gamma()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_gamma – non-negative real number, lower bound for gamma param

    • -
    • max_gamma – non-negative real number, upper bound for gamma

    • -
    • min_gain – lower bound for constant multiplier

    • -
    • max_gain – upper bound for constant multiplier

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomJpegQuality(min_quality: int = 60, max_quality: int = 100)[source]
    -

    Randomly adjust jpeg quality of a 3 dimensional RGB image

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = JpegQuality()
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_quality – int between [0, 100]

    • -
    • max_quality – int between [0, 100]

    • -
    -
    -
    -
    - -
    -
    -

    Composing transformations

    -

    It is common to require several transformations to be performed consecutively.

    -
    -
    -class doctr.transforms.Compose(transforms: List[Callable[[Any], Any]])[source]
    -

    Implements a wrapper that will apply transformations sequentially

    -
    -
    Example::
    >>> from doctr.transforms import Compose, Resize
    ->>> import tensorflow as tf
    ->>> transfos = Compose([Resize((32, 32))])
    ->>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformation modules

    -
    -
    -
    - -
    -
    -class doctr.transforms.OneOf(transforms: List[Callable[[Any], Any]])[source]
    -

    Randomly apply one of the input transformations

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = OneOf([JpegQuality(), Gamma()])
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformations, one only will be picked

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomApply(transform: Callable[[Any], Any], p: float = 0.5)[source]
    -

    Apply with a probability p the input transformation

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = RandomApply(Gamma(), p=.5)
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • transform – transformation to apply

    • -
    • p – probability to apply

    • -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.0/using_doctr/custom_models_training.html b/v0.3.0/using_doctr/custom_models_training.html index df39d8d568..b714c1f971 100644 --- a/v0.3.0/using_doctr/custom_models_training.html +++ b/v0.3.0/using_doctr/custom_models_training.html @@ -14,7 +14,7 @@ - + Train your own model - docTR documentation @@ -619,7 +619,7 @@

    Loading your custom trained orientation classification model - + diff --git a/v0.3.0/using_doctr/running_on_aws.html b/v0.3.0/using_doctr/running_on_aws.html index 16ceaca7a1..808ea541cd 100644 --- a/v0.3.0/using_doctr/running_on_aws.html +++ b/v0.3.0/using_doctr/running_on_aws.html @@ -14,7 +14,7 @@ - + AWS Lambda - docTR documentation @@ -362,7 +362,7 @@

    AWS Lambda - + diff --git a/v0.3.0/using_doctr/sharing_models.html b/v0.3.0/using_doctr/sharing_models.html index d76b4017f4..c9e978400a 100644 --- a/v0.3.0/using_doctr/sharing_models.html +++ b/v0.3.0/using_doctr/sharing_models.html @@ -14,7 +14,7 @@ - + Share your model with the community - docTR documentation @@ -544,7 +544,7 @@

    Recognition - + diff --git a/v0.3.0/using_doctr/using_contrib_modules.html b/v0.3.0/using_doctr/using_contrib_modules.html index 50598dae5d..0c5fffdf9f 100644 --- a/v0.3.0/using_doctr/using_contrib_modules.html +++ b/v0.3.0/using_doctr/using_contrib_modules.html @@ -14,7 +14,7 @@ - + Integrate contributions into your pipeline - docTR documentation @@ -415,7 +415,7 @@

    ArtefactDetection - + diff --git a/v0.3.0/using_doctr/using_datasets.html b/v0.3.0/using_doctr/using_datasets.html index 460476dbbf..8a7d4f0a64 100644 --- a/v0.3.0/using_doctr/using_datasets.html +++ b/v0.3.0/using_doctr/using_datasets.html @@ -14,7 +14,7 @@ - + Choose a ready to use dataset - docTR documentation @@ -642,7 +642,7 @@

    Data Loading - + diff --git a/v0.3.0/using_doctr/using_model_export.html b/v0.3.0/using_doctr/using_model_export.html index 6124c00ebe..6790dd0642 100644 --- a/v0.3.0/using_doctr/using_model_export.html +++ b/v0.3.0/using_doctr/using_model_export.html @@ -14,7 +14,7 @@ - + Preparing your model for inference - docTR documentation @@ -467,7 +467,7 @@

    Using your ONNX exported model - + diff --git a/v0.3.0/using_doctr/using_models.html b/v0.3.0/using_doctr/using_models.html index 61f1f5ab7a..9ead8498e1 100644 --- a/v0.3.0/using_doctr/using_models.html +++ b/v0.3.0/using_doctr/using_models.html @@ -14,7 +14,7 @@ - + Choosing the right model - docTR documentation @@ -1253,7 +1253,7 @@

    Advanced options - + diff --git a/v0.3.0/utils.html b/v0.3.0/utils.html deleted file mode 100644 index e2f223f06a..0000000000 --- a/v0.3.0/utils.html +++ /dev/null @@ -1,574 +0,0 @@ - - - - - - - - - - - - - doctr.utils - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.utils

    -

    This module regroups non-core features that are complementary to the rest of the package.

    -
    -

    Visualization

    -

    Easy-to-use functions to make sense of your model’s predictions.

    -
    -
    -doctr.utils.visualization.visualize_page(page: Dict[str, Any], image: ndarray, words_only: bool = True, display_artefacts: bool = True, scale: float = 10, interactive: bool = True, add_labels: bool = True, **kwargs: Any) Figure[source]
    -

    Visualize a full page with predicted blocks, lines and words

    -
    -
    Example::
    >>> import numpy as np
    ->>> import matplotlib.pyplot as plt
    ->>> from doctr.utils.visualization import visualize_page
    ->>> from doctr.models import ocr_db_crnn
    ->>> model = ocr_db_crnn(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([[input_page]])
    ->>> visualize_page(out[0].pages[0].export(), input_page)
    ->>> plt.show()
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • page – the exported Page of a Document

    • -
    • image – np array of the page, needs to have the same shape than page[‘dimensions’]

    • -
    • words_only – whether only words should be displayed

    • -
    • display_artefacts – whether artefacts should be displayed

    • -
    • scale – figsize of the largest windows side

    • -
    • interactive – whether the plot should be interactive

    • -
    • add_labels – for static plot, adds text labels on top of bounding box

    • -
    -
    -
    -
    - -
    -
    -

    Task evaluation

    -

    Implementations of task-specific metrics to easily assess your model performances.

    -
    -
    -class doctr.utils.metrics.TextMatch[source]
    -

    Implements text match metric (word-level accuracy) for recognition task.

    -

    The raw aggregated metric is computed as follows:

    -
    -
    -\[\forall X, Y \in \mathcal{W}^N, -TextMatch(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N f_{Y_i}(X_i)\]
    -
    -

    with the indicator function \(f_{a}\) defined as:

    -
    -
    -\[\begin{split}\forall a, x \in \mathcal{W}, -f_a(x) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } x = a \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{W}\) is the set of all possible character sequences, -\(N\) is a strictly positive integer.

    -
    -
    Example::
    >>> from doctr.utils import TextMatch
    ->>> metric = TextMatch()
    ->>> metric.update(['Hello', 'world'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    -summary() Dict[str, float][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a dictionary with the exact match score for the raw data, its lower-case counterpart, its unidecode -counterpart and its lower-case unidecode counterpart

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.LocalizationConfusion(iou_thresh: float = 0.5, rotated_bbox: bool = False, mask_shape: Tuple[int, int] = (1024, 1024))[source]
    -

    Implements common confusion metrics and mean IoU for localization evaluation.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall Y \in \mathcal{B}^N, \forall X \in \mathcal{B}^M, \\ -Recall(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -Precision(X, Y) = \frac{1}{M} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -meanIoU(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(X_i, Y_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(g_{X}\) defined as:

    -
    -
    -\[\begin{split}\forall y \in \mathcal{B}, -g_X(y) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } y\mbox{ has been assigned to any }(X_i)_i\mbox{ with an }IoU \geq 0.5 \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import LocalizationConfusion
    ->>> metric = LocalizationConfusion(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]))
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[float | None, float | None, float | None][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall, precision and meanIoU scores

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.OCRMetric(iou_thresh: float = 0.5, rotated_bbox: bool = False, mask_shape: Tuple[int, int] = (1024, 1024))[source]
    -

    Implements end-to-end OCR metric.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall (B, L) \in \mathcal{B}^N \times \mathcal{L}^N, -\forall (\hat{B}, \hat{L}) \in \mathcal{B}^M \times \mathcal{L}^M, \\ -Recall(B, \hat{B}, L, \hat{L}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -Precision(B, \hat{B}, L, \hat{L}) = \frac{1}{M} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(h_{B, L}\) defined as:

    -
    -
    -\[\begin{split}\forall (b, l) \in \mathcal{B} \times \mathcal{L}, -h_{B,L}(b, l) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ - & IoU \geq 0.5 \mbox{ and that for this assignment, } l = L_j\\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(\mathcal{L}\) is the set of possible character sequences, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import OCRMetric
    ->>> metric = OCRMetric(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]),
    -['hello'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[Dict[str, float | None], Dict[str, float | None], float | None][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall & precision for each string comparison flexibility and the mean IoU

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.3.1/_modules/doctr/datasets/cord.html b/v0.3.1/_modules/doctr/datasets/cord.html index f98ee6901c..55b0584830 100644 --- a/v0.3.1/_modules/doctr/datasets/cord.html +++ b/v0.3.1/_modules/doctr/datasets/cord.html @@ -13,7 +13,7 @@ - + doctr.datasets.cord - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.cord

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
     from .datasets import VisionDataset
    -from doctr.utils.geometry import fit_rbbox
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['CORD']
    +__all__ = ["CORD"]
     
     
     
    -[docs] +[docs] class CORD(VisionDataset): """CORD dataset from `"CORD: A Consolidated Receipt Dataset forPost-OCR Parsing" <https://openreview.net/pdf?id=SJl3z659UH>`_. - Example:: - >>> from doctr.datasets import CORD - >>> train_set = CORD(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/cord-grid.png&src=0 + :align: center + + >>> from doctr.datasets import CORD + >>> train_set = CORD(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - TRAIN = ('https://github.com/mindee/doctr/releases/download/v0.1.1/cord_train.zip', - '45f9dc77f126490f3e52d7cb4f70ef3c57e649ea86d19d862a2757c9c455d7f8') - TEST = ('https://github.com/mindee/doctr/releases/download/v0.1.1/cord_test.zip', - '8c895e3d6f7e1161c5b7245e3723ce15c04d84be89eaa6093949b75a66fb3c58') + TRAIN = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/cord_train.zip&src=0", + "45f9dc77f126490f3e52d7cb4f70ef3c57e649ea86d19d862a2757c9c455d7f8", + "cord_train.zip", + ) + + TEST = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/cord_test.zip&src=0", + "8c895e3d6f7e1161c5b7245e3723ce15c04d84be89eaa6093949b75a66fb3c58", + "cord_test.zip", + ) def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + url, sha256, name = self.TRAIN if train else self.TEST + super().__init__( + url, + name, + sha256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - url, sha256 = self.TRAIN if train else self.TEST - super().__init__(url, None, sha256, True, **kwargs) - - # # List images - self.root = os.path.join(self._root, 'image') - self.data: List[Tuple[str, Dict[str, Any]]] = [] + # List images + tmp_root = os.path.join(self.root, "image") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] self.train = train - self.sample_transforms = sample_transforms - for img_path in os.listdir(self.root): + np_dtype = np.float32 + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking CORD", total=len(os.listdir(tmp_root))): # File existence check - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem _targets = [] - with open(os.path.join(self._root, 'json', f"{stem}.json"), 'rb') as f: + with open(os.path.join(self.root, "json", f"{stem}.json"), "rb") as f: label = json.load(f) for line in label["valid_line"]: for word in line["words"]: if len(word["text"]) > 0: x = word["quad"]["x1"], word["quad"]["x2"], word["quad"]["x3"], word["quad"]["x4"] y = word["quad"]["y1"], word["quad"]["y2"], word["quad"]["y3"], word["quad"]["y4"] - if rotated_bbox: - box = list(fit_rbbox(np.array([ - [x[0], y[0]], - [x[1], y[1]], - [x[2], y[2]], - [x[3], y[3]], - ], dtype=np.float32))) + box: Union[List[float], np.ndarray] + if use_polygons: + # (x, y) coordinates of top left, top right, bottom right, bottom left corners + box = np.array( + [ + [x[0], y[0]], + [x[1], y[1]], + [x[2], y[2]], + [x[3], y[3]], + ], + dtype=np_dtype, + ) else: - # Reduce 8 coords to 4 + # Reduce 8 coords to 4 -> xmin, ymin, xmax, ymax box = [min(x), min(y), max(x), max(y)] - _targets.append((word['text'], box)) + _targets.append((word["text"], box)) text_targets, box_targets = zip(*_targets) - self.data.append(( - img_path, - dict(boxes=np.asarray(box_targets, dtype=int).clip(min=0), labels=text_targets) - )) + if recognition_task: + crops = crop_bboxes_from_image( + img_path=os.path.join(tmp_root, img_path), geoms=np.asarray(box_targets, dtype=int).clip(min=0) + ) + for crop, label in zip(crops, list(text_targets)): + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, np.asarray(box_targets, dtype=int).clip(min=0))) + else: + self.data.append(( + img_path, + dict(boxes=np.asarray(box_targets, dtype=int).clip(min=0), labels=list(text_targets)), + )) + + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -397,8 +461,8 @@

    Source code for doctr.datasets.cord

           
         
       
    -
    - + + diff --git a/v0.3.1/_modules/doctr/datasets/core.html b/v0.3.1/_modules/doctr/datasets/core.html deleted file mode 100644 index b3dcc29ff9..0000000000 --- a/v0.3.1/_modules/doctr/datasets/core.html +++ /dev/null @@ -1,417 +0,0 @@ - - - - - - - - - - - - doctr.datasets.core - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.core

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from pathlib import Path
    -from zipfile import ZipFile
    -from typing import List, Any, Optional, Tuple
    -import tensorflow as tf
    -
    -from doctr.models.utils import download_from_url
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset:
    -
    -    data: List[Any] = []
    -
    -    def __len__(self):
    -        return len(self.data)
    -
    -    def __getitem__(
    -        self,
    -        index: int
    -    ) -> Tuple[tf.Tensor, Any]:
    -
    -        img_name, target = self.data[index]
    -        # Read image
    -        img = tf.io.read_file(os.path.join(self.root, img_name))
    -        img = tf.image.decode_jpeg(img, channels=3)
    -        if self.sample_transforms is not None:
    -            img = self.sample_transforms(img)
    -
    -        return img, target
    -
    -    def extra_repr(self) -> str:
    -        return ""
    -
    -    def __repr__(self) -> str:
    -        return f"{self.__class__.__name__}({self.extra_repr()})"
    -
    -    @staticmethod
    -    def collate_fn(samples: List[Tuple[tf.Tensor, Any]]) -> Tuple[tf.Tensor, List[Any]]:
    -
    -        images, targets = zip(*samples)
    -        images = tf.stack(images, axis=0)
    -
    -        return images, list(targets)
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset): - """Implements an abstract dataset - - Args: - url: URL of the dataset - file_name: name of the file once downloaded - file_hash: expected SHA256 of the file - extract_archive: whether the downloaded file is an archive to be extracted - download: whether the dataset should be downloaded if not present on disk - overwrite: whether the archive should be re-extracted - """ - - def __init__( - self, - url: str, - file_name: Optional[str] = None, - file_hash: Optional[str] = None, - extract_archive: bool = False, - download: bool = False, - overwrite: bool = False, - ) -> None: - - dataset_cache = os.path.join(os.path.expanduser('~'), '.cache', 'doctr', 'datasets') - - file_name = file_name if isinstance(file_name, str) else os.path.basename(url) - # Download the file if not present - archive_path = os.path.join(dataset_cache, file_name) - - if not os.path.exists(archive_path) and not download: - raise ValueError("the dataset needs to be downloaded first with download=True") - - archive_path = download_from_url(url, file_name, file_hash, cache_subdir='datasets') - - # Extract the archive - if extract_archive: - archive_path = Path(archive_path) - dataset_path = archive_path.parent.joinpath(archive_path.stem) - if not dataset_path.is_dir() or overwrite: - with ZipFile(archive_path, 'r') as f: - f.extractall(path=dataset_path) - - # List images - self._root = dataset_path if extract_archive else archive_path - self.data: List[Any] = []
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.1/_modules/doctr/datasets/datasets/tensorflow.html b/v0.3.1/_modules/doctr/datasets/datasets/tensorflow.html deleted file mode 100644 index a236abd9fe..0000000000 --- a/v0.3.1/_modules/doctr/datasets/datasets/tensorflow.html +++ /dev/null @@ -1,356 +0,0 @@ - - - - - - - - - - - - doctr.datasets.datasets.tensorflow - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.datasets.tensorflow

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from typing import List, Any, Tuple
    -import tensorflow as tf
    -
    -from .base import _AbstractDataset, _VisionDataset
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset(_AbstractDataset):
    -
    -    def _read_sample(self, index: int) -> Tuple[tf.Tensor, Any]:
    -        img_name, target = self.data[index]
    -        # Read image
    -        img = tf.io.read_file(os.path.join(self.root, img_name))
    -        img = tf.image.decode_jpeg(img, channels=3)
    -
    -        return img, target
    -
    -    @staticmethod
    -    def collate_fn(samples: List[Tuple[tf.Tensor, Any]]) -> Tuple[tf.Tensor, List[Any]]:
    -
    -        images, targets = zip(*samples)
    -        images = tf.stack(images, axis=0)
    -
    -        return images, list(targets)
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset, _VisionDataset): - pass
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.1/_modules/doctr/datasets/detection.html b/v0.3.1/_modules/doctr/datasets/detection.html index 739563e466..718001e4cf 100644 --- a/v0.3.1/_modules/doctr/datasets/detection.html +++ b/v0.3.1/_modules/doctr/datasets/detection.html @@ -13,7 +13,7 @@ - + doctr.datasets.detection - docTR documentation @@ -430,7 +430,7 @@

    Source code for doctr.datasets.detection

         
       
    - + diff --git a/v0.3.1/_modules/doctr/datasets/doc_artefacts.html b/v0.3.1/_modules/doctr/datasets/doc_artefacts.html index 3313ae4660..94c32aaa0f 100644 --- a/v0.3.1/_modules/doctr/datasets/doc_artefacts.html +++ b/v0.3.1/_modules/doctr/datasets/doc_artefacts.html @@ -13,7 +13,7 @@ - + doctr.datasets.doc_artefacts - docTR documentation @@ -414,7 +414,7 @@

    Source code for doctr.datasets.doc_artefacts

       
    - + diff --git a/v0.3.1/_modules/doctr/datasets/funsd.html b/v0.3.1/_modules/doctr/datasets/funsd.html index 35d7ad4cf5..f08612f9fa 100644 --- a/v0.3.1/_modules/doctr/datasets/funsd.html +++ b/v0.3.1/_modules/doctr/datasets/funsd.html @@ -13,7 +13,7 @@ - + doctr.datasets.funsd - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.funsd

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
     from .datasets import VisionDataset
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['FUNSD']
    +__all__ = ["FUNSD"]
     
     
     
    -[docs] +[docs] class FUNSD(VisionDataset): """FUNSD dataset from `"FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents" <https://arxiv.org/pdf/1905.13538.pdf>`_. - Example:: - >>> from doctr.datasets import FUNSD - >>> train_set = FUNSD(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/funsd-grid.png&src=0 + :align: center + + >>> from doctr.datasets import FUNSD + >>> train_set = FUNSD(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - URL = 'https://guillaumejaume.github.io/FUNSD/dataset.zip' - SHA256 = 'c31735649e4f441bcbb4fd0f379574f7520b42286e80b01d80b445649d54761f' - FILE_NAME = 'funsd.zip' + URL = "https://guillaumejaume.github.io/FUNSD/dataset.zip" + SHA256 = "c31735649e4f441bcbb4fd0f379574f7520b42286e80b01d80b445649d54761f" + FILE_NAME = "funsd.zip" def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + super().__init__( + self.URL, + self.FILE_NAME, + self.SHA256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - super().__init__(self.URL, self.FILE_NAME, self.SHA256, True, **kwargs) self.train = train - self.sample_transforms = sample_transforms + np_dtype = np.float32 # Use the subset - subfolder = os.path.join('dataset', 'training_data' if train else 'testing_data') + subfolder = os.path.join("dataset", "training_data" if train else "testing_data") # # List images - self.root = os.path.join(self._root, subfolder, 'images') - self.data: List[Tuple[str, Dict[str, Any]]] = [] - for img_path in os.listdir(self.root): + tmp_root = os.path.join(self.root, subfolder, "images") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking FUNSD", total=len(os.listdir(tmp_root))): # File existence check - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem - with open(os.path.join(self._root, subfolder, 'annotations', f"{stem}.json"), 'rb') as f: + with open(os.path.join(self.root, subfolder, "annotations", f"{stem}.json"), "rb") as f: data = json.load(f) - _targets = [(word['text'], word['box']) for block in data['form'] - for word in block['words'] if len(word['text']) > 0] + _targets = [ + (word["text"], word["box"]) + for block in data["form"] + for word in block["words"] + if len(word["text"]) > 0 + ] text_targets, box_targets = zip(*_targets) - if rotated_bbox: - # box_targets: xmin, ymin, xmax, ymax -> x, y, w, h, alpha = 0 - box_targets = [ + if use_polygons: + # xmin, ymin, xmax, ymax -> (x, y) coordinates of top left, top right, bottom right, bottom left corners + box_targets = [ # type: ignore[assignment] [ - (box[0] + box[2]) / 2, (box[1] + box[3]) / 2, box[2] - box[0], box[3] - box[1], 0 - ] for box in box_targets + [box[0], box[1]], + [box[2], box[1]], + [box[2], box[3]], + [box[0], box[3]], + ] + for box in box_targets ] - self.data.append((img_path, dict(boxes=np.asarray(box_targets, dtype=int), labels=text_targets))) + if recognition_task: + crops = crop_bboxes_from_image( + img_path=os.path.join(tmp_root, img_path), geoms=np.asarray(box_targets, dtype=np_dtype) + ) + for crop, label in zip(crops, list(text_targets)): + # filter labels with unknown characters + if not any(char in label for char in ["☑", "☐", "\uf703", "\uf702"]): + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, np.asarray(box_targets, dtype=np_dtype))) + else: + self.data.append(( + img_path, + dict(boxes=np.asarray(box_targets, dtype=np_dtype), labels=list(text_targets)), + )) + + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -386,8 +453,8 @@

    Source code for doctr.datasets.funsd

           
         
       
    -
    - + + diff --git a/v0.3.1/_modules/doctr/datasets/generator/tensorflow.html b/v0.3.1/_modules/doctr/datasets/generator/tensorflow.html index 9f562582d9..a3e619f720 100644 --- a/v0.3.1/_modules/doctr/datasets/generator/tensorflow.html +++ b/v0.3.1/_modules/doctr/datasets/generator/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.datasets.generator.tensorflow - docTR documentation @@ -395,7 +395,7 @@

    Source code for doctr.datasets.generator.tensorflow

    - + diff --git a/v0.3.1/_modules/doctr/datasets/ic03.html b/v0.3.1/_modules/doctr/datasets/ic03.html index 3d221d07de..60e54a8a4b 100644 --- a/v0.3.1/_modules/doctr/datasets/ic03.html +++ b/v0.3.1/_modules/doctr/datasets/ic03.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic03 - docTR documentation @@ -468,7 +468,7 @@

    Source code for doctr.datasets.ic03

         
       
    - + diff --git a/v0.3.1/_modules/doctr/datasets/ic13.html b/v0.3.1/_modules/doctr/datasets/ic13.html index 8137e08e9f..219c98dcd1 100644 --- a/v0.3.1/_modules/doctr/datasets/ic13.html +++ b/v0.3.1/_modules/doctr/datasets/ic13.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic13 - docTR documentation @@ -440,7 +440,7 @@

    Source code for doctr.datasets.ic13

         
       
    - + diff --git a/v0.3.1/_modules/doctr/datasets/iiit5k.html b/v0.3.1/_modules/doctr/datasets/iiit5k.html index 1fc8ecfb27..b49c80fe18 100644 --- a/v0.3.1/_modules/doctr/datasets/iiit5k.html +++ b/v0.3.1/_modules/doctr/datasets/iiit5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiit5k - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.datasets.iiit5k

         
       
    - + diff --git a/v0.3.1/_modules/doctr/datasets/iiithws.html b/v0.3.1/_modules/doctr/datasets/iiithws.html index 07f5b13685..f7220afbc7 100644 --- a/v0.3.1/_modules/doctr/datasets/iiithws.html +++ b/v0.3.1/_modules/doctr/datasets/iiithws.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiithws - docTR documentation @@ -407,7 +407,7 @@

    Source code for doctr.datasets.iiithws

         
       
    - + diff --git a/v0.3.1/_modules/doctr/datasets/imgur5k.html b/v0.3.1/_modules/doctr/datasets/imgur5k.html index 68d433ca62..51c6545db4 100644 --- a/v0.3.1/_modules/doctr/datasets/imgur5k.html +++ b/v0.3.1/_modules/doctr/datasets/imgur5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.imgur5k - docTR documentation @@ -488,7 +488,7 @@

    Source code for doctr.datasets.imgur5k

         
       
    - + diff --git a/v0.3.1/_modules/doctr/datasets/loader.html b/v0.3.1/_modules/doctr/datasets/loader.html index d32e6da298..ed80350ef0 100644 --- a/v0.3.1/_modules/doctr/datasets/loader.html +++ b/v0.3.1/_modules/doctr/datasets/loader.html @@ -13,7 +13,7 @@ - + doctr.datasets.loader - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.loader

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import math
    -import tensorflow as tf
    -import numpy as np
    -from typing import Optional
    +from typing import Callable, Optional
     
    -from .multithreading import multithread_exec
    +import numpy as np
    +import tensorflow as tf
     
     __all__ = ["DataLoader"]
     
    @@ -293,12 +314,13 @@ 

    Source code for doctr.datasets.loader

         """Collate multiple elements into batches
     
         Args:
    +    ----
             samples: list of N tuples containing M elements
     
         Returns:
    +    -------
             Tuple of M sequences contianing N elements each
         """
    -
         batch_data = zip(*samples)
     
         tf_data = tuple(tf.stack(elt, axis=0) for elt in batch_data)
    @@ -307,23 +329,23 @@ 

    Source code for doctr.datasets.loader

     
     
     
    -[docs] +[docs] class DataLoader: """Implements a dataset wrapper for fast data loading - Example:: - >>> from doctr.datasets import FUNSD, 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) + >>> 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) Args: + ---- dataset: the dataset shuffle: whether the samples should be shuffled before passing it to the iterator batch_size: number of elements in each batch drop_last: if `True`, drops the last batch if it isn't full - workers: number of workers to use for data loading + collate_fn: function to merge samples into a batch """ def __init__( @@ -332,17 +354,22 @@

    Source code for doctr.datasets.loader

             shuffle: bool = True,
             batch_size: int = 1,
             drop_last: bool = False,
    -        workers: Optional[int] = None,
    +        collate_fn: Optional[Callable] = None,
         ) -> None:
             self.dataset = dataset
             self.shuffle = shuffle
             self.batch_size = batch_size
             nb = len(self.dataset) / batch_size
             self.num_batches = math.floor(nb) if drop_last else math.ceil(nb)
    -        self.collate_fn = self.dataset.collate_fn if hasattr(self.dataset, 'collate_fn') else default_collate
    -        self.workers = workers
    +        if collate_fn is None:
    +            self.collate_fn = self.dataset.collate_fn if hasattr(self.dataset, "collate_fn") else default_collate
    +        else:
    +            self.collate_fn = collate_fn
             self.reset()
     
    +    def __len__(self) -> int:
    +        return self.num_batches
    +
         def reset(self) -> None:
             # Updates indices after each epoch
             self._num_yielded = 0
    @@ -358,9 +385,9 @@ 

    Source code for doctr.datasets.loader

             if self._num_yielded < self.num_batches:
                 # Get next indices
                 idx = self._num_yielded * self.batch_size
    -            indices = self.indices[idx: min(len(self.dataset), idx + self.batch_size)]
    +            indices = self.indices[idx : min(len(self.dataset), idx + self.batch_size)]
     
    -            samples = multithread_exec(self.dataset.__getitem__, indices, threads=self.workers)
    +            samples = list(map(self.dataset.__getitem__, indices))
     
                 batch_data = self.collate_fn(samples)
     
    @@ -401,8 +428,8 @@ 

    Source code for doctr.datasets.loader

           
         
       
    -
    - +
    + diff --git a/v0.3.1/_modules/doctr/datasets/mjsynth.html b/v0.3.1/_modules/doctr/datasets/mjsynth.html index 77bb01d523..df34e49cf9 100644 --- a/v0.3.1/_modules/doctr/datasets/mjsynth.html +++ b/v0.3.1/_modules/doctr/datasets/mjsynth.html @@ -13,7 +13,7 @@ - + doctr.datasets.mjsynth - docTR documentation @@ -438,7 +438,7 @@

    Source code for doctr.datasets.mjsynth

         
       
    - + diff --git a/v0.3.1/_modules/doctr/datasets/ocr.html b/v0.3.1/_modules/doctr/datasets/ocr.html index 11297d5952..ce1ed8b0d4 100644 --- a/v0.3.1/_modules/doctr/datasets/ocr.html +++ b/v0.3.1/_modules/doctr/datasets/ocr.html @@ -13,7 +13,7 @@ - + doctr.datasets.ocr - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.ocr

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple
     
    -from .datasets import AbstractDataset
    -from doctr.utils.geometry import fit_rbbox
    +import numpy as np
     
    +from .datasets import AbstractDataset
     
    -__all__ = ['OCRDataset']
    +__all__ = ["OCRDataset"]
     
     
     
    -[docs] +[docs] class OCRDataset(AbstractDataset): """Implements an OCR dataset + >>> from doctr.datasets import OCRDataset + >>> train_set = OCRDataset(img_folder="/path/to/images", + >>> label_file="/path/to/labels.json") + >>> img, target = train_set[0] + Args: + ---- img_folder: local path to image folder (all jpg at the root) label_file: local path to the label file - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) - **kwargs: keyword arguments from `VisionDataset`. + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + **kwargs: keyword arguments from `AbstractDataset`. """ def __init__( self, img_folder: str, label_file: str, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, **kwargs: Any, ) -> None: - - self.sample_transforms = sample_transforms - self.root = img_folder + super().__init__(img_folder, **kwargs) # List images self.data: List[Tuple[str, Dict[str, Any]]] = [] - with open(label_file, 'rb') as f: + np_dtype = np.float32 + with open(label_file, "rb") as f: data = json.load(f) - for file_dic in data: + for img_name, annotations in data.items(): # Get image path - img_name = Path(os.path.basename(file_dic["raw-archive-filepath"])).stem + '.jpg' + img_name = Path(img_name) # File existence check if not os.path.exists(os.path.join(self.root, img_name)): raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_name)}") # handle empty images - if (len(file_dic["coordinates"]) == 0 or - (len(file_dic["coordinates"]) == 1 and file_dic["coordinates"][0] == "N/A")): - self.data.append((img_name, dict(boxes=np.zeros((0, 4), dtype=np.float32), labels=[]))) + if len(annotations["typed_words"]) == 0: + self.data.append((img_name, dict(boxes=np.zeros((0, 4), dtype=np_dtype), labels=[]))) continue - is_valid: List[bool] = [] - box_targets: List[List[float]] = [] - for box in file_dic["coordinates"]: - if rotated_bbox: - x, y, w, h, alpha = fit_rbbox(np.asarray(box, dtype=np.float32)) - box = [x, y, w, h, alpha] - is_valid.append(w > 0 and h > 0) - else: - xs, ys = zip(*box) - box = [min(xs), min(ys), max(xs), max(ys)] - is_valid.append(box[0] < box[2] and box[1] < box[3]) - if is_valid[-1]: - box_targets.append(box) + # Unpack the straight boxes (xmin, ymin, xmax, ymax) + geoms = [list(map(float, obj["geometry"][:4])) for obj in annotations["typed_words"]] + if use_polygons: + # (x, y) coordinates of top left, top right, bottom right, bottom left corners + geoms = [ + [geom[:2], [geom[2], geom[1]], geom[2:], [geom[0], geom[3]]] # type: ignore[list-item] + for geom in geoms + ] + + text_targets = [obj["value"] for obj in annotations["typed_words"]] - text_targets = [word for word, _valid in zip(file_dic["string"], is_valid) if _valid] - self.data.append((img_name, dict(boxes=np.asarray(box_targets, dtype=np.float32), labels=text_targets)))
    + self.data.append((img_name, dict(boxes=np.asarray(geoms, dtype=np_dtype), labels=text_targets)))
    @@ -383,8 +402,8 @@

    Source code for doctr.datasets.ocr

           
         
       
    - - + + diff --git a/v0.3.1/_modules/doctr/datasets/recognition.html b/v0.3.1/_modules/doctr/datasets/recognition.html index 512c70c308..1754789364 100644 --- a/v0.3.1/_modules/doctr/datasets/recognition.html +++ b/v0.3.1/_modules/doctr/datasets/recognition.html @@ -13,7 +13,7 @@ - + doctr.datasets.recognition - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.datasets.recognition

         
       
    - + diff --git a/v0.3.1/_modules/doctr/datasets/sroie.html b/v0.3.1/_modules/doctr/datasets/sroie.html index 66fd4ca3e0..04cf10bda2 100644 --- a/v0.3.1/_modules/doctr/datasets/sroie.html +++ b/v0.3.1/_modules/doctr/datasets/sroie.html @@ -13,7 +13,7 @@ - + doctr.datasets.sroie - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.sroie

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import csv
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
     from .datasets import VisionDataset
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['SROIE']
    +__all__ = ["SROIE"]
     
     
     
    -[docs] +[docs] class SROIE(VisionDataset): """SROIE dataset from `"ICDAR2019 Competition on Scanned Receipt OCR and Information Extraction" <https://arxiv.org/pdf/2103.10213.pdf>`_. - Example:: - >>> from doctr.datasets import SROIE - >>> train_set = SROIE(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/sroie-grid.png&src=0 + :align: center + + >>> from doctr.datasets import SROIE + >>> train_set = SROIE(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - TRAIN = ('https://github.com/mindee/doctr/releases/download/v0.1.1/sroie2019_train_task1.zip', - 'd4fa9e60abb03500d83299c845b9c87fd9c9430d1aeac96b83c5d0bb0ab27f6f') - TEST = ('https://github.com/mindee/doctr/releases/download/v0.1.1/sroie2019_test.zip', - '41b3c746a20226fddc80d86d4b2a903d43b5be4f521dd1bbe759dbf8844745e2') + TRAIN = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/sroie2019_train_task1.zip&src=0", + "d4fa9e60abb03500d83299c845b9c87fd9c9430d1aeac96b83c5d0bb0ab27f6f", + "sroie2019_train_task1.zip", + ) + TEST = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/sroie2019_test.zip&src=0", + "41b3c746a20226fddc80d86d4b2a903d43b5be4f521dd1bbe759dbf8844745e2", + "sroie2019_test.zip", + ) def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + url, sha256, name = self.TRAIN if train else self.TEST + super().__init__( + url, + name, + sha256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - url, sha256 = self.TRAIN if train else self.TEST - super().__init__(url, None, sha256, True, **kwargs) - self.sample_transforms = sample_transforms self.train = train - if rotated_bbox: - raise NotImplementedError + tmp_root = os.path.join(self.root, "images") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] + np_dtype = np.float32 - # # List images - self.root = os.path.join(self._root, 'images') - self.data: List[Tuple[str, Dict[str, Any]]] = [] - for img_path in os.listdir(self.root): + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking SROIE", total=len(os.listdir(tmp_root))): # File existence check - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem - _targets = [] - with open(os.path.join(self._root, 'annotations', f"{stem}.txt"), encoding='latin') as f: - for row in csv.reader(f, delimiter=','): - # Safeguard for blank lines - if len(row) > 0: - # Label may contain commas - label = ",".join(row[8:]) - # Reduce 8 coords to 4 - p1_x, p1_y, p2_x, p2_y, p3_x, p3_y, p4_x, p4_y = map(int, row[:8]) - left, right = min(p1_x, p2_x, p3_x, p4_x), max(p1_x, p2_x, p3_x, p4_x) - top, bot = min(p1_y, p2_y, p3_y, p4_y), max(p1_y, p2_y, p3_y, p4_y) - if len(label) > 0: - _targets.append((label, [left, top, right, bot])) - - text_targets, box_targets = zip(*_targets) - - self.data.append((img_path, dict(boxes=np.asarray(box_targets, dtype=np.float32), labels=text_targets))) + with open(os.path.join(self.root, "annotations", f"{stem}.txt"), encoding="latin") as f: + _rows = [row for row in list(csv.reader(f, delimiter=",")) if len(row) > 0] + + labels = [",".join(row[8:]) for row in _rows] + # reorder coordinates (8 -> (4,2) -> + # (x, y) coordinates of top left, top right, bottom right, bottom left corners) and filter empty lines + coords: np.ndarray = np.stack( + [np.array(list(map(int, row[:8])), dtype=np_dtype).reshape((4, 2)) for row in _rows], axis=0 + ) + + if not use_polygons: + # xmin, ymin, xmax, ymax + coords = np.concatenate((coords.min(axis=1), coords.max(axis=1)), axis=1) + + if recognition_task: + crops = crop_bboxes_from_image(img_path=os.path.join(tmp_root, img_path), geoms=coords) + for crop, label in zip(crops, labels): + if crop.shape[0] > 0 and crop.shape[1] > 0 and len(label) > 0: + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, coords)) + else: + self.data.append((img_path, dict(boxes=coords, labels=labels))) + + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -390,8 +444,8 @@

    Source code for doctr.datasets.sroie

           
         
       
    -
    - + + diff --git a/v0.3.1/_modules/doctr/datasets/svhn.html b/v0.3.1/_modules/doctr/datasets/svhn.html index 48e4e4d210..60e02b1b3b 100644 --- a/v0.3.1/_modules/doctr/datasets/svhn.html +++ b/v0.3.1/_modules/doctr/datasets/svhn.html @@ -13,7 +13,7 @@ - + doctr.datasets.svhn - docTR documentation @@ -473,7 +473,7 @@

    Source code for doctr.datasets.svhn

         
       
    - + diff --git a/v0.3.1/_modules/doctr/datasets/svt.html b/v0.3.1/_modules/doctr/datasets/svt.html index 4144dc6b9b..a997fcbb50 100644 --- a/v0.3.1/_modules/doctr/datasets/svt.html +++ b/v0.3.1/_modules/doctr/datasets/svt.html @@ -13,7 +13,7 @@ - + doctr.datasets.svt - docTR documentation @@ -459,7 +459,7 @@

    Source code for doctr.datasets.svt

         
       
    - + diff --git a/v0.3.1/_modules/doctr/datasets/synthtext.html b/v0.3.1/_modules/doctr/datasets/synthtext.html index 3b9de506a7..c776e1d673 100644 --- a/v0.3.1/_modules/doctr/datasets/synthtext.html +++ b/v0.3.1/_modules/doctr/datasets/synthtext.html @@ -13,7 +13,7 @@ - + doctr.datasets.synthtext - docTR documentation @@ -470,7 +470,7 @@

    Source code for doctr.datasets.synthtext

         
       
    - + diff --git a/v0.3.1/_modules/doctr/datasets/utils.html b/v0.3.1/_modules/doctr/datasets/utils.html index 2259698c0f..bde9304597 100644 --- a/v0.3.1/_modules/doctr/datasets/utils.html +++ b/v0.3.1/_modules/doctr/datasets/utils.html @@ -13,7 +13,7 @@ - + doctr.datasets.utils - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.utils

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import string
     import unicodedata
    +from collections.abc import Sequence
    +from functools import partial
    +from pathlib import Path
    +from typing import Any, Dict, List, Optional, Tuple, TypeVar, Union
    +from typing import Sequence as SequenceType
    +
     import numpy as np
    -from typing import List, Optional, Any
    +from PIL import Image
    +
    +from doctr.io.image import get_img_shape
    +from doctr.utils.geometry import convert_to_relative_coords, extract_crops, extract_rcrops
     
     from .vocabs import VOCABS
     
    -__all__ = ['translate', 'encode_sequence', 'decode_sequence', 'encode_sequences']
    +__all__ = ["translate", "encode_string", "decode_sequence", "encode_sequences", "pre_transform_multiclass"]
    +
    +ImageTensor = TypeVar("ImageTensor")
     
     
     def translate(
         input_string: str,
         vocab_name: str,
    -    unknown_char: str = '■',
    +    unknown_char: str = "■",
     ) -> str:
         """Translate a string input in a given vocabulary
     
         Args:
    +    ----
             input_string: input string to translate
             vocab_name: vocabulary to use (french, latin, ...)
             unknown_char: unknown character for non-translatable characters
     
         Returns:
    -        A string translated in a given vocab"""
    -
    +    -------
    +        A string translated in a given vocab
    +    """
         if VOCABS.get(vocab_name) is None:
             raise KeyError("output vocabulary must be in vocabs dictionnary")
     
    -    translated = ''
    +    translated = ""
         for char in input_string:
             if char not in VOCABS[vocab_name]:
                 # we need to translate char into a vocab char
    @@ -315,51 +350,63 @@ 

    Source code for doctr.datasets.utils

                     # remove whitespaces
                     continue
                 # normalize character if it is not in vocab
    -            char = unicodedata.normalize('NFD', char).encode('ascii', 'ignore').decode('ascii')
    -            if char == '' or char not in VOCABS[vocab_name]:
    +            char = unicodedata.normalize("NFD", char).encode("ascii", "ignore").decode("ascii")
    +            if char == "" or char not in VOCABS[vocab_name]:
                     # if normalization fails or char still not in vocab, return unknown character)
                     char = unknown_char
             translated += char
         return translated
     
     
    -def encode_sequence(
    +def encode_string(
         input_string: str,
         vocab: str,
     ) -> List[int]:
         """Given a predefined mapping, encode the string to a sequence of numbers
     
         Args:
    +    ----
             input_string: string to encode
             vocab: vocabulary (string), the encoding is given by the indexing of the character sequence
     
         Returns:
    -        A list encoding the input_string"""
    -
    -    return list(map(vocab.index, input_string))  # type: ignore[arg-type]
    +    -------
    +        A list encoding the input_string
    +    """
    +    try:
    +        return list(map(vocab.index, input_string))
    +    except ValueError:
    +        raise ValueError(
    +            f"some characters cannot be found in 'vocab'. \
    +                         Please check the input string {input_string} and the vocabulary {vocab}"
    +        )
     
     
     def decode_sequence(
    -    input_array: np.array,
    +    input_seq: Union[np.ndarray, SequenceType[int]],
         mapping: str,
     ) -> str:
         """Given a predefined mapping, decode the sequence of numbers to a string
     
         Args:
    -        input_array: array to decode
    +    ----
    +        input_seq: array to decode
             mapping: vocabulary (string), the encoding is given by the indexing of the character sequence
     
         Returns:
    -        A string, decoded from input_array"""
    -
    -    if not input_array.dtype == np.int_ or input_array.max() >= len(mapping):
    +    -------
    +        A string, decoded from input_seq
    +    """
    +    if not isinstance(input_seq, (Sequence, np.ndarray)):
    +        raise TypeError("Invalid sequence type")
    +    if isinstance(input_seq, np.ndarray) and (input_seq.dtype != np.int_ or input_seq.max() >= len(mapping)):
             raise AssertionError("Input must be an array of int, with max less than mapping size")
    -    decoded = ''.join(mapping[idx] for idx in input_array)
    -    return decoded
    +
    +    return "".join(map(mapping.__getitem__, input_seq))
     
     
     
    -[docs] +[docs] def encode_sequences( sequences: List[str], vocab: str, @@ -367,48 +414,53 @@

    Source code for doctr.datasets.utils

         eos: int = -1,
         sos: Optional[int] = None,
         pad: Optional[int] = None,
    -    **kwargs: Any,
    +    dynamic_seq_length: bool = False,
     ) -> np.ndarray:
         """Encode character sequences using a given vocab as mapping
     
         Args:
    +    ----
             sequences: the list of character sequences of size N
             vocab: the ordered vocab to use for encoding
             target_size: maximum length of the encoded data
             eos: encoding of End Of String
             sos: optional encoding of Start Of String
             pad: optional encoding for padding. In case of padding, all sequences are followed by 1 EOS then PAD
    +        dynamic_seq_length: if `target_size` is specified, uses it as upper bound and enables dynamic sequence size
     
         Returns:
    +    -------
             the padded encoded data as a tensor
         """
    -
         if 0 <= eos < len(vocab):
             raise ValueError("argument 'eos' needs to be outside of vocab possible indices")
     
    -    if not isinstance(target_size, int):
    -        target_size = max(len(w) for w in sequences)
    -        if sos:
    -            target_size += 1
    -        if pad:
    -            target_size += 1
    +    if not isinstance(target_size, int) or dynamic_seq_length:
    +        # Maximum string length + EOS
    +        max_length = max(len(w) for w in sequences) + 1
    +        if isinstance(sos, int):
    +            max_length += 1
    +        if isinstance(pad, int):
    +            max_length += 1
    +        target_size = max_length if not isinstance(target_size, int) else min(max_length, target_size)
     
         # Pad all sequences
    -    if pad:  # pad with padding symbol
    +    if isinstance(pad, int):  # pad with padding symbol
             if 0 <= pad < len(vocab):
                 raise ValueError("argument 'pad' needs to be outside of vocab possible indices")
             # In that case, add EOS at the end of the word before padding
    -        encoded_data = np.full([len(sequences), target_size], pad, dtype=np.int32)
    +        default_symbol = pad
         else:  # pad with eos symbol
    -        encoded_data = np.full([len(sequences), target_size], eos, dtype=np.int32)
    +        default_symbol = eos
    +    encoded_data: np.ndarray = np.full([len(sequences), target_size], default_symbol, dtype=np.int32)
     
    -    for idx, seq in enumerate(sequences):
    -        encoded_seq = encode_sequence(seq, vocab)
    -        if pad:  # add eos at the end of the sequence
    -            encoded_seq.append(eos)
    -        encoded_data[idx, :min(len(encoded_seq), target_size)] = encoded_seq[:min(len(encoded_seq), target_size)]
    +    # Encode the strings
    +    for idx, seq in enumerate(map(partial(encode_string, vocab=vocab), sequences)):
    +        if isinstance(pad, int):  # add eos at the end of the sequence
    +            seq.append(eos)
    +        encoded_data[idx, : min(len(seq), target_size)] = seq[: min(len(seq), target_size)]
     
    -    if sos:  # place eos symbol at the beginning of each sequence
    +    if isinstance(sos, int):  # place sos symbol at the beginning of each sequence
             if 0 <= sos < len(vocab):
                 raise ValueError("argument 'sos' needs to be outside of vocab possible indices")
             encoded_data = np.roll(encoded_data, 1)
    @@ -416,6 +468,59 @@ 

    Source code for doctr.datasets.utils

     
         return encoded_data
    + + +def convert_target_to_relative( + img: ImageTensor, target: Union[np.ndarray, Dict[str, Any]] +) -> Tuple[ImageTensor, Union[Dict[str, Any], np.ndarray]]: + if isinstance(target, np.ndarray): + target = convert_to_relative_coords(target, get_img_shape(img)) + else: + target["boxes"] = convert_to_relative_coords(target["boxes"], get_img_shape(img)) + return img, target + + +def crop_bboxes_from_image(img_path: Union[str, Path], geoms: np.ndarray) -> List[np.ndarray]: + """Crop a set of bounding boxes from an image + + Args: + ---- + img_path: path to the image + geoms: a array of polygons of shape (N, 4, 2) or of straight boxes of shape (N, 4) + + Returns: + ------- + a list of cropped images + """ + with Image.open(img_path) as pil_img: + img: np.ndarray = np.array(pil_img.convert("RGB")) + # Polygon + if geoms.ndim == 3 and geoms.shape[1:] == (4, 2): + return extract_rcrops(img, geoms.astype(dtype=int)) + if geoms.ndim == 2 and geoms.shape[1] == 4: + return extract_crops(img, geoms.astype(dtype=int)) + raise ValueError("Invalid geometry format") + + +def pre_transform_multiclass(img, target: Tuple[np.ndarray, List]) -> Tuple[np.ndarray, Dict[str, List]]: + """Converts multiclass target to relative coordinates. + + Args: + ---- + img: Image + target: tuple of target polygons and their classes names + + Returns: + ------- + Image and dictionary of boxes, with class names as keys + """ + boxes = convert_to_relative_coords(target[0], get_img_shape(img)) + boxes_classes = target[1] + boxes_dict: Dict = {k: [] for k in sorted(set(boxes_classes))} + for k, poly in zip(boxes_classes, boxes): + boxes_dict[k].append(poly) + boxes_dict = {k: np.stack(v, axis=0) for k, v in boxes_dict.items()} + return img, boxes_dict
    @@ -448,8 +553,8 @@

    Source code for doctr.datasets.utils

           
         
       
    - - + + diff --git a/v0.3.1/_modules/doctr/datasets/wildreceipt.html b/v0.3.1/_modules/doctr/datasets/wildreceipt.html index c543ee7cac..12c6aebd14 100644 --- a/v0.3.1/_modules/doctr/datasets/wildreceipt.html +++ b/v0.3.1/_modules/doctr/datasets/wildreceipt.html @@ -13,7 +13,7 @@ - + doctr.datasets.wildreceipt - docTR documentation @@ -454,7 +454,7 @@

    Source code for doctr.datasets.wildreceipt

         
       
    - + diff --git a/v0.3.1/_modules/doctr/documents/elements.html b/v0.3.1/_modules/doctr/documents/elements.html deleted file mode 100644 index 10c1e142d2..0000000000 --- a/v0.3.1/_modules/doctr/documents/elements.html +++ /dev/null @@ -1,577 +0,0 @@ - - - - - - - - - - - - doctr.documents.elements - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.elements

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import matplotlib.pyplot as plt
    -from typing import Tuple, Dict, List, Any, Optional, Union
    -
    -from doctr.utils.geometry import resolve_enclosing_bbox, resolve_enclosing_rbbox
    -from doctr.utils.visualization import visualize_page
    -from doctr.utils.common_types import BoundingBox, RotatedBbox
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['Element', 'Word', 'Artefact', 'Line', 'Block', 'Page', 'Document']
    -
    -
    -class Element(NestedObject):
    -    """Implements an abstract document element with exporting and text rendering capabilities"""
    -
    -    _exported_keys: List[str] = []
    -
    -    def __init__(self, **kwargs: Any) -> None:
    -        self._children_names: List[str] = []
    -        for k, v in kwargs.items():
    -            setattr(self, k, v)
    -            self._children_names.append(k)
    -
    -    def export(self) -> Dict[str, Any]:
    -        """Exports the object into a nested dict format"""
    -
    -        export_dict = {k: getattr(self, k) for k in self._exported_keys}
    -        for children_name in self._children_names:
    -            export_dict[children_name] = [c.export() for c in getattr(self, children_name)]
    -
    -        return export_dict
    -
    -    def render(self) -> str:
    -        raise NotImplementedError
    -
    -
    -
    -[docs] -class Word(Element): - """Implements a word element - - Args: - value: the text string of the word - confidence: the confidence associated with the text prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size - """ - - _exported_keys: List[str] = ["value", "confidence", "geometry"] - - def __init__(self, value: str, confidence: float, geometry: Union[BoundingBox, RotatedBbox]) -> None: - super().__init__() - self.value = value - self.confidence = confidence - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return self.value - - def extra_repr(self) -> str: - return f"value='{self.value}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Artefact(Element): - """Implements a non-textual element - - Args: - artefact_type: the type of artefact - confidence: the confidence of the type prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. - """ - - _exported_keys: List[str] = ["geometry", "type", "confidence"] - - def __init__(self, artefact_type: str, confidence: float, geometry: BoundingBox) -> None: - super().__init__() - self.geometry = geometry - self.type = artefact_type - self.confidence = confidence - - def render(self) -> str: - """Renders the full text of the element""" - return f"[{self.type.upper()}]" - - def extra_repr(self) -> str: - return f"type='{self.type}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Line(Element): - """Implements a line element as a collection of words - - Args: - words: list of word elements - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all words in it. - """ - - _exported_keys: List[str] = ["geometry"] - words: List[Word] = [] - - def __init__( - self, - words: List[Word], - geometry: Optional[Union[BoundingBox, RotatedBbox]] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - # Check whether this is a rotated or straight box - box_resolution_fn = resolve_enclosing_rbbox if len(words[0].geometry) == 5 else resolve_enclosing_bbox - geometry = box_resolution_fn([w.geometry for w in words]) # type: ignore[operator, misc] - - super().__init__(words=words) - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return " ".join(w.render() for w in self.words)
    - - - -
    -[docs] -class Block(Element): - """Implements a block element as a collection of lines and artefacts - - Args: - lines: list of line elements - artefacts: list of artefacts - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all lines and artefacts in it. - """ - - _exported_keys: List[str] = ["geometry"] - lines: List[Line] = [] - artefacts: List[Artefact] = [] - - def __init__( - self, - lines: List[Line] = [], - artefacts: List[Artefact] = [], - geometry: Optional[Union[BoundingBox, RotatedBbox]] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - line_boxes = [word.geometry for line in lines for word in line.words] - artefact_boxes = [artefact.geometry for artefact in artefacts] - box_resolution_fn = resolve_enclosing_rbbox if len(lines[0].geometry) == 5 else resolve_enclosing_bbox - geometry = box_resolution_fn(line_boxes + artefact_boxes) # type: ignore[operator, arg-type] - - super().__init__(lines=lines, artefacts=artefacts) - self.geometry = geometry - - def render(self, line_break: str = '\n') -> str: - """Renders the full text of the element""" - return line_break.join(line.render() for line in self.lines)
    - - - -
    -[docs] -class Page(Element): - """Implements a page element as a collection of blocks - - Args: - blocks: list of block elements - page_idx: the index of the page in the input raw document - dimensions: the page size in pixels in format (width, height) - orientation: a dictionary with the value of the rotation angle in degress and confidence of the prediction - language: a dictionary with the language value and confidence of the prediction - """ - - _exported_keys: List[str] = ["page_idx", "dimensions", "orientation", "language"] - blocks: List[Block] = [] - - def __init__( - self, - blocks: List[Block], - page_idx: int, - dimensions: Tuple[int, int], - orientation: Optional[Dict[str, Any]] = None, - language: Optional[Dict[str, Any]] = None, - ) -> None: - super().__init__(blocks=blocks) - self.page_idx = page_idx - self.dimensions = dimensions - self.orientation = orientation if isinstance(orientation, dict) else dict(value=None, confidence=None) - self.language = language if isinstance(language, dict) else dict(value=None, confidence=None) - - def render(self, block_break: str = '\n\n') -> str: - """Renders the full text of the element""" - return block_break.join(b.render() for b in self.blocks) - - def extra_repr(self) -> str: - return f"dimensions={self.dimensions}" - -
    -[docs] - def show( - self, page: np.ndarray, interactive: bool = True, **kwargs - ) -> None: - """Overlay the result on a given image - - Args: - page: image encoded as a numpy array in uint8 - interactive: whether the display should be interactive - """ - visualize_page(self.export(), page, interactive=interactive) - plt.show(**kwargs)
    -
    - - - -
    -[docs] -class Document(Element): - """Implements a document element as a collection of pages - - Args: - pages: list of page elements - """ - - pages: List[Page] = [] - - def __init__( - self, - pages: List[Page], - ) -> None: - super().__init__(pages=pages) - - def render(self, page_break: str = '\n\n\n\n') -> str: - """Renders the full text of the element""" - return page_break.join(p.render() for p in self.pages) - -
    -[docs] - def show(self, pages: List[np.ndarray], **kwargs) -> None: - """Overlay the result on a given image - - Args: - pages: list of images encoded as numpy arrays in uint8 - """ - for img, result in zip(pages, self.pages): - result.show(img, **kwargs)
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.1/_modules/doctr/documents/reader.html b/v0.3.1/_modules/doctr/documents/reader.html deleted file mode 100644 index cdcd814b6c..0000000000 --- a/v0.3.1/_modules/doctr/documents/reader.html +++ /dev/null @@ -1,612 +0,0 @@ - - - - - - - - - - - - doctr.documents.reader - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.reader

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import cv2
    -from pathlib import Path
    -import fitz
    -from weasyprint import HTML
    -from typing import List, Tuple, Optional, Any, Union, Sequence, Dict
    -
    -__all__ = ['read_pdf', 'read_img', 'read_html', 'DocumentFile', 'PDF']
    -
    -
    -AbstractPath = Union[str, Path]
    -AbstractFile = Union[AbstractPath, bytes]
    -Bbox = Tuple[float, float, float, float]
    -
    -
    -
    -[docs] -def read_img( - file: AbstractFile, - output_size: Optional[Tuple[int, int]] = None, - rgb_output: bool = True, -) -> np.ndarray: - """Read an image file into numpy format - - Example:: - >>> from doctr.documents import read_img - >>> page = read_img("path/to/your/doc.jpg") - - Args: - file: the path to the image file - output_size: the expected output size of each page in format H x W - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - Returns: - the page decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)): - if not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - img = cv2.imread(str(file), cv2.IMREAD_COLOR) - elif isinstance(file, bytes): - file = np.frombuffer(file, np.uint8) - img = cv2.imdecode(file, cv2.IMREAD_COLOR) - else: - raise TypeError("unsupported object type for argument 'file'") - - # Validity check - if img is None: - raise ValueError("unable to read file.") - # Resizing - if isinstance(output_size, tuple): - img = cv2.resize(img, output_size[::-1], interpolation=cv2.INTER_LINEAR) - # Switch the channel order - if rgb_output: - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - return img
    - - - -
    -[docs] -def read_pdf(file: AbstractFile, **kwargs: Any) -> fitz.Document: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_pdf - >>> doc = read_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)) and not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - - fitz_args: Dict[str, AbstractFile] = {} - - if isinstance(file, (str, Path)): - fitz_args['filename'] = file - elif isinstance(file, bytes): - fitz_args['stream'] = file - else: - raise TypeError("unsupported object type for argument 'file'") - - # Read pages with fitz and convert them to numpy ndarrays - return fitz.open(**fitz_args, filetype="pdf", **kwargs)
    - - - -def convert_page_to_numpy( - page: fitz.fitz.Page, - output_size: Optional[Tuple[int, int]] = None, - bgr_output: bool = False, - default_scales: Tuple[float, float] = (2, 2), -) -> np.ndarray: - """Convert a fitz page to a numpy-formatted image - - Args: - page: the page of a file read with PyMuPDF - output_size: the expected output size of each page in format H x W. Default goes to 840 x 595 for A4 pdf, - if you want to increase the resolution while preserving the original A4 aspect ratio can pass (1024, 726) - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - default_scales: spatial scaling to be applied when output_size is not specified where (1, 1) - corresponds to 72 dpi rendering. - - Returns: - the rendered image in numpy format - """ - - # If no output size is specified, keep the origin one - if output_size is not None: - scales = (output_size[1] / page.MediaBox[2], output_size[0] / page.MediaBox[3]) - else: - # Default 72 DPI (scales of (1, 1)) is unnecessarily low - scales = default_scales - - transform_matrix = fitz.Matrix(*scales) - - # Generate the pixel map using the transformation matrix - pixmap = page.getPixmap(matrix=transform_matrix) - # Decode it into a numpy - img = np.frombuffer(pixmap.samples, dtype=np.uint8).reshape(pixmap.height, pixmap.width, 3) - - # Switch the channel order - if bgr_output: - img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) - - return img - - -
    -[docs] -def read_html(url: str, **kwargs: Any) -> bytes: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_html - >>> doc = read_html("https://www.yoursite.com") - - Args: - url: URL of the target web page - Returns: - decoded PDF file as a bytes stream - """ - - return HTML(url, **kwargs).write_pdf()
    - - - -
    -[docs] -class PDF: - """PDF document template - - Args: - doc: input PDF document - """ - def __init__(self, doc: fitz.Document) -> None: - self.doc = doc - -
    -[docs] - def as_images(self, **kwargs) -> List[np.ndarray]: - """Convert all document pages to images - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images() - - Args: - kwargs: keyword arguments of `convert_page_to_numpy` - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - return [convert_page_to_numpy(page, **kwargs) for page in self.doc]
    - - - def get_page_words(self, idx, **kwargs) -> List[Tuple[Bbox, str]]: - """Get the annotations for all words of a given page""" - - # xmin, ymin, xmax, ymax, value, block_idx, line_idx, word_idx - return [(info[:4], info[4]) for info in self.doc[idx].getTextWords(**kwargs)] - -
    -[docs] - def get_words(self, **kwargs) -> List[List[Tuple[Bbox, str]]]: - """Get the annotations for all words in the document - - Example:: - >>> from doctr.documents import DocumentFile - >>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words() - - Args: - kwargs: keyword arguments of `fitz.Page.getTextWords` - Returns: - the list of pages annotations, represented as a list of tuple (bounding box, value) - """ - return [self.get_page_words(idx, **kwargs) for idx in range(len(self.doc))]
    - - - def get_page_artefacts(self, idx) -> List[Tuple[float, float, float, float]]: - return [tuple(self.doc[idx].getImageBbox(artefact)) # type: ignore[misc] - for artefact in self.doc[idx].get_images(full=True)] - -
    -[docs] - def get_artefacts(self) -> List[List[Tuple[float, float, float, float]]]: - """Get the artefacts for the entire document - - Example:: - >>> from doctr.documents import DocumentFile - >>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts() - - Returns: - the list of pages artefacts, represented as a list of bounding boxes - """ - - return [self.get_page_artefacts(idx) for idx in range(len(self.doc))]
    -
    - - - -
    -[docs] -class DocumentFile: - """Read a document from multiple extensions""" - -
    -[docs] - @classmethod - def from_pdf(cls, file: AbstractFile, **kwargs) -> PDF: - """Read a PDF file - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file or a binary stream - Returns: - a PDF document - """ - - doc = read_pdf(file, **kwargs) - - return PDF(doc)
    - - -
    -[docs] - @classmethod - def from_url(cls, url: str, **kwargs) -> PDF: - """Interpret a web page as a PDF document - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_url("https://www.yoursite.com") - - Args: - url: the URL of the target web page - Returns: - a PDF document - """ - pdf_stream = read_html(url) - return cls.from_pdf(pdf_stream, **kwargs)
    - - -
    -[docs] - @classmethod - def from_images(cls, files: Union[Sequence[AbstractFile], AbstractFile], **kwargs) -> List[np.ndarray]: - """Read an image file (or a collection of image files) and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"]) - - Args: - files: the path to the image file or a binary stream, or a collection of those - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - if isinstance(files, (str, Path, bytes)): - files = [files] - - return [read_img(file, **kwargs) for file in files]
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.1/_modules/doctr/io/elements.html b/v0.3.1/_modules/doctr/io/elements.html index 753a47455c..e049d6ce30 100644 --- a/v0.3.1/_modules/doctr/io/elements.html +++ b/v0.3.1/_modules/doctr/io/elements.html @@ -13,7 +13,7 @@ - + doctr.io.elements - docTR documentation @@ -1008,7 +1008,7 @@

    Source code for doctr.io.elements

         
       
    - + diff --git a/v0.3.1/_modules/doctr/io/html.html b/v0.3.1/_modules/doctr/io/html.html index 7ad5b97031..be73631500 100644 --- a/v0.3.1/_modules/doctr/io/html.html +++ b/v0.3.1/_modules/doctr/io/html.html @@ -13,7 +13,7 @@ - + doctr.io.html - docTR documentation @@ -360,7 +360,7 @@

    Source code for doctr.io.html

         
       
    - + diff --git a/v0.3.1/_modules/doctr/io/image/base.html b/v0.3.1/_modules/doctr/io/image/base.html index 336b4bff0e..a50c95d595 100644 --- a/v0.3.1/_modules/doctr/io/image/base.html +++ b/v0.3.1/_modules/doctr/io/image/base.html @@ -13,7 +13,7 @@ - + doctr.io.image.base - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.io.image.base

         
       
    - + diff --git a/v0.3.1/_modules/doctr/io/image/tensorflow.html b/v0.3.1/_modules/doctr/io/image/tensorflow.html index f1846820a3..3b9e731756 100644 --- a/v0.3.1/_modules/doctr/io/image/tensorflow.html +++ b/v0.3.1/_modules/doctr/io/image/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.io.image.tensorflow - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.io.image.tensorflow

         
       
    - + diff --git a/v0.3.1/_modules/doctr/io/pdf.html b/v0.3.1/_modules/doctr/io/pdf.html index e3abf6960b..e5b94811c3 100644 --- a/v0.3.1/_modules/doctr/io/pdf.html +++ b/v0.3.1/_modules/doctr/io/pdf.html @@ -13,7 +13,7 @@ - + doctr.io.pdf - docTR documentation @@ -377,7 +377,7 @@

    Source code for doctr.io.pdf

         
       
    - + diff --git a/v0.3.1/_modules/doctr/io/reader.html b/v0.3.1/_modules/doctr/io/reader.html index c1ddc26edd..d36e5bb553 100644 --- a/v0.3.1/_modules/doctr/io/reader.html +++ b/v0.3.1/_modules/doctr/io/reader.html @@ -13,7 +13,7 @@ - + doctr.io.reader - docTR documentation @@ -426,7 +426,7 @@

    Source code for doctr.io.reader

         
       
    - + diff --git a/v0.3.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html b/v0.3.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html index 9f074805c1..61a010d548 100644 --- a/v0.3.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html +++ b/v0.3.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.magc_resnet.tensorflow - docTR documentation @@ -531,7 +531,7 @@

    Source code for doctr.models.classification.magc_resnet.tensorflow

    - + diff --git a/v0.3.1/_modules/doctr/models/classification/mobilenet/tensorflow.html b/v0.3.1/_modules/doctr/models/classification/mobilenet/tensorflow.html index 6a63851276..7c448394ad 100644 --- a/v0.3.1/_modules/doctr/models/classification/mobilenet/tensorflow.html +++ b/v0.3.1/_modules/doctr/models/classification/mobilenet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.mobilenet.tensorflow - docTR documentation @@ -793,7 +793,7 @@

    Source code for doctr.models.classification.mobilenet.tensorflow

    - + diff --git a/v0.3.1/_modules/doctr/models/classification/resnet/tensorflow.html b/v0.3.1/_modules/doctr/models/classification/resnet/tensorflow.html index 095d377f31..aed4343741 100644 --- a/v0.3.1/_modules/doctr/models/classification/resnet/tensorflow.html +++ b/v0.3.1/_modules/doctr/models/classification/resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.resnet.tensorflow - docTR documentation @@ -749,7 +749,7 @@

    Source code for doctr.models.classification.resnet.tensorflow

    - + diff --git a/v0.3.1/_modules/doctr/models/classification/textnet/tensorflow.html b/v0.3.1/_modules/doctr/models/classification/textnet/tensorflow.html index ad254ebbfb..c5567d7d67 100644 --- a/v0.3.1/_modules/doctr/models/classification/textnet/tensorflow.html +++ b/v0.3.1/_modules/doctr/models/classification/textnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.textnet.tensorflow - docTR documentation @@ -611,7 +611,7 @@

    Source code for doctr.models.classification.textnet.tensorflow

    - + diff --git a/v0.3.1/_modules/doctr/models/classification/vgg/tensorflow.html b/v0.3.1/_modules/doctr/models/classification/vgg/tensorflow.html index 01ae452624..788111ae87 100644 --- a/v0.3.1/_modules/doctr/models/classification/vgg/tensorflow.html +++ b/v0.3.1/_modules/doctr/models/classification/vgg/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vgg.tensorflow - docTR documentation @@ -451,7 +451,7 @@

    Source code for doctr.models.classification.vgg.tensorflow

    - + diff --git a/v0.3.1/_modules/doctr/models/classification/vit/tensorflow.html b/v0.3.1/_modules/doctr/models/classification/vit/tensorflow.html index 1333cf6045..971ba5abe9 100644 --- a/v0.3.1/_modules/doctr/models/classification/vit/tensorflow.html +++ b/v0.3.1/_modules/doctr/models/classification/vit/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vit.tensorflow - docTR documentation @@ -533,7 +533,7 @@

    Source code for doctr.models.classification.vit.tensorflow

    - + diff --git a/v0.3.1/_modules/doctr/models/classification/zoo.html b/v0.3.1/_modules/doctr/models/classification/zoo.html index f7796a7522..3eb2a3ec4e 100644 --- a/v0.3.1/_modules/doctr/models/classification/zoo.html +++ b/v0.3.1/_modules/doctr/models/classification/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.classification.zoo - docTR documentation @@ -447,7 +447,7 @@

    Source code for doctr.models.classification.zoo

    <
    - + diff --git a/v0.3.1/_modules/doctr/models/detection/differentiable_binarization.html b/v0.3.1/_modules/doctr/models/detection/differentiable_binarization.html deleted file mode 100644 index 38e9b36ec2..0000000000 --- a/v0.3.1/_modules/doctr/models/detection/differentiable_binarization.html +++ /dev/null @@ -1,879 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.differentiable_binarization - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.differentiable_binarization

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -import cv2
    -from copy import deepcopy
    -import numpy as np
    -from shapely.geometry import Polygon
    -import pyclipper
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers
    -from typing import Union, List, Tuple, Optional, Any, Dict
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..utils import IntermediateLayerGetter, load_pretrained_params, conv_sequence
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['DBPostProcessor', 'DBNet', 'db_resnet50']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'db_resnet50': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'backbone': 'ResNet50',
    -        'fpn_layers': ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"],
    -        'fpn_channels': 128,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'DBPostProcessor',
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.2.0/db_resnet50-adcafc63.zip',
    -    },
    -}
    -
    -
    -class DBPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for DBNet adapted from the implementation of `xuannianz
    -    <https://github.com/xuannianz/DifferentiableBinarization>`_.
    -
    -    Args:
    -        unclip ratio: ratio used to unshrink polygons
    -        min_size_box: minimal length (pix) to keep a box
    -        max_candidates: maximum boxes to consider in a single page
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        unclip_ratio: Union[float, int] = 1.5,
    -        max_candidates: int = 1000,
    -        box_thresh: float = 0.1,
    -        bin_thresh: float = 0.3,
    -    ) -> None:
    -
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -        self.unclip_ratio = unclip_ratio
    -        self.max_candidates = max_candidates
    -
    -    def polygon_to_box(
    -        self,
    -        points: np.ndarray,
    -    ) -> Optional[Tuple[int, int, int, int]]:
    -        """Expand a polygon (points) by a factor unclip_ratio, and returns a 4-points box
    -
    -        Args:
    -            points: The first parameter.
    -
    -        Returns:
    -            a box in absolute coordinates (x, y, w, h)
    -        """
    -        poly = Polygon(points)
    -        distance = poly.area * self.unclip_ratio / poly.length  # compute distance to expand polygon
    -        offset = pyclipper.PyclipperOffset()
    -        offset.AddPath(points, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        _points = offset.Execute(distance)
    -        # Take biggest stack of points
    -        idx = 0
    -        if len(_points) > 1:
    -            max_size = 0
    -            for _idx, p in enumerate(_points):
    -                if len(p) > max_size:
    -                    idx = _idx
    -                    max_size = len(p)
    -            # We ensure that _points can be correctly casted to a ndarray
    -            _points = [_points[idx]]
    -        expanded_points = np.asarray(_points)  # expand polygon
    -        if len(expanded_points) < 1:
    -            return None
    -        x, y, w, h = cv2.boundingRect(expanded_points)  # compute a 4-points box from expanded polygon
    -        return x, y, w, h
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map
    -
    -        Args:
    -            pred: Pred map from differentiable binarization output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        # get contours from connected components on the bitmap
    -        contours, _ = cv2.findContours(bitmap.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    -        for contour in contours[:self.max_candidates]:
    -            # Check whether smallest enclosing bounding box is not too small
    -            if np.any(contour[:, 0].max(axis=0) - contour[:, 0].min(axis=0) < min_size_box):
    -                continue
    -            x, y, w, h = cv2.boundingRect(contour)
    -            points = np.array([[x, y], [x, y + h], [x + w, y + h], [x + w, y]])
    -            # Compute objectness
    -            score = self.box_score(pred, points)
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            _box = self.polygon_to_box(points)
    -
    -            if _box is None or _box[2] < min_size_box or _box[3] < min_size_box:  # remove to small boxes
    -                continue
    -            x, y, w, h = _box
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -class FeaturePyramidNetwork(layers.Layer, NestedObject):
    -    """Feature Pyramid Network as described in `"Feature Pyramid Networks for Object Detection"
    -    <https://arxiv.org/pdf/1612.03144.pdf>`_.
    -
    -    Args:
    -        channels: number of channel to output
    -    """
    -
    -    def __init__(
    -        self,
    -        channels: int,
    -    ) -> None:
    -        super().__init__()
    -        self.channels = channels
    -        self.upsample = layers.UpSampling2D(size=(2, 2), interpolation='nearest')
    -        self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer='he_normal') for _ in range(4)]
    -        self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2 ** idx) for idx in range(4)]
    -
    -    @staticmethod
    -    def build_upsampling(
    -        channels: int,
    -        dilation_factor: int = 1,
    -    ) -> layers.Layer:
    -        """Module which performs a 3x3 convolution followed by up-sampling
    -
    -        Args:
    -            channels: number of output channels
    -            dilation_factor (int): dilation factor to scale the convolution output before concatenation
    -
    -        Returns:
    -            a keras.layers.Layer object, wrapping these operations in a sequential module
    -
    -        """
    -
    -        _layers = conv_sequence(channels, 'relu', True, kernel_size=3)
    -
    -        if dilation_factor > 1:
    -            _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation='nearest'))
    -
    -        module = keras.Sequential(_layers)
    -
    -        return module
    -
    -    def extra_repr(self) -> str:
    -        return f"channels={self.channels}"
    -
    -    def call(
    -        self,
    -        x: List[tf.Tensor],
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # Channel mapping
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.inner_blocks, x)]
    -        # Upsample & sum
    -        for idx in range(len(results) - 1, -1):
    -            results[idx] += self.upsample(results[idx + 1])
    -        # Conv & upsample
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.layer_blocks, results)]
    -
    -        return layers.concatenate(results)
    -
    -
    -class DBNet(DetectionModel, NestedObject):
    -    """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization"
    -    <https://arxiv.org/pdf/1911.08947.pdf>`_.
    -
    -    Args:
    -        feature extractor: the backbone serving as feature extractor
    -        fpn_channels: number of channels each extracted feature maps is mapped to
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'fpn', 'probability_head', 'threshold_head', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: IntermediateLayerGetter,
    -        fpn_channels: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(cfg=cfg)
    -
    -        self.shrink_ratio = 0.4
    -        self.thresh_min = 0.3
    -        self.thresh_max = 0.7
    -        self.min_size_box = 3
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.fpn = FeaturePyramidNetwork(channels=fpn_channels)
    -        # Initialize kernels
    -        _inputs = [layers.Input(shape=in_shape[1:]) for in_shape in self.feat_extractor.output_shape]
    -        output_shape = tuple(self.fpn(_inputs).shape)
    -
    -        self.probability_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -        self.threshold_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -
    -        self.postprocessor = DBPostProcessor()
    -
    -    @staticmethod
    -    def compute_distance(
    -        xs: np.array,
    -        ys: np.array,
    -        a: np.array,
    -        b: np.array,
    -        eps: float = 1e-7,
    -    ) -> float:
    -        """Compute the distance for each point of the map (xs, ys) to the (a, b) segment
    -
    -        Args:
    -            xs : map of x coordinates (height, width)
    -            ys : map of y coordinates (height, width)
    -            a: first point defining the [ab] segment
    -            b: second point defining the [ab] segment
    -
    -        Returns:
    -            The computed distance
    -
    -        """
    -        square_dist_1 = np.square(xs - a[0]) + np.square(ys - a[1])
    -        square_dist_2 = np.square(xs - b[0]) + np.square(ys - b[1])
    -        square_dist = np.square(a[0] - b[0]) + np.square(a[1] - b[1])
    -        cosin = (square_dist - square_dist_1 - square_dist_2) / (2 * np.sqrt(square_dist_1 * square_dist_2) + eps)
    -        square_sin = 1 - np.square(cosin)
    -        square_sin = np.nan_to_num(square_sin)
    -        result = np.sqrt(square_dist_1 * square_dist_2 * square_sin / square_dist)
    -        result[cosin < 0] = np.sqrt(np.fmin(square_dist_1, square_dist_2))[cosin < 0]
    -        return result
    -
    -    def draw_thresh_map(
    -        self,
    -        polygon: np.array,
    -        canvas: np.array,
    -        mask: np.array,
    -    ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    -        """Draw a polygon treshold map on a canvas, as described in the DB paper
    -
    -        Args:
    -            polygon : array of coord., to draw the boundary of the polygon
    -            canvas : threshold map to fill with polygons
    -            mask : mask for training on threshold polygons
    -        """
    -        if polygon.ndim != 2 or polygon.shape[1] != 2:
    -            raise AttributeError("polygon should be a 2 dimensional array of coords")
    -
    -        # Augment polygon by shrink_ratio
    -        polygon_shape = Polygon(polygon)
    -        distance = polygon_shape.area * (1 - np.power(self.shrink_ratio, 2)) / polygon_shape.length
    -        subject = [tuple(coor) for coor in polygon]  # Get coord as list of tuples
    -        padding = pyclipper.PyclipperOffset()
    -        padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        padded_polygon = np.array(padding.Execute(distance)[0])
    -
    -        # Fill the mask with 1 on the new padded polygon
    -        cv2.fillPoly(mask, [padded_polygon.astype(np.int32)], 1.0)
    -
    -        # Get min/max to recover polygon after distance computation
    -        xmin = padded_polygon[:, 0].min()
    -        xmax = padded_polygon[:, 0].max()
    -        ymin = padded_polygon[:, 1].min()
    -        ymax = padded_polygon[:, 1].max()
    -        width = xmax - xmin + 1
    -        height = ymax - ymin + 1
    -        # Get absolute polygon for distance computation
    -        polygon[:, 0] = polygon[:, 0] - xmin
    -        polygon[:, 1] = polygon[:, 1] - ymin
    -        # Get absolute padded polygon
    -        xs = np.broadcast_to(np.linspace(0, width - 1, num=width).reshape(1, width), (height, width))
    -        ys = np.broadcast_to(np.linspace(0, height - 1, num=height).reshape(height, 1), (height, width))
    -
    -        # Compute distance map to fill the padded polygon
    -        distance_map = np.zeros((polygon.shape[0], height, width), dtype=np.float32)
    -        for i in range(polygon.shape[0]):
    -            j = (i + 1) % polygon.shape[0]
    -            absolute_distance = self.compute_distance(xs, ys, polygon[i], polygon[j])
    -            distance_map[i] = np.clip(absolute_distance / distance, 0, 1)
    -        distance_map = np.min(distance_map, axis=0)
    -
    -        # Clip the padded polygon inside the canvas
    -        xmin_valid = min(max(0, xmin), canvas.shape[1] - 1)
    -        xmax_valid = min(max(0, xmax), canvas.shape[1] - 1)
    -        ymin_valid = min(max(0, ymin), canvas.shape[0] - 1)
    -        ymax_valid = min(max(0, ymax), canvas.shape[0] - 1)
    -
    -        # Fill the canvas with the distances computed inside the valid padded polygon
    -        canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1] = np.fmax(
    -            1 - distance_map[
    -                ymin_valid - ymin:ymax_valid - ymin + 1,
    -                xmin_valid - xmin:xmax_valid - xmin + 1
    -            ],
    -            canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1]
    -        )
    -
    -        return polygon, canvas, mask
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor, tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.uint8)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -        thresh_target = np.zeros(output_shape, dtype=np.uint8)
    -        thresh_mask = np.ones(output_shape, dtype=np.uint8)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            polys = np.stack([
    -                abs_boxes[:, [0, 1]],
    -                abs_boxes[:, [0, 3]],
    -                abs_boxes[:, [2, 3]],
    -                abs_boxes[:, [2, 1]],
    -            ], axis=1)
    -
    -            for box, box_size, poly, is_ambiguous in zip(abs_boxes, boxes_size, polys, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -
    -                # Negative shrink for gt, as described in paper
    -                polygon = Polygon(poly)
    -                distance = polygon.area * (1 - np.power(self.shrink_ratio, 2)) / polygon.length
    -                subject = [tuple(coor) for coor in poly]
    -                padding = pyclipper.PyclipperOffset()
    -                padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -                shrinked = padding.Execute(-distance)
    -
    -                # Draw polygon on gt if it is valid
    -                if len(shrinked) == 0:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                shrinked = np.array(shrinked[0]).reshape(-1, 2)
    -                if shrinked.shape[0] <= 2 or not Polygon(shrinked).is_valid:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                cv2.fillPoly(seg_target[idx], [shrinked.astype(np.int32)], 1)
    -
    -                # Draw on both thresh map and thresh mask
    -                poly, thresh_target[idx], thresh_mask[idx] = self.draw_thresh_map(poly, thresh_target[idx],
    -                                                                                  thresh_mask[idx])
    -
    -        thresh_target = thresh_target.astype(np.float32) * (self.thresh_max - self.thresh_min) + self.thresh_min
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -        thresh_target = tf.convert_to_tensor(thresh_target, dtype=tf.float32)
    -        thresh_mask = tf.convert_to_tensor(thresh_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask, thresh_target, thresh_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        thresh_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts, masks, thresh_gts, thresh_masks from a list of boxes
    -        and a list of masks for each image. From there it computes the loss with the model output
    -
    -        Args:
    -            out_map: output feature map of the model of shape (N, H, W, C)
    -            thresh_map: threshold map of shape (N, H, W, C)
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -
    -        prob_map = tf.math.sigmoid(tf.squeeze(out_map, axis=[-1]))
    -        thresh_map = tf.math.sigmoid(tf.squeeze(thresh_map, axis=[-1]))
    -
    -        seg_target, seg_mask, thresh_target, thresh_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute balanced BCE loss for proba_map
    -        bce_scale = 5.
    -        bce_loss = tf.keras.losses.binary_crossentropy(seg_target[..., None], out_map, from_logits=True)[seg_mask]
    -
    -        neg_target = 1 - seg_target[seg_mask]
    -        positive_count = tf.math.reduce_sum(seg_target[seg_mask])
    -        negative_count = tf.math.reduce_min([tf.math.reduce_sum(neg_target), 3. * positive_count])
    -        negative_loss = bce_loss * neg_target
    -        negative_loss, _ = tf.nn.top_k(negative_loss, tf.cast(negative_count, tf.int32))
    -        sum_losses = tf.math.reduce_sum(bce_loss * seg_target[seg_mask]) + tf.math.reduce_sum(negative_loss)
    -        balanced_bce_loss = sum_losses / (positive_count + negative_count + 1e-6)
    -
    -        # Compute dice loss for approxbin_map
    -        bin_map = 1 / (1 + tf.exp(-50. * (prob_map[seg_mask] - thresh_map[seg_mask])))
    -
    -        bce_min = tf.math.reduce_min(bce_loss)
    -        weights = (bce_loss - bce_min) / (tf.math.reduce_max(bce_loss) - bce_min) + 1.
    -        inter = tf.math.reduce_sum(bin_map * seg_target[seg_mask] * weights)
    -        union = tf.math.reduce_sum(bin_map) + tf.math.reduce_sum(seg_target[seg_mask]) + 1e-8
    -        dice_loss = 1 - 2.0 * inter / union
    -
    -        # Compute l1 loss for thresh_map
    -        l1_scale = 10.
    -        if tf.reduce_any(thresh_mask):
    -            l1_loss = tf.math.reduce_mean(tf.math.abs(thresh_map[thresh_mask] - thresh_target[thresh_mask]))
    -        else:
    -            l1_loss = tf.constant(0.)
    -
    -        return l1_scale * l1_loss + bce_scale * balanced_bce_loss + dice_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        feat_maps = self.feat_extractor(x, **kwargs)
    -        feat_concat = self.fpn(feat_maps, **kwargs)
    -        logits = self.probability_head(feat_concat, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            thresh_map = self.threshold_head(feat_concat, **kwargs)
    -            loss = self.compute_loss(logits, thresh_map, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _db_resnet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> DBNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['fpn_channels'] = kwargs.get('fpn_channels', _cfg['fpn_channels'])
    -
    -    # Feature extractor
    -    resnet = tf.keras.applications.__dict__[_cfg['backbone']](
    -        include_top=False,
    -        weights=None,
    -        input_shape=_cfg['input_shape'],
    -        pooling=None,
    -    )
    -
    -    feat_extractor = IntermediateLayerGetter(
    -        resnet,
    -        _cfg['fpn_layers'],
    -    )
    -
    -    kwargs['fpn_channels'] = _cfg['fpn_channels']
    -
    -    # Build the model
    -    model = DBNet(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def db_resnet50(pretrained: bool = False, **kwargs: Any) -> DBNet: - """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" - <https://arxiv.org/pdf/1911.08947.pdf>`_, using a ResNet-50 backbone. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _db_resnet('db_resnet50', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html b/v0.3.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html index 9145c7c3fd..66cef8663d 100644 --- a/v0.3.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html +++ b/v0.3.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.differentiable_binarization.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     # Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
     
     from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers
    -from typing import List, Tuple, Optional, Any, Dict
    +from typing import Any, Dict, List, Optional, Tuple
     
    +import numpy as np
    +import tensorflow as tf
    +from tensorflow.keras import Model, Sequential, layers, losses
    +from tensorflow.keras.applications import ResNet50
    +
    +from doctr.file_utils import CLASS_NAME
    +from doctr.models.utils import (
    +    IntermediateLayerGetter,
    +    _bf16_to_float32,
    +    _build_model,
    +    conv_sequence,
    +    load_pretrained_params,
    +)
     from doctr.utils.repr import NestedObject
    -from doctr.models.utils import IntermediateLayerGetter, load_pretrained_params, conv_sequence
    +
    +from ...classification import mobilenet_v3_large
     from .base import DBPostProcessor, _DBNet
     
    -__all__ = ['DBNet', 'db_resnet50']
    +__all__ = ["DBNet", "db_resnet50", "db_mobilenet_v3_large"]
     
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'db_resnet50': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'backbone': 'ResNet50',
    -        'fpn_layers': ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"],
    -        'fpn_channels': 128,
    -        'input_shape': (1024, 1024, 3),
    -        'rotated_bbox': False,
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.2.0/db_resnet50-adcafc63.zip',
    +    "db_resnet50": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/db_resnet50-649fa22b.weights.h5&src=0",
    +    },
    +    "db_mobilenet_v3_large": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/db_mobilenet_v3_large-ee2e1dbe.weights.h5&src=0",
         },
     }
     
    @@ -313,6 +348,7 @@ 

    Source code for doctr.models.detection.differentiable_binarization.tensorflo <https://arxiv.org/pdf/1612.03144.pdf>`_. Args: + ---- channels: number of channel to output """ @@ -322,9 +358,9 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo ) -> None: super().__init__() self.channels = channels - self.upsample = layers.UpSampling2D(size=(2, 2), interpolation='nearest') - self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer='he_normal') for _ in range(4)] - self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2 ** idx) for idx in range(4)] + self.upsample = layers.UpSampling2D(size=(2, 2), interpolation="nearest") + self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer="he_normal") for _ in range(4)] + self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2**idx) for idx in range(4)] @staticmethod def build_upsampling( @@ -334,20 +370,21 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo """Module which performs a 3x3 convolution followed by up-sampling Args: + ---- channels: number of output channels dilation_factor (int): dilation factor to scale the convolution output before concatenation Returns: + ------- a keras.layers.Layer object, wrapping these operations in a sequential module """ - - _layers = conv_sequence(channels, 'relu', True, kernel_size=3) + _layers = conv_sequence(channels, "relu", True, kernel_size=3) if dilation_factor > 1: - _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation='nearest')) + _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation="nearest")) - module = keras.Sequential(_layers) + module = Sequential(_layers) return module @@ -359,7 +396,6 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo x: List[tf.Tensor], **kwargs: Any, ) -> tf.Tensor: - # Channel mapping results = [block(fmap, **kwargs) for block, fmap in zip(self.inner_blocks, x)] # Upsample & sum @@ -371,200 +407,324 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo return layers.concatenate(results) -class DBNet(_DBNet, keras.Model, NestedObject): +class DBNet(_DBNet, Model, NestedObject): """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" <https://arxiv.org/pdf/1911.08947.pdf>`_. Args: + ---- feature extractor: the backbone serving as feature extractor fpn_channels: number of channels each extracted feature maps is mapped to + bin_thresh: threshold for binarization + box_thresh: minimal objectness score to consider a box + assume_straight_pages: if True, fit straight bounding boxes only + exportable: onnx exportable returns only logits + cfg: the configuration dict of the model + class_names: list of class names """ - _children_names: List[str] = ['feat_extractor', 'fpn', 'probability_head', 'threshold_head', 'postprocessor'] + _children_names: List[str] = ["feat_extractor", "fpn", "probability_head", "threshold_head", "postprocessor"] def __init__( self, feature_extractor: IntermediateLayerGetter, - fpn_channels: int = 128, - rotated_bbox: bool = False, + fpn_channels: int = 128, # to be set to 256 to represent the author's initial idea + bin_thresh: float = 0.3, + box_thresh: float = 0.1, + assume_straight_pages: bool = True, + exportable: bool = False, cfg: Optional[Dict[str, Any]] = None, + class_names: List[str] = [CLASS_NAME], ) -> None: - super().__init__() + self.class_names = class_names + num_classes: int = len(self.class_names) self.cfg = cfg self.feat_extractor = feature_extractor - self.rotated_bbox = rotated_bbox + self.exportable = exportable + self.assume_straight_pages = assume_straight_pages self.fpn = FeaturePyramidNetwork(channels=fpn_channels) # Initialize kernels _inputs = [layers.Input(shape=in_shape[1:]) for in_shape in self.feat_extractor.output_shape] output_shape = tuple(self.fpn(_inputs).shape) - self.probability_head = keras.Sequential( - [ - *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]), - layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'), - layers.BatchNormalization(), - layers.Activation('relu'), - layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'), - ] + self.probability_head = Sequential([ + *conv_sequence(64, "relu", True, kernel_size=3, input_shape=output_shape[1:]), + layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer="he_normal"), + layers.BatchNormalization(), + layers.Activation("relu"), + layers.Conv2DTranspose(num_classes, 2, strides=2, kernel_initializer="he_normal"), + ]) + self.threshold_head = Sequential([ + *conv_sequence(64, "relu", True, kernel_size=3, input_shape=output_shape[1:]), + layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer="he_normal"), + layers.BatchNormalization(), + layers.Activation("relu"), + layers.Conv2DTranspose(num_classes, 2, strides=2, kernel_initializer="he_normal"), + ]) + + self.postprocessor = DBPostProcessor( + assume_straight_pages=assume_straight_pages, bin_thresh=bin_thresh, box_thresh=box_thresh ) - self.threshold_head = keras.Sequential( - [ - *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]), - layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'), - layers.BatchNormalization(), - layers.Activation('relu'), - layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'), - ] - ) - - self.postprocessor = DBPostProcessor(rotated_bbox=rotated_bbox) def compute_loss( self, out_map: tf.Tensor, thresh_map: tf.Tensor, - target: List[Dict[str, Any]] + target: List[Dict[str, np.ndarray]], + gamma: float = 2.0, + alpha: float = 0.5, + eps: float = 1e-8, ) -> tf.Tensor: """Compute a batch of gts, masks, thresh_gts, thresh_masks from a list of boxes and a list of masks for each image. From there it computes the loss with the model output Args: + ---- out_map: output feature map of the model of shape (N, H, W, C) thresh_map: threshold map of shape (N, H, W, C) target: list of dictionary where each dict has a `boxes` and a `flags` entry + gamma: modulating factor in the focal loss formula + alpha: balancing factor in the focal loss formula + eps: epsilon factor in dice loss Returns: + ------- A loss tensor """ + if gamma < 0: + raise ValueError("Value of gamma should be greater than or equal to zero.") - prob_map = tf.math.sigmoid(tf.squeeze(out_map, axis=[-1])) - thresh_map = tf.math.sigmoid(tf.squeeze(thresh_map, axis=[-1])) + prob_map = tf.math.sigmoid(out_map) + thresh_map = tf.math.sigmoid(thresh_map) - seg_target, seg_mask, thresh_target, thresh_mask = self.compute_target(target, out_map.shape[:3]) - seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32) + seg_target, seg_mask, thresh_target, thresh_mask = self.build_target(target, out_map.shape[1:], True) + seg_target = tf.convert_to_tensor(seg_target, dtype=out_map.dtype) seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool) - thresh_target = tf.convert_to_tensor(thresh_target, dtype=tf.float32) + seg_mask = tf.cast(seg_mask, tf.float32) + thresh_target = tf.convert_to_tensor(thresh_target, dtype=out_map.dtype) thresh_mask = tf.convert_to_tensor(thresh_mask, dtype=tf.bool) - # Compute balanced BCE loss for proba_map - bce_scale = 5. - bce_loss = tf.keras.losses.binary_crossentropy(seg_target[..., None], out_map, from_logits=True)[seg_mask] - - neg_target = 1 - seg_target[seg_mask] - positive_count = tf.math.reduce_sum(seg_target[seg_mask]) - negative_count = tf.math.reduce_min([tf.math.reduce_sum(neg_target), 3. * positive_count]) - negative_loss = bce_loss * neg_target - negative_loss, _ = tf.nn.top_k(negative_loss, tf.cast(negative_count, tf.int32)) - sum_losses = tf.math.reduce_sum(bce_loss * seg_target[seg_mask]) + tf.math.reduce_sum(negative_loss) - balanced_bce_loss = sum_losses / (positive_count + negative_count + 1e-6) - - # Compute dice loss for approxbin_map - bin_map = 1 / (1 + tf.exp(-50. * (prob_map[seg_mask] - thresh_map[seg_mask]))) - - bce_min = tf.math.reduce_min(bce_loss) - weights = (bce_loss - bce_min) / (tf.math.reduce_max(bce_loss) - bce_min) + 1. - inter = tf.math.reduce_sum(bin_map * seg_target[seg_mask] * weights) - union = tf.math.reduce_sum(bin_map) + tf.math.reduce_sum(seg_target[seg_mask]) + 1e-8 - dice_loss = 1 - 2.0 * inter / union + # Focal loss + focal_scale = 10.0 + bce_loss = losses.binary_crossentropy(seg_target[..., None], out_map[..., None], from_logits=True) + + # Convert logits to prob, compute gamma factor + p_t = (seg_target * prob_map) + ((1 - seg_target) * (1 - prob_map)) + alpha_t = seg_target * alpha + (1 - seg_target) * (1 - alpha) + # Unreduced loss + focal_loss = alpha_t * (1 - p_t) ** gamma * bce_loss + # Class reduced + focal_loss = tf.reduce_sum(seg_mask * focal_loss, (0, 1, 2, 3)) / tf.reduce_sum(seg_mask, (0, 1, 2, 3)) + + # Compute dice loss for each class or for approx binary_map + if len(self.class_names) > 1: + dice_map = tf.nn.softmax(out_map, axis=-1) + else: + # compute binary map instead + dice_map = 1.0 / (1.0 + tf.exp(-50 * (prob_map - thresh_map))) + # Class-reduced dice loss + inter = tf.reduce_sum(seg_mask * dice_map * seg_target, axis=[0, 1, 2]) + cardinality = tf.reduce_sum(seg_mask * (dice_map + seg_target), axis=[0, 1, 2]) + dice_loss = tf.reduce_mean(1 - 2 * inter / (cardinality + eps)) # Compute l1 loss for thresh_map - l1_scale = 10. if tf.reduce_any(thresh_mask): - l1_loss = tf.math.reduce_mean(tf.math.abs(thresh_map[thresh_mask] - thresh_target[thresh_mask])) + thresh_mask = tf.cast(thresh_mask, tf.float32) + l1_loss = tf.reduce_sum(tf.abs(thresh_map - thresh_target) * thresh_mask) / ( + tf.reduce_sum(thresh_mask) + eps + ) else: - l1_loss = tf.constant(0.) + l1_loss = tf.constant(0.0) - return l1_scale * l1_loss + bce_scale * balanced_bce_loss + dice_loss + return l1_loss + focal_scale * focal_loss + dice_loss def call( self, x: tf.Tensor, - target: Optional[List[Dict[str, Any]]] = None, + target: Optional[List[Dict[str, np.ndarray]]] = None, return_model_output: bool = False, - return_boxes: bool = False, + return_preds: bool = False, **kwargs: Any, ) -> Dict[str, Any]: - feat_maps = self.feat_extractor(x, **kwargs) feat_concat = self.fpn(feat_maps, **kwargs) logits = self.probability_head(feat_concat, **kwargs) out: Dict[str, tf.Tensor] = {} - if return_model_output or target is None or return_boxes: - prob_map = tf.math.sigmoid(logits) + if self.exportable: + out["logits"] = logits + return out + + if return_model_output or target is None or return_preds: + prob_map = _bf16_to_float32(tf.math.sigmoid(logits)) if return_model_output: out["out_map"] = prob_map - if target is None or return_boxes: - # Post-process boxes - out["preds"] = self.postprocessor(tf.squeeze(prob_map, axis=-1).numpy()) + if target is None or return_preds: + # Post-process boxes (keep only text predictions) + out["preds"] = [dict(zip(self.class_names, preds)) for preds in self.postprocessor(prob_map.numpy())] if target is not None: thresh_map = self.threshold_head(feat_concat, **kwargs) loss = self.compute_loss(logits, thresh_map, target) - out['loss'] = loss + out["loss"] = loss return out -def _db_resnet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> DBNet: +def _db_resnet( + arch: str, + pretrained: bool, + backbone_fn, + fpn_layers: List[str], + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> DBNet: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['fpn_channels'] = kwargs.get('fpn_channels', _cfg['fpn_channels']) - _cfg['rotated_bbox'] = kwargs.get('rotated_bbox', _cfg['rotated_bbox']) + _cfg["input_shape"] = input_shape or _cfg["input_shape"] + if not kwargs.get("class_names", None): + kwargs["class_names"] = _cfg.get("class_names", [CLASS_NAME]) + else: + kwargs["class_names"] = sorted(kwargs["class_names"]) # Feature extractor - resnet = tf.keras.applications.__dict__[_cfg['backbone']]( - include_top=False, - weights=None, - input_shape=_cfg['input_shape'], - pooling=None, + feat_extractor = IntermediateLayerGetter( + backbone_fn( + weights="imagenet" if pretrained_backbone else None, + include_top=False, + pooling=None, + input_shape=_cfg["input_shape"], + ), + fpn_layers, ) + # Build the model + model = DBNet(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) + + # Load pretrained parameters + if pretrained: + # The given class_names differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, + _cfg["url"], + skip_mismatch=kwargs["class_names"] != default_cfgs[arch].get("class_names", [CLASS_NAME]), + ) + + return model + + +def _db_mobilenet( + arch: str, + pretrained: bool, + backbone_fn, + fpn_layers: List[str], + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> DBNet: + pretrained_backbone = pretrained_backbone and not pretrained + + # Patch the config + _cfg = deepcopy(default_cfgs[arch]) + _cfg["input_shape"] = input_shape or _cfg["input_shape"] + if not kwargs.get("class_names", None): + kwargs["class_names"] = default_cfgs[arch].get("class_names", [CLASS_NAME]) + else: + kwargs["class_names"] = sorted(kwargs["class_names"]) + + # Feature extractor feat_extractor = IntermediateLayerGetter( - resnet, - _cfg['fpn_layers'], + backbone_fn( + input_shape=_cfg["input_shape"], + include_top=False, + pretrained=pretrained_backbone, + ), + fpn_layers, ) - kwargs['fpn_channels'] = _cfg['fpn_channels'] - kwargs['rotated_bbox'] = _cfg['rotated_bbox'] - # Build the model model = DBNet(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) # Load pretrained parameters if pretrained: - load_pretrained_params(model, _cfg['url']) + # The given class_names differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, + _cfg["url"], + skip_mismatch=kwargs["class_names"] != default_cfgs[arch].get("class_names", [CLASS_NAME]), + ) return model
    -[docs] +[docs] def db_resnet50(pretrained: bool = False, **kwargs: Any) -> DBNet: """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" <https://arxiv.org/pdf/1911.08947.pdf>`_, using a ResNet-50 backbone. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import db_resnet50 + >>> model = db_resnet50(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the DBNet architecture Returns: + ------- text detection architecture """ + return _db_resnet( + "db_resnet50", + pretrained, + ResNet50, + ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"], + **kwargs, + )
    + + + +
    +[docs] +def db_mobilenet_v3_large(pretrained: bool = False, **kwargs: Any) -> DBNet: + """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" + <https://arxiv.org/pdf/1911.08947.pdf>`_, using a mobilenet v3 large backbone. + + >>> import tensorflow as tf + >>> from doctr.models import db_mobilenet_v3_large + >>> model = db_mobilenet_v3_large(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) - return _db_resnet('db_resnet50', pretrained, **kwargs)
    + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the DBNet architecture + + Returns: + ------- + text detection architecture + """ + return _db_mobilenet( + "db_mobilenet_v3_large", + pretrained, + mobilenet_v3_large, + ["inverted_2", "inverted_5", "inverted_11", "final_block"], + **kwargs, + )

    @@ -598,8 +758,8 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo - - + + diff --git a/v0.3.1/_modules/doctr/models/detection/fast/tensorflow.html b/v0.3.1/_modules/doctr/models/detection/fast/tensorflow.html index 5b84d2dea1..65e1a77af8 100644 --- a/v0.3.1/_modules/doctr/models/detection/fast/tensorflow.html +++ b/v0.3.1/_modules/doctr/models/detection/fast/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.fast.tensorflow - docTR documentation @@ -769,7 +769,7 @@

    Source code for doctr.models.detection.fast.tensorflow

    - + diff --git a/v0.3.1/_modules/doctr/models/detection/linknet.html b/v0.3.1/_modules/doctr/models/detection/linknet.html deleted file mode 100644 index 129cfdce8b..0000000000 --- a/v0.3.1/_modules/doctr/models/detection/linknet.html +++ /dev/null @@ -1,644 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.linknet - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.linknet

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -import numpy as np
    -import cv2
    -from tensorflow.keras import layers, Sequential
    -from typing import Dict, Any, Tuple, Optional, List
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..backbones import ResnetStage
    -from ..utils import conv_sequence, load_pretrained_params
    -from ...utils.repr import NestedObject
    -
    -__all__ = ['LinkNet', 'linknet', 'LinkNetPostProcessor']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'linknet': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'out_chan': 1,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'LinkNetPostProcessor',
    -        'url': None,
    -    },
    -}
    -
    -
    -class LinkNetPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for LinkNet model.
    -
    -    Args:
    -        min_size_box: minimal length (pix) to keep a box
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        min_size_box: int = 3,
    -        bin_thresh: float = 0.15,
    -        box_thresh: float = 0.1,
    -    ) -> None:
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map: find connected components then filter boxes
    -
    -        Args:
    -            pred: Pred map from differentiable linknet output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        label_num, labelimage = cv2.connectedComponents(bitmap.astype(np.uint8), connectivity=4)
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        for label in range(1, label_num + 1):
    -            points = np.array(np.where(labelimage == label)[::-1]).T
    -            if points.shape[0] < 4:  # remove polygons with 3 points or less
    -                continue
    -            score = self.box_score(pred, points.reshape(-1, 2))
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            x, y, w, h = cv2.boundingRect(points)
    -            if min(w, h) < min_size_box:  # filter too small boxes
    -                continue
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -def decoder_block(in_chan: int, out_chan: int) -> Sequential:
    -    """Creates a LinkNet decoder block"""
    -
    -    return Sequential([
    -        *conv_sequence(in_chan // 4, 'relu', True, kernel_size=1),
    -        layers.Conv2DTranspose(
    -            filters=in_chan // 4,
    -            kernel_size=3,
    -            strides=2,
    -            padding="same",
    -            use_bias=False,
    -            kernel_initializer='he_normal'
    -        ),
    -        layers.BatchNormalization(),
    -        layers.Activation('relu'),
    -        *conv_sequence(out_chan, 'relu', True, kernel_size=1),
    -    ])
    -
    -
    -class LinkNetFPN(layers.Layer, NestedObject):
    -    """LinkNet Encoder-Decoder module
    -
    -    """
    -
    -    def __init__(
    -        self,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.encoder_1 = ResnetStage(num_blocks=2, output_channels=64, downsample=True)
    -        self.encoder_2 = ResnetStage(num_blocks=2, output_channels=128, downsample=True)
    -        self.encoder_3 = ResnetStage(num_blocks=2, output_channels=256, downsample=True)
    -        self.encoder_4 = ResnetStage(num_blocks=2, output_channels=512, downsample=True)
    -        self.decoder_1 = decoder_block(in_chan=64, out_chan=64)
    -        self.decoder_2 = decoder_block(in_chan=128, out_chan=64)
    -        self.decoder_3 = decoder_block(in_chan=256, out_chan=128)
    -        self.decoder_4 = decoder_block(in_chan=512, out_chan=256)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor
    -    ) -> tf.Tensor:
    -        x_1 = self.encoder_1(x)
    -        x_2 = self.encoder_2(x_1)
    -        x_3 = self.encoder_3(x_2)
    -        x_4 = self.encoder_4(x_3)
    -        y_4 = self.decoder_4(x_4)
    -        y_3 = self.decoder_3(y_4 + x_3)
    -        y_2 = self.decoder_2(y_3 + x_2)
    -        y_1 = self.decoder_1(y_2 + x_1)
    -        return y_1
    -
    -
    -class LinkNet(DetectionModel, NestedObject):
    -    """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation"
    -    <https://arxiv.org/pdf/1707.03718.pdf>`_.
    -
    -    Args:
    -        out_chan: number of channels for the output
    -    """
    -
    -    _children_names: List[str] = ['stem', 'fpn', 'classifier', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        out_chan: int = 1,
    -        input_shape: Tuple[int, int, int] = (512, 512, 3),
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(cfg=cfg)
    -
    -        self.stem = Sequential([
    -            *conv_sequence(64, 'relu', True, strides=2, kernel_size=7, input_shape=input_shape),
    -            layers.MaxPool2D(pool_size=(3, 3), strides=2, padding='same'),
    -        ])
    -
    -        self.fpn = LinkNetFPN()
    -
    -        self.classifier = Sequential([
    -            layers.Conv2DTranspose(
    -                filters=32,
    -                kernel_size=3,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -            layers.BatchNormalization(),
    -            layers.Activation('relu'),
    -            *conv_sequence(32, 'relu', True, strides=1, kernel_size=3),
    -            layers.Conv2DTranspose(
    -                filters=out_chan,
    -                kernel_size=2,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -        ])
    -
    -        self.min_size_box = 3
    -
    -        self.postprocessor = LinkNetPostProcessor()
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.bool)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            for box, box_size, is_ambiguous in zip(abs_boxes, boxes_size, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Fill polygon with 1
    -                seg_target[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = True
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts and masks from a list of boxes and a list of masks for each image
    -        Then, it computes the loss function with proba_map, gts and masks
    -
    -        Args:
    -            out_map: output feature map of the model of shape N x H x W x 1
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -        seg_target, seg_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute BCE loss
    -        return tf.math.reduce_mean(tf.keras.losses.binary_crossentropy(
    -            seg_target[seg_mask],
    -            tf.squeeze(out_map, axis=[-1])[seg_mask],
    -            from_logits=True
    -        ))
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        logits = self.stem(x)
    -        logits = self.fpn(logits)
    -        logits = self.classifier(logits)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            loss = self.compute_loss(logits, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _linknet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> LinkNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['out_chan'] = kwargs.get('out_chan', _cfg['out_chan'])
    -
    -    kwargs['out_chan'] = _cfg['out_chan']
    -    kwargs['input_shape'] = _cfg['input_shape']
    -    # Build the model
    -    model = LinkNet(cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def linknet(pretrained: bool = False, **kwargs: Any) -> LinkNet: - """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" - <https://arxiv.org/pdf/1707.03718.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import linknet - >>> model = linknet(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _linknet('linknet', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.1/_modules/doctr/models/detection/linknet/tensorflow.html b/v0.3.1/_modules/doctr/models/detection/linknet/tensorflow.html index cd4f446673..ce995f99d4 100644 --- a/v0.3.1/_modules/doctr/models/detection/linknet/tensorflow.html +++ b/v0.3.1/_modules/doctr/models/detection/linknet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.linknet.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.detection.linknet.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     # Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
     
     from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers, Sequential
    -from typing import Dict, Any, Tuple, Optional, List
    +from typing import Any, Dict, List, Optional, Tuple
     
    +import numpy as np
    +import tensorflow as tf
    +from tensorflow.keras import Model, Sequential, layers, losses
    +
    +from doctr.file_utils import CLASS_NAME
    +from doctr.models.classification import resnet18, resnet34, resnet50
    +from doctr.models.utils import (
    +    IntermediateLayerGetter,
    +    _bf16_to_float32,
    +    _build_model,
    +    conv_sequence,
    +    load_pretrained_params,
    +)
     from doctr.utils.repr import NestedObject
    -from doctr.models.backbones import ResnetStage
    -from doctr.models.utils import conv_sequence, load_pretrained_params
    -from .base import LinkNetPostProcessor, _LinkNet
     
    -__all__ = ['LinkNet', 'linknet16']
    +from .base import LinkNetPostProcessor, _LinkNet
     
    +__all__ = ["LinkNet", "linknet_resnet18", "linknet_resnet34", "linknet_resnet50"]
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'linknet16': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'num_classes': 1,
    -        'input_shape': (1024, 1024, 3),
    -        'rotated_bbox': False,
    -        'url': None,
    +    "linknet_resnet18": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/linknet_resnet18-615a82c5.weights.h5&src=0",
    +    },
    +    "linknet_resnet34": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/linknet_resnet34-9d772be5.weights.h5&src=0",
    +    },
    +    "linknet_resnet50": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/linknet_resnet50-6bf6c8b5.weights.h5&src=0",
         },
     }
     
     
    -def decoder_block(in_chan: int, out_chan: int) -> Sequential:
    +def decoder_block(in_chan: int, out_chan: int, stride: int, **kwargs: Any) -> Sequential:
         """Creates a LinkNet decoder block"""
    -
         return Sequential([
    -        *conv_sequence(in_chan // 4, 'relu', True, kernel_size=1),
    +        *conv_sequence(in_chan // 4, "relu", True, kernel_size=1, **kwargs),
             layers.Conv2DTranspose(
                 filters=in_chan // 4,
                 kernel_size=3,
    -            strides=2,
    +            strides=stride,
                 padding="same",
                 use_bias=False,
    -            kernel_initializer='he_normal'
    +            kernel_initializer="he_normal",
             ),
             layers.BatchNormalization(),
    -        layers.Activation('relu'),
    -        *conv_sequence(out_chan, 'relu', True, kernel_size=1),
    +        layers.Activation("relu"),
    +        *conv_sequence(out_chan, "relu", True, kernel_size=1),
         ])
     
     
    -class LinkNetFPN(layers.Layer, NestedObject):
    -    """LinkNet Encoder-Decoder module"""
    +class LinkNetFPN(Model, NestedObject):
    +    """LinkNet Decoder module"""
     
         def __init__(
             self,
    +        out_chans: int,
    +        in_shapes: List[Tuple[int, ...]],
         ) -> None:
    -
             super().__init__()
    -        self.encoder_1 = ResnetStage(num_blocks=2, output_channels=64, downsample=True)
    -        self.encoder_2 = ResnetStage(num_blocks=2, output_channels=128, downsample=True)
    -        self.encoder_3 = ResnetStage(num_blocks=2, output_channels=256, downsample=True)
    -        self.encoder_4 = ResnetStage(num_blocks=2, output_channels=512, downsample=True)
    -        self.decoder_1 = decoder_block(in_chan=64, out_chan=64)
    -        self.decoder_2 = decoder_block(in_chan=128, out_chan=64)
    -        self.decoder_3 = decoder_block(in_chan=256, out_chan=128)
    -        self.decoder_4 = decoder_block(in_chan=512, out_chan=256)
    +        self.out_chans = out_chans
    +        strides = [2] * (len(in_shapes) - 1) + [1]
    +        i_chans = [s[-1] for s in in_shapes[::-1]]
    +        o_chans = i_chans[1:] + [out_chans]
    +        self.decoders = [
    +            decoder_block(in_chan, out_chan, s, input_shape=in_shape)
    +            for in_chan, out_chan, s, in_shape in zip(i_chans, o_chans, strides, in_shapes[::-1])
    +        ]
    +
    +    def call(self, x: List[tf.Tensor], **kwargs: Any) -> tf.Tensor:
    +        out = 0
    +        for decoder, fmap in zip(self.decoders, x[::-1]):
    +            out = decoder(out + fmap, **kwargs)
    +        return out
     
    -    def call(
    -        self,
    -        x: tf.Tensor
    -    ) -> tf.Tensor:
    -        x_1 = self.encoder_1(x)
    -        x_2 = self.encoder_2(x_1)
    -        x_3 = self.encoder_3(x_2)
    -        x_4 = self.encoder_4(x_3)
    -        y_4 = self.decoder_4(x_4)
    -        y_3 = self.decoder_3(y_4 + x_3)
    -        y_2 = self.decoder_2(y_3 + x_2)
    -        y_1 = self.decoder_1(y_2 + x_1)
    -        return y_1
    -
    -
    -class LinkNet(_LinkNet, keras.Model):
    +    def extra_repr(self) -> str:
    +        return f"out_chans={self.out_chans}"
    +
    +
    +class LinkNet(_LinkNet, Model):
         """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation"
         <https://arxiv.org/pdf/1707.03718.pdf>`_.
     
         Args:
    -        num_classes: number of channels for the output
    +    ----
    +        feature extractor: the backbone serving as feature extractor
    +        fpn_channels: number of channels each extracted feature maps is mapped to
    +        bin_thresh: threshold for binarization of the output feature map
    +        box_thresh: minimal objectness score to consider a box
    +        assume_straight_pages: if True, fit straight bounding boxes only
    +        exportable: onnx exportable returns only logits
    +        cfg: the configuration dict of the model
    +        class_names: list of class names
         """
     
    -    _children_names: List[str] = ['stem', 'fpn', 'classifier', 'postprocessor']
    +    _children_names: List[str] = ["feat_extractor", "fpn", "classifier", "postprocessor"]
     
         def __init__(
             self,
    -        num_classes: int = 1,
    -        input_shape: Tuple[int, int, int] = (512, 512, 3),
    -        rotated_bbox: bool = False,
    +        feat_extractor: IntermediateLayerGetter,
    +        fpn_channels: int = 64,
    +        bin_thresh: float = 0.1,
    +        box_thresh: float = 0.1,
    +        assume_straight_pages: bool = True,
    +        exportable: bool = False,
             cfg: Optional[Dict[str, Any]] = None,
    +        class_names: List[str] = [CLASS_NAME],
         ) -> None:
             super().__init__(cfg=cfg)
     
    -        self.rotated_bbox = rotated_bbox
    +        self.class_names = class_names
    +        num_classes: int = len(self.class_names)
     
    -        self.stem = Sequential([
    -            *conv_sequence(64, 'relu', True, strides=2, kernel_size=7, input_shape=input_shape),
    -            layers.MaxPool2D(pool_size=(3, 3), strides=2, padding='same'),
    -        ])
    +        self.exportable = exportable
    +        self.assume_straight_pages = assume_straight_pages
    +
    +        self.feat_extractor = feat_extractor
     
    -        self.fpn = LinkNetFPN()
    +        self.fpn = LinkNetFPN(fpn_channels, [_shape[1:] for _shape in self.feat_extractor.output_shape])
    +        self.fpn.build(self.feat_extractor.output_shape)
     
             self.classifier = Sequential([
                 layers.Conv2DTranspose(
    @@ -393,154 +442,246 @@ 

    Source code for doctr.models.detection.linknet.tensorflow

    strides=2, padding="same", use_bias=False, - kernel_initializer='he_normal' + kernel_initializer="he_normal", + input_shape=self.fpn.decoders[-1].output_shape[1:], ), layers.BatchNormalization(), - layers.Activation('relu'), - *conv_sequence(32, 'relu', True, strides=1, kernel_size=3), + layers.Activation("relu"), + *conv_sequence(32, "relu", True, kernel_size=3, strides=1), layers.Conv2DTranspose( filters=num_classes, kernel_size=2, strides=2, padding="same", - use_bias=False, - kernel_initializer='he_normal' + use_bias=True, + kernel_initializer="he_normal", ), ]) - self.postprocessor = LinkNetPostProcessor(rotated_bbox=rotated_bbox) + self.postprocessor = LinkNetPostProcessor( + assume_straight_pages=assume_straight_pages, bin_thresh=bin_thresh, box_thresh=box_thresh + ) def compute_loss( self, out_map: tf.Tensor, - target: List[Dict[str, Any]], - focal_loss: bool = False, - alpha: float = .5, - gamma: float = 2., - edge_factor: float = 2., + target: List[Dict[str, np.ndarray]], + gamma: float = 2.0, + alpha: float = 0.5, + eps: float = 1e-8, ) -> tf.Tensor: """Compute linknet loss, BCE with boosted box edges or focal loss. Focal loss implementation based on <https://github.com/tensorflow/addons/>`_. Args: + ---- out_map: output feature map of the model of shape N x H x W x 1 target: list of dictionary where each dict has a `boxes` and a `flags` entry - focal_loss: if True, use focal loss instead of BCE - edge_factor: boost factor for box edges (in case of BCE) + gamma: modulating factor in the focal loss formula alpha: balancing factor in the focal loss formula - gammma: modulating factor in the focal loss formula + eps: epsilon factor in dice loss Returns: + ------- A loss tensor """ - seg_target, seg_mask, edge_mask = self.compute_target(target, out_map.shape[:3]) - seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32) - edge_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool) + seg_target, seg_mask = self.build_target(target, out_map.shape[1:], True) + seg_target = tf.convert_to_tensor(seg_target, dtype=out_map.dtype) seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool) - - # Get the cross_entropy for each entry - bce = tf.keras.losses.binary_crossentropy( - seg_target[seg_mask], - tf.squeeze(out_map, axis=[-1])[seg_mask], - from_logits=True) - - if focal_loss: - if gamma and gamma < 0: - raise ValueError("Value of gamma should be greater than or equal to zero.") - - # Convert logits to prob, compute gamma factor - pred_prob = tf.sigmoid(tf.squeeze(out_map, axis=[-1])[seg_mask]) - p_t = (seg_target[seg_mask] * pred_prob) + ((1 - seg_target[seg_mask]) * (1 - pred_prob)) - modulating_factor = tf.pow((1.0 - p_t), gamma) - - # Compute alpha factor - alpha_factor = seg_target[seg_mask] * alpha + (1 - seg_target[seg_mask]) * (1 - alpha) - - # compute the final loss - loss = tf.reduce_mean(alpha_factor * modulating_factor * bce) - - else: - # Compute BCE loss with highlighted edges - loss = tf.math.multiply( - 1 + (edge_factor - 1) * tf.cast(edge_mask, tf.float32), - bce - ) - loss = tf.reduce_mean(loss) - - return loss + seg_mask = tf.cast(seg_mask, tf.float32) + + bce_loss = losses.binary_crossentropy(seg_target[..., None], out_map[..., None], from_logits=True) + proba_map = tf.sigmoid(out_map) + + # Focal loss + if gamma < 0: + raise ValueError("Value of gamma should be greater than or equal to zero.") + # Convert logits to prob, compute gamma factor + p_t = (seg_target * proba_map) + ((1 - seg_target) * (1 - proba_map)) + alpha_t = seg_target * alpha + (1 - seg_target) * (1 - alpha) + # Unreduced loss + focal_loss = alpha_t * (1 - p_t) ** gamma * bce_loss + # Class reduced + focal_loss = tf.reduce_sum(seg_mask * focal_loss, (0, 1, 2, 3)) / tf.reduce_sum(seg_mask, (0, 1, 2, 3)) + + # Compute dice loss for each class + dice_map = tf.nn.softmax(out_map, axis=-1) if len(self.class_names) > 1 else proba_map + # Class-reduced dice loss + inter = tf.reduce_sum(seg_mask * dice_map * seg_target, axis=[0, 1, 2]) + cardinality = tf.reduce_sum(seg_mask * (dice_map + seg_target), axis=[0, 1, 2]) + dice_loss = tf.reduce_mean(1 - 2 * inter / (cardinality + eps)) + + return focal_loss + dice_loss def call( self, x: tf.Tensor, - target: Optional[List[Dict[str, Any]]] = None, + target: Optional[List[Dict[str, np.ndarray]]] = None, return_model_output: bool = False, - return_boxes: bool = False, - focal_loss: bool = True, + return_preds: bool = False, **kwargs: Any, ) -> Dict[str, Any]: - - logits = self.stem(x) - logits = self.fpn(logits) - logits = self.classifier(logits) + feat_maps = self.feat_extractor(x, **kwargs) + logits = self.fpn(feat_maps, **kwargs) + logits = self.classifier(logits, **kwargs) out: Dict[str, tf.Tensor] = {} - if return_model_output or target is None or return_boxes: - prob_map = tf.math.sigmoid(logits) + if self.exportable: + out["logits"] = logits + return out + + if return_model_output or target is None or return_preds: + prob_map = _bf16_to_float32(tf.math.sigmoid(logits)) + if return_model_output: out["out_map"] = prob_map - if target is None or return_boxes: + if target is None or return_preds: # Post-process boxes - out["preds"] = self.postprocessor(tf.squeeze(prob_map, axis=-1).numpy()) + out["preds"] = [dict(zip(self.class_names, preds)) for preds in self.postprocessor(prob_map.numpy())] if target is not None: - loss = self.compute_loss(logits, target, focal_loss) - out['loss'] = loss + loss = self.compute_loss(logits, target) + out["loss"] = loss return out -def _linknet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> LinkNet: +def _linknet( + arch: str, + pretrained: bool, + backbone_fn, + fpn_layers: List[str], + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> LinkNet: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['num_classes'] = kwargs.get('num_classes', _cfg['num_classes']) - _cfg['rotated_bbox'] = kwargs.get('rotated_bbox', _cfg['rotated_bbox']) + _cfg["input_shape"] = input_shape or default_cfgs[arch]["input_shape"] + if not kwargs.get("class_names", None): + kwargs["class_names"] = _cfg.get("class_names", [CLASS_NAME]) + else: + kwargs["class_names"] = sorted(kwargs["class_names"]) + + # Feature extractor + feat_extractor = IntermediateLayerGetter( + backbone_fn( + pretrained=pretrained_backbone, + include_top=False, + input_shape=_cfg["input_shape"], + ), + fpn_layers, + ) - kwargs['num_classes'] = _cfg['num_classes'] - kwargs['input_shape'] = _cfg['input_shape'] - kwargs['rotated_bbox'] = _cfg['rotated_bbox'] # Build the model - model = LinkNet(cfg=_cfg, **kwargs) + model = LinkNet(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) + # Load pretrained parameters if pretrained: - load_pretrained_params(model, _cfg['url']) + # The given class_names differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, + _cfg["url"], + skip_mismatch=kwargs["class_names"] != default_cfgs[arch].get("class_names", [CLASS_NAME]), + ) return model -
    -[docs] -def linknet16(pretrained: bool = False, **kwargs: Any) -> LinkNet: +
    +[docs] +def linknet_resnet18(pretrained: bool = False, **kwargs: Any) -> LinkNet: + """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" + <https://arxiv.org/pdf/1707.03718.pdf>`_. + + >>> import tensorflow as tf + >>> from doctr.models import linknet_resnet18 + >>> model = linknet_resnet18(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the LinkNet architecture + + Returns: + ------- + text detection architecture + """ + return _linknet( + "linknet_resnet18", + pretrained, + resnet18, + ["resnet_block_1", "resnet_block_3", "resnet_block_5", "resnet_block_7"], + **kwargs, + )
    + + + +
    +[docs] +def linknet_resnet34(pretrained: bool = False, **kwargs: Any) -> LinkNet: """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" <https://arxiv.org/pdf/1707.03718.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import linknet16 - >>> model = linknet16(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import linknet_resnet34 + >>> model = linknet_resnet34(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the LinkNet architecture Returns: + ------- text detection architecture """ + return _linknet( + "linknet_resnet34", + pretrained, + resnet34, + ["resnet_block_2", "resnet_block_6", "resnet_block_12", "resnet_block_15"], + **kwargs, + )
    + + + +
    +[docs] +def linknet_resnet50(pretrained: bool = False, **kwargs: Any) -> LinkNet: + """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" + <https://arxiv.org/pdf/1707.03718.pdf>`_. + + >>> import tensorflow as tf + >>> from doctr.models import linknet_resnet50 + >>> model = linknet_resnet50(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the LinkNet architecture - return _linknet('linknet16', pretrained, **kwargs)
    + Returns: + ------- + text detection architecture + """ + return _linknet( + "linknet_resnet50", + pretrained, + resnet50, + ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"], + **kwargs, + )
    @@ -574,8 +715,8 @@

    Source code for doctr.models.detection.linknet.tensorflow

    - +
    + diff --git a/v0.3.1/_modules/doctr/models/detection/zoo.html b/v0.3.1/_modules/doctr/models/detection/zoo.html index d3128b8d14..3651c4e2d3 100644 --- a/v0.3.1/_modules/doctr/models/detection/zoo.html +++ b/v0.3.1/_modules/doctr/models/detection/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.detection.zoo - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.detection.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -from typing import Any
    +from typing import Any, List
     
     from doctr.file_utils import is_tf_available, is_torch_available
    -from .core import DetectionPredictor
    -from ..preprocessor import PreProcessor
    -from .. import detection
     
    +from .. import detection
    +from ..detection.fast import reparameterize
    +from ..preprocessor import PreProcessor
    +from .predictor import DetectionPredictor
     
     __all__ = ["detection_predictor"]
     
    +ARCHS: List[str]
    +
     
     if is_tf_available():
    -    ARCHS = ['db_resnet50', 'linknet16']
    +    ARCHS = [
    +        "db_resnet50",
    +        "db_mobilenet_v3_large",
    +        "linknet_resnet18",
    +        "linknet_resnet34",
    +        "linknet_resnet50",
    +        "fast_tiny",
    +        "fast_small",
    +        "fast_base",
    +    ]
     elif is_torch_available():
    -    ARCHS = ['db_resnet34', 'db_resnet50', 'db_mobilenet_v3', 'linknet16']
    +    ARCHS = [
    +        "db_resnet34",
    +        "db_resnet50",
    +        "db_mobilenet_v3_large",
    +        "linknet_resnet18",
    +        "linknet_resnet34",
    +        "linknet_resnet50",
    +        "fast_tiny",
    +        "fast_small",
    +        "fast_base",
    +    ]
    +
     
    +def _predictor(arch: Any, pretrained: bool, assume_straight_pages: bool = True, **kwargs: Any) -> DetectionPredictor:
    +    if isinstance(arch, str):
    +        if arch not in ARCHS:
    +            raise ValueError(f"unknown architecture '{arch}'")
     
    -def _predictor(arch: str, pretrained: bool, **kwargs: Any) -> DetectionPredictor:
    +        _model = detection.__dict__[arch](
    +            pretrained=pretrained,
    +            pretrained_backbone=kwargs.get("pretrained_backbone", True),
    +            assume_straight_pages=assume_straight_pages,
    +        )
    +        # Reparameterize FAST models by default to lower inference latency and memory usage
    +        if isinstance(_model, detection.FAST):
    +            _model = reparameterize(_model)
    +    else:
    +        if not isinstance(arch, (detection.DBNet, detection.LinkNet, detection.FAST)):
    +            raise ValueError(f"unknown architecture: {type(arch)}")
     
    -    if arch not in ARCHS:
    -        raise ValueError(f"unknown architecture '{arch}'")
    +        _model = arch
    +        _model.assume_straight_pages = assume_straight_pages
    +        _model.postprocessor.assume_straight_pages = assume_straight_pages
     
    -    # Detection
    -    _model = detection.__dict__[arch](pretrained=pretrained)
    -    kwargs['mean'] = kwargs.get('mean', _model.cfg['mean'])
    -    kwargs['std'] = kwargs.get('std', _model.cfg['std'])
    -    kwargs['batch_size'] = kwargs.get('batch_size', 1)
    +    kwargs.pop("pretrained_backbone", None)
    +
    +    kwargs["mean"] = kwargs.get("mean", _model.cfg["mean"])
    +    kwargs["std"] = kwargs.get("std", _model.cfg["std"])
    +    kwargs["batch_size"] = kwargs.get("batch_size", 2)
         predictor = DetectionPredictor(
    -        PreProcessor(_model.cfg['input_shape'][:2], **kwargs),
    -        _model
    +        PreProcessor(_model.cfg["input_shape"][:-1] if is_tf_available() else _model.cfg["input_shape"][1:], **kwargs),
    +        _model,
         )
         return predictor
     
     
     
    -[docs] -def detection_predictor(arch: str = 'db_resnet50', pretrained: bool = False, **kwargs: Any) -> DetectionPredictor: +[docs] +def detection_predictor( + arch: Any = "fast_base", + pretrained: bool = False, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + batch_size: int = 2, + **kwargs: Any, +) -> DetectionPredictor: """Text detection architecture. - Example:: - >>> import numpy as np - >>> from doctr.models import detection_predictor - >>> model = detection_predictor(pretrained=True) - >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) - >>> out = model([input_page]) + >>> import numpy as np + >>> from doctr.models import detection_predictor + >>> model = detection_predictor(arch='db_resnet50', pretrained=True) + >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) Args: - arch: name of the architecture to use ('db_resnet50') + ---- + arch: name of the architecture or model itself to use (e.g. 'db_resnet50') pretrained: If True, returns a model pre-trained on our text detection dataset + assume_straight_pages: If True, fit straight boxes to the page + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right + batch_size: number of samples the model processes in parallel + **kwargs: optional keyword arguments passed to the architecture Returns: + ------- Detection predictor """ - - return _predictor(arch, pretrained, **kwargs)
    + return _predictor( + arch=arch, + pretrained=pretrained, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + batch_size=batch_size, + **kwargs, + )
    @@ -367,8 +449,8 @@

    Source code for doctr.models.detection.zoo

           
         
       
    - - + + diff --git a/v0.3.1/_modules/doctr/models/export.html b/v0.3.1/_modules/doctr/models/export.html deleted file mode 100644 index f25a81aa21..0000000000 --- a/v0.3.1/_modules/doctr/models/export.html +++ /dev/null @@ -1,411 +0,0 @@ - - - - - - - - - - - - doctr.models.export - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.export

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import logging
    -import numpy as np
    -import tensorflow as tf
    -from tensorflow.keras import Model
    -from typing import Tuple
    -
    -logging.getLogger("tensorflow").setLevel(logging.DEBUG)
    -
    -
    -__all__ = ['convert_to_tflite', 'convert_to_fp16', 'quantize_model']
    -
    -
    -
    -[docs] -def convert_to_tflite(tf_model: Model) -> bytes: - """Converts a model to TFLite format - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_tflite, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_tflite(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - return converter.convert()
    - - - -
    -[docs] -def convert_to_fp16(tf_model: Model) -> bytes: - """Converts a model to half precision - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_fp16, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_fp16(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the serialized FP16 model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - converter.target_spec.supported_types = [tf.float16] - return converter.convert()
    - - - -
    -[docs] -def quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) -> bytes: - """Quantize a Tensorflow model - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import quantize_model, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = quantize_model(model, (224, 224, 3)) - - Args: - tf_model: a keras model - input_shape: shape of the expected input tensor (excluding batch dimension) with channel last order - - Returns: - bytes: the serialized quantized model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - - # Float fallback for operators that do not have an integer implementation - def representative_dataset(): - for _ in range(100): - data = np.random.rand(1, *input_shape) - yield [data.astype(np.float32)] - - converter.representative_dataset = representative_dataset - converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] - converter.inference_input_type = tf.int8 - converter.inference_output_type = tf.int8 - - return converter.convert()
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.1/_modules/doctr/models/factory/hub.html b/v0.3.1/_modules/doctr/models/factory/hub.html index 8274a809f5..756b2c7a17 100644 --- a/v0.3.1/_modules/doctr/models/factory/hub.html +++ b/v0.3.1/_modules/doctr/models/factory/hub.html @@ -13,7 +13,7 @@ - + doctr.models.factory.hub - docTR documentation @@ -568,7 +568,7 @@

    Source code for doctr.models.factory.hub

         
       
    - + diff --git a/v0.3.1/_modules/doctr/models/recognition/crnn.html b/v0.3.1/_modules/doctr/models/recognition/crnn.html deleted file mode 100644 index daa2393439..0000000000 --- a/v0.3.1/_modules/doctr/models/recognition/crnn.html +++ /dev/null @@ -1,565 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.crnn - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.crnn

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import layers
    -from tensorflow.keras.models import Sequential
    -from typing import Tuple, Dict, Any, Optional, List
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel, RecognitionPostProcessor
    -
    -__all__ = ['CRNN', 'crnn_vgg16_bn', 'crnn_resnet31', 'CTCPostProcessor']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'crnn_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/crnn_vgg16_bn-748c855f.zip',
    -    },
    -    'crnn_resnet31': {
    -        'mean': (0.694, 0.695, 0.693),
    -        'std': (0.299, 0.296, 0.301),
    -        'backbone': 'resnet31', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.1/crnn_resnet31-69ab71db.zip',
    -    },
    -}
    -
    -
    -class CTCPostProcessor(RecognitionPostProcessor):
    -    """
    -    Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor
    -    ) -> List[Tuple[str, float]]:
    -        """
    -        Performs decoding of raw output with CTC and decoding of CTC predictions
    -        with label_to_idx mapping dictionnary
    -
    -        Args:
    -            logits: raw output of the model, shape BATCH_SIZE X SEQ_LEN X NUM_CLASSES + 1
    -
    -        Returns:
    -            A list of decoded words of length BATCH_SIZE
    -
    -        """
    -        # Decode CTC
    -        _decoded, _log_prob = tf.nn.ctc_beam_search_decoder(
    -            tf.transpose(logits, perm=[1, 0, 2]),
    -            tf.fill(logits.shape[0], logits.shape[1]),
    -            beam_width=1, top_paths=1,
    -        )
    -        out_idxs = tf.sparse.to_dense(_decoded[0], default_value=len(self.vocab))
    -        probs = tf.math.exp(tf.squeeze(_log_prob, axis=1))
    -
    -        # Map it to characters
    -        _decoded_strings_pred = tf.strings.reduce_join(
    -            inputs=tf.nn.embedding_lookup(self._embedding, out_idxs),
    -            axis=-1
    -        )
    -        _decoded_strings_pred = tf.strings.split(_decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -class CRNN(RecognitionModel):
    -    """Implements a CRNN architecture as described in `"An End-to-End Trainable Neural Network for Image-based
    -    Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of units in the LSTM layers
    -        cfg: configuration dictionary
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: tf.keras.Model,
    -        vocab: str,
    -        rnn_units: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(vocab=vocab, cfg=cfg)
    -        self.feat_extractor = feature_extractor
    -
    -        # Initialize kernels
    -        h, w, c = self.feat_extractor.output_shape[1:]
    -        self.max_length = w
    -
    -        self.decoder = Sequential(
    -            [
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Dense(units=len(vocab) + 1)
    -            ]
    -        )
    -        self.decoder.build(input_shape=(None, w, h * c))
    -
    -        self.postprocessor = CTCPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        target: List[str],
    -    ) -> tf.Tensor:
    -        """Compute CTC loss for the model.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        gt, seq_len = self.compute_target(target)
    -        batch_len = model_output.shape[0]
    -        input_length = model_output.shape[1] * tf.ones(shape=(batch_len))
    -        ctc_loss = tf.nn.ctc_loss(
    -            gt, model_output, seq_len, input_length, logits_time_major=False, blank_index=len(self.vocab)
    -        )
    -        return ctc_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        # B x H x W x C --> B x W x H x C
    -        transposed_feat = tf.transpose(features, perm=[0, 2, 1, 3])
    -        w, h, c = transposed_feat.get_shape().as_list()[1:]
    -        # B x W x H x C --> B x W x H * C
    -        features_seq = tf.reshape(transposed_feat, shape=(-1, w, h * c))
    -        logits = self.decoder(features_seq, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = logits
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(logits)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(logits, target)
    -
    -        return out
    -
    -
    -def _crnn(arch: str, pretrained: bool, input_shape: Optional[Tuple[int, int, int]] = None, **kwargs: Any) -> CRNN:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[_cfg['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -
    -    # Build the model
    -    model = CRNN(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a VGG-16 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_vgg16_bn - >>> model = crnn_vgg16_bn(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_vgg16_bn', pretrained, **kwargs)
    - - - -def crnn_resnet31(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a resnet31 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_resnet31 - >>> model = crnn_resnet31(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_resnet31', pretrained, **kwargs) -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.1/_modules/doctr/models/recognition/crnn/tensorflow.html b/v0.3.1/_modules/doctr/models/recognition/crnn/tensorflow.html index 41cc93dd23..bc64da9a1b 100644 --- a/v0.3.1/_modules/doctr/models/recognition/crnn/tensorflow.html +++ b/v0.3.1/_modules/doctr/models/recognition/crnn/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.crnn.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple, Union
    +
     import tensorflow as tf
     from tensorflow.keras import layers
    -from tensorflow.keras.models import Sequential, Model
    -from typing import Tuple, Dict, Any, Optional, List
    +from tensorflow.keras.models import Model, Sequential
    +
    +from doctr.datasets import VOCABS
     
    -from ... import backbones
    -from ...utils import load_pretrained_params
    +from ...classification import mobilenet_v3_large_r, mobilenet_v3_small_r, vgg16_bn_r
    +from ...utils.tensorflow import _bf16_to_float32, _build_model, load_pretrained_params
     from ..core import RecognitionModel, RecognitionPostProcessor
     
    -__all__ = ['CRNN', 'crnn_vgg16_bn', 'crnn_resnet31', 'CTCPostProcessor']
    +__all__ = ["CRNN", "crnn_vgg16_bn", "crnn_mobilenet_v3_small", "crnn_mobilenet_v3_large"]
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'crnn_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/crnn_vgg16_bn-748c855f.zip',
    +    "crnn_vgg16_bn": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["legacy_french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/crnn_vgg16_bn-9c188f45.weights.h5&src=0",
         },
    -    'crnn_resnet31': {
    -        'mean': (0.694, 0.695, 0.693),
    -        'std': (0.299, 0.296, 0.301),
    -        'backbone': 'resnet31', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.1/crnn_resnet31-69ab71db.zip',
    +    "crnn_mobilenet_v3_small": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/crnn_mobilenet_v3_small-54850265.weights.h5&src=0",
    +    },
    +    "crnn_mobilenet_v3_large": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/crnn_mobilenet_v3_large-c64045e5.weights.h5&src=0",
         },
     }
     
     
     class CTCPostProcessor(RecognitionPostProcessor):
    -    """
    -    Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
    +    """Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
     
         Args:
    +    ----
             vocab: string containing the ordered sequence of supported characters
             ignore_case: if True, ignore case of letters
             ignore_accents: if True, ignore accents of letters
    @@ -325,37 +353,57 @@ 

    Source code for doctr.models.recognition.crnn.tensorflow

    def __call__( self, - logits: tf.Tensor - ) -> List[Tuple[str, float]]: - """ - Performs decoding of raw output with CTC and decoding of CTC predictions + logits: tf.Tensor, + beam_width: int = 1, + top_paths: int = 1, + ) -> Union[List[Tuple[str, float]], List[Tuple[List[str], List[float]]]]: + """Performs decoding of raw output with CTC and decoding of CTC predictions with label_to_idx mapping dictionnary Args: + ---- logits: raw output of the model, shape BATCH_SIZE X SEQ_LEN X NUM_CLASSES + 1 + beam_width: An int scalar >= 0 (beam search beam width). + top_paths: An int scalar >= 0, <= beam_width (controls output size). Returns: + ------- A list of decoded words of length BATCH_SIZE + """ # Decode CTC _decoded, _log_prob = tf.nn.ctc_beam_search_decoder( tf.transpose(logits, perm=[1, 0, 2]), - tf.fill(logits.shape[0], logits.shape[1]), - beam_width=1, top_paths=1, + tf.fill(tf.shape(logits)[:1], tf.shape(logits)[1]), + beam_width=beam_width, + top_paths=top_paths, ) - out_idxs = tf.sparse.to_dense(_decoded[0], default_value=len(self.vocab)) - probs = tf.math.exp(tf.squeeze(_log_prob, axis=1)) + + _decoded = tf.sparse.concat( + 1, + [tf.sparse.expand_dims(dec, axis=1) for dec in _decoded], + expand_nonconcat_dims=True, + ) # dim : batchsize x beamwidth x actual_max_len_predictions + out_idxs = tf.sparse.to_dense(_decoded, default_value=len(self.vocab)) # Map it to characters _decoded_strings_pred = tf.strings.reduce_join( inputs=tf.nn.embedding_lookup(tf.constant(self._embedding, dtype=tf.string), out_idxs), - axis=-1 + axis=-1, ) _decoded_strings_pred = tf.strings.split(_decoded_strings_pred, "<eos>") - decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0] - word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] - + decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value="not valid")[ + :, :, 0 + ] # dim : batch_size x beam_width + + if top_paths == 1: + probs = tf.math.exp(tf.squeeze(_log_prob, axis=1)) # dim : batchsize + decoded_strings_pred = tf.squeeze(decoded_strings_pred, axis=1) + word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] + else: + probs = tf.math.exp(_log_prob) # dim : batchsize x beamwidth + word_values = [[word.decode() for word in words] for words in decoded_strings_pred.numpy().tolist()] return list(zip(word_values, probs.numpy().tolist())) @@ -364,19 +412,26 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. Args: + ---- feature_extractor: the backbone serving as feature extractor vocab: vocabulary used for encoding rnn_units: number of units in the LSTM layers + exportable: onnx exportable returns only logits + beam_width: beam width for beam search decoding + top_paths: number of top paths for beam search decoding cfg: configuration dictionary """ - _children_names: List[str] = ['feat_extractor', 'decoder', 'postprocessor'] + _children_names: List[str] = ["feat_extractor", "decoder", "postprocessor"] def __init__( self, - feature_extractor: tf.keras.Model, + feature_extractor: Model, vocab: str, rnn_units: int = 128, + exportable: bool = False, + beam_width: int = 1, + top_paths: int = 1, cfg: Optional[Dict[str, Any]] = None, ) -> None: # Initialize kernels @@ -386,19 +441,21 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    self.vocab = vocab self.max_length = w self.cfg = cfg + self.exportable = exportable self.feat_extractor = feature_extractor - self.decoder = Sequential( - [ - layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), - layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), - layers.Dense(units=len(vocab) + 1) - ] - ) + self.decoder = Sequential([ + layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), + layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), + layers.Dense(units=len(vocab) + 1), + ]) self.decoder.build(input_shape=(None, w, h * c)) self.postprocessor = CTCPostProcessor(vocab=vocab) + self.beam_width = beam_width + self.top_paths = top_paths + def compute_loss( self, model_output: tf.Tensor, @@ -407,16 +464,17 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    """Compute CTC loss for the model. Args: - gt: the encoded tensor with gt labels + ---- model_output: predicted logits of the model - seq_len: lengths of each gt word inside the batch + target: lengths of each gt word inside the batch Returns: + ------- The loss of the model on the batch """ - gt, seq_len = self.compute_target(target) + gt, seq_len = self.build_target(target) batch_len = model_output.shape[0] - input_length = model_output.shape[1] * tf.ones(shape=(batch_len)) + input_length = tf.fill((batch_len,), model_output.shape[1]) ctc_loss = tf.nn.ctc_loss( gt, model_output, seq_len, input_length, logits_time_major=False, blank_index=len(self.vocab) ) @@ -428,8 +486,12 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    target: Optional[List[str]] = None, return_model_output: bool = False, return_preds: bool = False, + beam_width: int = 1, + top_paths: int = 1, **kwargs: Any, ) -> Dict[str, Any]: + if kwargs.get("training", False) and target is None: + raise ValueError("Need to provide labels during training") features = self.feat_extractor(x, **kwargs) # B x H x W x C --> B x W x H x C @@ -437,91 +499,132 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    w, h, c = transposed_feat.get_shape().as_list()[1:] # B x W x H x C --> B x W x H * C features_seq = tf.reshape(transposed_feat, shape=(-1, w, h * c)) - logits = self.decoder(features_seq, **kwargs) + logits = _bf16_to_float32(self.decoder(features_seq, **kwargs)) out: Dict[str, tf.Tensor] = {} + if self.exportable: + out["logits"] = logits + return out + if return_model_output: out["out_map"] = logits if target is None or return_preds: # Post-process boxes - out["preds"] = self.postprocessor(logits) + out["preds"] = self.postprocessor(logits, beam_width=beam_width, top_paths=top_paths) if target is not None: - out['loss'] = self.compute_loss(logits, target) + out["loss"] = self.compute_loss(logits, target) return out -def _crnn(arch: str, pretrained: bool, input_shape: Optional[Tuple[int, int, int]] = None, **kwargs: Any) -> CRNN: +def _crnn( + arch: str, + pretrained: bool, + backbone_fn, + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> CRNN: + pretrained_backbone = pretrained_backbone and not pretrained + + kwargs["vocab"] = kwargs.get("vocab", default_cfgs[arch]["vocab"]) - # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab']) - _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units']) + _cfg["vocab"] = kwargs["vocab"] + _cfg["input_shape"] = input_shape or default_cfgs[arch]["input_shape"] - # Feature extractor - feat_extractor = backbones.__dict__[_cfg['backbone']]( - input_shape=_cfg['input_shape'], + feat_extractor = backbone_fn( + input_shape=_cfg["input_shape"], include_top=False, + pretrained=pretrained_backbone, ) - kwargs['vocab'] = _cfg['vocab'] - kwargs['rnn_units'] = _cfg['rnn_units'] - # Build the model model = CRNN(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) # Load pretrained parameters if pretrained: - load_pretrained_params(model, _cfg['url']) + # The given vocab differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params(model, _cfg["url"], skip_mismatch=kwargs["vocab"] != default_cfgs[arch]["vocab"]) return model
    -[docs] +[docs] def crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> CRNN: """CRNN with a VGG-16 backbone as described in `"An End-to-End Trainable Neural Network for Image-based Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_vgg16_bn - >>> model = crnn_vgg16_bn(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import crnn_vgg16_bn + >>> model = crnn_vgg16_bn(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the CRNN architecture Returns: + ------- text recognition architecture """ + return _crnn("crnn_vgg16_bn", pretrained, vgg16_bn_r, **kwargs)
    + + + +
    +[docs] +def crnn_mobilenet_v3_small(pretrained: bool = False, **kwargs: Any) -> CRNN: + """CRNN with a MobileNet V3 Small backbone as described in `"An End-to-End Trainable Neural Network for Image-based + Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. + + >>> import tensorflow as tf + >>> from doctr.models import crnn_mobilenet_v3_small + >>> model = crnn_mobilenet_v3_small(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the CRNN architecture - return _crnn('crnn_vgg16_bn', pretrained, **kwargs)
    + Returns: + ------- + text recognition architecture + """ + return _crnn("crnn_mobilenet_v3_small", pretrained, mobilenet_v3_small_r, **kwargs)
    -def crnn_resnet31(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a resnet31 backbone as described in `"An End-to-End Trainable Neural Network for Image-based +
    +[docs] +def crnn_mobilenet_v3_large(pretrained: bool = False, **kwargs: Any) -> CRNN: + """CRNN with a MobileNet V3 Large backbone as described in `"An End-to-End Trainable Neural Network for Image-based Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_resnet31 - >>> model = crnn_resnet31(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import crnn_mobilenet_v3_large + >>> model = crnn_mobilenet_v3_large(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the CRNN architecture Returns: + ------- text recognition architecture """ + return _crnn("crnn_mobilenet_v3_large", pretrained, mobilenet_v3_large_r, **kwargs)
    - return _crnn('crnn_resnet31', pretrained, **kwargs)
    @@ -554,8 +657,8 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    - +
    + diff --git a/v0.3.1/_modules/doctr/models/recognition/master/tensorflow.html b/v0.3.1/_modules/doctr/models/recognition/master/tensorflow.html index 2dc5a27717..aa6aa69325 100644 --- a/v0.3.1/_modules/doctr/models/recognition/master/tensorflow.html +++ b/v0.3.1/_modules/doctr/models/recognition/master/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.master.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.master.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import tensorflow as tf
    -from tensorflow.keras import layers, Sequential, Model
    -from typing import Tuple, List, Dict, Any, Optional
     from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple
     
    -from ..core import RecognitionPostProcessor
    -from ...backbones.resnet import ResnetStage
    -from ...utils import conv_sequence, load_pretrained_params
    -from ..transformer import Decoder, positional_encoding, create_look_ahead_mask, create_padding_mask
    -from ....datasets import VOCABS
    -from .base import _MASTER, _MASTERPostProcessor
    +import tensorflow as tf
    +from tensorflow.keras import Model, layers
    +
    +from doctr.datasets import VOCABS
    +from doctr.models.classification import magc_resnet31
    +from doctr.models.modules.transformer import Decoder, PositionalEncoding
     
    +from ...utils.tensorflow import _bf16_to_float32, _build_model, load_pretrained_params
    +from .base import _MASTER, _MASTERPostProcessor
     
    -__all__ = ['MASTER', 'master', 'MASTERPostProcessor']
    +__all__ = ["MASTER", "master"]
     
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'master': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'input_shape': (48, 160, 3),
    -        'vocab': VOCABS['french'],
    -        'url': None,
    +    "master": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/master-d7fdaeff.weights.h5&src=0",
         },
     }
     
     
    -class MAGC(layers.Layer):
    -
    -    """Implements the Multi-Aspect Global Context Attention, as described in
    -    <https://arxiv.org/pdf/1910.02562.pdf>`_.
    -
    -    Args:
    -        inplanes: input channels
    -        headers: number of headers to split channels
    -        att_scale: if True, re-scale attention to counteract the variance distibutions
    -        **kwargs
    -    """
    -
    -    def __init__(
    -        self,
    -        inplanes: int,
    -        headers: int = 1,
    -        att_scale: bool = False,
    -        **kwargs
    -    ) -> None:
    -        super().__init__(**kwargs)
    -
    -        self.headers = headers  # h
    -        self.inplanes = inplanes  # C
    -        self.att_scale = att_scale
    -
    -        self.single_header_inplanes = int(inplanes / headers)  # C / h
    -
    -        self.conv_mask = tf.keras.layers.Conv2D(
    -            filters=1,
    -            kernel_size=1,
    -            kernel_initializer=tf.initializers.he_normal()
    -        )
    -
    -        self.transform = tf.keras.Sequential(
    -            [
    -                tf.keras.layers.Conv2D(
    -                    filters=self.inplanes,
    -                    kernel_size=1,
    -                    kernel_initializer=tf.initializers.he_normal()
    -                ),
    -                tf.keras.layers.LayerNormalization([1, 2, 3]),
    -                tf.keras.layers.ReLU(),
    -                tf.keras.layers.Conv2D(
    -                    filters=self.inplanes,
    -                    kernel_size=1,
    -                    kernel_initializer=tf.initializers.he_normal()
    -                ),
    -            ],
    -            name='transform'
    -        )
    -
    -    @tf.function
    -    def context_modeling(self, inputs: tf.Tensor) -> tf.Tensor:
    -        b, h, w, c = (tf.shape(inputs)[i] for i in range(4))
    -
    -        # B, H, W, C -->> B*h, H, W, C/h
    -        x = tf.reshape(inputs, shape=(b, h, w, self.headers, self.single_header_inplanes))
    -        x = tf.transpose(x, perm=(0, 3, 1, 2, 4))
    -        x = tf.reshape(x, shape=(b * self.headers, h, w, self.single_header_inplanes))
    -
    -        # Compute shorcut
    -        shortcut = x
    -        # B*h, 1, H*W, C/h
    -        shortcut = tf.reshape(shortcut, shape=(b * self.headers, 1, h * w, self.single_header_inplanes))
    -        # B*h, 1, C/h, H*W
    -        shortcut = tf.transpose(shortcut, perm=[0, 1, 3, 2])
    -
    -        # Compute context mask
    -        # B*h, H, W, 1,
    -        context_mask = self.conv_mask(x)
    -        # B*h, 1, H*W, 1
    -        context_mask = tf.reshape(context_mask, shape=(b * self.headers, 1, h * w, 1))
    -        # scale variance
    -        if self.att_scale and self.headers > 1:
    -            context_mask = context_mask / tf.sqrt(self.single_header_inplanes)
    -        # B*h, 1, H*W, 1
    -        context_mask = tf.keras.activations.softmax(context_mask, axis=2)
    -
    -        # Compute context
    -        # B*h, 1, C/h, 1
    -        context = tf.matmul(shortcut, context_mask)
    -        context = tf.reshape(context, shape=(b, 1, c, 1))
    -        # B, 1, 1, C
    -        context = tf.transpose(context, perm=(0, 1, 3, 2))
    -        # Set shape to resolve shape when calling this module in the Sequential MAGCResnet
    -        batch, chan = inputs.get_shape().as_list()[0], inputs.get_shape().as_list()[-1]
    -        context.set_shape([batch, 1, 1, chan])
    -        return context
    -
    -    def call(self, inputs: tf.Tensor, **kwargs) -> tf.Tensor:
    -        # Context modeling: B, H, W, C  ->  B, 1, 1, C
    -        context = self.context_modeling(inputs)
    -        # Transform: B, 1, 1, C  ->  B, 1, 1, C
    -        transformed = self.transform(context)
    -        return inputs + transformed
    -
    -
    -class MAGCResnet(Sequential):
    -
    -    """Implements the modified resnet with MAGC layers, as described in paper.
    -
    -    Args:
    -        headers: number of header to split channels in MAGC layers
    -        input_shape: shape of the model input (without batch dim)
    -    """
    -
    -    def __init__(
    -        self,
    -        headers: int = 1,
    -        input_shape: Tuple[int, int, int] = (48, 160, 3),
    -    ) -> None:
    -        _layers = [
    -            # conv_1x
    -            *conv_sequence(out_channels=64, activation='relu', bn=True, kernel_size=3, input_shape=input_shape),
    -            *conv_sequence(out_channels=128, activation='relu', bn=True, kernel_size=3),
    -            layers.MaxPooling2D((2, 2), (2, 2)),
    -            # conv_2x
    -            ResnetStage(num_blocks=1, output_channels=256),
    -            MAGC(inplanes=256, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=256, activation='relu', bn=True, kernel_size=3),
    -            layers.MaxPooling2D((2, 2), (2, 2)),
    -            # conv_3x
    -            ResnetStage(num_blocks=2, output_channels=512),
    -            MAGC(inplanes=512, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=512, activation='relu', bn=True, kernel_size=3),
    -            layers.MaxPooling2D((2, 1), (2, 1)),
    -            # conv_4x
    -            ResnetStage(num_blocks=5, output_channels=512),
    -            MAGC(inplanes=512, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=512, activation='relu', bn=True, kernel_size=3),
    -            # conv_5x
    -            ResnetStage(num_blocks=3, output_channels=512),
    -            MAGC(inplanes=512, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=512, activation='relu', bn=True, kernel_size=3),
    -        ]
    -        super().__init__(_layers)
    -
    -
     class MASTER(_MASTER, Model):
    -
         """Implements MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_.
         Implementation based on the official TF implementation: <https://github.com/jiangxiluning/MASTER-TF>`_.
     
         Args:
    +    ----
    +        feature_extractor: the backbone serving as feature extractor
             vocab: vocabulary, (without EOS, SOS, PAD)
             d_model: d parameter for the transformer decoder
    -        headers: headers for the MAGC module
             dff: depth of the pointwise feed-forward layer
             num_heads: number of heads for the mutli-head attention module
             num_layers: number of decoder layers to stack
             max_length: maximum length of character sequence handled by the model
    -        input_size: size of the image inputs
    +        dropout: dropout probability of the decoder
    +        input_shape: size of the image inputs
    +        exportable: onnx exportable returns only logits
    +        cfg: dictionary containing information about the model
         """
     
         def __init__(
             self,
    +        feature_extractor: Model,
             vocab: str,
             d_model: int = 512,
    -        headers: int = 1,
             dff: int = 2048,
    -        num_heads: int = 8,
    +        num_heads: int = 8,  # number of heads in the transformer decoder
             num_layers: int = 3,
             max_length: int = 50,
    -        input_shape: Tuple[int, int, int] = (48, 160, 3),
    +        dropout: float = 0.2,
    +        input_shape: Tuple[int, int, int] = (32, 128, 3),  # different from the paper
    +        exportable: bool = False,
             cfg: Optional[Dict[str, Any]] = None,
         ) -> None:
             super().__init__()
     
    -        self.vocab = vocab
    +        self.exportable = exportable
             self.max_length = max_length
    +        self.d_model = d_model
    +        self.vocab = vocab
             self.cfg = cfg
             self.vocab_size = len(vocab)
     
    -        self.feature_extractor = MAGCResnet(headers=headers, input_shape=input_shape)
    -        self.seq_embedding = layers.Embedding(self.vocab_size + 3, d_model)  # 3 more classes: EOS/PAD/SOS
    +        self.feat_extractor = feature_extractor
    +        self.positional_encoding = PositionalEncoding(self.d_model, dropout, max_len=input_shape[0] * input_shape[1])
     
             self.decoder = Decoder(
                 num_layers=num_layers,
    -            d_model=d_model,
    +            d_model=self.d_model,
                 num_heads=num_heads,
    +            vocab_size=self.vocab_size + 3,  # EOS, SOS, PAD
                 dff=dff,
    -            vocab_size=self.vocab_size,
    -            maximum_position_encoding=max_length,
    +            dropout=dropout,
    +            maximum_position_encoding=self.max_length,
             )
    -        self.feature_pe = positional_encoding(input_shape[0] * input_shape[1], d_model)
    -        self.linear = layers.Dense(self.vocab_size + 3, kernel_initializer=tf.initializers.he_uniform())
     
    +        self.linear = layers.Dense(self.vocab_size + 3, kernel_initializer=tf.initializers.he_uniform())
             self.postprocessor = MASTERPostProcessor(vocab=self.vocab)
     
         @tf.function
    -    def make_mask(self, target: tf.Tensor) -> tf.Tensor:
    -        look_ahead_mask = create_look_ahead_mask(tf.shape(target)[1])
    -        target_padding_mask = create_padding_mask(target, self.vocab_size + 2)  # Pad symbol
    -        combined_mask = tf.maximum(target_padding_mask, look_ahead_mask)
    -        return combined_mask
    +    def make_source_and_target_mask(self, source: tf.Tensor, target: tf.Tensor) -> Tuple[tf.Tensor, tf.Tensor]:
    +        # [1, 1, 1, ..., 0, 0, 0] -> 0 is masked
    +        # (N, 1, 1, max_length)
    +        target_pad_mask = tf.cast(tf.math.not_equal(target, self.vocab_size + 2), dtype=tf.uint8)
    +        target_pad_mask = target_pad_mask[:, tf.newaxis, tf.newaxis, :]
    +        target_length = target.shape[1]
    +        # sub mask filled diagonal with 1 = see 0 = masked (max_length, max_length)
    +        target_sub_mask = tf.linalg.band_part(tf.ones((target_length, target_length)), -1, 0)
    +        # source mask filled with ones (max_length, positional_encoded_seq_len)
    +        source_mask = tf.ones((target_length, source.shape[1]))
    +        # combine the two masks into one boolean mask where False is masked (N, 1, max_length, max_length)
    +        target_mask = tf.math.logical_and(
    +            tf.cast(target_sub_mask, dtype=tf.bool), tf.cast(target_pad_mask, dtype=tf.bool)
    +        )
    +        return source_mask, target_mask
     
    +    @staticmethod
         def compute_loss(
    -        self,
             model_output: tf.Tensor,
             gt: tf.Tensor,
             seq_len: List[int],
    @@ -512,11 +413,13 @@ 

    Source code for doctr.models.recognition.master.tensorflow

    Sequences are masked after the EOS character. Args: + ---- gt: the encoded tensor with gt labels model_output: predicted logits of the model seq_len: lengths of each gt word inside the batch Returns: + ------- The loss of the model on the batch """ # Input length : number of timesteps @@ -532,7 +435,7 @@

    Source code for doctr.models.recognition.master.tensorflow

    mask_values = tf.zeros_like(cce) mask_2d = tf.sequence_mask(seq_len, input_len - 1) # delete the last mask timestep as well masked_loss = tf.where(mask_2d, cce, mask_values) - ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32)) + ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, model_output.dtype)) return tf.expand_dims(ce_loss, axis=1) @@ -547,94 +450,103 @@

    Source code for doctr.models.recognition.master.tensorflow

    """Call function for training Args: + ---- x: images target: list of str labels return_model_output: if True, return logits return_preds: if True, decode logits + **kwargs: keyword arguments passed to the decoder - Return: + Returns: + ------- A dictionnary containing eventually loss, logits and predictions. """ - # Encode - feature = self.feature_extractor(x, **kwargs) - b, h, w, c = (tf.shape(feature)[i] for i in range(4)) + feature = self.feat_extractor(x, **kwargs) + b, h, w, c = feature.get_shape() + # (N, H, W, C) --> (N, H * W, C) feature = tf.reshape(feature, shape=(b, h * w, c)) - encoded = feature + self.feature_pe[:, :h * w, :] + # add positional encoding to features + encoded = self.positional_encoding(feature, **kwargs) out: Dict[str, tf.Tensor] = {} + if kwargs.get("training", False) and target is None: + raise ValueError("Need to provide labels during training") + if target is not None: # Compute target: tensor of gts and sequence lengths - gt, seq_len = self.compute_target(target) - - if kwargs.get('training', False): - if target is None: - raise AssertionError("In training mode, you need to pass a value to 'target'") - tgt_mask = self.make_mask(gt) + gt, seq_len = self.build_target(target) + # Compute decoder masks + source_mask, target_mask = self.make_source_and_target_mask(encoded, gt) # Compute logits - output = self.decoder(gt, encoded, tgt_mask, None, **kwargs) + output = self.decoder(gt, encoded, source_mask, target_mask, **kwargs) logits = self.linear(output, **kwargs) - else: - # When not training, we want to compute logits in with the decoder, although - # we have access to gts (we need gts to compute the loss, but not in the decoder) logits = self.decode(encoded, **kwargs) + logits = _bf16_to_float32(logits) + + if self.exportable: + out["logits"] = logits + return out + if target is not None: - out['loss'] = self.compute_loss(logits, gt, seq_len) + out["loss"] = self.compute_loss(logits, gt, seq_len) if return_model_output: - out['out_map'] = logits + out["out_map"] = logits if return_preds: - predictions = self.postprocessor(logits) - out['preds'] = predictions + out["preds"] = self.postprocessor(logits) return out + @tf.function def decode(self, encoded: tf.Tensor, **kwargs: Any) -> tf.Tensor: """Decode function for prediction Args: + ---- encoded: encoded features + **kwargs: keyword arguments passed to the decoder - Return: + Returns: + ------- A Tuple of tf.Tensor: predictions, logits """ - b = tf.shape(encoded)[0] - max_len = tf.constant(self.max_length, dtype=tf.int32) + b = encoded.shape[0] + start_symbol = tf.constant(self.vocab_size + 1, dtype=tf.int32) # SOS padding_symbol = tf.constant(self.vocab_size + 2, dtype=tf.int32) # PAD - ys = tf.fill(dims=(b, max_len - 1), value=padding_symbol) + ys = tf.fill(dims=(b, self.max_length - 1), value=padding_symbol) start_vector = tf.fill(dims=(b, 1), value=start_symbol) ys = tf.concat([start_vector, ys], axis=-1) - logits = tf.zeros(shape=(b, max_len - 1, self.vocab_size + 3), dtype=tf.float32) # 3 symbols - # max_len = len + 2 (sos + eos) + # Final dimension include EOS/SOS/PAD for i in range(self.max_length - 1): - ys_mask = self.make_mask(ys) - output = self.decoder(ys, encoded, ys_mask, None, **kwargs) + source_mask, target_mask = self.make_source_and_target_mask(encoded, ys) + output = self.decoder(ys, encoded, source_mask, target_mask, **kwargs) logits = self.linear(output, **kwargs) prob = tf.nn.softmax(logits, axis=-1) - next_word = tf.argmax(prob, axis=-1, output_type=ys.dtype) - # ys.shape = B, T - i_mesh, j_mesh = tf.meshgrid(tf.range(b), tf.range(max_len), indexing='ij') + next_token = tf.argmax(prob, axis=-1, output_type=ys.dtype) + # update ys with the next token and ignore the first token (SOS) + i_mesh, j_mesh = tf.meshgrid(tf.range(b), tf.range(self.max_length), indexing="ij") indices = tf.stack([i_mesh[:, i + 1], j_mesh[:, i + 1]], axis=1) - ys = tf.tensor_scatter_nd_update(ys, indices, next_word[:, i + 1]) + ys = tf.tensor_scatter_nd_update(ys, indices, next_token[:, i]) - # final_logits of shape (N, max_length - 1, vocab_size + 1) (whithout sos) + # Shape (N, max_length, vocab_size + 1) return logits class MASTERPostProcessor(_MASTERPostProcessor): """Post processor for MASTER architectures + Args: + ---- vocab: string containing the ordered sequence of supported characters - ignore_case: if True, ignore case of letters - ignore_accents: if True, ignore accents of letters """ def __call__( @@ -649,51 +561,66 @@

    Source code for doctr.models.recognition.master.tensorflow

    probs = tf.math.reduce_min(probs, axis=1) # decode raw output of the model with tf_label_to_idx - out_idxs = tf.cast(out_idxs, dtype='int32') + out_idxs = tf.cast(out_idxs, dtype="int32") embedding = tf.constant(self._embedding, dtype=tf.string) decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(embedding, out_idxs), axis=-1) decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>") - decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0] + decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value="not valid")[:, 0] word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] - return list(zip(word_values, probs.numpy().tolist())) + return list(zip(word_values, probs.numpy().clip(0, 1).tolist())) -def _master(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> MASTER: +def _master(arch: str, pretrained: bool, backbone_fn, pretrained_backbone: bool = True, **kwargs: Any) -> MASTER: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab']) + _cfg["input_shape"] = kwargs.get("input_shape", _cfg["input_shape"]) + _cfg["vocab"] = kwargs.get("vocab", _cfg["vocab"]) - kwargs['vocab'] = _cfg['vocab'] + kwargs["vocab"] = _cfg["vocab"] + kwargs["input_shape"] = _cfg["input_shape"] # Build the model - model = MASTER(cfg=_cfg, **kwargs) + model = MASTER( + backbone_fn(pretrained=pretrained_backbone, input_shape=_cfg["input_shape"], include_top=False), + cfg=_cfg, + **kwargs, + ) + _build_model(model) + # Load pretrained parameters if pretrained: - load_pretrained_params(model, default_cfgs[arch]['url']) + # The given vocab differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, default_cfgs[arch]["url"], skip_mismatch=kwargs["vocab"] != default_cfgs[arch]["vocab"] + ) return model
    -[docs] +[docs] def master(pretrained: bool = False, **kwargs: Any) -> MASTER: """MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import master - >>> model = master(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 48, 160, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + + >>> import tensorflow as tf + >>> from doctr.models import master + >>> model = master(pretrained=False) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keywoard arguments passed to the MASTER architecture + Returns: + ------- text recognition architecture """ - - return _master('master', pretrained, **kwargs)
    + return _master("master", pretrained, magc_resnet31, **kwargs)
    @@ -727,8 +654,8 @@

    Source code for doctr.models.recognition.master.tensorflow

    - +
    + diff --git a/v0.3.1/_modules/doctr/models/recognition/parseq/tensorflow.html b/v0.3.1/_modules/doctr/models/recognition/parseq/tensorflow.html index 0819737dfc..b181acef53 100644 --- a/v0.3.1/_modules/doctr/models/recognition/parseq/tensorflow.html +++ b/v0.3.1/_modules/doctr/models/recognition/parseq/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.parseq.tensorflow - docTR documentation @@ -845,7 +845,7 @@

    Source code for doctr.models.recognition.parseq.tensorflow

    - + diff --git a/v0.3.1/_modules/doctr/models/recognition/sar.html b/v0.3.1/_modules/doctr/models/recognition/sar.html deleted file mode 100644 index 2482e9f156..0000000000 --- a/v0.3.1/_modules/doctr/models/recognition/sar.html +++ /dev/null @@ -1,712 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.sar - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.sar

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import Sequential, layers
    -from typing import Tuple, Dict, List, Any, Optional
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel
    -from .core import RecognitionPostProcessor
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['SAR', 'SARPostProcessor', 'sar_vgg16_bn', 'sar_resnet31']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'sar_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1-models/sar_vgg16bn-0d7e2c26.zip',
    -    },
    -    'sar_resnet31': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'resnet31', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/sar_resnet31-ea202587.zip',
    -    },
    -}
    -
    -
    -class AttentionModule(layers.Layer, NestedObject):
    -    """Implements attention module of the SAR model
    -
    -    Args:
    -        attention_units: number of hidden attention units
    -
    -    """
    -    def __init__(
    -        self,
    -        attention_units: int
    -    ) -> None:
    -
    -        super().__init__()
    -        self.hidden_state_projector = layers.Conv2D(
    -            attention_units, 1, strides=1, use_bias=False, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.features_projector = layers.Conv2D(
    -            attention_units, 3, strides=1, use_bias=True, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.attention_projector = layers.Conv2D(
    -            1, 1, strides=1, use_bias=False, padding="same", kernel_initializer='he_normal',
    -        )
    -        self.flatten = layers.Flatten()
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        hidden_state: tf.Tensor,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        [H, W] = features.get_shape().as_list()[1:3]
    -        # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units)
    -        hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs)
    -        # shape (N, H, W, vgg_units) -> (N, H, W, attention_units)
    -        features_projection = self.features_projector(features, **kwargs)
    -        projection = tf.math.tanh(hidden_state_projection + features_projection)
    -        # shape (N, H, W, attention_units) -> (N, H, W, 1)
    -        attention = self.attention_projector(projection, **kwargs)
    -        # shape (N, H, W, 1) -> (N, H * W)
    -        attention = self.flatten(attention)
    -        attention = tf.nn.softmax(attention)
    -        # shape (N, H * W) -> (N, H, W, 1)
    -        attention_map = tf.reshape(attention, [-1, H, W, 1])
    -        glimpse = tf.math.multiply(features, attention_map)
    -        # shape (N, H * W) -> (N, 1)
    -        glimpse = tf.reduce_sum(glimpse, axis=[1, 2])
    -        return glimpse
    -
    -
    -class SARDecoder(layers.Layer, NestedObject):
    -    """Implements decoder module of the SAR model
    -
    -    Args:
    -        rnn_units: number of hidden units in recurrent cells
    -        max_length: maximum length of a sequence
    -        vocab_size: number of classes in the model alphabet
    -        embedding_units: number of hidden embedding units
    -        attention_units: number of hidden attention units
    -        num_decoder_layers: number of LSTM layers to stack
    -
    -    """
    -    def __init__(
    -        self,
    -        rnn_units: int,
    -        max_length: int,
    -        vocab_size: int,
    -        embedding_units: int,
    -        attention_units: int,
    -        num_decoder_layers: int = 2,
    -        input_shape: Optional[List[Tuple[Optional[int]]]] = None,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.vocab_size = vocab_size
    -        self.lstm_decoder = layers.StackedRNNCells(
    -            [layers.LSTMCell(rnn_units, dtype=tf.float32, implementation=1) for _ in range(num_decoder_layers)]
    -        )
    -        self.embed = layers.Dense(embedding_units, use_bias=False, input_shape=(None, self.vocab_size + 1))
    -        self.attention_module = AttentionModule(attention_units)
    -        self.output_dense = layers.Dense(vocab_size + 1, use_bias=True, input_shape=(None, 2 * rnn_units))
    -        self.max_length = max_length
    -
    -        # Initialize kernels
    -        if input_shape is not None:
    -            self.attention_module.call(layers.Input(input_shape[0][1:]), layers.Input((1, 1, rnn_units)))
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        holistic: tf.Tensor,
    -        gt: Optional[tf.Tensor] = None,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # initialize states (each of shape (N, rnn_units))
    -        states = self.lstm_decoder.get_initial_state(
    -            inputs=None, batch_size=features.shape[0], dtype=tf.float32
    -        )
    -        # run first step of lstm
    -        # holistic: shape (N, rnn_units)
    -        _, states = self.lstm_decoder(holistic, states, **kwargs)
    -        # Initialize with the index of virtual START symbol (placed after <eos>)
    -        symbol = tf.fill(features.shape[0], self.vocab_size + 1)
    -        logits_list = []
    -        if kwargs.get('training') and gt is None:
    -            raise ValueError('Need to provide labels during training for teacher forcing')
    -        for t in range(self.max_length + 1):  # keep 1 step for <eos>
    -            # one-hot symbol with depth vocab_size + 1
    -            # embeded_symbol: shape (N, embedding_units)
    -            embeded_symbol = self.embed(tf.one_hot(symbol, depth=self.vocab_size + 1), **kwargs)
    -            logits, states = self.lstm_decoder(embeded_symbol, states, **kwargs)
    -            glimpse = self.attention_module(
    -                features, tf.expand_dims(tf.expand_dims(logits, axis=1), axis=1), **kwargs,
    -            )
    -            # logits: shape (N, rnn_units), glimpse: shape (N, 1)
    -            logits = tf.concat([logits, glimpse], axis=-1)
    -            # shape (N, rnn_units + 1) -> (N, vocab_size + 1)
    -            logits = self.output_dense(logits, **kwargs)
    -            # update symbol with predicted logits for t+1 step
    -            if kwargs.get('training'):
    -                symbol = gt[:, t]
    -            else:
    -                symbol = tf.argmax(logits, axis=-1)
    -            logits_list.append(logits)
    -        outputs = tf.stack(logits_list, axis=1)  # shape (N, max_length + 1, vocab_size + 1)
    -
    -        return outputs
    -
    -
    -class SAR(RecognitionModel):
    -    """Implements a SAR architecture as described in `"Show, Attend and Read:A Simple and Strong Baseline for
    -    Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of hidden units in both encoder and decoder LSTM
    -        embedding_units: number of embedding units
    -        attention_units: number of hidden units in attention module
    -        max_length: maximum word length handled by the model
    -        num_decoders: number of LSTM to stack in decoder layer
    -
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'encoder', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor,
    -        vocab: str,
    -        rnn_units: int = 512,
    -        embedding_units: int = 512,
    -        attention_units: int = 512,
    -        max_length: int = 30,
    -        num_decoders: int = 2,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(vocab=vocab, cfg=cfg)
    -
    -        self.max_length = max_length + 1  # Add 1 timestep for EOS after the longest word
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.encoder = Sequential(
    -            [
    -                layers.LSTM(units=rnn_units, return_sequences=True),
    -                layers.LSTM(units=rnn_units, return_sequences=False)
    -            ]
    -        )
    -        # Initialize the kernels (watch out for reduce_max)
    -        self.encoder.build(input_shape=(None,) + self.feat_extractor.output_shape[2:])
    -
    -        self.decoder = SARDecoder(
    -            rnn_units, max_length, len(vocab), embedding_units, attention_units, num_decoders,
    -            input_shape=[self.feat_extractor.output_shape, self.encoder.output_shape]
    -        )
    -
    -        self.postprocessor = SARPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        gt: tf.Tensor,
    -        seq_len: tf.Tensor,
    -    ) -> tf.Tensor:
    -        """Compute categorical cross-entropy loss for the model.
    -        Sequences are masked after the EOS character.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        # Input length : number of timesteps
    -        input_len = tf.shape(model_output)[1]
    -        # Add one for additional <eos> token
    -        seq_len = seq_len + 1
    -        # One-hot gt labels
    -        oh_gt = tf.one_hot(gt, depth=model_output.shape[2])
    -        # Compute loss
    -        cce = tf.nn.softmax_cross_entropy_with_logits(oh_gt, model_output)
    -        # Compute mask
    -        mask_values = tf.zeros_like(cce)
    -        mask_2d = tf.sequence_mask(seq_len, input_len)
    -        masked_loss = tf.where(mask_2d, cce, mask_values)
    -        ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32))
    -        return tf.expand_dims(ce_loss, axis=1)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        pooled_features = tf.reduce_max(features, axis=1)  # vertical max pooling
    -        encoded = self.encoder(pooled_features, **kwargs)
    -        if target is not None:
    -            gt, seq_len = self.compute_target(target)
    -        decoded_features = self.decoder(features, encoded, gt=None if target is None else gt, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = decoded_features
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(decoded_features)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(decoded_features, gt, seq_len)
    -
    -        return out
    -
    -
    -class SARPostProcessor(RecognitionPostProcessor):
    -    """Post processor for SAR architectures
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor,
    -    ) -> List[Tuple[str, float]]:
    -        # compute pred with argmax for attention models
    -        out_idxs = tf.math.argmax(logits, axis=2)
    -        # N x L
    -        probs = tf.gather(tf.nn.softmax(logits, axis=-1), out_idxs, axis=-1, batch_dims=2)
    -        # Take the minimum confidence of the sequence
    -        probs = tf.math.reduce_min(probs, axis=1)
    -
    -        # decode raw output of the model with tf_label_to_idx
    -        out_idxs = tf.cast(out_idxs, dtype='int32')
    -        decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(self._embedding, out_idxs), axis=-1)
    -        decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -def _sar(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> SAR:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -    _cfg['embedding_units'] = kwargs.get('embedding_units', _cfg['rnn_units'])
    -    _cfg['attention_units'] = kwargs.get('attention_units', _cfg['rnn_units'])
    -    _cfg['max_length'] = kwargs.get('max_length', _cfg['max_length'])
    -    _cfg['num_decoders'] = kwargs.get('num_decoders', _cfg['num_decoders'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[default_cfgs[arch]['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -    kwargs['embedding_units'] = _cfg['embedding_units']
    -    kwargs['attention_units'] = _cfg['attention_units']
    -    kwargs['max_length'] = _cfg['max_length']
    -    kwargs['num_decoders'] = _cfg['num_decoders']
    -
    -    # Build the model
    -    model = SAR(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, default_cfgs[arch]['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a VGG16 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import sar_vgg16_bn - >>> model = sar_vgg16_bn(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_vgg16_bn', pretrained, **kwargs)
    - - - -
    -[docs] -def sar_resnet31(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a resnet-31 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example: - >>> import tensorflow as tf - >>> from doctr.models import sar_resnet31 - >>> model = sar_resnet31(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_resnet31', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.1/_modules/doctr/models/recognition/sar/tensorflow.html b/v0.3.1/_modules/doctr/models/recognition/sar/tensorflow.html index e514e4f0c4..4a591e6451 100644 --- a/v0.3.1/_modules/doctr/models/recognition/sar/tensorflow.html +++ b/v0.3.1/_modules/doctr/models/recognition/sar/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.sar.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.sar.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple
    +
     import tensorflow as tf
    -from tensorflow.keras import Sequential, layers, Model
    -from typing import Tuple, Dict, List, Any, Optional
    +from tensorflow.keras import Model, Sequential, layers
     
    -from ... import backbones
    -from ...utils import load_pretrained_params
    -from ..core import RecognitionModel, RecognitionPostProcessor
    +from doctr.datasets import VOCABS
     from doctr.utils.repr import NestedObject
     
    -__all__ = ['SAR', 'SARPostProcessor', 'sar_vgg16_bn', 'sar_resnet31']
    +from ...classification import resnet31
    +from ...utils.tensorflow import _bf16_to_float32, _build_model, load_pretrained_params
    +from ..core import RecognitionModel, RecognitionPostProcessor
    +
    +__all__ = ["SAR", "sar_resnet31"]
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'sar_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1-models/sar_vgg16bn-0d7e2c26.zip',
    -    },
    -    'sar_resnet31': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'resnet31', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/sar_resnet31-ea202587.zip',
    +    "sar_resnet31": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/sar_resnet31-5a58806c.weights.h5&src=0",
         },
     }
     
     
    +class SAREncoder(layers.Layer, NestedObject):
    +    """Implements encoder module of the SAR model
    +
    +    Args:
    +    ----
    +        rnn_units: number of hidden rnn units
    +        dropout_prob: dropout probability
    +    """
    +
    +    def __init__(self, rnn_units: int, dropout_prob: float = 0.0) -> None:
    +        super().__init__()
    +        self.rnn = Sequential([
    +            layers.LSTM(units=rnn_units, return_sequences=True, recurrent_dropout=dropout_prob),
    +            layers.LSTM(units=rnn_units, return_sequences=False, recurrent_dropout=dropout_prob),
    +        ])
    +
    +    def call(
    +        self,
    +        x: tf.Tensor,
    +        **kwargs: Any,
    +    ) -> tf.Tensor:
    +        # (N, C)
    +        return self.rnn(x, **kwargs)
    +
    +
     class AttentionModule(layers.Layer, NestedObject):
         """Implements attention module of the SAR model
     
         Args:
    +    ----
             attention_units: number of hidden attention units
     
         """
    -    def __init__(
    -        self,
    -        attention_units: int
    -    ) -> None:
     
    +    def __init__(self, attention_units: int) -> None:
             super().__init__()
             self.hidden_state_projector = layers.Conv2D(
    -            attention_units, 1, strides=1, use_bias=False, padding='same', kernel_initializer='he_normal',
    +            attention_units,
    +            1,
    +            strides=1,
    +            use_bias=False,
    +            padding="same",
    +            kernel_initializer="he_normal",
             )
             self.features_projector = layers.Conv2D(
    -            attention_units, 3, strides=1, use_bias=True, padding='same', kernel_initializer='he_normal',
    +            attention_units,
    +            3,
    +            strides=1,
    +            use_bias=True,
    +            padding="same",
    +            kernel_initializer="he_normal",
             )
             self.attention_projector = layers.Conv2D(
    -            1, 1, strides=1, use_bias=False, padding="same", kernel_initializer='he_normal',
    +            1,
    +            1,
    +            strides=1,
    +            use_bias=False,
    +            padding="same",
    +            kernel_initializer="he_normal",
             )
             self.flatten = layers.Flatten()
     
    @@ -343,12 +395,12 @@ 

    Source code for doctr.models.recognition.sar.tensorflow

    hidden_state: tf.Tensor, **kwargs: Any, ) -> tf.Tensor: - [H, W] = features.get_shape().as_list()[1:3] - # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units) - hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs) # shape (N, H, W, vgg_units) -> (N, H, W, attention_units) features_projection = self.features_projector(features, **kwargs) + # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units) + hidden_state = tf.expand_dims(tf.expand_dims(hidden_state, axis=1), axis=1) + hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs) projection = tf.math.tanh(hidden_state_projection + features_projection) # shape (N, H, W, attention_units) -> (N, H, W, 1) attention = self.attention_projector(projection, **kwargs) @@ -358,23 +410,25 @@

    Source code for doctr.models.recognition.sar.tensorflow

    # shape (N, H * W) -> (N, H, W, 1) attention_map = tf.reshape(attention, [-1, H, W, 1]) glimpse = tf.math.multiply(features, attention_map) - # shape (N, H * W) -> (N, 1) - glimpse = tf.reduce_sum(glimpse, axis=[1, 2]) - return glimpse + # shape (N, H * W) -> (N, C) + return tf.reduce_sum(glimpse, axis=[1, 2]) class SARDecoder(layers.Layer, NestedObject): """Implements decoder module of the SAR model Args: + ---- rnn_units: number of hidden units in recurrent cells max_length: maximum length of a sequence vocab_size: number of classes in the model alphabet embedding_units: number of hidden embedding units attention_units: number of hidden attention units - num_decoder_layers: number of LSTM layers to stack + num_decoder_cells: number of LSTMCell layers to stack + dropout_prob: dropout probability """ + def __init__( self, rnn_units: int, @@ -382,23 +436,22 @@

    Source code for doctr.models.recognition.sar.tensorflow

    vocab_size: int, embedding_units: int, attention_units: int, - num_decoder_layers: int = 2, - input_shape: Optional[List[Tuple[Optional[int]]]] = None, + num_decoder_cells: int = 2, + dropout_prob: float = 0.0, ) -> None: - super().__init__() self.vocab_size = vocab_size - self.lstm_decoder = layers.StackedRNNCells( - [layers.LSTMCell(rnn_units, dtype=tf.float32, implementation=1) for _ in range(num_decoder_layers)] - ) - self.embed = layers.Dense(embedding_units, use_bias=False, input_shape=(None, self.vocab_size + 1)) - self.attention_module = AttentionModule(attention_units) - self.output_dense = layers.Dense(vocab_size + 1, use_bias=True, input_shape=(None, 2 * rnn_units)) self.max_length = max_length - # Initialize kernels - if input_shape is not None: - self.attention_module.call(layers.Input(input_shape[0][1:]), layers.Input((1, 1, rnn_units))) + self.embed = layers.Dense(embedding_units, use_bias=False) + self.embed_tgt = layers.Embedding(embedding_units, self.vocab_size + 1) + + self.lstm_cells = layers.StackedRNNCells([ + layers.LSTMCell(rnn_units, implementation=1) for _ in range(num_decoder_cells) + ]) + self.attention_module = AttentionModule(attention_units) + self.output_dense = layers.Dense(self.vocab_size + 1, use_bias=True) + self.dropout = layers.Dropout(dropout_prob) def call( self, @@ -407,40 +460,47 @@

    Source code for doctr.models.recognition.sar.tensorflow

    gt: Optional[tf.Tensor] = None, **kwargs: Any, ) -> tf.Tensor: - - # initialize states (each of shape (N, rnn_units)) - states = self.lstm_decoder.get_initial_state( - inputs=None, batch_size=features.shape[0], dtype=tf.float32 - ) - # run first step of lstm - # holistic: shape (N, rnn_units) - _, states = self.lstm_decoder(holistic, states, **kwargs) - # Initialize with the index of virtual START symbol (placed after <eos>) - symbol = tf.fill(features.shape[0], self.vocab_size + 1) - logits_list = [] - if kwargs.get('training') and gt is None: - raise ValueError('Need to provide labels during training for teacher forcing') - for t in range(self.max_length + 1): # keep 1 step for <eos> - # one-hot symbol with depth vocab_size + 1 - # embeded_symbol: shape (N, embedding_units) - embeded_symbol = self.embed(tf.one_hot(symbol, depth=self.vocab_size + 1), **kwargs) - logits, states = self.lstm_decoder(embeded_symbol, states, **kwargs) - glimpse = self.attention_module( - features, tf.expand_dims(tf.expand_dims(logits, axis=1), axis=1), **kwargs, - ) - # logits: shape (N, rnn_units), glimpse: shape (N, 1) - logits = tf.concat([logits, glimpse], axis=-1) - # shape (N, rnn_units + 1) -> (N, vocab_size + 1) - logits = self.output_dense(logits, **kwargs) - # update symbol with predicted logits for t+1 step - if kwargs.get('training'): - symbol = gt[:, t] # type: ignore[index] + if gt is not None: + gt_embedding = self.embed_tgt(gt, **kwargs) + + logits_list: List[tf.Tensor] = [] + + for t in range(self.max_length + 1): # 32 + if t == 0: + # step to init the first states of the LSTMCell + states = self.lstm_cells.get_initial_state( + inputs=None, batch_size=features.shape[0], dtype=features.dtype + ) + prev_symbol = holistic + elif t == 1: + # step to init a 'blank' sequence of length vocab_size + 1 filled with zeros + # (N, vocab_size + 1) --> (N, embedding_units) + prev_symbol = tf.zeros([features.shape[0], self.vocab_size + 1], dtype=features.dtype) + prev_symbol = self.embed(prev_symbol, **kwargs) else: - symbol = tf.argmax(logits, axis=-1) - logits_list.append(logits) - outputs = tf.stack(logits_list, axis=1) # shape (N, max_length + 1, vocab_size + 1) - - return outputs + if gt is not None and kwargs.get("training", False): + # (N, embedding_units) -2 because of <bos> and <eos> (same) + prev_symbol = self.embed(gt_embedding[:, t - 2], **kwargs) + else: + # -1 to start at timestep where prev_symbol was initialized + index = tf.argmax(logits_list[t - 1], axis=-1) + # update prev_symbol with ones at the index of the previous logit vector + prev_symbol = self.embed(self.embed_tgt(index, **kwargs), **kwargs) + + # (N, C), (N, C) take the last hidden state and cell state from current timestep + _, states = self.lstm_cells(prev_symbol, states, **kwargs) + # states = (hidden_state, cell_state) + hidden_state = states[0][0] + # (N, H, W, C), (N, C) --> (N, C) + glimpse = self.attention_module(features, hidden_state, **kwargs) + # (N, C), (N, C) --> (N, 2 * C) + logits = tf.concat([hidden_state, glimpse], axis=1) + logits = self.dropout(logits, **kwargs) + # (N, vocab_size + 1) + logits_list.append(self.output_dense(logits, **kwargs)) + + # (max_length + 1, N, vocab_size + 1) --> (N, max_length + 1, vocab_size + 1) + return tf.transpose(tf.stack(logits_list[1:]), (1, 0, 2)) class SAR(Model, RecognitionModel): @@ -448,17 +508,20 @@

    Source code for doctr.models.recognition.sar.tensorflow

    Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. Args: + ---- feature_extractor: the backbone serving as feature extractor vocab: vocabulary used for encoding rnn_units: number of hidden units in both encoder and decoder LSTM embedding_units: number of embedding units attention_units: number of hidden units in attention module max_length: maximum word length handled by the model - num_decoders: number of LSTM to stack in decoder layer - + num_decoder_cells: number of LSTMCell layers to stack + dropout_prob: dropout probability for the encoder and decoder + exportable: onnx exportable returns only logits + cfg: dictionary containing information about the model """ - _children_names: List[str] = ['feat_extractor', 'encoder', 'decoder', 'postprocessor'] + _children_names: List[str] = ["feat_extractor", "encoder", "decoder", "postprocessor"] def __init__( self, @@ -468,36 +531,34 @@

    Source code for doctr.models.recognition.sar.tensorflow

    embedding_units: int = 512, attention_units: int = 512, max_length: int = 30, - num_decoders: int = 2, + num_decoder_cells: int = 2, + dropout_prob: float = 0.0, + exportable: bool = False, cfg: Optional[Dict[str, Any]] = None, ) -> None: - super().__init__() self.vocab = vocab + self.exportable = exportable self.cfg = cfg - self.max_length = max_length + 1 # Add 1 timestep for EOS after the longest word self.feat_extractor = feature_extractor - self.encoder = Sequential( - [ - layers.LSTM(units=rnn_units, return_sequences=True), - layers.LSTM(units=rnn_units, return_sequences=False) - ] - ) - # Initialize the kernels (watch out for reduce_max) - self.encoder.build(input_shape=(None,) + self.feat_extractor.output_shape[2:]) - + self.encoder = SAREncoder(rnn_units, dropout_prob) self.decoder = SARDecoder( - rnn_units, max_length, len(vocab), embedding_units, attention_units, num_decoders, - input_shape=[self.feat_extractor.output_shape, self.encoder.output_shape] + rnn_units, + self.max_length, + len(vocab), + embedding_units, + attention_units, + num_decoder_cells, + dropout_prob, ) self.postprocessor = SARPostProcessor(vocab=vocab) + @staticmethod def compute_loss( - self, model_output: tf.Tensor, gt: tf.Tensor, seq_len: tf.Tensor, @@ -506,11 +567,13 @@

    Source code for doctr.models.recognition.sar.tensorflow

    Sequences are masked after the EOS character. Args: + ---- gt: the encoded tensor with gt labels model_output: predicted logits of the model seq_len: lengths of each gt word inside the batch Returns: + ------- The loss of the model on the batch """ # Input length : number of timesteps @@ -525,7 +588,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    mask_values = tf.zeros_like(cce) mask_2d = tf.sequence_mask(seq_len, input_len) masked_loss = tf.where(mask_2d, cce, mask_values) - ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32)) + ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, model_output.dtype)) return tf.expand_dims(ce_loss, axis=1) def call( @@ -536,16 +599,28 @@

    Source code for doctr.models.recognition.sar.tensorflow

    return_preds: bool = False, **kwargs: Any, ) -> Dict[str, Any]: - features = self.feat_extractor(x, **kwargs) - pooled_features = tf.reduce_max(features, axis=1) # vertical max pooling + # vertical max pooling --> (N, C, W) + pooled_features = tf.reduce_max(features, axis=1) + # holistic (N, C) encoded = self.encoder(pooled_features, **kwargs) + if target is not None: - gt, seq_len = self.compute_target(target) + gt, seq_len = self.build_target(target) seq_len = tf.cast(seq_len, tf.int32) - decoded_features = self.decoder(features, encoded, gt=None if target is None else gt, **kwargs) + + if kwargs.get("training", False) and target is None: + raise ValueError("Need to provide labels during training for teacher forcing") + + decoded_features = _bf16_to_float32( + self.decoder(features, encoded, gt=None if target is None else gt, **kwargs) + ) out: Dict[str, tf.Tensor] = {} + if self.exportable: + out["logits"] = decoded_features + return out + if return_model_output: out["out_map"] = decoded_features @@ -554,7 +629,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    out["preds"] = self.postprocessor(decoded_features) if target is not None: - out['loss'] = self.compute_loss(decoded_features, gt, seq_len) + out["loss"] = self.compute_loss(decoded_features, gt, seq_len) return out @@ -563,9 +638,8 @@

    Source code for doctr.models.recognition.sar.tensorflow

    """Post processor for SAR architectures Args: + ---- vocab: string containing the ordered sequence of supported characters - ignore_case: if True, ignore case of letters - ignore_accents: if True, ignore accents of letters """ def __call__( @@ -580,95 +654,75 @@

    Source code for doctr.models.recognition.sar.tensorflow

    probs = tf.math.reduce_min(probs, axis=1) # decode raw output of the model with tf_label_to_idx - out_idxs = tf.cast(out_idxs, dtype='int32') + out_idxs = tf.cast(out_idxs, dtype="int32") embedding = tf.constant(self._embedding, dtype=tf.string) decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(embedding, out_idxs), axis=-1) decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>") - decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0] + decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value="not valid")[:, 0] word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] - return list(zip(word_values, probs.numpy().tolist())) + return list(zip(word_values, probs.numpy().clip(0, 1).tolist())) -def _sar(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> SAR: +def _sar( + arch: str, + pretrained: bool, + backbone_fn, + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> SAR: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab']) - _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units']) - _cfg['embedding_units'] = kwargs.get('embedding_units', _cfg['rnn_units']) - _cfg['attention_units'] = kwargs.get('attention_units', _cfg['rnn_units']) - _cfg['max_length'] = kwargs.get('max_length', _cfg['max_length']) - _cfg['num_decoders'] = kwargs.get('num_decoders', _cfg['num_decoders']) + _cfg["input_shape"] = input_shape or _cfg["input_shape"] + _cfg["vocab"] = kwargs.get("vocab", _cfg["vocab"]) # Feature extractor - feat_extractor = backbones.__dict__[default_cfgs[arch]['backbone']]( - input_shape=_cfg['input_shape'], + feat_extractor = backbone_fn( + pretrained=pretrained_backbone, + input_shape=_cfg["input_shape"], include_top=False, ) - kwargs['vocab'] = _cfg['vocab'] - kwargs['rnn_units'] = _cfg['rnn_units'] - kwargs['embedding_units'] = _cfg['embedding_units'] - kwargs['attention_units'] = _cfg['attention_units'] - kwargs['max_length'] = _cfg['max_length'] - kwargs['num_decoders'] = _cfg['num_decoders'] + kwargs["vocab"] = _cfg["vocab"] # Build the model model = SAR(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) # Load pretrained parameters if pretrained: - load_pretrained_params(model, default_cfgs[arch]['url']) + # The given vocab differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, default_cfgs[arch]["url"], skip_mismatch=kwargs["vocab"] != default_cfgs[arch]["vocab"] + ) return model -
    -[docs] -def sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a VGG16 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import sar_vgg16_bn - >>> model = sar_vgg16_bn(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_vgg16_bn', pretrained, **kwargs)
    - - -
    -[docs] +[docs] def sar_resnet31(pretrained: bool = False, **kwargs: Any) -> SAR: """SAR with a resnet-31 feature extractor as described in `"Show, Attend and Read:A Simple and Strong Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - Example: - >>> import tensorflow as tf - >>> from doctr.models import sar_resnet31 - >>> model = sar_resnet31(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import sar_resnet31 + >>> model = sar_resnet31(pretrained=False) + >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the SAR architecture Returns: + ------- text recognition architecture """ - - return _sar('sar_resnet31', pretrained, **kwargs)
    + return _sar("sar_resnet31", pretrained, resnet31, **kwargs)
    @@ -702,8 +756,8 @@

    Source code for doctr.models.recognition.sar.tensorflow

    - +
    + diff --git a/v0.3.1/_modules/doctr/models/recognition/vitstr/tensorflow.html b/v0.3.1/_modules/doctr/models/recognition/vitstr/tensorflow.html index 6e101893bf..c594d40a56 100644 --- a/v0.3.1/_modules/doctr/models/recognition/vitstr/tensorflow.html +++ b/v0.3.1/_modules/doctr/models/recognition/vitstr/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.vitstr.tensorflow - docTR documentation @@ -621,7 +621,7 @@

    Source code for doctr.models.recognition.vitstr.tensorflow

    - + diff --git a/v0.3.1/_modules/doctr/models/recognition/zoo.html b/v0.3.1/_modules/doctr/models/recognition/zoo.html index bf0ae6af6e..f664304019 100644 --- a/v0.3.1/_modules/doctr/models/recognition/zoo.html +++ b/v0.3.1/_modules/doctr/models/recognition/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.zoo - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -from typing import Any
    +from typing import Any, List
     
    -from doctr.file_utils import is_tf_available, is_torch_available
    -from .core import RecognitionPredictor
    -from ..preprocessor import PreProcessor
    -from .. import recognition
    +from doctr.file_utils import is_tf_available
    +from doctr.models.preprocessor import PreProcessor
     
    +from .. import recognition
    +from .predictor import RecognitionPredictor
     
     __all__ = ["recognition_predictor"]
     
     
    -if is_tf_available():
    -    ARCHS = ['crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31', 'master']
    -elif is_torch_available():
    -    ARCHS = ['crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31']
    +ARCHS: List[str] = [
    +    "crnn_vgg16_bn",
    +    "crnn_mobilenet_v3_small",
    +    "crnn_mobilenet_v3_large",
    +    "sar_resnet31",
    +    "master",
    +    "vitstr_small",
    +    "vitstr_base",
    +    "parseq",
    +]
    +
     
    +def _predictor(arch: Any, pretrained: bool, **kwargs: Any) -> RecognitionPredictor:
    +    if isinstance(arch, str):
    +        if arch not in ARCHS:
    +            raise ValueError(f"unknown architecture '{arch}'")
     
    -def _predictor(arch: str, pretrained: bool, **kwargs: Any) -> RecognitionPredictor:
    +        _model = recognition.__dict__[arch](
    +            pretrained=pretrained, pretrained_backbone=kwargs.get("pretrained_backbone", True)
    +        )
    +    else:
    +        if not isinstance(
    +            arch, (recognition.CRNN, recognition.SAR, recognition.MASTER, recognition.ViTSTR, recognition.PARSeq)
    +        ):
    +            raise ValueError(f"unknown architecture: {type(arch)}")
    +        _model = arch
     
    -    if arch not in ARCHS:
    -        raise ValueError(f"unknown architecture '{arch}'")
    +    kwargs.pop("pretrained_backbone", None)
     
    -    _model = recognition.__dict__[arch](pretrained=pretrained)
    -    kwargs['mean'] = kwargs.get('mean', _model.cfg['mean'])
    -    kwargs['std'] = kwargs.get('std', _model.cfg['std'])
    -    kwargs['batch_size'] = kwargs.get('batch_size', 32)
    -    predictor = RecognitionPredictor(
    -        PreProcessor(_model.cfg['input_shape'][:2], preserve_aspect_ratio=True, **kwargs),
    -        _model
    -    )
    +    kwargs["mean"] = kwargs.get("mean", _model.cfg["mean"])
    +    kwargs["std"] = kwargs.get("std", _model.cfg["std"])
    +    kwargs["batch_size"] = kwargs.get("batch_size", 128)
    +    input_shape = _model.cfg["input_shape"][:2] if is_tf_available() else _model.cfg["input_shape"][-2:]
    +    predictor = RecognitionPredictor(PreProcessor(input_shape, preserve_aspect_ratio=True, **kwargs), _model)
     
         return predictor
     
     
     
    -[docs] -def recognition_predictor(arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) -> RecognitionPredictor: +[docs] +def recognition_predictor( + arch: Any = "crnn_vgg16_bn", + pretrained: bool = False, + symmetric_pad: bool = False, + batch_size: int = 128, + **kwargs: Any, +) -> RecognitionPredictor: """Text recognition architecture. Example:: @@ -326,14 +369,18 @@

    Source code for doctr.models.recognition.zoo

            >>> out = model([input_page])
     
         Args:
    -        arch: name of the architecture to use ('crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31')
    +    ----
    +        arch: name of the architecture or model itself to use (e.g. 'crnn_vgg16_bn')
             pretrained: If True, returns a model pre-trained on our text recognition dataset
    +        symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right
    +        batch_size: number of samples the model processes in parallel
    +        **kwargs: optional parameters to be passed to the architecture
     
         Returns:
    +    -------
             Recognition predictor
         """
    -
    -    return _predictor(arch, pretrained, **kwargs)
    + return _predictor(arch=arch, pretrained=pretrained, symmetric_pad=symmetric_pad, batch_size=batch_size, **kwargs)
    @@ -367,8 +414,8 @@

    Source code for doctr.models.recognition.zoo

       
    -
    - +
    + diff --git a/v0.3.1/_modules/doctr/models/zoo.html b/v0.3.1/_modules/doctr/models/zoo.html index dec6857019..d459671648 100644 --- a/v0.3.1/_modules/doctr/models/zoo.html +++ b/v0.3.1/_modules/doctr/models/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.zoo - docTR documentation @@ -225,15 +225,42 @@

    Source code for doctr.models.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     from typing import Any
    -from .core import OCRPredictor
    +
     from .detection.zoo import detection_predictor
    +from .kie_predictor import KIEPredictor
    +from .predictor import OCRPredictor
     from .recognition.zoo import recognition_predictor
     
    +__all__ = ["ocr_predictor", "kie_predictor"]
     
    -__all__ = ["ocr_predictor"]
    -
    -
    -def _predictor(det_arch: str, reco_arch: str, pretrained: bool, det_bs=2, reco_bs=128) -> OCRPredictor:
     
    +def _predictor(
    +    det_arch: Any,
    +    reco_arch: Any,
    +    pretrained: bool,
    +    pretrained_backbone: bool = True,
    +    assume_straight_pages: bool = True,
    +    preserve_aspect_ratio: bool = True,
    +    symmetric_pad: bool = True,
    +    det_bs: int = 2,
    +    reco_bs: int = 128,
    +    detect_orientation: bool = False,
    +    straighten_pages: bool = False,
    +    detect_language: bool = False,
    +    **kwargs,
    +) -> OCRPredictor:
         # Detection
    -    det_predictor = detection_predictor(det_arch, pretrained=pretrained, batch_size=det_bs)
    +    det_predictor = detection_predictor(
    +        det_arch,
    +        pretrained=pretrained,
    +        pretrained_backbone=pretrained_backbone,
    +        batch_size=det_bs,
    +        assume_straight_pages=assume_straight_pages,
    +        preserve_aspect_ratio=preserve_aspect_ratio,
    +        symmetric_pad=symmetric_pad,
    +    )
     
         # Recognition
    -    reco_predictor = recognition_predictor(reco_arch, pretrained=pretrained, batch_size=reco_bs)
    +    reco_predictor = recognition_predictor(
    +        reco_arch,
    +        pretrained=pretrained,
    +        pretrained_backbone=pretrained_backbone,
    +        batch_size=reco_bs,
    +    )
     
    -    return OCRPredictor(det_predictor, reco_predictor)
    +    return OCRPredictor(
    +        det_predictor,
    +        reco_predictor,
    +        assume_straight_pages=assume_straight_pages,
    +        preserve_aspect_ratio=preserve_aspect_ratio,
    +        symmetric_pad=symmetric_pad,
    +        detect_orientation=detect_orientation,
    +        straighten_pages=straighten_pages,
    +        detect_language=detect_language,
    +        **kwargs,
    +    )
     
     
     
    -[docs] +[docs] def ocr_predictor( - det_arch: str = 'db_resnet50', - reco_arch: str = 'crnn_vgg16_bn', + det_arch: Any = "fast_base", + reco_arch: Any = "crnn_vgg16_bn", pretrained: bool = False, - **kwargs: Any + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + export_as_straight_boxes: bool = False, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs: Any, ) -> OCRPredictor: """End-to-end OCR architecture using one model for localization, and another for text recognition. - Example:: - >>> import numpy as np - >>> from doctr.models import ocr_predictor - >>> model = ocr_predictor(pretrained=True) - >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) - >>> out = model([input_page]) + >>> 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(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) Args: - arch: name of the architecture to use ('db_sar_vgg', 'db_sar_resnet', 'db_crnn_vgg', 'db_crnn_resnet') + ---- + det_arch: name of the detection architecture or the model itself to use + (e.g. 'db_resnet50', 'db_mobilenet_v3_large') + reco_arch: name of the recognition architecture or the model itself to use + (e.g. 'crnn_vgg16_bn', 'sar_resnet31') pretrained: If True, returns a model pre-trained on our OCR dataset + pretrained_backbone: If True, returns a model with a pretrained backbone + assume_straight_pages: if True, speeds up the inference by assuming you only pass straight pages + without rotated textual elements. + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it. + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right. + export_as_straight_boxes: when assume_straight_pages is set to False, export final predictions + (potentially rotated) as straight bounding boxes. + detect_orientation: if True, the estimated general page orientation will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + straighten_pages: if True, estimates the page general orientation + based on the segmentation map median line orientation. + Then, rotates page before passing it again to the deep learning detection module. + Doing so will improve performances for documents with page-uniform rotations. + detect_language: if True, the language prediction will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + kwargs: keyword args of `OCRPredictor` Returns: + ------- OCR predictor """ + return _predictor( + det_arch, + reco_arch, + pretrained, + pretrained_backbone=pretrained_backbone, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + export_as_straight_boxes=export_as_straight_boxes, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + )
    + + - return _predictor(det_arch, reco_arch, pretrained, **kwargs)
    +def _kie_predictor( + det_arch: Any, + reco_arch: Any, + pretrained: bool, + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + det_bs: int = 2, + reco_bs: int = 128, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs, +) -> KIEPredictor: + # Detection + det_predictor = detection_predictor( + det_arch, + pretrained=pretrained, + pretrained_backbone=pretrained_backbone, + batch_size=det_bs, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + ) + + # Recognition + reco_predictor = recognition_predictor( + reco_arch, + pretrained=pretrained, + pretrained_backbone=pretrained_backbone, + batch_size=reco_bs, + ) + + return KIEPredictor( + det_predictor, + reco_predictor, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + ) + + +
    +[docs] +def kie_predictor( + det_arch: Any = "fast_base", + reco_arch: Any = "crnn_vgg16_bn", + pretrained: bool = False, + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + export_as_straight_boxes: bool = False, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs: Any, +) -> KIEPredictor: + """End-to-end KIE architecture using one model for localization, and another for text recognition. + + >>> 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(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) + + Args: + ---- + det_arch: name of the detection architecture or the model itself to use + (e.g. 'db_resnet50', 'db_mobilenet_v3_large') + reco_arch: name of the recognition architecture or the model itself to use + (e.g. 'crnn_vgg16_bn', 'sar_resnet31') + pretrained: If True, returns a model pre-trained on our OCR dataset + pretrained_backbone: If True, returns a model with a pretrained backbone + assume_straight_pages: if True, speeds up the inference by assuming you only pass straight pages + without rotated textual elements. + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it. + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right. + export_as_straight_boxes: when assume_straight_pages is set to False, export final predictions + (potentially rotated) as straight bounding boxes. + detect_orientation: if True, the estimated general page orientation will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + straighten_pages: if True, estimates the page general orientation + based on the segmentation map median line orientation. + Then, rotates page before passing it again to the deep learning detection module. + Doing so will improve performances for documents with page-uniform rotations. + detect_language: if True, the language prediction will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + kwargs: keyword args of `OCRPredictor` + + Returns: + ------- + KIE predictor + """ + return _kie_predictor( + det_arch, + reco_arch, + pretrained, + pretrained_backbone=pretrained_backbone, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + export_as_straight_boxes=export_as_straight_boxes, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + )
    @@ -353,8 +575,8 @@

    Source code for doctr.models.zoo

           
         
       
    - - + + diff --git a/v0.3.1/_modules/doctr/transforms/modules.html b/v0.3.1/_modules/doctr/transforms/modules.html deleted file mode 100644 index ba8269e7ef..0000000000 --- a/v0.3.1/_modules/doctr/transforms/modules.html +++ /dev/null @@ -1,734 +0,0 @@ - - - - - - - - - - - - doctr.transforms.modules - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.transforms.modules

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import random
    -import tensorflow as tf
    -from typing import List, Any, Tuple, Callable
    -
    -from doctr.utils.repr import NestedObject
    -from . import functional as F
    -
    -
    -__all__ = ['Compose', 'Resize', 'Normalize', 'LambdaTransformation', 'ToGray', 'ColorInversion',
    -           'RandomBrightness', 'RandomContrast', 'RandomSaturation', 'RandomHue', 'RandomGamma', 'RandomJpegQuality',
    -           'OneOf', 'RandomApply']
    -
    -
    -
    -[docs] -class Compose(NestedObject): - """Implements a wrapper that will apply transformations sequentially - - Example:: - >>> from doctr.transforms import Compose, Resize - >>> import tensorflow as tf - >>> transfos = Compose([Resize((32, 32))]) - >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformation modules - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, x: Any) -> Any: - for t in self.transforms: - x = t(x) - - return x
    - - - -
    -[docs] -class Resize(NestedObject): - """Resizes a tensor to a target size - - Example:: - >>> from doctr.transforms import Resize - >>> import tensorflow as tf - >>> transfo = Resize((32, 32)) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - output_size: expected output size - method: interpolation method - preserve_aspect_ratio: if `True`, preserve aspect ratio and pad the rest with zeros - symmetric_pad: if `True` while preserving aspect ratio, the padding will be done symmetrically - """ - def __init__( - self, - output_size: Tuple[int, int], - method: str = 'bilinear', - preserve_aspect_ratio: bool = False, - symmetric_pad: bool = False, - ) -> None: - self.output_size = output_size - self.method = method - self.preserve_aspect_ratio = preserve_aspect_ratio - self.symmetric_pad = symmetric_pad - - def extra_repr(self) -> str: - _repr = f"output_size={self.output_size}, method='{self.method}'" - if self.preserve_aspect_ratio: - _repr += f", preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}" - return _repr - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img = tf.image.resize(img, self.output_size, self.method, self.preserve_aspect_ratio) - if self.preserve_aspect_ratio: - # pad width - if not self.symmetric_pad: - offset = (0, 0) - elif self.output_size[0] == img.shape[0]: - offset = (0, int((self.output_size[1] - img.shape[1]) / 2)) - else: - offset = (int((self.output_size[0] - img.shape[0]) / 2), 0) - img = tf.image.pad_to_bounding_box(img, *offset, *self.output_size) - return img
    - - - -
    -[docs] -class Normalize(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - mean: average value per channel - std: standard deviation per channel - """ - def __init__(self, mean: Tuple[float, float, float], std: Tuple[float, float, float]) -> None: - self.mean = tf.constant(mean, dtype=tf.float32) - self.std = tf.constant(std, dtype=tf.float32) - - def extra_repr(self) -> str: - return f"mean={self.mean.numpy().tolist()}, std={self.std.numpy().tolist()}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img -= self.mean - img /= self.std - return img
    - - - -
    -[docs] -class LambdaTransformation(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import LambdaTransformation - >>> import tensorflow as tf - >>> transfo = LambdaTransformation(lambda x: x/ 255.) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - fn: the function to be applied to the input tensor - """ - def __init__(self, fn: Callable[[tf.Tensor], tf.Tensor]) -> None: - self.fn = fn - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return self.fn(img)
    - - - -
    -[docs] -class ToGray(NestedObject): - """Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ToGray() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - """ - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.rgb_to_grayscale(img)
    - - - -
    -[docs] -class ColorInversion(NestedObject): - """Applies the following tranformation to a tensor (image or batch of images): - convert to grayscale, colorize (shift 0-values randomly), and then invert colors - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ColorInversion(min_val=0.6) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_val: range [min_val, 1] to colorize RGB pixels - """ - def __init__(self, min_val: float = 0.6) -> None: - self.min_val = min_val - - def extra_repr(self) -> str: - return f"min_val={self.min_val}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return F.invert_colors(img, self.min_val)
    - - - -
    -[docs] -class RandomBrightness(NestedObject): - """Randomly adjust brightness of a tensor (batch of images or image) by adding a delta - to all pixels - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Brightness() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - p: probability to apply transformation - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_brightness(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomContrast(NestedObject): - """Randomly adjust contrast of a tensor (batch of images or image) by adjusting - each pixel: (img - mean) * contrast_factor + mean. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Contrast() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1) - """ - def __init__(self, delta: float = .3) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_contrast(img, lower=1 - self.delta, upper=1 / (1 - self.delta))
    - - - -
    -[docs] -class RandomSaturation(NestedObject): - """Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and - increasing saturation by a factor. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Saturation() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1) - """ - def __init__(self, delta: float = .5) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_saturation(img, lower=1 - self.delta, upper=1 + self.delta)
    - - - -
    -[docs] -class RandomHue(NestedObject): - """Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Hue() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_hue(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomGamma(NestedObject): - """randomly performs gamma correction for a tensor (batch of images or image) - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Gamma() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_gamma: non-negative real number, lower bound for gamma param - max_gamma: non-negative real number, upper bound for gamma - min_gain: lower bound for constant multiplier - max_gain: upper bound for constant multiplier - """ - def __init__( - self, - min_gamma: float = 0.5, - max_gamma: float = 1.5, - min_gain: float = 0.8, - max_gain: float = 1.2, - ) -> None: - self.min_gamma = min_gamma - self.max_gamma = max_gamma - self.min_gain = min_gain - self.max_gain = max_gain - - def extra_repr(self) -> str: - return f"""gamma_range=({self.min_gamma}, {self.max_gamma}), - gain_range=({self.min_gain}, {self.max_gain})""" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - gamma = random.uniform(self.min_gamma, self.max_gamma) - gain = random.uniform(self.min_gain, self.max_gain) - return tf.image.adjust_gamma(img, gamma=gamma, gain=gain)
    - - - -
    -[docs] -class RandomJpegQuality(NestedObject): - """Randomly adjust jpeg quality of a 3 dimensional RGB image - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = JpegQuality() - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - min_quality: int between [0, 100] - max_quality: int between [0, 100] - """ - def __init__(self, min_quality: int = 60, max_quality: int = 100) -> None: - self.min_quality = min_quality - self.max_quality = max_quality - - def extra_repr(self) -> str: - return f"min_quality={self.min_quality}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_jpeg_quality( - img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality - )
    - - - -
    -[docs] -class OneOf(NestedObject): - """Randomly apply one of the input transformations - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = OneOf([JpegQuality(), Gamma()]) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformations, one only will be picked - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - # Pick transformation - transfo = self.transforms[int(random.random() * len(self.transforms))] - # Apply - return transfo(img)
    - - - -
    -[docs] -class RandomApply(NestedObject): - """Apply with a probability p the input transformation - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = RandomApply(Gamma(), p=.5) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transform: transformation to apply - p: probability to apply - """ - def __init__(self, transform: NestedObject, p: float = .5) -> None: - self.transform = transform - self.p = p - - def extra_repr(self) -> str: - return f"transform={self.transform}, p={self.p}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - if random.random() < self.p: - return self.transform(img) - return img
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.1/_modules/doctr/transforms/modules/base.html b/v0.3.1/_modules/doctr/transforms/modules/base.html index c42079a8fd..4596df3848 100644 --- a/v0.3.1/_modules/doctr/transforms/modules/base.html +++ b/v0.3.1/_modules/doctr/transforms/modules/base.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.base - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.transforms.modules.base

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    +import math
     import random
    -from typing import List, Any, Callable
    +from typing import Any, Callable, List, Optional, Tuple, Union
    +
    +import numpy as np
     
     from doctr.utils.repr import NestedObject
    +
     from .. import functional as F
     
    +__all__ = ["SampleCompose", "ImageTransform", "ColorInversion", "OneOf", "RandomApply", "RandomRotate", "RandomCrop"]
    +
    +
    +class SampleCompose(NestedObject):
    +    """Implements a wrapper that will apply transformations sequentially on both image and target
    +
    +    .. tabs::
    +
    +        .. tab:: TensorFlow
    +
    +            .. code:: python
    +
    +                >>> import numpy as np
    +                >>> import tensorflow as tf
    +                >>> from doctr.transforms import SampleCompose, ImageTransform, ColorInversion, RandomRotate
    +                >>> transfo = SampleCompose([ImageTransform(ColorInversion((32, 32))), RandomRotate(30)])
    +                >>> out, out_boxes = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1), np.zeros((2, 4)))
    +
    +        .. tab:: PyTorch
    +
    +            .. code:: python
    +
    +                >>> import numpy as np
    +                >>> import torch
    +                >>> from doctr.transforms import SampleCompose, ImageTransform, ColorInversion, RandomRotate
    +                >>> transfos = SampleCompose([ImageTransform(ColorInversion((32, 32))), RandomRotate(30)])
    +                >>> out, out_boxes = transfos(torch.rand(8, 64, 64, 3), np.zeros((2, 4)))
    +
    +    Args:
    +    ----
    +        transforms: list of transformation modules
    +    """
    +
    +    _children_names: List[str] = ["sample_transforms"]
    +
    +    def __init__(self, transforms: List[Callable[[Any, Any], Tuple[Any, Any]]]) -> None:
    +        self.sample_transforms = transforms
    +
    +    def __call__(self, x: Any, target: Any) -> Tuple[Any, Any]:
    +        for t in self.sample_transforms:
    +            x, target = t(x, target)
    +
    +        return x, target
    +
    +
    +class ImageTransform(NestedObject):
    +    """Implements a transform wrapper to turn an image-only transformation into an image+target transform
    +
    +    .. tabs::
    +
    +        .. tab:: TensorFlow
    +
    +            .. code:: python
    +
    +                >>> import tensorflow as tf
    +                >>> from doctr.transforms import ImageTransform, ColorInversion
    +                >>> transfo = ImageTransform(ColorInversion((32, 32)))
    +                >>> out, _ = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1), None)
    +
    +        .. tab:: PyTorch
    +
    +            .. code:: python
    +
    +                >>> import torch
    +                >>> from doctr.transforms import ImageTransform, ColorInversion
    +                >>> transfo = ImageTransform(ColorInversion((32, 32)))
    +                >>> out, _ = transfo(torch.rand(8, 64, 64, 3), None)
    +
    +    Args:
    +    ----
    +        transform: the image transformation module to wrap
    +    """
    +
    +    _children_names: List[str] = ["img_transform"]
    +
    +    def __init__(self, transform: Callable[[Any], Any]) -> None:
    +        self.img_transform = transform
     
    -__all__ = ['ColorInversion', 'OneOf', 'RandomApply']
    +    def __call__(self, img: Any, target: Any) -> Tuple[Any, Any]:
    +        img = self.img_transform(img)
    +        return img, target
     
     
     
    -[docs] +[docs] class ColorInversion(NestedObject): """Applies the following tranformation to a tensor (image or batch of images): convert to grayscale, colorize (shift 0-values randomly), and then invert colors - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ColorInversion(min_val=0.6) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + .. tabs:: + + .. tab:: TensorFlow + + .. code:: python + + >>> import tensorflow as tf + >>> from doctr.transforms import ColorInversion + >>> transfo = ColorInversion(min_val=0.6) + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + + .. tab:: PyTorch + + .. code:: python + + >>> import torch + >>> from doctr.transforms import ColorInversion + >>> transfo = ColorInversion(min_val=0.6) + >>> out = transfo(torch.rand(8, 64, 64, 3)) Args: + ---- min_val: range [min_val, 1] to colorize RGB pixels """ + def __init__(self, min_val: float = 0.5) -> None: self.min_val = min_val @@ -316,59 +437,178 @@

    Source code for doctr.transforms.modules.base

    -[docs] +[docs] class OneOf(NestedObject): """Randomly apply one of the input transformations - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = OneOf([JpegQuality(), Gamma()]) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + .. tabs:: + + .. tab:: TensorFlow + + .. code:: python + + >>> import tensorflow as tf + >>> from doctr.transforms import OneOf + >>> transfo = OneOf([JpegQuality(), Gamma()]) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + .. tab:: PyTorch + + .. code:: python + + >>> import torch + >>> from doctr.transforms import OneOf + >>> transfo = OneOf([JpegQuality(), Gamma()]) + >>> out = transfo(torch.rand(1, 64, 64, 3)) Args: + ---- transforms: list of transformations, one only will be picked """ - _children_names: List[str] = ['transforms'] + _children_names: List[str] = ["transforms"] def __init__(self, transforms: List[Callable[[Any], Any]]) -> None: self.transforms = transforms - def __call__(self, img: Any) -> Any: + def __call__(self, img: Any, target: Optional[np.ndarray] = None) -> Union[Any, Tuple[Any, np.ndarray]]: # Pick transformation transfo = self.transforms[int(random.random() * len(self.transforms))] # Apply - return transfo(img)
    + return transfo(img) if target is None else transfo(img, target) # type: ignore[call-arg]
    -[docs] +[docs] class RandomApply(NestedObject): """Apply with a probability p the input transformation - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = RandomApply(Gamma(), p=.5) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + .. tabs:: + + .. tab:: TensorFlow + + .. code:: python + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomApply + >>> transfo = RandomApply(Gamma(), p=.5) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + .. tab:: PyTorch + + .. code:: python + + >>> import torch + >>> from doctr.transforms import RandomApply + >>> transfo = RandomApply(Gamma(), p=.5) + >>> out = transfo(torch.rand(1, 64, 64, 3)) Args: + ---- transform: transformation to apply p: probability to apply """ - def __init__(self, transform: Callable[[Any], Any], p: float = .5) -> None: + + def __init__(self, transform: Callable[[Any], Any], p: float = 0.5) -> None: self.transform = transform self.p = p def extra_repr(self) -> str: return f"transform={self.transform}, p={self.p}" - def __call__(self, img: Any) -> Any: + def __call__(self, img: Any, target: Optional[np.ndarray] = None) -> Union[Any, Tuple[Any, np.ndarray]]: if random.random() < self.p: - return self.transform(img) - return img
    + return self.transform(img) if target is None else self.transform(img, target) # type: ignore[call-arg] + return img if target is None else (img, target)
    + + + +
    +[docs] +class RandomRotate(NestedObject): + """Randomly rotate a tensor image and its boxes + + .. image:: https://doctr-static.mindee.com/models?id=v0.4.0/rotation_illustration.png&src=0 + :align: center + + Args: + ---- + max_angle: maximum angle for rotation, in degrees. Angles will be uniformly picked in + [-max_angle, max_angle] + expand: whether the image should be padded before the rotation + """ + + def __init__(self, max_angle: float = 5.0, expand: bool = False) -> None: + self.max_angle = max_angle + self.expand = expand + + def extra_repr(self) -> str: + return f"max_angle={self.max_angle}, expand={self.expand}" + + def __call__(self, img: Any, target: np.ndarray) -> Tuple[Any, np.ndarray]: + angle = random.uniform(-self.max_angle, self.max_angle) + r_img, r_polys = F.rotate_sample(img, target, angle, self.expand) + # Removes deleted boxes + is_kept = (r_polys.max(1) > r_polys.min(1)).sum(1) == 2 + return r_img, r_polys[is_kept]
    + + + +
    +[docs] +class RandomCrop(NestedObject): + """Randomly crop a tensor image and its boxes + + Args: + ---- + scale: tuple of floats, relative (min_area, max_area) of the crop + ratio: tuple of float, relative (min_ratio, max_ratio) where ratio = h/w + """ + + def __init__(self, scale: Tuple[float, float] = (0.08, 1.0), ratio: Tuple[float, float] = (0.75, 1.33)) -> None: + self.scale = scale + self.ratio = ratio + + def extra_repr(self) -> str: + return f"scale={self.scale}, ratio={self.ratio}" + + def __call__(self, img: Any, target: np.ndarray) -> Tuple[Any, np.ndarray]: + scale = random.uniform(self.scale[0], self.scale[1]) + ratio = random.uniform(self.ratio[0], self.ratio[1]) + + height, width = img.shape[:2] + + # Calculate crop size + crop_area = scale * width * height + aspect_ratio = ratio * (width / height) + crop_width = int(round(math.sqrt(crop_area * aspect_ratio))) + crop_height = int(round(math.sqrt(crop_area / aspect_ratio))) + + # Ensure crop size does not exceed image dimensions + crop_width = min(crop_width, width) + crop_height = min(crop_height, height) + + # Randomly select crop position + x = random.randint(0, width - crop_width) + y = random.randint(0, height - crop_height) + + # relative crop box + crop_box = (x / width, y / height, (x + crop_width) / width, (y + crop_height) / height) + if target.shape[1:] == (4, 2): + min_xy = np.min(target, axis=1) + max_xy = np.max(target, axis=1) + _target = np.concatenate((min_xy, max_xy), axis=1) + else: + _target = target + + # Crop image and targets + croped_img, crop_boxes = F.crop_detection(img, _target, crop_box) + # hard fallback if no box is kept + if crop_boxes.shape[0] == 0: + return img, target + # clip boxes + return croped_img, np.clip(crop_boxes, 0, 1)
    @@ -402,8 +642,8 @@

    Source code for doctr.transforms.modules.base

    - - + + diff --git a/v0.3.1/_modules/doctr/transforms/modules/tensorflow.html b/v0.3.1/_modules/doctr/transforms/modules/tensorflow.html index 1d192a876b..acbbe96225 100644 --- a/v0.3.1/_modules/doctr/transforms/modules/tensorflow.html +++ b/v0.3.1/_modules/doctr/transforms/modules/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.transforms.modules.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import random
    +from typing import Any, Callable, Iterable, List, Optional, Tuple, Union
    +
    +import numpy as np
     import tensorflow as tf
    -from typing import List, Any, Tuple, Callable
     
     from doctr.utils.repr import NestedObject
     
    -
    -__all__ = ['Compose', 'Resize', 'Normalize', 'LambdaTransformation', 'ToGray', 'RandomBrightness',
    -           'RandomContrast', 'RandomSaturation', 'RandomHue', 'RandomGamma', 'RandomJpegQuality']
    +from ..functional.tensorflow import _gaussian_filter, random_shadow
    +
    +__all__ = [
    +    "Compose",
    +    "Resize",
    +    "Normalize",
    +    "LambdaTransformation",
    +    "ToGray",
    +    "RandomBrightness",
    +    "RandomContrast",
    +    "RandomSaturation",
    +    "RandomHue",
    +    "RandomGamma",
    +    "RandomJpegQuality",
    +    "GaussianBlur",
    +    "ChannelShuffle",
    +    "GaussianNoise",
    +    "RandomHorizontalFlip",
    +    "RandomShadow",
    +    "RandomResize",
    +]
     
     
     
    -[docs] +[docs] class Compose(NestedObject): """Implements a wrapper that will apply transformations sequentially - Example:: - >>> from doctr.transforms import Compose, Resize - >>> import tensorflow as tf - >>> transfos = Compose([Resize((32, 32))]) - >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import Compose, Resize + >>> transfos = Compose([Resize((32, 32))]) + >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) Args: + ---- transforms: list of transformation modules """ - _children_names: List[str] = ['transforms'] + _children_names: List[str] = ["transforms"] def __init__(self, transforms: List[Callable[[Any], Any]]) -> None: self.transforms = transforms @@ -319,26 +361,27 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class Resize(NestedObject): """Resizes a tensor to a target size - Example:: - >>> from doctr.transforms import Resize - >>> import tensorflow as tf - >>> transfo = Resize((32, 32)) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import Resize + >>> transfo = Resize((32, 32)) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) Args: + ---- output_size: expected output size method: interpolation method preserve_aspect_ratio: if `True`, preserve aspect ratio and pad the rest with zeros symmetric_pad: if `True` while preserving aspect ratio, the padding will be done symmetrically """ + def __init__( self, - output_size: Tuple[int, int], - method: str = 'bilinear', + output_size: Union[int, Tuple[int, int]], + method: str = "bilinear", preserve_aspect_ratio: bool = False, symmetric_pad: bool = False, ) -> None: @@ -346,6 +389,14 @@

    Source code for doctr.transforms.modules.tensorflow

    self.method = method self.preserve_aspect_ratio = preserve_aspect_ratio self.symmetric_pad = symmetric_pad + self.antialias = True + + if isinstance(self.output_size, int): + self.wanted_size = (self.output_size, self.output_size) + elif isinstance(self.output_size, (tuple, list)): + self.wanted_size = self.output_size + else: + raise AssertionError("Output size should be either a list, a tuple or an int") def extra_repr(self) -> str: _repr = f"output_size={self.output_size}, method='{self.method}'" @@ -353,64 +404,106 @@

    Source code for doctr.transforms.modules.tensorflow

    _repr += f", preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}" return _repr - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img = tf.image.resize(img, self.output_size, self.method, self.preserve_aspect_ratio) + def __call__( + self, + img: tf.Tensor, + target: Optional[np.ndarray] = None, + ) -> Union[tf.Tensor, Tuple[tf.Tensor, np.ndarray]]: + input_dtype = img.dtype + self.output_size = ( + (self.output_size, self.output_size) if isinstance(self.output_size, int) else self.output_size + ) + + img = tf.image.resize(img, self.wanted_size, self.method, self.preserve_aspect_ratio, self.antialias) + # It will produce an un-padded resized image, with a side shorter than wanted if we preserve aspect ratio + raw_shape = img.shape[:2] + if self.symmetric_pad: + half_pad = (int((self.output_size[0] - img.shape[0]) / 2), 0) if self.preserve_aspect_ratio: - # pad width - if not self.symmetric_pad: - offset = (0, 0) - elif self.output_size[0] == img.shape[0]: - offset = (0, int((self.output_size[1] - img.shape[1]) / 2)) - else: - offset = (int((self.output_size[0] - img.shape[0]) / 2), 0) - img = tf.image.pad_to_bounding_box(img, *offset, *self.output_size) - return img
    + if isinstance(self.output_size, (tuple, list)): + # In that case we need to pad because we want to enforce both width and height + if not self.symmetric_pad: + half_pad = (0, 0) + elif self.output_size[0] == img.shape[0]: + half_pad = (0, int((self.output_size[1] - img.shape[1]) / 2)) + # Pad image + img = tf.image.pad_to_bounding_box(img, *half_pad, *self.output_size) + + # In case boxes are provided, resize boxes if needed (for detection task if preserve aspect ratio) + if target is not None: + if self.symmetric_pad: + offset = half_pad[0] / img.shape[0], half_pad[1] / img.shape[1] + + if self.preserve_aspect_ratio: + # Get absolute coords + if target.shape[1:] == (4,): + if isinstance(self.output_size, (tuple, list)) and self.symmetric_pad: + target[:, [0, 2]] = offset[1] + target[:, [0, 2]] * raw_shape[1] / img.shape[1] + target[:, [1, 3]] = offset[0] + target[:, [1, 3]] * raw_shape[0] / img.shape[0] + else: + target[:, [0, 2]] *= raw_shape[1] / img.shape[1] + target[:, [1, 3]] *= raw_shape[0] / img.shape[0] + elif target.shape[1:] == (4, 2): + if isinstance(self.output_size, (tuple, list)) and self.symmetric_pad: + target[..., 0] = offset[1] + target[..., 0] * raw_shape[1] / img.shape[1] + target[..., 1] = offset[0] + target[..., 1] * raw_shape[0] / img.shape[0] + else: + target[..., 0] *= raw_shape[1] / img.shape[1] + target[..., 1] *= raw_shape[0] / img.shape[0] + else: + raise AssertionError("Boxes should be in the format (n_boxes, 4, 2) or (n_boxes, 4)") + + return tf.cast(img, dtype=input_dtype), np.clip(target, 0, 1) + + return tf.cast(img, dtype=input_dtype)
    -[docs] +[docs] class Normalize(NestedObject): """Normalize a tensor to a Gaussian distribution for each channel - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import Normalize + >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- mean: average value per channel std: standard deviation per channel """ + def __init__(self, mean: Tuple[float, float, float], std: Tuple[float, float, float]) -> None: - self.mean = tf.constant(mean, dtype=tf.float32) - self.std = tf.constant(std, dtype=tf.float32) + self.mean = tf.constant(mean) + self.std = tf.constant(std) def extra_repr(self) -> str: return f"mean={self.mean.numpy().tolist()}, std={self.std.numpy().tolist()}" def __call__(self, img: tf.Tensor) -> tf.Tensor: - img -= self.mean - img /= self.std + img -= tf.cast(self.mean, dtype=img.dtype) + img /= tf.cast(self.std, dtype=img.dtype) return img
    -[docs] +[docs] class LambdaTransformation(NestedObject): """Normalize a tensor to a Gaussian distribution for each channel - Example:: - >>> from doctr.transforms import LambdaTransformation - >>> import tensorflow as tf - >>> transfo = LambdaTransformation(lambda x: x/ 255.) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import LambdaTransformation + >>> transfo = LambdaTransformation(lambda x: x/ 255.) + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- fn: the function to be applied to the input tensor """ + def __init__(self, fn: Callable[[tf.Tensor], tf.Tensor]) -> None: self.fn = fn @@ -420,37 +513,42 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class ToGray(NestedObject): """Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ToGray() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import ToGray + >>> transfo = ToGray() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) """ + + def __init__(self, num_output_channels: int = 1): + self.num_output_channels = num_output_channels + def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.rgb_to_grayscale(img)
    + img = tf.image.rgb_to_grayscale(img) + return img if self.num_output_channels == 1 else tf.repeat(img, self.num_output_channels, axis=-1)
    -[docs] +[docs] class RandomBrightness(NestedObject): """Randomly adjust brightness of a tensor (batch of images or image) by adding a delta to all pixels - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Brightness() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomBrightness + >>> transfo = RandomBrightness() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] p: probability to apply transformation """ + def __init__(self, max_delta: float = 0.3) -> None: self.max_delta = max_delta @@ -463,21 +561,22 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomContrast(NestedObject): """Randomly adjust contrast of a tensor (batch of images or image) by adjusting each pixel: (img - mean) * contrast_factor + mean. - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Contrast() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomContrast + >>> transfo = RandomContrast() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1) """ - def __init__(self, delta: float = .3) -> None: + + def __init__(self, delta: float = 0.3) -> None: self.delta = delta def extra_repr(self) -> str: @@ -489,21 +588,22 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomSaturation(NestedObject): """Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and increasing saturation by a factor. - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Saturation() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomSaturation + >>> transfo = RandomSaturation() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1) """ - def __init__(self, delta: float = .5) -> None: + + def __init__(self, delta: float = 0.5) -> None: self.delta = delta def extra_repr(self) -> str: @@ -515,19 +615,20 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomHue(NestedObject): """Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Hue() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomHue + >>> transfo = RandomHue() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] """ + def __init__(self, max_delta: float = 0.3) -> None: self.max_delta = max_delta @@ -540,22 +641,23 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomGamma(NestedObject): """randomly performs gamma correction for a tensor (batch of images or image) - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Gamma() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomGamma + >>> transfo = RandomGamma() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- min_gamma: non-negative real number, lower bound for gamma param max_gamma: non-negative real number, upper bound for gamma min_gain: lower bound for constant multiplier max_gain: upper bound for constant multiplier """ + def __init__( self, min_gamma: float = 0.5, @@ -580,20 +682,21 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomJpegQuality(NestedObject): """Randomly adjust jpeg quality of a 3 dimensional RGB image - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = JpegQuality() - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomJpegQuality + >>> transfo = RandomJpegQuality() + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) Args: + ---- min_quality: int between [0, 100] max_quality: int between [0, 100] """ + def __init__(self, min_quality: int = 60, max_quality: int = 100) -> None: self.min_quality = min_quality self.max_quality = max_quality @@ -602,10 +705,224 @@

    Source code for doctr.transforms.modules.tensorflow

    return f"min_quality={self.min_quality}" def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_jpeg_quality( - img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality + return tf.image.random_jpeg_quality(img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality)
    + + + +
    +[docs] +class GaussianBlur(NestedObject): + """Randomly adjust jpeg quality of a 3 dimensional RGB image + + >>> import tensorflow as tf + >>> from doctr.transforms import GaussianBlur + >>> transfo = GaussianBlur(3, (.1, 5)) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + kernel_shape: size of the blurring kernel + std: min and max value of the standard deviation + """ + + def __init__(self, kernel_shape: Union[int, Iterable[int]], std: Tuple[float, float]) -> None: + self.kernel_shape = kernel_shape + self.std = std + + def extra_repr(self) -> str: + return f"kernel_shape={self.kernel_shape}, std={self.std}" + + def __call__(self, img: tf.Tensor) -> tf.Tensor: + return tf.squeeze( + _gaussian_filter( + img[tf.newaxis, ...], + kernel_size=self.kernel_shape, + sigma=random.uniform(self.std[0], self.std[1]), + mode="REFLECT", + ), + axis=0, )
    + + +
    +[docs] +class ChannelShuffle(NestedObject): + """Randomly shuffle channel order of a given image""" + + def __init__(self): + pass + + def __call__(self, img: tf.Tensor) -> tf.Tensor: + return tf.transpose(tf.random.shuffle(tf.transpose(img, perm=[2, 0, 1])), perm=[1, 2, 0])
    + + + +
    +[docs] +class GaussianNoise(NestedObject): + """Adds Gaussian Noise to the input tensor + + >>> import tensorflow as tf + >>> from doctr.transforms import GaussianNoise + >>> transfo = GaussianNoise(0., 1.) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + mean : mean of the gaussian distribution + std : std of the gaussian distribution + """ + + def __init__(self, mean: float = 0.0, std: float = 1.0) -> None: + super().__init__() + self.std = std + self.mean = mean + + def __call__(self, x: tf.Tensor) -> tf.Tensor: + # Reshape the distribution + noise = self.mean + 2 * self.std * tf.random.uniform(x.shape) - self.std + if x.dtype == tf.uint8: + return tf.cast( + tf.clip_by_value(tf.math.round(tf.cast(x, dtype=tf.float32) + 255 * noise), 0, 255), dtype=tf.uint8 + ) + else: + return tf.cast(tf.clip_by_value(x + noise, 0, 1), dtype=x.dtype) + + def extra_repr(self) -> str: + return f"mean={self.mean}, std={self.std}"
    + + + +
    +[docs] +class RandomHorizontalFlip(NestedObject): + """Adds random horizontal flip to the input tensor/np.ndarray + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomHorizontalFlip + >>> transfo = RandomHorizontalFlip(p=0.5) + >>> image = tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1) + >>> target = np.array([[0.1, 0.1, 0.4, 0.5] ], dtype= np.float32) + >>> out = transfo(image, target) + + Args: + ---- + p : probability of Horizontal Flip + """ + + def __init__(self, p: float) -> None: + super().__init__() + self.p = p + + def __call__(self, img: Union[tf.Tensor, np.ndarray], target: np.ndarray) -> Tuple[tf.Tensor, np.ndarray]: + if np.random.rand(1) <= self.p: + _img = tf.image.flip_left_right(img) + _target = target.copy() + # Changing the relative bbox coordinates + if target.shape[1:] == (4,): + _target[:, ::2] = 1 - target[:, [2, 0]] + else: + _target[..., 0] = 1 - target[..., 0] + return _img, _target + return img, target
    + + + +
    +[docs] +class RandomShadow(NestedObject): + """Adds random shade to the input image + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomShadow + >>> transfo = RandomShadow(0., 1.) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + opacity_range : minimum and maximum opacity of the shade + """ + + def __init__(self, opacity_range: Optional[Tuple[float, float]] = None) -> None: + super().__init__() + self.opacity_range = opacity_range if isinstance(opacity_range, tuple) else (0.2, 0.8) + + def __call__(self, x: tf.Tensor) -> tf.Tensor: + # Reshape the distribution + if x.dtype == tf.uint8: + return tf.cast( + tf.clip_by_value( + tf.math.round(255 * random_shadow(tf.cast(x, dtype=tf.float32) / 255, self.opacity_range)), + 0, + 255, + ), + dtype=tf.uint8, + ) + else: + return tf.clip_by_value(random_shadow(x, self.opacity_range), 0, 1) + + def extra_repr(self) -> str: + return f"opacity_range={self.opacity_range}"
    + + + +
    +[docs] +class RandomResize(NestedObject): + """Randomly resize the input image and align corresponding targets + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomResize + >>> transfo = RandomResize((0.3, 0.9), preserve_aspect_ratio=True, symmetric_pad=True, p=0.5) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + scale_range: range of the resizing factor for width and height (independently) + preserve_aspect_ratio: whether to preserve the aspect ratio of the image, + given a float value, the aspect ratio will be preserved with this probability + symmetric_pad: whether to symmetrically pad the image, + given a float value, the symmetric padding will be applied with this probability + p: probability to apply the transformation + """ + + def __init__( + self, + scale_range: Tuple[float, float] = (0.3, 0.9), + preserve_aspect_ratio: Union[bool, float] = False, + symmetric_pad: Union[bool, float] = False, + p: float = 0.5, + ): + super().__init__() + self.scale_range = scale_range + self.preserve_aspect_ratio = preserve_aspect_ratio + self.symmetric_pad = symmetric_pad + self.p = p + self._resize = Resize + + def __call__(self, img: tf.Tensor, target: np.ndarray) -> Tuple[tf.Tensor, np.ndarray]: + if np.random.rand(1) <= self.p: + scale_h = random.uniform(*self.scale_range) + scale_w = random.uniform(*self.scale_range) + new_size = (int(img.shape[-3] * scale_h), int(img.shape[-2] * scale_w)) + + _img, _target = self._resize( + new_size, + preserve_aspect_ratio=self.preserve_aspect_ratio + if isinstance(self.preserve_aspect_ratio, bool) + else bool(np.random.rand(1) <= self.symmetric_pad), + symmetric_pad=self.symmetric_pad + if isinstance(self.symmetric_pad, bool) + else bool(np.random.rand(1) <= self.symmetric_pad), + )(img, target) + + return _img, _target + return img, target + + def extra_repr(self) -> str: + return f"scale_range={self.scale_range}, preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}, p={self.p}" # noqa: E501
    +
    @@ -638,8 +955,8 @@

    Source code for doctr.transforms.modules.tensorflow

    - +
    + diff --git a/v0.3.1/_modules/doctr/utils/metrics.html b/v0.3.1/_modules/doctr/utils/metrics.html index 460c64a385..8a37d5949a 100644 --- a/v0.3.1/_modules/doctr/utils/metrics.html +++ b/v0.3.1/_modules/doctr/utils/metrics.html @@ -13,7 +13,7 @@ - + doctr.utils.metrics - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.utils.metrics

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
    +
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +from typing import Dict, List, Optional, Tuple
     
     import numpy as np
    -import cv2
    -from typing import List, Tuple, Dict, Optional
    -from unidecode import unidecode
    +from anyascii import anyascii
     from scipy.optimize import linear_sum_assignment
    -from doctr.utils.geometry import rbbox_to_polygon
    +from shapely.geometry import Polygon
     
    -__all__ = ['TextMatch', 'box_iou', 'box_ioa', 'mask_iou', 'rbox_to_mask',
    -           'nms', 'LocalizationConfusion', 'OCRMetric']
    +__all__ = [
    +    "TextMatch",
    +    "box_iou",
    +    "polygon_iou",
    +    "nms",
    +    "LocalizationConfusion",
    +    "OCRMetric",
    +    "DetectionMetric",
    +]
     
     
     def string_match(word1: str, word2: str) -> Tuple[bool, bool, bool, bool]:
    -    """Perform string comparison with multiple levels of tolerance
    +    """Performs string comparison with multiple levels of tolerance
     
         Args:
    +    ----
             word1: a string
             word2: another string
     
         Returns:
    +    -------
             a tuple with booleans specifying respectively whether the raw strings, their lower-case counterparts, their
    -            unidecode counterparts and their lower-case unidecode counterparts match
    +            anyascii counterparts and their lower-case anyascii counterparts match
         """
    -    raw_match = (word1 == word2)
    -    caseless_match = (word1.lower() == word2.lower())
    -    unidecode_match = (unidecode(word1) == unidecode(word2))
    +    raw_match = word1 == word2
    +    caseless_match = word1.lower() == word2.lower()
    +    anyascii_match = anyascii(word1) == anyascii(word2)
     
         # Warning: the order is important here otherwise the pair ("EUR", "€") cannot be matched
    -    unicase_match = (unidecode(word1).lower() == unidecode(word2).lower())
    +    unicase_match = anyascii(word1).lower() == anyascii(word2).lower()
     
    -    return raw_match, caseless_match, unidecode_match, unicase_match
    +    return raw_match, caseless_match, anyascii_match, unicase_match
     
     
     
    -[docs] +[docs] class TextMatch: - """Implements text match metric (word-level accuracy) for recognition task. + r"""Implements text match metric (word-level accuracy) for recognition task. The raw aggregated metric is computed as follows: .. math:: - \\forall X, Y \\in \\mathcal{W}^N, - TextMatch(X, Y) = \\frac{1}{N} \\sum\\limits_{i=1}^N f_{Y_i}(X_i) + \forall X, Y \in \mathcal{W}^N, + TextMatch(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N f_{Y_i}(X_i) with the indicator function :math:`f_{a}` defined as: .. math:: - \\forall a, x \\in \\mathcal{W}, - f_a(x) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } x = a \\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{W}` is the set of all possible character sequences, + \forall a, x \in \mathcal{W}, + f_a(x) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } x = a \\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{W}` is the set of all possible character sequences, :math:`N` is a strictly positive integer. - Example:: - >>> from doctr.utils import TextMatch - >>> metric = TextMatch() - >>> metric.update(['Hello', 'world'], ['hello', 'world']) - >>> metric.summary() + >>> from doctr.utils import TextMatch + >>> metric = TextMatch() + >>> metric.update(['Hello', 'world'], ['hello', 'world']) + >>> metric.summary() """ def __init__(self) -> None: self.reset() +
    +[docs] def update( self, gt: List[str], @@ -354,29 +386,32 @@

    Source code for doctr.utils.metrics

             """Update the state of the metric with new predictions
     
             Args:
    +        ----
                 gt: list of groung-truth character sequences
    -            pred: list of predicted character sequences"""
    -
    +            pred: list of predicted character sequences
    +        """
             if len(gt) != len(pred):
                 raise AssertionError("prediction size does not match with ground-truth labels size")
     
             for gt_word, pred_word in zip(gt, pred):
    -            _raw, _caseless, _unidecode, _unicase = string_match(gt_word, pred_word)
    +            _raw, _caseless, _anyascii, _unicase = string_match(gt_word, pred_word)
                 self.raw += int(_raw)
                 self.caseless += int(_caseless)
    -            self.unidecode += int(_unidecode)
    +            self.anyascii += int(_anyascii)
                 self.unicase += int(_unicase)
     
    -        self.total += len(gt)
    +        self.total += len(gt)
    +
    -[docs] +[docs] def summary(self) -> Dict[str, float]: """Computes the aggregated metrics - Returns: - a dictionary with the exact match score for the raw data, its lower-case counterpart, its unidecode - counterpart and its lower-case unidecode counterpart + Returns + ------- + a dictionary with the exact match score for the raw data, its lower-case counterpart, its anyascii + counterpart and its lower-case anyascii counterpart """ if self.total == 0: raise AssertionError("you need to update the metric before getting the summary") @@ -384,7 +419,7 @@

    Source code for doctr.utils.metrics

             return dict(
                 raw=self.raw / self.total,
                 caseless=self.caseless / self.total,
    -            unidecode=self.unidecode / self.total,
    +            anyascii=self.anyascii / self.total,
                 unicase=self.unicase / self.total,
             )
    @@ -392,23 +427,25 @@

    Source code for doctr.utils.metrics

         def reset(self) -> None:
             self.raw = 0
             self.caseless = 0
    -        self.unidecode = 0
    +        self.anyascii = 0
             self.unicase = 0
             self.total = 0
    def box_iou(boxes_1: np.ndarray, boxes_2: np.ndarray) -> np.ndarray: - """Compute the IoU between two sets of bounding boxes + """Computes the IoU between two sets of bounding boxes Args: + ---- boxes_1: bounding boxes of shape (N, 4) in format (xmin, ymin, xmax, ymax) boxes_2: bounding boxes of shape (M, 4) in format (xmin, ymin, xmax, ymax) + Returns: + ------- the IoU matrix of shape (N, M) """ - - iou_mat = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32) + iou_mat: np.ndarray = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32) if boxes_1.shape[0] > 0 and boxes_2.shape[0] > 0: l1, t1, r1, b1 = np.split(boxes_1, 4, axis=1) @@ -419,107 +456,54 @@

    Source code for doctr.utils.metrics

             right = np.minimum(r1, r2.T)
             bot = np.minimum(b1, b2.T)
     
    -        intersection = np.clip(right - left, 0, np.Inf) * np.clip(bot - top, 0, np.Inf)
    +        intersection = np.clip(right - left, 0, np.inf) * np.clip(bot - top, 0, np.inf)
             union = (r1 - l1) * (b1 - t1) + ((r2 - l2) * (b2 - t2)).T - intersection
             iou_mat = intersection / union
     
         return iou_mat
     
     
    -def box_ioa(boxes_1: np.ndarray, boxes_2: np.ndarray) -> np.ndarray:
    -    """Compute the IoA (intersection over area) between two sets of bounding boxes:
    -    ioa(i, j) = inter(i, j) / area(i)
    -
    -    Args:
    -        boxes_1: bounding boxes of shape (N, 4) in format (xmin, ymin, xmax, ymax)
    -        boxes_2: bounding boxes of shape (M, 4) in format (xmin, ymin, xmax, ymax)
    -    Returns:
    -        the IoA matrix of shape (N, M)
    -    """
    -
    -    ioa_mat = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32)
    -
    -    if boxes_1.shape[0] > 0 and boxes_2.shape[0] > 0:
    -        l1, t1, r1, b1 = np.split(boxes_1, 4, axis=1)
    -        l2, t2, r2, b2 = np.split(boxes_2, 4, axis=1)
    -
    -        left = np.maximum(l1, l2.T)
    -        top = np.maximum(t1, t2.T)
    -        right = np.minimum(r1, r2.T)
    -        bot = np.minimum(b1, b2.T)
    -
    -        intersection = np.clip(right - left, 0, np.Inf) * np.clip(bot - top, 0, np.Inf)
    -        area = (r1 - l1) * (b1 - t1)
    -        ioa_mat = intersection / area
    -
    -    return ioa_mat
    -
    -
    -def mask_iou(masks_1: np.ndarray, masks_2: np.ndarray) -> np.ndarray:
    -    """Compute the IoU between two sets of boolean masks
    +def polygon_iou(polys_1: np.ndarray, polys_2: np.ndarray) -> np.ndarray:
    +    """Computes the IoU between two sets of rotated bounding boxes
     
         Args:
    -        masks_1: boolean masks of shape (N, H, W)
    -        masks_2: boolean masks of shape (M, H, W)
    +    ----
    +        polys_1: rotated bounding boxes of shape (N, 4, 2)
    +        polys_2: rotated bounding boxes of shape (M, 4, 2)
    +        mask_shape: spatial shape of the intermediate masks
    +        use_broadcasting: if set to True, leverage broadcasting speedup by consuming more memory
     
         Returns:
    +    -------
             the IoU matrix of shape (N, M)
         """
    +    if polys_1.ndim != 3 or polys_2.ndim != 3:
    +        raise AssertionError("expects boxes to be in format (N, 4, 2)")
     
    -    if masks_1.shape[1:] != masks_2.shape[1:]:
    -        raise AssertionError("both boolean masks should have the same spatial shape")
    +    iou_mat = np.zeros((polys_1.shape[0], polys_2.shape[0]), dtype=np.float32)
     
    -    iou_mat = np.zeros((masks_1.shape[0], masks_2.shape[0]), dtype=np.float32)
    +    shapely_polys_1 = [Polygon(poly) for poly in polys_1]
    +    shapely_polys_2 = [Polygon(poly) for poly in polys_2]
     
    -    if masks_1.shape[0] > 0 and masks_2.shape[0] > 0:
    -        intersection = np.logical_and(masks_1[:, None, ...], masks_2[None, ...])
    -        union = np.logical_or(masks_1[:, None, ...], masks_2[None, ...])
    -        axes = tuple(range(2, masks_1.ndim + 1))
    -        iou_mat = intersection.sum(axis=axes) / union.sum(axis=axes)
    +    for i, poly1 in enumerate(shapely_polys_1):
    +        for j, poly2 in enumerate(shapely_polys_2):
    +            intersection_area = poly1.intersection(poly2).area
    +            union_area = poly1.area + poly2.area - intersection_area
    +            iou_mat[i, j] = intersection_area / union_area
     
         return iou_mat
     
     
    -def rbox_to_mask(boxes: np.ndarray, shape: Tuple[int, int]) -> np.ndarray:
    -    """Convert boxes to masks
    -
    -    Args:
    -        boxes: rotated bounding boxes of shape (N, 5) in format (x, y, w, h, alpha)
    -        shape: spatial shapes of the output masks
    -
    -    Returns:
    -        the boolean masks of shape (N, H, W)
    -    """
    -
    -    masks = np.zeros((boxes.shape[0], *shape), dtype=np.uint8)
    -
    -    if boxes.shape[0] > 0:
    -        # Get absolute coordinates
    -        if boxes.dtype != np.int:
    -            abs_boxes = boxes.copy()
    -            abs_boxes[:, [0, 2]] = abs_boxes[:, [0, 2]] * shape[1]
    -            abs_boxes[:, [1, 3]] = abs_boxes[:, [1, 3]] * shape[0]
    -            abs_boxes = abs_boxes.round().astype(np.int)
    -        else:
    -            abs_boxes = boxes
    -            abs_boxes[:, 2:] = abs_boxes[:, 2:] + 1
    -
    -        # TODO: optimize slicing to improve vectorization
    -        for idx, _box in enumerate(abs_boxes):
    -            box = rbbox_to_polygon(_box)
    -            cv2.fillPoly(masks[idx], [np.array(box, np.int32)], 1)
    -
    -    return masks.astype(bool)
    -
    -
    -def nms(boxes: np.ndarray, thresh: float = .5) -> List[int]:
    +def nms(boxes: np.ndarray, thresh: float = 0.5) -> List[int]:
         """Perform non-max suppression, borrowed from <https://github.com/rbgirshick/fast-rcnn>`_.
     
         Args:
    +    ----
             boxes: np array of straight boxes: (*, 5), (xmin, ymin, xmax, ymax, score)
             thresh: iou threshold to perform box suppression.
     
         Returns:
    +    -------
             A list of box indexes to keep
         """
         x1 = boxes[:, 0]
    @@ -551,66 +535,71 @@ 

    Source code for doctr.utils.metrics

     
     
     
    -[docs] +[docs] class LocalizationConfusion: - """Implements common confusion metrics and mean IoU for localization evaluation. + r"""Implements common confusion metrics and mean IoU for localization evaluation. The aggregated metrics are computed as follows: .. math:: - \\forall Y \\in \\mathcal{B}^N, \\forall X \\in \\mathcal{B}^M, \\\\ - Recall(X, Y) = \\frac{1}{N} \\sum\\limits_{i=1}^N g_{X}(Y_i) \\\\ - Precision(X, Y) = \\frac{1}{M} \\sum\\limits_{i=1}^N g_{X}(Y_i) \\\\ - meanIoU(X, Y) = \\frac{1}{M} \\sum\\limits_{i=1}^M \\max\\limits_{j \\in [1, N]} IoU(X_i, Y_j) + \forall Y \in \mathcal{B}^N, \forall X \in \mathcal{B}^M, \\ + Recall(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N g_{X}(Y_i) \\ + Precision(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M g_{X}(Y_i) \\ + meanIoU(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(X_i, Y_j) with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and :math:`y`, and the function :math:`g_{X}` defined as: .. math:: - \\forall y \\in \\mathcal{B}, - g_X(y) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } y\\mbox{ has been assigned to any }(X_i)_i\\mbox{ with an }IoU \\geq 0.5 \\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{B}` is the set of possible bounding boxes, + \forall y \in \mathcal{B}, + g_X(y) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } y\mbox{ has been assigned to any }(X_i)_i\mbox{ with an }IoU \geq 0.5 \\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. - Example:: - >>> import numpy as np - >>> from doctr.utils import LocalizationConfusion - >>> metric = LocalizationConfusion(iou_thresh=0.5) - >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]])) - >>> metric.summary() + >>> import numpy as np + >>> from doctr.utils import LocalizationConfusion + >>> metric = LocalizationConfusion(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]])) + >>> metric.summary() Args: + ---- iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format """ def __init__( self, iou_thresh: float = 0.5, - rotated_bbox: bool = False, - mask_shape: Tuple[int, int] = (1024, 1024), + use_polygons: bool = False, ) -> None: self.iou_thresh = iou_thresh - self.rotated_bbox = rotated_bbox - self.mask_shape = mask_shape + self.use_polygons = use_polygons self.reset() +
    +[docs] def update(self, gts: np.ndarray, preds: np.ndarray) -> None: + """Updates the metric + Args: + ---- + gts: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones + preds: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones + """ if preds.shape[0] > 0: # Compute IoU - if self.rotated_bbox: - mask_gts = rbox_to_mask(gts, shape=self.mask_shape) - mask_preds = rbox_to_mask(preds, shape=self.mask_shape) - iou_mat = mask_iou(mask_gts, mask_preds) + if self.use_polygons: + iou_mat = polygon_iou(gts, preds) else: iou_mat = box_iou(gts, preds) - self.tot_iou += float(iou_mat.max(axis=1).sum()) + self.tot_iou += float(iou_mat.max(axis=0).sum()) # Assign pairs gt_indices, pred_indices = linear_sum_assignment(-iou_mat) @@ -618,17 +607,18 @@

    Source code for doctr.utils.metrics

     
             # Update counts
             self.num_gts += gts.shape[0]
    -        self.num_preds += preds.shape[0]
    +        self.num_preds += preds.shape[0]
    +
    -[docs] +[docs] def summary(self) -> Tuple[Optional[float], Optional[float], Optional[float]]: """Computes the aggregated metrics - Returns: + Returns + ------- a tuple with the recall, precision and meanIoU scores """ - # Recall recall = self.matches / self.num_gts if self.num_gts > 0 else None @@ -636,7 +626,7 @@

    Source code for doctr.utils.metrics

             precision = self.matches / self.num_preds if self.num_preds > 0 else None
     
             # mean IoU
    -        mean_iou = self.tot_iou / self.num_preds if self.num_preds > 0 else None
    +        mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None
     
             return recall, precision, mean_iou
    @@ -645,64 +635,65 @@

    Source code for doctr.utils.metrics

             self.num_gts = 0
             self.num_preds = 0
             self.matches = 0
    -        self.tot_iou = 0.
    + self.tot_iou = 0.0
    -[docs] +[docs] class OCRMetric: - """Implements end-to-end OCR metric. + r"""Implements an end-to-end OCR metric. The aggregated metrics are computed as follows: .. math:: - \\forall (B, L) \\in \\mathcal{B}^N \\times \\mathcal{L}^N, - \\forall (\\hat{B}, \\hat{L}) \\in \\mathcal{B}^M \\times \\mathcal{L}^M, \\\\ - Recall(B, \\hat{B}, L, \\hat{L}) = \\frac{1}{N} \\sum\\limits_{i=1}^N h_{B,L}(\\hat{B}_i, \\hat{L}_i) \\\\ - Precision(B, \\hat{B}, L, \\hat{L}) = \\frac{1}{M} \\sum\\limits_{i=1}^N h_{B,L}(\\hat{B}_i, \\hat{L}_i) \\\\ - meanIoU(B, \\hat{B}) = \\frac{1}{M} \\sum\\limits_{i=1}^M \\max\\limits_{j \\in [1, N]} IoU(\\hat{B}_i, B_j) + \forall (B, L) \in \mathcal{B}^N \times \mathcal{L}^N, + \forall (\hat{B}, \hat{L}) \in \mathcal{B}^M \times \mathcal{L}^M, \\ + Recall(B, \hat{B}, L, \hat{L}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ + Precision(B, \hat{B}, L, \hat{L}) = \frac{1}{M} \sum\limits_{i=1}^M h_{B,L}(\hat{B}_i, \hat{L}_i) \\ + meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j) with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and :math:`y`, and the function :math:`h_{B, L}` defined as: .. math:: - \\forall (b, l) \\in \\mathcal{B} \\times \\mathcal{L}, - h_{B,L}(b, l) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } b\\mbox{ has been assigned to a given }B_j\\mbox{ with an } \\\\ - & IoU \\geq 0.5 \\mbox{ and that for this assignment, } l = L_j\\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{B}` is the set of possible bounding boxes, - :math:`\\mathcal{L}` is the set of possible character sequences, + \forall (b, l) \in \mathcal{B} \times \mathcal{L}, + h_{B,L}(b, l) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ + & IoU \geq 0.5 \mbox{ and that for this assignment, } l = L_j\\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, + :math:`\mathcal{L}` is the set of possible character sequences, :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. - Example:: - >>> import numpy as np - >>> from doctr.utils import OCRMetric - >>> metric = OCRMetric(iou_thresh=0.5) - >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), - ['hello'], ['hello', 'world']) - >>> metric.summary() + >>> import numpy as np + >>> from doctr.utils import OCRMetric + >>> metric = OCRMetric(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), + >>> ['hello'], ['hello', 'world']) + >>> metric.summary() Args: + ---- iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format """ def __init__( self, iou_thresh: float = 0.5, - rotated_bbox: bool = False, - mask_shape: Tuple[int, int] = (1024, 1024), + use_polygons: bool = False, ) -> None: self.iou_thresh = iou_thresh - self.rotated_bbox = rotated_bbox - self.mask_shape = mask_shape + self.use_polygons = use_polygons self.reset() +
    +[docs] def update( self, gt_boxes: np.ndarray, @@ -710,50 +701,58 @@

    Source code for doctr.utils.metrics

             gt_labels: List[str],
             pred_labels: List[str],
         ) -> None:
    +        """Updates the metric
     
    +        Args:
    +        ----
    +            gt_boxes: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones
    +            pred_boxes: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones
    +            gt_labels: a list of N string labels
    +            pred_labels: a list of M string labels
    +        """
             if gt_boxes.shape[0] != len(gt_labels) or pred_boxes.shape[0] != len(pred_labels):
    -            raise AssertionError("there should be the same number of boxes and string both for the ground truth "
    -                                 "and the predictions")
    +            raise AssertionError(
    +                "there should be the same number of boxes and string both for the ground truth and the predictions"
    +            )
     
             # Compute IoU
             if pred_boxes.shape[0] > 0:
    -            if self.rotated_bbox:
    -                mask_gts = rbox_to_mask(gt_boxes, shape=self.mask_shape)
    -                mask_preds = rbox_to_mask(pred_boxes, shape=self.mask_shape)
    -                iou_mat = mask_iou(mask_gts, mask_preds)
    +            if self.use_polygons:
    +                iou_mat = polygon_iou(gt_boxes, pred_boxes)
                 else:
                     iou_mat = box_iou(gt_boxes, pred_boxes)
     
    -            self.tot_iou += float(iou_mat.max(axis=1).sum())
    +            self.tot_iou += float(iou_mat.max(axis=0).sum())
     
                 # Assign pairs
                 gt_indices, pred_indices = linear_sum_assignment(-iou_mat)
                 is_kept = iou_mat[gt_indices, pred_indices] >= self.iou_thresh
                 # String comparison
                 for gt_idx, pred_idx in zip(gt_indices[is_kept], pred_indices[is_kept]):
    -                _raw, _caseless, _unidecode, _unicase = string_match(gt_labels[gt_idx], pred_labels[pred_idx])
    +                _raw, _caseless, _anyascii, _unicase = string_match(gt_labels[gt_idx], pred_labels[pred_idx])
                     self.raw_matches += int(_raw)
                     self.caseless_matches += int(_caseless)
    -                self.unidecode_matches += int(_unidecode)
    +                self.anyascii_matches += int(_anyascii)
                     self.unicase_matches += int(_unicase)
     
             self.num_gts += gt_boxes.shape[0]
    -        self.num_preds += pred_boxes.shape[0]
    +        self.num_preds += pred_boxes.shape[0]
    +
    -[docs] +[docs] def summary(self) -> Tuple[Dict[str, Optional[float]], Dict[str, Optional[float]], Optional[float]]: """Computes the aggregated metrics - Returns: - a tuple with the recall & precision for each string comparison flexibility and the mean IoU + Returns + ------- + a tuple with the recall & precision for each string comparison and the mean IoU """ - # Recall recall = dict( raw=self.raw_matches / self.num_gts if self.num_gts > 0 else None, caseless=self.caseless_matches / self.num_gts if self.num_gts > 0 else None, - unidecode=self.unidecode_matches / self.num_gts if self.num_gts > 0 else None, + anyascii=self.anyascii_matches / self.num_gts if self.num_gts > 0 else None, unicase=self.unicase_matches / self.num_gts if self.num_gts > 0 else None, ) @@ -761,12 +760,12 @@

    Source code for doctr.utils.metrics

             precision = dict(
                 raw=self.raw_matches / self.num_preds if self.num_preds > 0 else None,
                 caseless=self.caseless_matches / self.num_preds if self.num_preds > 0 else None,
    -            unidecode=self.unidecode_matches / self.num_preds if self.num_preds > 0 else None,
    +            anyascii=self.anyascii_matches / self.num_preds if self.num_preds > 0 else None,
                 unicase=self.unicase_matches / self.num_preds if self.num_preds > 0 else None,
             )
     
             # mean IoU (overall detected boxes)
    -        mean_iou = self.tot_iou / self.num_preds if self.num_preds > 0 else None
    +        mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None
     
             return recall, precision, mean_iou
    @@ -774,12 +773,136 @@

    Source code for doctr.utils.metrics

         def reset(self) -> None:
             self.num_gts = 0
             self.num_preds = 0
    -        self.tot_iou = 0.
    +        self.tot_iou = 0.0
             self.raw_matches = 0
             self.caseless_matches = 0
    -        self.unidecode_matches = 0
    +        self.anyascii_matches = 0
             self.unicase_matches = 0
    + + +
    +[docs] +class DetectionMetric: + r"""Implements an object detection metric. + + The aggregated metrics are computed as follows: + + .. math:: + \forall (B, C) \in \mathcal{B}^N \times \mathcal{C}^N, + \forall (\hat{B}, \hat{C}) \in \mathcal{B}^M \times \mathcal{C}^M, \\ + Recall(B, \hat{B}, C, \hat{C}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,C}(\hat{B}_i, \hat{C}_i) \\ + Precision(B, \hat{B}, C, \hat{C}) = \frac{1}{M} \sum\limits_{i=1}^M h_{B,C}(\hat{B}_i, \hat{C}_i) \\ + meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j) + + with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and + :math:`y`, and the function :math:`h_{B, C}` defined as: + + .. math:: + \forall (b, c) \in \mathcal{B} \times \mathcal{C}, + h_{B,C}(b, c) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ + & IoU \geq 0.5 \mbox{ and that for this assignment, } c = C_j\\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, + :math:`\mathcal{C}` is the set of possible class indices, + :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. + + >>> import numpy as np + >>> from doctr.utils import DetectionMetric + >>> metric = DetectionMetric(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), + >>> np.zeros(1, dtype=np.int64), np.array([0, 1], dtype=np.int64)) + >>> metric.summary() + + Args: + ---- + iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format + """ + + def __init__( + self, + iou_thresh: float = 0.5, + use_polygons: bool = False, + ) -> None: + self.iou_thresh = iou_thresh + self.use_polygons = use_polygons + self.reset() + +
    +[docs] + def update( + self, + gt_boxes: np.ndarray, + pred_boxes: np.ndarray, + gt_labels: np.ndarray, + pred_labels: np.ndarray, + ) -> None: + """Updates the metric + + Args: + ---- + gt_boxes: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones + pred_boxes: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones + gt_labels: an array of class indices of shape (N,) + pred_labels: an array of class indices of shape (M,) + """ + if gt_boxes.shape[0] != gt_labels.shape[0] or pred_boxes.shape[0] != pred_labels.shape[0]: + raise AssertionError( + "there should be the same number of boxes and string both for the ground truth and the predictions" + ) + + # Compute IoU + if pred_boxes.shape[0] > 0: + if self.use_polygons: + iou_mat = polygon_iou(gt_boxes, pred_boxes) + else: + iou_mat = box_iou(gt_boxes, pred_boxes) + + self.tot_iou += float(iou_mat.max(axis=0).sum()) + + # Assign pairs + gt_indices, pred_indices = linear_sum_assignment(-iou_mat) + is_kept = iou_mat[gt_indices, pred_indices] >= self.iou_thresh + # Category comparison + self.num_matches += int((gt_labels[gt_indices[is_kept]] == pred_labels[pred_indices[is_kept]]).sum()) + + self.num_gts += gt_boxes.shape[0] + self.num_preds += pred_boxes.shape[0]
    + + +
    +[docs] + def summary(self) -> Tuple[Optional[float], Optional[float], Optional[float]]: + """Computes the aggregated metrics + + Returns + ------- + a tuple with the recall & precision for each class prediction and the mean IoU + """ + # Recall + recall = self.num_matches / self.num_gts if self.num_gts > 0 else None + + # Precision + precision = self.num_matches / self.num_preds if self.num_preds > 0 else None + + # mean IoU (overall detected boxes) + mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None + + return recall, precision, mean_iou
    + + + def reset(self) -> None: + self.num_gts = 0 + self.num_preds = 0 + self.tot_iou = 0.0 + self.num_matches = 0
    +
    @@ -812,8 +935,8 @@

    Source code for doctr.utils.metrics

           
         
       
    - - + + diff --git a/v0.3.1/_modules/doctr/utils/visualization.html b/v0.3.1/_modules/doctr/utils/visualization.html index 8e7dcca811..c818be6d7b 100644 --- a/v0.3.1/_modules/doctr/utils/visualization.html +++ b/v0.3.1/_modules/doctr/utils/visualization.html @@ -13,7 +13,7 @@ - + doctr.utils.visualization - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.utils.visualization

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
    +import colorsys
    +from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple, Union
     
    -import matplotlib.pyplot as plt
    -from matplotlib.figure import Figure
    +import cv2
     import matplotlib.patches as patches
    -import mplcursors
    -from PIL import ImageFont, ImageDraw, Image
    +import matplotlib.pyplot as plt
     import numpy as np
    -import cv2
    -from typing import Tuple, List, Dict, Any, Union
    +from matplotlib.figure import Figure
     
    -from .common_types import BoundingBox, RotatedBbox
    +from .common_types import BoundingBox, Polygon4P
     
    -__all__ = ['visualize_page', 'synthetize_page']
    +__all__ = ["visualize_page", "visualize_kie_page", "draw_boxes"]
     
     
    -def create_rect_patch(
    -    geometry: Union[BoundingBox, RotatedBbox],
    -    label: str,
    +def rect_patch(
    +    geometry: BoundingBox,
         page_dimensions: Tuple[int, int],
    -    color: Tuple[int, int, int],
    +    label: Optional[str] = None,
    +    color: Tuple[float, float, float] = (0, 0, 0),
         alpha: float = 0.3,
         linewidth: int = 2,
         fill: bool = True,
    -) -> patches.Patch:
    -    """Create a matplotlib patch (rectangle) bounding the element
    +    preserve_aspect_ratio: bool = False,
    +) -> patches.Rectangle:
    +    """Create a matplotlib rectangular patch for the element
     
         Args:
    +    ----
             geometry: bounding box of the element
    +        page_dimensions: dimensions of the Page in format (height, width)
             label: label to display when hovered
    -        page_dimensions: dimensions of the Page
             color: color to draw box
             alpha: opacity parameter to fill the boxes, 0 = transparent
             linewidth: line width
    +        fill: whether the patch should be filled
    +        preserve_aspect_ratio: pass True if you passed True to the predictor
     
         Returns:
    +    -------
             a rectangular Patch
         """
    +    if len(geometry) != 2 or any(not isinstance(elt, tuple) or len(elt) != 2 for elt in geometry):
    +        raise ValueError("invalid geometry format")
    +
    +    # Unpack
         height, width = page_dimensions
    -    if len(geometry) == 5:
    -        x, y, w, h, a = geometry  # type: ignore[misc]
    -        x, w = x * width, w * width
    -        y, h = y * height, h * height
    -        points = cv2.boxPoints(((x, y), (w, h), a))
    -        return patches.Polygon(
    -            points,
    -            fill=fill,
    -            linewidth=linewidth,
    -            edgecolor=(*color, alpha),
    -            facecolor=(*color, alpha),
    -            label=label
    -        )
    -    else:
    -        (xmin, ymin), (xmax, ymax) = geometry  # type: ignore[misc]
    -        xmin, xmax = xmin * width, xmax * width
    -        ymin, ymax = ymin * height, ymax * height
    -        return patches.Rectangle(
    -            (xmin, ymin),
    -            xmax - xmin,
    -            ymax - ymin,
    -            fill=fill,
    -            linewidth=linewidth,
    -            edgecolor=(*color, alpha),
    -            facecolor=(*color, alpha),
    -            label=label
    -        )
    +    (xmin, ymin), (xmax, ymax) = geometry
    +    # Switch to absolute coords
    +    if preserve_aspect_ratio:
    +        width = height = max(height, width)
    +    xmin, w = xmin * width, (xmax - xmin) * width
    +    ymin, h = ymin * height, (ymax - ymin) * height
    +
    +    return patches.Rectangle(
    +        (xmin, ymin),
    +        w,
    +        h,
    +        fill=fill,
    +        linewidth=linewidth,
    +        edgecolor=(*color, alpha),
    +        facecolor=(*color, alpha),
    +        label=label,
    +    )
    +
    +
    +def polygon_patch(
    +    geometry: np.ndarray,
    +    page_dimensions: Tuple[int, int],
    +    label: Optional[str] = None,
    +    color: Tuple[float, float, float] = (0, 0, 0),
    +    alpha: float = 0.3,
    +    linewidth: int = 2,
    +    fill: bool = True,
    +    preserve_aspect_ratio: bool = False,
    +) -> patches.Polygon:
    +    """Create a matplotlib polygon patch for the element
    +
    +    Args:
    +    ----
    +        geometry: bounding box of the element
    +        page_dimensions: dimensions of the Page in format (height, width)
    +        label: label to display when hovered
    +        color: color to draw box
    +        alpha: opacity parameter to fill the boxes, 0 = transparent
    +        linewidth: line width
    +        fill: whether the patch should be filled
    +        preserve_aspect_ratio: pass True if you passed True to the predictor
    +
    +    Returns:
    +    -------
    +        a polygon Patch
    +    """
    +    if not geometry.shape == (4, 2):
    +        raise ValueError("invalid geometry format")
    +
    +    # Unpack
    +    height, width = page_dimensions
    +    geometry[:, 0] = geometry[:, 0] * (max(width, height) if preserve_aspect_ratio else width)
    +    geometry[:, 1] = geometry[:, 1] * (max(width, height) if preserve_aspect_ratio else height)
    +
    +    return patches.Polygon(
    +        geometry,
    +        fill=fill,
    +        linewidth=linewidth,
    +        edgecolor=(*color, alpha),
    +        facecolor=(*color, alpha),
    +        label=label,
    +    )
    +
    +
    +def create_obj_patch(
    +    geometry: Union[BoundingBox, Polygon4P, np.ndarray],
    +    page_dimensions: Tuple[int, int],
    +    **kwargs: Any,
    +) -> patches.Patch:
    +    """Create a matplotlib patch for the element
    +
    +    Args:
    +    ----
    +        geometry: bounding box (straight or rotated) of the element
    +        page_dimensions: dimensions of the page in format (height, width)
    +        **kwargs: keyword arguments for the patch
    +
    +    Returns:
    +    -------
    +        a matplotlib Patch
    +    """
    +    if isinstance(geometry, tuple):
    +        if len(geometry) == 2:  # straight word BB (2 pts)
    +            return rect_patch(geometry, page_dimensions, **kwargs)
    +        elif len(geometry) == 4:  # rotated word BB (4 pts)
    +            return polygon_patch(np.asarray(geometry), page_dimensions, **kwargs)
    +    elif isinstance(geometry, np.ndarray) and geometry.shape == (4, 2):  # rotated line
    +        return polygon_patch(geometry, page_dimensions, **kwargs)
    +    raise ValueError("invalid geometry format")
    +
    +
    +def get_colors(num_colors: int) -> List[Tuple[float, float, float]]:
    +    """Generate num_colors color for matplotlib
    +
    +    Args:
    +    ----
    +        num_colors: number of colors to generate
    +
    +    Returns:
    +    -------
    +        colors: list of generated colors
    +    """
    +    colors = []
    +    for i in np.arange(0.0, 360.0, 360.0 / num_colors):
    +        hue = i / 360.0
    +        lightness = (50 + np.random.rand() * 10) / 100.0
    +        saturation = (90 + np.random.rand() * 10) / 100.0
    +        colors.append(colorsys.hls_to_rgb(hue, lightness, saturation))
    +    return colors
     
     
     
    -[docs] +[docs] def visualize_page( page: Dict[str, Any], image: np.ndarray, @@ -359,18 +472,18 @@

    Source code for doctr.utils.visualization

     ) -> Figure:
         """Visualize a full page with predicted blocks, lines and words
     
    -    Example::
    -        >>> import numpy as np
    -        >>> import matplotlib.pyplot as plt
    -        >>> from doctr.utils.visualization import visualize_page
    -        >>> from doctr.models import ocr_db_crnn
    -        >>> model = ocr_db_crnn(pretrained=True)
    -        >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    -        >>> out = model([[input_page]])
    -        >>> visualize_page(out[0].pages[0].export(), input_page)
    -        >>> plt.show()
    +    >>> import numpy as np
    +    >>> import matplotlib.pyplot as plt
    +    >>> from doctr.utils.visualization import visualize_page
    +    >>> from doctr.models import ocr_db_crnn
    +    >>> model = ocr_db_crnn(pretrained=True)
    +    >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    +    >>> out = model([[input_page]])
    +    >>> visualize_page(out[0].pages[0].export(), input_page)
    +    >>> plt.show()
     
         Args:
    +    ----
             page: the exported Page of a Document
             image: np array of the page, needs to have the same shape than page['dimensions']
             words_only: whether only words should be displayed
    @@ -378,6 +491,11 @@ 

    Source code for doctr.utils.visualization

             scale: figsize of the largest windows side
             interactive: whether the plot should be interactive
             add_labels: for static plot, adds text labels on top of bounding box
    +        **kwargs: keyword arguments for the polygon patch
    +
    +    Returns:
    +    -------
    +        the matplotlib figure
         """
         # Get proper scale and aspect ratio
         h, w = image.shape[:2]
    @@ -386,128 +504,189 @@ 

    Source code for doctr.utils.visualization

         # Display the image
         ax.imshow(image)
         # hide both axis
    -    ax.axis('off')
    +    ax.axis("off")
     
         if interactive:
             artists: List[patches.Patch] = []  # instantiate an empty list of patches (to be drawn on the page)
     
    -    for block in page['blocks']:
    +    for block in page["blocks"]:
             if not words_only:
    -            rect = create_rect_patch(block['geometry'], 'block', page['dimensions'], (0, 1, 0), linewidth=1, **kwargs)
    +            rect = create_obj_patch(
    +                block["geometry"], page["dimensions"], label="block", color=(0, 1, 0), linewidth=1, **kwargs
    +            )
                 # add patch on figure
                 ax.add_patch(rect)
                 if interactive:
                     # add patch to cursor's artists
                     artists.append(rect)
     
    -        for line in block['lines']:
    +        for line in block["lines"]:
                 if not words_only:
    -                rect = create_rect_patch(line['geometry'], 'line', page['dimensions'], (1, 0, 0), linewidth=1, **kwargs)
    +                rect = create_obj_patch(
    +                    line["geometry"], page["dimensions"], label="line", color=(1, 0, 0), linewidth=1, **kwargs
    +                )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
     
    -            for word in line['words']:
    -                rect = create_rect_patch(word['geometry'], f"{word['value']} (confidence: {word['confidence']:.2%})",
    -                                         page['dimensions'], (0, 0, 1), **kwargs)
    +            for word in line["words"]:
    +                rect = create_obj_patch(
    +                    word["geometry"],
    +                    page["dimensions"],
    +                    label=f"{word['value']} (confidence: {word['confidence']:.2%})",
    +                    color=(0, 0, 1),
    +                    **kwargs,
    +                )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
                     elif add_labels:
    -                    if len(word['geometry']) == 5:
    +                    if len(word["geometry"]) == 5:
                             text_loc = (
    -                            int(page['dimensions'][1] * (word['geometry'][0] - word['geometry'][2] / 2)),
    -                            int(page['dimensions'][0] * (word['geometry'][1] - word['geometry'][3] / 2))
    +                            int(page["dimensions"][1] * (word["geometry"][0] - word["geometry"][2] / 2)),
    +                            int(page["dimensions"][0] * (word["geometry"][1] - word["geometry"][3] / 2)),
                             )
                         else:
                             text_loc = (
    -                            int(page['dimensions'][1] * word['geometry'][0][0]),
    -                            int(page['dimensions'][0] * word['geometry'][0][1])
    +                            int(page["dimensions"][1] * word["geometry"][0][0]),
    +                            int(page["dimensions"][0] * word["geometry"][0][1]),
    +                        )
    +
    +                    if len(word["geometry"]) == 2:
    +                        # We draw only if boxes are in straight format
    +                        ax.text(
    +                            *text_loc,
    +                            word["value"],
    +                            size=10,
    +                            alpha=0.5,
    +                            color=(0, 0, 1),
                             )
    -                    ax.text(
    -                        *text_loc,
    -                        word['value'],
    -                        size=10,
    -                        alpha=0.5,
    -                        color=(0, 0, 1),
    -                    )
     
             if display_artefacts:
    -            for artefact in block['artefacts']:
    -                rect = create_rect_patch(
    -                    artefact['geometry'],
    -                    'artefact',
    -                    page['dimensions'],
    -                    (0.5, 0.5, 0.5),  # type: ignore[arg-type]
    +            for artefact in block["artefacts"]:
    +                rect = create_obj_patch(
    +                    artefact["geometry"],
    +                    page["dimensions"],
    +                    label="artefact",
    +                    color=(0.5, 0.5, 0.5),
                         linewidth=1,
    -                    **kwargs
    +                    **kwargs,
                     )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
     
         if interactive:
    +        import mplcursors
    +
             # Create mlp Cursor to hover patches in artists
             mplcursors.Cursor(artists, hover=2).connect("add", lambda sel: sel.annotation.set_text(sel.artist.get_label()))
    -    fig.tight_layout(pad=0.)
    +    fig.tight_layout(pad=0.0)
     
         return fig
    -def synthetize_page( +def visualize_kie_page( page: Dict[str, Any], - draw_proba: bool = False, - font_size: int = 13, -) -> np.ndarray: - """Draw a the content of the element page (OCR response) on a blank page. + image: np.ndarray, + words_only: bool = False, + display_artefacts: bool = True, + scale: float = 10, + interactive: bool = True, + add_labels: bool = True, + **kwargs: Any, +) -> Figure: + """Visualize a full page with predicted blocks, lines and words + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from doctr.utils.visualization import visualize_page + >>> from doctr.models import ocr_db_crnn + >>> model = ocr_db_crnn(pretrained=True) + >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) + >>> out = model([[input_page]]) + >>> visualize_kie_page(out[0].pages[0].export(), input_page) + >>> plt.show() Args: - page: exported Page object to represent - draw_proba: if True, draw words in colors to represent confidence. Blue: p=1, red: p=0 - font_size: size of the font, default font = 13 + ---- + page: the exported Page of a Document + image: np array of the page, needs to have the same shape than page['dimensions'] + words_only: whether only words should be displayed + display_artefacts: whether artefacts should be displayed + scale: figsize of the largest windows side + interactive: whether the plot should be interactive + add_labels: for static plot, adds text labels on top of bounding box + **kwargs: keyword arguments for the polygon patch - Return: - A np array (drawn page) + Returns: + ------- + the matplotlib figure """ - # Draw template - h, w = page["dimensions"] - response = 255 * np.ones((h, w, 3), dtype=np.int32) + # Get proper scale and aspect ratio + h, w = image.shape[:2] + size = (scale * w / h, scale) if h > w else (scale, h / w * scale) + fig, ax = plt.subplots(figsize=size) + # Display the image + ax.imshow(image) + # hide both axis + ax.axis("off") - # Draw each word - for block in page["blocks"]: - for line in block["lines"]: - for word in line["words"]: - # Get aboslute word geometry - (xmin, ymin), (xmax, ymax) = word["geometry"] - xmin, xmax = int(w * xmin), int(w * xmax) - ymin, ymax = int(h * ymin), int(h * ymax) - - # White drawing context adapted to font size, 0.75 factor to convert pts --> pix - h_box, w_box = ymax - ymin, xmax - xmin - h_font, w_font = font_size, int(font_size * w_box / (h_box * 0.75)) - img = Image.new('RGB', (w_font, h_font), color=(255, 255, 255)) - d = ImageDraw.Draw(img) - - # Draw in black the value of the word - d.text((0, 0), word["value"], font=ImageFont.load_default(), fill=(0, 0, 0)) - - # Resize back to box size - img = img.resize((w_box, h_box), Image.NEAREST) - - # Colorize if draw_proba - if draw_proba: - p = int(255 * word["confidence"]) - mask = np.where(np.array(img) == 0, 1, 0) - proba = np.array([255 - p, 0, p]) - color = mask * proba[np.newaxis, np.newaxis, :] - white_mask = 255 * (1 - mask) - img = color + white_mask - - # Write to response page - response[ymin:ymax, xmin:xmax, :] = np.array(img) - - return response + if interactive: + artists: List[patches.Patch] = [] # instantiate an empty list of patches (to be drawn on the page) + + colors = {k: color for color, k in zip(get_colors(len(page["predictions"])), page["predictions"])} + for key, value in page["predictions"].items(): + for prediction in value: + if not words_only: + rect = create_obj_patch( + prediction["geometry"], + page["dimensions"], + label=f"{key} \n {prediction['value']} (confidence: {prediction['confidence']:.2%}", + color=colors[key], + linewidth=1, + **kwargs, + ) + # add patch on figure + ax.add_patch(rect) + if interactive: + # add patch to cursor's artists + artists.append(rect) + + if interactive: + import mplcursors + + # Create mlp Cursor to hover patches in artists + mplcursors.Cursor(artists, hover=2).connect("add", lambda sel: sel.annotation.set_text(sel.artist.get_label())) + fig.tight_layout(pad=0.0) + + return fig + + +def draw_boxes(boxes: np.ndarray, image: np.ndarray, color: Optional[Tuple[int, int, int]] = None, **kwargs) -> None: + """Draw an array of relative straight boxes on an image + + Args: + ---- + boxes: array of relative boxes, of shape (*, 4) + image: np array, float32 or uint8 + color: color to use for bounding box edges + **kwargs: keyword arguments from `matplotlib.pyplot.plot` + """ + h, w = image.shape[:2] + # Convert boxes to absolute coords + _boxes = deepcopy(boxes) + _boxes[:, [0, 2]] *= w + _boxes[:, [1, 3]] *= h + _boxes = _boxes.astype(np.int32) + for box in _boxes.tolist(): + xmin, ymin, xmax, ymax = box + image = cv2.rectangle( + image, (xmin, ymin), (xmax, ymax), color=color if isinstance(color, tuple) else (0, 0, 255), thickness=2 + ) + plt.imshow(image) + plt.plot(**kwargs)
    @@ -540,8 +719,8 @@

    Source code for doctr.utils.visualization

           
         
       
    - - + + diff --git a/v0.3.1/_modules/index.html b/v0.3.1/_modules/index.html index e86abcd4d4..5793c44f20 100644 --- a/v0.3.1/_modules/index.html +++ b/v0.3.1/_modules/index.html @@ -13,7 +13,7 @@ - + Overview: module code - docTR documentation @@ -225,20 +225,42 @@ - - + + diff --git a/v0.3.1/_sources/changelog.rst.txt b/v0.3.1/_sources/changelog.rst.txt index 430097d6c8..35befe7b96 100644 --- a/v0.3.1/_sources/changelog.rst.txt +++ b/v0.3.1/_sources/changelog.rst.txt @@ -1,6 +1,54 @@ Changelog ========= +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 `_ diff --git a/v0.3.1/_sources/datasets.rst.txt b/v0.3.1/_sources/datasets.rst.txt deleted file mode 100644 index 354122f1e5..0000000000 --- a/v0.3.1/_sources/datasets.rst.txt +++ /dev/null @@ -1,68 +0,0 @@ -doctr.datasets -============== - -.. currentmodule:: doctr.datasets - -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. - - -.. _datasets: - -Available Datasets ------------------- -The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL. - -.. autoclass:: doctr.datasets.datasets.VisionDataset - - -Here are all datasets that are available through DocTR: - -.. autoclass:: FUNSD -.. autoclass:: SROIE -.. autoclass:: CORD -.. autoclass:: OCRDataset - - -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. - -.. autoclass:: doctr.datasets.loader.DataLoader - - -.. _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 - * - digits - - 10 - - 0123456789 - * - ascii_letters - - 52 - - abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ - * - punctuation - - 32 - - !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ - * - currency - - 5 - - £€¥¢฿ - * - latin - - 96 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~° - * - french - - 154 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿ - -.. autofunction:: encode_sequences diff --git a/v0.3.1/_sources/documents.rst.txt b/v0.3.1/_sources/documents.rst.txt deleted file mode 100644 index 655730073e..0000000000 --- a/v0.3.1/_sources/documents.rst.txt +++ /dev/null @@ -1,87 +0,0 @@ -doctr.documents -=============== - - -.. currentmodule:: doctr.documents - -The documents module enables users to easily access content from documents and export analysis -results to structured formats. - - -Document structure ------------------- - -Structural organization of the documents. - -Word -^^^^ -A Word is an uninterrupted sequence of characters. - -.. autoclass:: Word - -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 - -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 - - -Document -^^^^^^^^ - -A Document is a collection of Pages. - -.. autoclass:: Document - - .. automethod:: show - - -File reading ------------- - -High-performance file reading and conversion to processable structured data. - -.. autofunction:: read_pdf - -.. autofunction:: read_img - -.. autofunction:: read_html - - -.. autoclass:: DocumentFile - - .. automethod:: from_pdf - - .. automethod:: from_url - - .. automethod:: from_images - -.. autoclass:: PDF - - .. automethod:: as_images - - .. automethod:: get_words - - .. automethod:: get_artefacts diff --git a/v0.3.1/_sources/getting_started/installing.rst.txt b/v0.3.1/_sources/getting_started/installing.rst.txt index e764e734a7..39e79aa3dd 100644 --- a/v0.3.1/_sources/getting_started/installing.rst.txt +++ b/v0.3.1/_sources/getting_started/installing.rst.txt @@ -3,7 +3,7 @@ Installation ************ -This library requires `Python `_ 3.9 or higher. +This library requires `Python `_ 3.10 or higher. Prerequisites diff --git a/v0.3.1/_sources/index.rst.txt b/v0.3.1/_sources/index.rst.txt index fc3ff89fdf..53251db142 100644 --- a/v0.3.1/_sources/index.rst.txt +++ b/v0.3.1/_sources/index.rst.txt @@ -1,7 +1,8 @@ -DocTR: Document Text Recognition -================================ +******************************** +docTR: Document Text Recognition +******************************** -State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 (PyTorch now in beta) +State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 & PyTorch .. image:: https://github.com/mindee/doctr/releases/download/v0.2.0/ocr.png :align: center @@ -9,38 +10,29 @@ State-of-the-art Optical Character Recognition made seamless & accessible to any DocTR provides an easy and powerful way to extract valuable information from your documents: -* |:receipt:| **for automation**: seemlessly process documents for Natural Language Understanding tasks: we provide OCR predictors to parse textual information (localize and identify each word) 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. -Welcome to the documentation of `DocTR `_! - - Main Features ------------- * |:robot:| Robust 2-stage (detection + recognition) OCR predictors with pretrained parameters * |:zap:| User-friendly, 3 lines of code to load a document and extract text with a predictor -* |:rocket:| State-of-the-art performances on public document datasets, comparable with GoogleVision/AWS Textract +* |: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, small dependencies -* |:tools:| Daily maintained -* |:factory:| Easy integration - +* |:bird:| Light package, minimal dependencies +* |:tools:| Actively maintained by Mindee +* |:factory:| Easy integration (available templates for browser demo & API deployment) -Getting Started ---------------- .. toctree:: :maxdepth: 2 + :caption: Getting started + :hidden: - installing - - -Build & train your predictor -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* Compose your own end-to-end OCR predictor: mix and match detection & recognition predictors (all-pretrained) -* Fine-tune or train from scratch any detection or recognition model to specialize on your data + getting_started/installing + notebooks Model zoo @@ -48,36 +40,83 @@ Model zoo Text detection models """"""""""""""""""""" - * `DBNet `_ (Differentiable Binarization) - * `LinkNet `_ +* 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 `_ (Show, Attend and Read) - * `CRNN `_ (Convolutional Recurrent Neural Network) - * `MASTER `_ (Multi-Aspect Non-local Network for Scene Text Recognition) +* 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" `_ 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 `_. +* 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" `_. .. toctree:: :maxdepth: 2 - :caption: Notes + :caption: Using docTR + :hidden: - changelog + 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 + + +.. toctree:: + :maxdepth: 2 + :caption: Community + :hidden: + + community/resources .. toctree:: :maxdepth: 2 :caption: Package Reference + :hidden: - datasets - documents - models - transforms - utils + 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/v0.3.1/_sources/installing.rst.txt b/v0.3.1/_sources/installing.rst.txt deleted file mode 100644 index 5c8779dc1c..0000000000 --- a/v0.3.1/_sources/installing.rst.txt +++ /dev/null @@ -1,46 +0,0 @@ - -************ -Installation -************ - -This library requires Python 3.6 or higher. - - -Prerequisites -============= - -Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so: - -* TensorFlow: `installation page `_. -* PyTorch: `installation page `_. - -If you are running another OS than Linux, you will need a few extra dependencies. - -For MacOS users, you can install them as follows: - -.. code:: shell - - brew install cairo pango gdk-pixbuf libffi - -For Windows users, those dependencies are included in GTK. You can find the latest installer over `here `_. - - -Via Python Package -================== - -Install the last stable release of the package using pip: - -.. code:: bash - - pip install python-doctr - - -Via Git -======= - -Install the library in developper mode: - -.. code:: bash - - git clone https://github.com/mindee/doctr.git - pip install -e doctr/. diff --git a/v0.3.1/_sources/models.rst.txt b/v0.3.1/_sources/models.rst.txt deleted file mode 100644 index 9830c6c153..0000000000 --- a/v0.3.1/_sources/models.rst.txt +++ /dev/null @@ -1,215 +0,0 @@ -doctr.models -============ - -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. - -.. currentmodule:: doctr.models - -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 TensorFlow model. -* Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable. - - -Text Detection --------------- -Localizing text elements in images - -+---------------------------------------------------+----------------------------+----------------------------+---------+ -| | FUNSD | CORD | | -+==================+=================+==============+============+===============+============+===============+=========+ -| **Architecture** | **Input shape** | **# params** | **Recall** | **Precision** | **Recall** | **Precision** | **FPS** | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ -| db_resnet50 | (1024, 1024, 3) | 25.2 M | 82.14 | 87.64 | 92.49 | 89.66 | 2.1 | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ - -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 combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for detection -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for detection is the following: - -1. resize each input image to the target size (bilinear interpolation by default) with potential deformation. -2. batch images together -3. normalize the batch using the training data statistics - - -Detection models -^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - -.. autofunction:: doctr.models.detection.db_resnet50 -.. autofunction:: doctr.models.detection.linknet16 - -Detection predictors -^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information. - -.. autofunction:: doctr.models.detection.detection_predictor - - -Text Recognition ----------------- -Identifying strings in images - -.. list-table:: Text recognition model zoo - :widths: 20 20 15 10 10 10 - :header-rows: 1 - - * - Architecture - - Input shape - - # params - - FUNSD - - CORD - - FPS - * - crnn_vgg16_bn - - (32, 128, 3) - - 15.8M - - 86.02 - - 91.3 - - 12.8 - * - sar_vgg16_bn - - (32, 128, 3) - - 21.5M - - 86.2 - - 91.7 - - 3.3 - * - sar_resnet31 - - (32, 128, 3) - - 53.1M - - **86.3** - - **92.1** - - 2.7 - -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 metrics being used are available in :ref:`metrics`. - -All these recognition models are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for recognition -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for recognition is the following: - -1. resize each input image to the target size (bilinear interpolation by default) without deformation. -2. pad the image to the target size (with zeros by default) -3. batch images together -4. normalize the batch using the training data statistics - -Recognition models -^^^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - - -.. autofunction:: doctr.models.recognition.crnn_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_resnet31 -.. autofunction:: doctr.models.recognition.master - - -Recognition predictors -^^^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage. - -.. autofunction:: doctr.models.recognition.recognition_predictor - - -End-to-End OCR --------------- -Predictors that localize and identify text elements in images - -+-----------------------------+--------------------------------------+--------------------------------------+ -| | FUNSD | CORD | -+=============================+============+===============+=========+============+===============+=========+ -| **Architecture** | **Recall** | **Precision** | **FPS** | **Recall** | **Precision** | **FPS** | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + crnn_vgg16_bn | 70.08 | 74.77 | 0.85 | 82.19 | **79.67** | 1.6 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_vgg16_bn | N/A | N/A | 0.49 | N/A | N/A | 1.0 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_resnet31 | N/A | N/A | 0.27 | N/A | N/A | 0.83 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision text detection | 59.50 | 62.50 | | 75.30 | 70.00 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision doc. text detection | 64.00 | 53.30 | | 68.90 | 61.10 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| AWS textract | **78.10** | **83.00** | | **87.50** | 66.00 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ - -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`. - -All recognition models of predictors are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Results on private ocr datasets - -+------------------------------------+----------------------------+----------------------------+----------------------------+ -| | Receipts | Invoices | IDs | -+====================================+============+===============+============+===============+============+===============+ -| **Architecture** | **Recall** | **Precision** | **Recall** | **Precision** | **Recall** | **Precision** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| db_resnet50 + crnn_vgg16_bn (ours) | **78.90** | **81.01** | 65.68 | **69.86** | **49.48** | **50.46** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| Gvision doc. text detection | 68.91 | 59.89 | 63.20 | 52.85 | 43.70 | 29.21 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| AWS textract | 75.77 | 77.70 | **70.47** | 69.13 | 46.39 | 43.32 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ - - -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. - -.. autofunction:: doctr.models.zoo.ocr_predictor - - -Model export ------------- -Utility functions to make the most of document analysis models. - -.. currentmodule:: doctr.models.export - -Model compression -^^^^^^^^^^^^^^^^^ - -.. autofunction:: convert_to_tflite - -.. autofunction:: convert_to_fp16 - -.. autofunction:: quantize_model - -Using SavedModel -^^^^^^^^^^^^^^^^ - -Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -`SavedModel `_ format as follows: - - - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> _ = model(input_t, training=False) - >>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/') - -And loaded just as easily: - - - >>> import tensorflow as tf - >>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/') diff --git a/v0.3.1/_sources/transforms.rst.txt b/v0.3.1/_sources/transforms.rst.txt deleted file mode 100644 index 0230fe75f5..0000000000 --- a/v0.3.1/_sources/transforms.rst.txt +++ /dev/null @@ -1,32 +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: - -.. autoclass:: Resize -.. autoclass:: Normalize -.. autoclass:: LambdaTransformation -.. autoclass:: ToGray -.. autoclass:: ColorInversion -.. autoclass:: RandomBrightness -.. autoclass:: RandomContrast -.. autoclass:: RandomSaturation -.. autoclass:: RandomHue -.. autoclass:: RandomGamma -.. autoclass:: RandomJpegQuality - - -Composing transformations ---------------------------------------------- -It is common to require several transformations to be performed consecutively. - -.. autoclass:: Compose -.. autoclass:: OneOf -.. autoclass:: RandomApply diff --git a/v0.3.1/_sources/utils.rst.txt b/v0.3.1/_sources/utils.rst.txt deleted file mode 100644 index 69c1abe0eb..0000000000 --- a/v0.3.1/_sources/utils.rst.txt +++ /dev/null @@ -1,36 +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 - - -.. _metrics: - -Task evaluation ---------------- -Implementations of task-specific metrics to easily assess your model performances. - -.. currentmodule:: doctr.utils.metrics - -.. autoclass:: TextMatch - - .. automethod:: summary - -.. autoclass:: LocalizationConfusion - - .. automethod:: summary - -.. autoclass:: OCRMetric - - .. automethod:: summary diff --git a/v0.3.1/_static/basic.css b/v0.3.1/_static/basic.css index f316efcb47..7ebbd6d07b 100644 --- a/v0.3.1/_static/basic.css +++ b/v0.3.1/_static/basic.css @@ -1,12 +1,5 @@ /* - * basic.css - * ~~~~~~~~~ - * * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ /* -- main layout ----------------------------------------------------------- */ @@ -115,15 +108,11 @@ img { /* -- search page ----------------------------------------------------------- */ ul.search { - margin: 10px 0 0 20px; - padding: 0; + margin-top: 10px; } ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; + padding: 5px 0; } ul.search li a { diff --git a/v0.3.1/_static/doctools.js b/v0.3.1/_static/doctools.js index 4d67807d17..0398ebb9f0 100644 --- a/v0.3.1/_static/doctools.js +++ b/v0.3.1/_static/doctools.js @@ -1,12 +1,5 @@ /* - * doctools.js - * ~~~~~~~~~~~ - * * Base JavaScript utilities for all Sphinx HTML documentation. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; diff --git a/v0.3.1/_static/documentation_options.js b/v0.3.1/_static/documentation_options.js index a7b5cbe04a..4f656fdbea 100644 --- a/v0.3.1/_static/documentation_options.js +++ b/v0.3.1/_static/documentation_options.js @@ -1,5 +1,5 @@ const DOCUMENTATION_OPTIONS = { - VERSION: '0.3.0a0-git', + VERSION: '0.10.1a0-git', LANGUAGE: 'en', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/v0.3.1/_static/language_data.js b/v0.3.1/_static/language_data.js index 367b8ed81b..c7fe6c6faf 100644 --- a/v0.3.1/_static/language_data.js +++ b/v0.3.1/_static/language_data.js @@ -1,13 +1,6 @@ /* - * language_data.js - * ~~~~~~~~~~~~~~~~ - * * This script contains the language-specific data used by searchtools.js, * namely the list of stopwords, stemmer, scorer and splitter. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; diff --git a/v0.3.1/_static/searchtools.js b/v0.3.1/_static/searchtools.js index b08d58c9b9..2c774d17af 100644 --- a/v0.3.1/_static/searchtools.js +++ b/v0.3.1/_static/searchtools.js @@ -1,12 +1,5 @@ /* - * searchtools.js - * ~~~~~~~~~~~~~~~~ - * * Sphinx JavaScript utilities for the full-text search. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; @@ -20,7 +13,7 @@ if (typeof Scorer === "undefined") { // and returns the new score. /* score: result => { - const [docname, title, anchor, descr, score, filename] = result + const [docname, title, anchor, descr, score, filename, kind] = result return score }, */ @@ -47,6 +40,14 @@ if (typeof Scorer === "undefined") { }; } +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + const _removeChildren = (element) => { while (element && element.lastChild) element.removeChild(element.lastChild); }; @@ -64,9 +65,13 @@ const _displayItem = (item, searchTerms, highlightTerms) => { const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; const contentRoot = document.documentElement.dataset.content_root; - const [docName, title, anchor, descr, score, _filename] = item; + const [docName, title, anchor, descr, score, _filename, kind] = item; let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); let requestUrl; let linkUrl; if (docBuilder === "dirhtml") { @@ -115,8 +120,10 @@ const _finishSearch = (resultCount) => { "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." ); else - Search.status.innerText = _( - "Search finished, found ${resultCount} page(s) matching the search query." + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, ).replace('${resultCount}', resultCount); }; const _displayNextItem = ( @@ -138,7 +145,7 @@ const _displayNextItem = ( else _finishSearch(resultCount); }; // Helper function used by query() to order search results. -// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. // Order the results by score (in opposite order of appearance, since the // `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. const _orderResultsByScoreThenName = (a, b) => { @@ -248,6 +255,7 @@ const Search = { searchSummary.classList.add("search-summary"); searchSummary.innerText = ""; const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); searchList.classList.add("search"); const out = document.getElementById("search-results"); @@ -318,7 +326,7 @@ const Search = { const indexEntries = Search._index.indexentries; // Collect multiple result groups to be sorted separately and then ordered. - // Each is an array of [docname, title, anchor, descr, score, filename]. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. const normalResults = []; const nonMainIndexResults = []; @@ -337,6 +345,7 @@ const Search = { null, score + boost, filenames[file], + SearchResultKind.title, ]); } } @@ -354,6 +363,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.index, ]; if (isMain) { normalResults.push(result); @@ -475,6 +485,7 @@ const Search = { descr, score, filenames[match[0]], + SearchResultKind.object, ]); }; Object.keys(objects).forEach((prefix) => @@ -585,6 +596,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.text, ]); } return results; diff --git a/v0.3.1/changelog.html b/v0.3.1/changelog.html index eafac3a877..fc45a50384 100644 --- a/v0.3.1/changelog.html +++ b/v0.3.1/changelog.html @@ -12,9 +12,9 @@ gtag('js', new Date()); gtag('config', 'G-40DVRMX8T4'); - + - + Changelog - docTR documentation @@ -226,20 +226,42 @@ + diff --git a/v0.3.1/community/resources.html b/v0.3.1/community/resources.html index 2564037893..9a1988258c 100644 --- a/v0.3.1/community/resources.html +++ b/v0.3.1/community/resources.html @@ -14,7 +14,7 @@ - + Community resources - docTR documentation @@ -389,7 +389,7 @@

    Community resources - + diff --git a/v0.3.1/contributing/code_of_conduct.html b/v0.3.1/contributing/code_of_conduct.html index 5ea4a1f99d..03422dbb4d 100644 --- a/v0.3.1/contributing/code_of_conduct.html +++ b/v0.3.1/contributing/code_of_conduct.html @@ -14,7 +14,7 @@ - + Contributor Covenant Code of Conduct - docTR documentation @@ -504,7 +504,7 @@

    Attribution - + diff --git a/v0.3.1/contributing/contributing.html b/v0.3.1/contributing/contributing.html index e5a85682c6..05e2b3641b 100644 --- a/v0.3.1/contributing/contributing.html +++ b/v0.3.1/contributing/contributing.html @@ -14,7 +14,7 @@ - + Contributing to docTR - docTR documentation @@ -481,7 +481,7 @@

    Let’s connect - + diff --git a/v0.3.1/datasets.html b/v0.3.1/datasets.html deleted file mode 100644 index 193e576c57..0000000000 --- a/v0.3.1/datasets.html +++ /dev/null @@ -1,578 +0,0 @@ - - - - - - - - - - - - - doctr.datasets - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.datasets

    -

    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.

    -
    -

    Available Datasets

    -

    The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL.

    -
    -
    -class doctr.datasets.datasets.VisionDataset(url: str, file_name: str | None = None, file_hash: str | None = None, extract_archive: bool = False, download: bool = False, overwrite: bool = False)[source]
    -
    - -

    Here are all datasets that are available through DocTR:

    -
    -
    -class doctr.datasets.FUNSD(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    FUNSD dataset from “FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents”.

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD
    ->>> train_set = FUNSD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.SROIE(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    SROIE dataset from “ICDAR2019 Competition on Scanned Receipt OCR and Information Extraction”.

    -
    -
    Example::
    >>> from doctr.datasets import SROIE
    ->>> train_set = SROIE(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.CORD(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    CORD dataset from “CORD: A Consolidated Receipt Dataset forPost-OCR Parsing”.

    -
    -
    Example::
    >>> from doctr.datasets import CORD
    ->>> train_set = CORD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.OCRDataset(img_folder: str, label_file: str, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    Implements an OCR dataset

    -
    -
    Parameters:
    -
      -
    • img_folder – local path to image folder (all jpg at the root)

    • -
    • label_file – local path to the label file

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -

    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.

    -
    -
    -class doctr.datasets.loader.DataLoader(dataset, shuffle: bool = True, batch_size: int = 1, drop_last: bool = False, workers: int | None = None)[source]
    -

    Implements a dataset wrapper for fast data loading

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD, 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)
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • dataset – the dataset

    • -
    • shuffle – whether the samples should be shuffled before passing it to the iterator

    • -
    • batch_size – number of elements in each batch

    • -
    • drop_last – if True, drops the last batch if it isn’t full

    • -
    • workers – number of workers to use for data loading

    • -
    -
    -
    -
    - -
    -
    -

    Supported Vocabs

    -

    Since textual content has to be encoded properly for models to interpret them efficiently, DocTR supports multiple sets -of vocabs.

    -
    - - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    DocTR Vocabs

    Name

    size

    characters

    digits

    10

    0123456789

    ascii_letters

    52

    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

    punctuation

    32

    !”#$%&'()*+,-./:;<=>?@[\]^_`{|}~

    currency

    5

    £€¥¢฿

    latin

    96

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°

    french

    154

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿

    -
    -
    -
    -doctr.datasets.encode_sequences(sequences: List[str], vocab: str, target_size: int | None = None, eos: int = -1, sos: int | None = None, pad: int | None = None, **kwargs: Any) ndarray[source]
    -

    Encode character sequences using a given vocab as mapping

    -
    -
    Parameters:
    -
      -
    • sequences – the list of character sequences of size N

    • -
    • vocab – the ordered vocab to use for encoding

    • -
    • target_size – maximum length of the encoded data

    • -
    • eos – encoding of End Of String

    • -
    • sos – optional encoding of Start Of String

    • -
    • pad – optional encoding for padding. In case of padding, all sequences are followed by 1 EOS then PAD

    • -
    -
    -
    Returns:
    -

    the padded encoded data as a tensor

    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.1/documents.html b/v0.3.1/documents.html deleted file mode 100644 index 98cbb2c5ef..0000000000 --- a/v0.3.1/documents.html +++ /dev/null @@ -1,772 +0,0 @@ - - - - - - - - - - - - - doctr.documents - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.documents

    -

    The documents module enables users to easily access content from documents and export analysis -results to structured formats.

    -
    -

    Document structure

    -

    Structural organization of the documents.

    -
    -

    Word

    -

    A Word is an uninterrupted sequence of characters.

    -
    -
    -class doctr.documents.Word(value: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float])[source]
    -

    Implements a word element

    -
    -
    Parameters:
    -
      -
    • value – the text string of the word

    • -
    • confidence – the confidence associated with the text prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to

    • -
    • size (the page's)

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Line(words: List[Word], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float] | None = None)[source]
    -

    Implements a line element as a collection of words

    -
    -
    Parameters:
    -
      -
    • words – list of word elements

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all words in it.

    • -
    -
    -
    -
    - -
    -
    -

    Artefact

    -

    An Artefact is a non-textual element (e.g. QR code, picture, chart, signature, logo, etc.).

    -
    -
    -class doctr.documents.Artefact(artefact_type: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]])[source]
    -

    Implements a non-textual element

    -
    -
    Parameters:
    -
      -
    • artefact_type – the type of artefact

    • -
    • confidence – the confidence of the type prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size.

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Block(lines: List[Line] = [], artefacts: List[Artefact] = [], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float] | None = None)[source]
    -

    Implements a block element as a collection of lines and artefacts

    -
    -
    Parameters:
    -
      -
    • lines – list of line elements

    • -
    • artefacts – list of artefacts

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all lines and artefacts in it.

    • -
    -
    -
    -
    - -
    -
    -

    Page

    -

    A Page is a collection of Blocks that were on the same physical page.

    -
    -
    -class doctr.documents.Page(blocks: List[Block], page_idx: int, dimensions: Tuple[int, int], orientation: Dict[str, Any] | None = None, language: Dict[str, Any] | None = None)[source]
    -

    Implements a page element as a collection of blocks

    -
    -
    Parameters:
    -
      -
    • blocks – list of block elements

    • -
    • page_idx – the index of the page in the input raw document

    • -
    • dimensions – the page size in pixels in format (width, height)

    • -
    • orientation – a dictionary with the value of the rotation angle in degress and confidence of the prediction

    • -
    • language – a dictionary with the language value and confidence of the prediction

    • -
    -
    -
    -
    -
    -show(page: ndarray, interactive: bool = True, **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -
      -
    • page – image encoded as a numpy array in uint8

    • -
    • interactive – whether the display should be interactive

    • -
    -
    -
    -
    - -
    - -
    -
    -

    Document

    -

    A Document is a collection of Pages.

    -
    -
    -class doctr.documents.Document(pages: List[Page])[source]
    -

    Implements a document element as a collection of pages

    -
    -
    Parameters:
    -

    pages – list of page elements

    -
    -
    -
    -
    -show(pages: List[ndarray], **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -

    pages – list of images encoded as numpy arrays in uint8

    -
    -
    -
    - -
    - -
    -
    -
    -

    File reading

    -

    High-performance file reading and conversion to processable structured data.

    -
    -
    -doctr.documents.read_pdf(file: str | Path | bytes, **kwargs: Any) Document[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_pdf
    ->>> doc = read_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_img(file: str | Path | bytes, output_size: Tuple[int, int] | None = None, rgb_output: bool = True) ndarray[source]
    -

    Read an image file into numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_img
    ->>> page = read_img("path/to/your/doc.jpg")
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • file – the path to the image file

    • -
    • output_size – the expected output size of each page in format H x W

    • -
    • rgb_output – whether the output ndarray channel order should be RGB instead of BGR.

    • -
    -
    -
    Returns:
    -

    the page decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_html(url: str, **kwargs: Any) bytes[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_html
    ->>> doc = read_html("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – URL of the target web page

    -
    -
    Returns:
    -

    decoded PDF file as a bytes stream

    -
    -
    -
    - -
    -
    -class doctr.documents.DocumentFile[source]
    -

    Read a document from multiple extensions

    -
    -
    -classmethod from_pdf(file: str | Path | bytes, **kwargs) PDF[source]
    -

    Read a PDF file

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file or a binary stream

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_url(url: str, **kwargs) PDF[source]
    -

    Interpret a web page as a PDF document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_url("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – the URL of the target web page

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_images(files: Sequence[str | Path | bytes] | str | Path | bytes, **kwargs) List[ndarray][source]
    -

    Read an image file (or a collection of image files) and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"])
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    files – the path to the image file or a binary stream, or a collection of those

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    - -
    -
    -class doctr.documents.PDF(doc: Document)[source]
    -

    PDF document template

    -
    -
    Parameters:
    -

    doc – input PDF document

    -
    -
    -
    -
    -as_images(**kwargs) List[ndarray][source]
    -

    Convert all document pages to images

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of convert_page_to_numpy

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -get_words(**kwargs) List[List[Tuple[Tuple[float, float, float, float], str]]][source]
    -

    Get the annotations for all words in the document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of fitz.Page.getTextWords

    -
    -
    Returns:
    -

    the list of pages annotations, represented as a list of tuple (bounding box, value)

    -
    -
    -
    - -
    -
    -get_artefacts() List[List[Tuple[float, float, float, float]]][source]
    -

    Get the artefacts for the entire document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts()
    -
    -
    -
    -
    -
    -
    Returns:
    -

    the list of pages artefacts, represented as a list of bounding boxes

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.1/genindex.html b/v0.3.1/genindex.html index a19b433943..21520455b4 100644 --- a/v0.3.1/genindex.html +++ b/v0.3.1/genindex.html @@ -13,7 +13,7 @@ - Index - docTR documentation + Index - docTR documentation @@ -224,20 +224,42 @@

    +
    +

    U

    + + +
    +
    +

    V

    @@ -561,7 +711,13 @@

    V

    W

    +
    @@ -599,8 +755,8 @@

    W

    - - + + diff --git a/v0.3.1/getting_started/installing.html b/v0.3.1/getting_started/installing.html index a488e9a030..af3b58193e 100644 --- a/v0.3.1/getting_started/installing.html +++ b/v0.3.1/getting_started/installing.html @@ -14,7 +14,7 @@ - + Installation - docTR documentation @@ -305,7 +305,7 @@

    Installation

    -

    This library requires Python 3.9 or higher.

    +

    This library requires Python 3.10 or higher.

    Prerequisites

    Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so:

    @@ -435,7 +435,7 @@

    Via Git - + diff --git a/v0.3.1/index.html b/v0.3.1/index.html index 4c6a28c66a..3a06afc6d9 100644 --- a/v0.3.1/index.html +++ b/v0.3.1/index.html @@ -12,9 +12,9 @@ gtag('js', new Date()); gtag('config', 'G-40DVRMX8T4'); - + - + docTR documentation @@ -226,20 +226,42 @@
    -

    DocTR: Document Text Recognition

    -

    State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 (PyTorch now in beta)

    +

    docTR: Document Text Recognition

    +

    State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 & PyTorch

    https://github.com/mindee/doctr/releases/download/v0.2.0/ocr.png

    DocTR provides an easy and powerful way to extract valuable information from your documents:

      -
    • 🧾 for automation: seemlessly process documents for Natural Language Understanding tasks: we provide OCR predictors to parse textual information (localize and identify each word) from your documents.

    • +
    • 🧾 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.

    • 👩‍🔬 for research: quickly compare your own architectures speed & performances with state-of-art models on public datasets.

    -

    Welcome to the documentation of DocTR!

    Main Features

    • 🤖 Robust 2-stage (detection + recognition) OCR predictors with pretrained parameters

    • ⚡ User-friendly, 3 lines of code to load a document and extract text with a predictor

    • -
    • 🚀 State-of-the-art performances on public document datasets, comparable with GoogleVision/AWS Textract

    • +
    • 🚀 State-of-the-art performance on public document datasets, comparable with GoogleVision/AWS Textract

    • ⚡ Optimized for inference speed on both CPU & GPU

    • -
    • 🐦 Light package, small dependencies

    • -
    • 🛠️ Daily maintained

    • -
    • 🏭 Easy integration

    • +
    • 🐦 Light package, minimal dependencies

    • +
    • 🛠️ Actively maintained by Mindee

    • +
    • 🏭 Easy integration (available templates for browser demo & API deployment)

    -
    -
    -

    Getting Started

    -
    -

    Build & train your predictor

    -
      -
    • Compose your own end-to-end OCR predictor: mix and match detection & recognition predictors (all-pretrained)

    • -
    • Fine-tune or train from scratch any detection or recognition model to specialize on your data

    • -
    -

    Model zoo

    Text detection models

    -
    -

    Text recognition models

    -
    -

    Supported datasets

    -
    -
    +
    +
    +
    +
    +
    @@ -406,7 +381,7 @@

    Supported datasets - +
    Next @@ -446,10 +421,8 @@

    Supported datasets + diff --git a/v0.3.1/installing.html b/v0.3.1/installing.html deleted file mode 100644 index b61c60134b..0000000000 --- a/v0.3.1/installing.html +++ /dev/null @@ -1,395 +0,0 @@ - - - - - - - - - - - - - Installation - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    Installation

    -

    This library requires Python 3.6 or higher.

    -
    -

    Prerequisites

    -

    Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so:

    - -

    If you are running another OS than Linux, you will need a few extra dependencies.

    -

    For MacOS users, you can install them as follows:

    -
    brew install cairo pango gdk-pixbuf libffi
    -
    -
    -

    For Windows users, those dependencies are included in GTK. You can find the latest installer over here.

    -
    -
    -

    Via Python Package

    -

    Install the last stable release of the package using pip:

    -
    pip install python-doctr
    -
    -
    -
    -
    -

    Via Git

    -

    Install the library in developper mode:

    -
    git clone https://github.com/mindee/doctr.git
    -pip install -e doctr/.
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.1/models.html b/v0.3.1/models.html deleted file mode 100644 index b5cd44c9fa..0000000000 --- a/v0.3.1/models.html +++ /dev/null @@ -1,1002 +0,0 @@ - - - - - - - - - - - - - doctr.models - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.models

    -

    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 TensorFlow model.

    • -
    • Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable.

    • -
    -
    -

    Text Detection

    -

    Localizing text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Input shape

    # params

    Recall

    Precision

    Recall

    Precision

    FPS

    db_resnet50

    (1024, 1024, 3)

    25.2 M

    82.14

    87.64

    92.49

    89.66

    2.1

    -
    -

    All text detection models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for detection

    -

    In DocTR, the pre-processing scheme for detection is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) with potential deformation.

    2. -
    3. batch images together

    4. -
    5. normalize the batch using the training data statistics

    6. -
    -
    -
    -

    Detection models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.detection.db_resnet50(pretrained: bool = False, **kwargs: Any) DBNet[source]
    -

    DBNet as described in “Real-time Scene Text Detection with Differentiable Binarization”, using a ResNet-50 backbone.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -doctr.models.detection.linknet16(pretrained: bool = False, **kwargs: Any) LinkNet[source]
    -

    LinkNet as described in “LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import linknet16
    ->>> model = linknet16(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -

    Detection predictors

    -

    Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information.

    -
    -
    -doctr.models.detection.detection_predictor(arch: str = 'db_resnet50', pretrained: bool = False, **kwargs: Any) DetectionPredictor[source]
    -

    Text detection architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import detection_predictor
    ->>> model = detection_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_resnet50’)

    • -
    • pretrained – If True, returns a model pre-trained on our text detection dataset

    • -
    -
    -
    Returns:
    -

    Detection predictor

    -
    -
    -
    - -
    -
    -
    -

    Text Recognition

    -

    Identifying strings in images

    -
    - - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Text recognition model zoo

    Architecture

    Input shape

    # params

    FUNSD

    CORD

    FPS

    crnn_vgg16_bn

    (32, 128, 3)

    15.8M

    86.02

    91.3

    12.8

    sar_vgg16_bn

    (32, 128, 3)

    21.5M

    86.2

    91.7

    3.3

    sar_resnet31

    (32, 128, 3)

    53.1M

    86.3

    92.1

    2.7

    -
    -

    All text recognition models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All these recognition models are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for recognition

    -

    In DocTR, the pre-processing scheme for recognition is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) without deformation.

    2. -
    3. pad the image to the target size (with zeros by default)

    4. -
    5. batch images together

    6. -
    7. normalize the batch using the training data statistics

    8. -
    -
    -
    -

    Recognition models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.recognition.crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) CRNN[source]
    -

    CRNN with a VGG-16 backbone as described in “An End-to-End Trainable Neural Network for Image-based -Sequence Recognition and Its Application to Scene Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import crnn_vgg16_bn
    ->>> model = crnn_vgg16_bn(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a VGG16 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_vgg16_bn
    ->>> model = sar_vgg16_bn(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_resnet31(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a resnet-31 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -

    Example

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_resnet31
    ->>> model = sar_resnet31(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.master(pretrained: bool = False, **kwargs: Any) MASTER[source]
    -

    MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_. -Example:

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import master
    ->>> model = master(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 48, 160, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -

    Recognition predictors

    -

    Combining the right components around a given architecture for easier usage.

    -
    -
    -doctr.models.recognition.recognition_predictor(arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) RecognitionPredictor[source]
    -

    Text recognition architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import recognition_predictor
    ->>> model = recognition_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(32, 128, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘crnn_vgg16_bn’, ‘crnn_resnet31’, ‘sar_vgg16_bn’, ‘sar_resnet31’)

    • -
    • pretrained – If True, returns a model pre-trained on our text recognition dataset

    • -
    -
    -
    Returns:
    -

    Recognition predictor

    -
    -
    -
    - -
    -
    -
    -

    End-to-End OCR

    -

    Predictors that localize and identify text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Recall

    Precision

    FPS

    Recall

    Precision

    FPS

    db_resnet50 + crnn_vgg16_bn

    70.08

    74.77

    0.85

    82.19

    79.67

    1.6

    db_resnet50 + sar_vgg16_bn

    N/A

    N/A

    0.49

    N/A

    N/A

    1.0

    db_resnet50 + sar_resnet31

    N/A

    N/A

    0.27

    N/A

    N/A

    0.83

    Gvision text detection

    59.50

    62.50

    75.30

    70.00

    Gvision doc. text detection

    64.00

    53.30

    68.90

    61.10

    AWS textract

    78.10

    83.00

    87.50

    66.00

    -
    -

    All OCR models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All recognition models of predictors are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -

    Results on private ocr datasets

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Receipts

    Invoices

    IDs

    Architecture

    Recall

    Precision

    Recall

    Precision

    Recall

    Precision

    db_resnet50 + crnn_vgg16_bn (ours)

    78.90

    81.01

    65.68

    69.86

    49.48

    50.46

    Gvision doc. text detection

    68.91

    59.89

    63.20

    52.85

    43.70

    29.21

    AWS textract

    75.77

    77.70

    70.47

    69.13

    46.39

    43.32

    -
    -
    -

    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.

    -
    -
    -doctr.models.zoo.ocr_predictor(det_arch: str = 'db_resnet50', reco_arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) OCRPredictor[source]
    -

    End-to-end OCR architecture using one model for localization, and another for text recognition.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import ocr_predictor
    ->>> model = ocr_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_sar_vgg’, ‘db_sar_resnet’, ‘db_crnn_vgg’, ‘db_crnn_resnet’)

    • -
    • pretrained – If True, returns a model pre-trained on our OCR dataset

    • -
    -
    -
    Returns:
    -

    OCR predictor

    -
    -
    -
    - -
    -
    -
    -

    Model export

    -

    Utility functions to make the most of document analysis models.

    -
    -

    Model compression

    -
    -
    -doctr.models.export.convert_to_tflite(tf_model: Model) bytes[source]
    -

    Converts a model to TFLite format

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_tflite, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_tflite(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.convert_to_fp16(tf_model: Model) bytes[source]
    -

    Converts a model to half precision

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_fp16, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_fp16(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the serialized FP16 model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) bytes[source]
    -

    Quantize a Tensorflow model

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import quantize_model, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = quantize_model(model, (224, 224, 3))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • tf_model – a keras model

    • -
    • input_shape – shape of the expected input tensor (excluding batch dimension) with channel last order

    • -
    -
    -
    Returns:
    -

    the serialized quantized model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -

    Using SavedModel

    -

    Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -SavedModel format as follows:

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> _ = model(input_t, training=False)
    ->>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/')
    -
    -
    -

    And loaded just as easily:

    -
    >>> import tensorflow as tf
    ->>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/')
    -
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.1/modules/contrib.html b/v0.3.1/modules/contrib.html index e99f6b3f74..7fb86b8b38 100644 --- a/v0.3.1/modules/contrib.html +++ b/v0.3.1/modules/contrib.html @@ -14,7 +14,7 @@ - + doctr.contrib - docTR documentation @@ -380,7 +380,7 @@

    Supported contribution modules - + diff --git a/v0.3.1/modules/datasets.html b/v0.3.1/modules/datasets.html index 456e10b172..380a986793 100644 --- a/v0.3.1/modules/datasets.html +++ b/v0.3.1/modules/datasets.html @@ -14,7 +14,7 @@ - + doctr.datasets - docTR documentation @@ -1081,7 +1081,7 @@

    Returns:

    - + diff --git a/v0.3.1/modules/io.html b/v0.3.1/modules/io.html index 01eadaa4b8..24c41954be 100644 --- a/v0.3.1/modules/io.html +++ b/v0.3.1/modules/io.html @@ -14,7 +14,7 @@ - + doctr.io - docTR documentation @@ -760,7 +760,7 @@

    Returns: - + diff --git a/v0.3.1/modules/models.html b/v0.3.1/modules/models.html index c465cc0586..91b8810a6a 100644 --- a/v0.3.1/modules/models.html +++ b/v0.3.1/modules/models.html @@ -14,7 +14,7 @@ - + doctr.models - docTR documentation @@ -1612,7 +1612,7 @@

    Args: - + diff --git a/v0.3.1/modules/transforms.html b/v0.3.1/modules/transforms.html index 30f7a2631a..c5ead3f3ce 100644 --- a/v0.3.1/modules/transforms.html +++ b/v0.3.1/modules/transforms.html @@ -14,7 +14,7 @@ - + doctr.transforms - docTR documentation @@ -835,7 +835,7 @@

    Args:< - + diff --git a/v0.3.1/modules/utils.html b/v0.3.1/modules/utils.html index 888a32c321..b7f6fc570b 100644 --- a/v0.3.1/modules/utils.html +++ b/v0.3.1/modules/utils.html @@ -14,7 +14,7 @@ - + doctr.utils - docTR documentation @@ -715,7 +715,7 @@

    Args: - + diff --git a/v0.3.1/notebooks.html b/v0.3.1/notebooks.html index f97771aebb..d36539f59e 100644 --- a/v0.3.1/notebooks.html +++ b/v0.3.1/notebooks.html @@ -14,7 +14,7 @@ - + docTR Notebooks - docTR documentation @@ -391,7 +391,7 @@

    docTR Notebooks - + diff --git a/v0.3.1/objects.inv b/v0.3.1/objects.inv index a22d2ce821026792e7b2f75cbac9aecf06cc0d89..c1700f291b7b6bc7252a625eb1acde3a63b67eca 100644 GIT binary patch delta 4090 zcmVE-{flJb$&E$#&Z~7KZnH3ZBzB-Bk-HmK|4yO&>?j;A&50 zIZ5wyAQF;Tp$HZSIj&W|M!#M^Nn<2H5+ngqwX#Iv|L(z?S&7#up!*R3hb(vqotp}EyrnZK7Dx@Y4_&W z<#ST(MrrRB4^xK50}AjqiKdRQ<-^_8hjGfUpKnJBAIIrvQ$L~~<0|^?>iY8G!{Wo$ z{W$C2a28IB2mv89!V>6`hku4()&hny#PkFkTMI2h+Zqm5PzIxp$YgCzSGO#oB)}nB z<>-%+yhMz@DHGt-NkK&o$-!=X=}GG zZ<&mOle5NJU`u8y18{!USR?F#a}zL%iu3QS)x#Rfbw=#&$*{DzfPbm5P2O+IjC$N$ zc&y1n*doUmhA5lSDSXXdj*3(J-*Xyi+l!m6e^S^Y;~+CGd$wRr+hS*GJ?vc@ZEYDC zEt`3UBh*|%Z4Gc)n|atJjkVR9NL2=4QD1WHagPQX?b(7X!lvcenp{a+{HG`mNmrsM zEzC={bzIZL(n^G9a(~$|%?$E!V@{AK?_(T%w=PW!dWoOcle2__Ndt5&{p;_ zijlJ|Dw=IFykA9oxaI4syID?1!_5?VnZ@?<>nO3W>^-TOQ(?y*pzEeJKSV5}A(4Lt z^A%sbJF*(19>s{ZTW%P>pe=0=b`z>Gu=_Z?s0q>@?|q!Aw0{Xz@7+EOD``U1`@9dq zN}3S$ZtX*`k{TjIFkI0hE*;B6Vpt^QK7Af5b{|trEvB!Fh9(%s=ws9KOd)}!G>XZ- zh^nJa6hO9k2N_ORIEzSW_F$D}D2#%lvvva0@YW0`@h(_pC{IvvHa`XruStyvZFM+X z#W3UJ@95A~c7JSazK3!Amq1s|4*YAZq+|yF^;VA2?oCgRI-vPjp1(O7>L}Afpk5sf zb$)UL6ir)c@2*LPctqT>rjq0pQSy8Q++n+&|FT*qM;XJ1h&0=gIQbZkI_nD8tK&r7 z8gj}o(?Igov^lPT8<>wDuMN!TaoXUF9iIvlN6B~Edw)}cR~Kzu(CMO$8xS<-#@RF9 z;_S-x6h@4_J$4~>xeLxf$O!5uR_UxEHNl?7pm79(E z=te8tgMYy>&aRRJ%5stWj>PO!oR4?o6D-N<4wLtBnN5b=;%I^^*KofM;iG&|?FmM~ zZYd#-{v7YlU4rg5qty>E3Gx0SP2=Z@jQ3fz-I261&nJQwI3dsSVk(Ag4ksc$!Tlai zWPB$3u|G+TVEi$TraJVwL=y?WrfBjK zy`8D%OmH_Q2tDTN7YU5nfztq~Gb{}^69Km&$;Ec2%sGRLy&A#IXd}lMW0fQ9%=RZG zE`QdIBs(Vz$&8C-CATxzop-oT9*yE+yq&9z2ojRvVz`l-xLf4_7n5C?jJxdy$;pXw zQjDZ>7jwpR$=$4uaWUV_C`K#_oZw>Aluo%D)+kQK8!6Zb!<=AgbV>ueG;K4vb1@{e z(ok7MWaGa_MHU6xb;=v|z?436K_heQrGL!wl0=8>*?37|1g=a&k=%@-UE_c*3v4iv zV7V!~P9ny#rdHPsomT0)#lxf?t@KkXr$EyxL~mi;z#`a?#l5tjNQ#3sIp$^l|^yw zmsokq{g~~)y6IV&&>6bI$%+&g^rsvM-Nm=1&$n>inq8(F7Ex zh>Z#Pkd^fp@@=EE8bh`6gsm$u-M2%Z-s^hy3rwn1HKkiiauk4op+JEFLCIDnCVLEV zH)y0QcF1QtKA(+9mtiXzfG_}K0M1x+z`gp-gl=2;4rWbjq!DXskIqg153!f%o5Jdi zTsJ}lq;R28I5IDu$w)k)tbfB%8#-fv8e?H*Gn?$2vb@MjHtC(sQ8`fZMTPQ}O6)&V7uF!UBdeBV?66D zw&g&Twkkw*^TR3Ctn@(BUmBpq(t#u_WP8DSg2c6)G(zki*eVX|dQjtBcH5BlY3I2| zHzgK8?&f3V3<&p?!9R0#%jcc&dY(0RHZZD$#cX>Rrd`gN1(23K>x(p z5k?j5Lsz>rhl4a#?PzCkPtWDNtWdtA^Me1@h+eDqI~yrVLi9t&NFAWMS`)FE_Rg<< z#KeBUsQpaTtA9)mZ0;mM7Z}rR!^Gjfsq&XCL`7X4m5P#(re@@ikAuu5%qu0J$`AZ4 z`QzU$9tf9od4SsOW^?xOL5XXiZ5a{!QG#+;rbYo8MKk1osvg&DlB7Q+`FFIA$o4Io zX1T@E?LRa=^w5tKX9dkHagzM7>Y5eT2+Qm>?G&c}P=Dd>Zg2nXZ4hK*BBTF~Atzz} z@BX^nlDDjs%R(qfK#~CA0Kxg#XNCBH!ZmCjn+)z5KMI+*p+7sLXAiB!tvMNJNgzvp z)MVZkB`jFf_3|mt^snekQnH|4koiZC48xZqm1t}lx=TrxmVoSFyrEfA5akvrTf2*2 z_@8S8Pk&WKxTDo#Fwk}9UjvmQe$=WZcbE0!J+^Vh{xhZ*M52_%F^8$ZYZnxwO zNe+-tFZMf~V+j(VB%qN1C0*-gK5J$^m+Q(0)_*i&AIG*|C>%Px>)W$Qhgj!`4V=J= z^StXJ!@aHz?eUK9_0aaSw?aeidV5mN7jZT3%xq{WP?fr&8WC@EPkjG2m}I^1A7TF8 zM+X>}eDZSl>DMoN@wJ{_qwNiAYq4}RwrF~QHtpX0+HTKl?$1o}+TN{sD4+t4Nh-pS zFL!xfeN$kPXu-zUHyB=fD`1j0HVS@od*Rgvs)?XRu&-??yaeuPr;&y`kpAlbGSU<{ zxgO~H+ZNKR@92eR_5FNn|Blzc)5T%r@qY|gYdg<7j#Gy=7Rh__#D}KoYz2%_xJ9mc zEn8-}@{U06*-OSFi9iyCsm6%eO@}0-N?;lu2>3@IlNG_DJj^&Y;#~3oINLbmR zjQ-;>_i$D*`BK265@7zbNz{sFP#?~(?sYLm$+bj%aWZtMY8vng1b@`` zC<1ifZN#HLI~2#>G(t|wRs@S~RA?k_<}3GgQ_YM;3%mmn`Tn`s@z|~cGuxH4zHxJt{Oo8sOeIr%NA3sgnJF<5?))11| z6FwU7Uh#x!)i|6z-lYggY!>1BmDII$*tP>uPJke8MlsS4@+A!x0j{<#bU^!;Y z!=w8)T9*mKoi+WUTK6j11vIRsR_o;8abB0f?z2Ca1HJCy*KfDJmF~ae)<4wx?X(7^ z%_gEN7Fd|lD`dJkcQ~2NXD9worzfX#aB>Dt&VTN`a(Flaq#RJieSg3ibH$jyv0%)B zDlQl&!jV^CPAtraAt&4uQJzyyRc+3}d>C?C)rJIK>sEBeuMFoST{Foqx z!S~jglMRpSfz(s9IC;3|kG$G%S}acJzv|^Z`wi2J_73T32T2p{a2bbwg8%>k delta 1868 zcmV-S2ebIcAgT|LI|DN=Fp)nzf2CSYZ`(K!z4KQH*kG}Hpb@8NQRLRfO*V_9$=YuA zXwVYv2$4jUq@1R|zWk6VlKNIpp``cTkeuOgNGBp8r;7-2#4u-ztB3U}$lor*1ThvY z1M}gTr^z2@0R)jtxc>HaGmY_ZwO;A=B-&>EaQkHvBP2BP1_XUt)H3{re~@WB#VjV- zoZg!T#~CB^kdW6dwV^(C^rm4FXCaC3j^XcxXksQU9EvRDf;6Vf0?Q)bzeAnV@P<}G zP=x71_VrFRCrus+X=~IBb;jZ}G#Mo^_Je9jP{WND35yhG;{7Me@d1TyNSLqwu*`?g z2?<{&a#m$)CT+o<$*bh1f0#d$Xd3xCPVh{-lDarhlJ4RZ9d$6y?Sj_Hqr>lu6f~JC zau;V)C*g5*J)N;YZ01}^@)7eLDx-3?z^h20)5)UCQ%5T(vjX#f(ZPwfaDuzmENlpL zuJayUZ&446YC?};d}I4?+)uJe|%GqVifHp!QTLf zgGEu}^f;4Qrl{#mxmC92-0_ZAyiRo|BaQHsfLB_nC@Ki`fZNzT;$f$bVOXACj2k85-XU zl=1i{-l^A91Sk&4e=&>IJcKmMcBkWU`C+%u=8B|x+F}gRGQ{33e@A1^ zl>8(_)}Ipx!70kvfzVVODM&)-tqcuWt=qem1?r=xIbnD*?+&H=2yLquh|e+pRWcJ1 zkCT}X8GblSX01^ck@QoZCvP*kpJ{x2<{4&eroa`+#5=}kf6{MInxdK9e+oR4`EQPo z{}sYheD1t$5HIMwAX#HJCqlO5hN9`+73-}?Fk9@!iL7&N!KT>Ix*Rg)1@ssTnldEZ z8uMf1ZDNlR>%yUtOUBKUCXF-EpLWLBYgHI$yd_ikkw(3wJj#^jj5FY;?=c2S zPGz9rv8bHHd7s9iSob89i%<c7_oe3s#6wq;JmU!U(a4tM$EVPV9u? z%{9EmKlnZ3;qyephBwvlDQ1P4H}iydd3m6>%3V*Tf4X7{7>_O!v=?U<*mBCP?o9wX zp;*Ag(X_f#^_b4>t3gA9{$vEon_UL>>i(p^1L}?i?29;wf$tPIhqf5m4zZj>hZeY3^uXK( zlqWOSqJ3I3NLV6Q9@Ww^ZXKm(p;s*u-cJ#|Gb)jF^f&Hvdc(dX8?1Y>?HP#Tm8j-! z&>vr#Y@wZ0<8rNJqG=jaGA_+{0xmLJV4dbWe;2DeWGH#i=B-G$U0(*~c2uU!U|j=% zG1;F_Dghf$i%@LRc*rHXXEl7)PynubtEhLB0xu8%&P0+TQ2Yro~2f=5J$*@C6wV@5Y_sON4zSSI3 zDpB_qrR4u4q^D-rh<7Fe0B^oM0Ff3UNkgtmlf{syZLnscuz-!foTJ=E_%`I`n1 zvmo{W63V#{;UV&1P;Vujli{+UV8NR8*dy6yuQOQ=ShRVMy|U6O#ovpV#k?7%{?d80 z!N8j4f%$)Y6Xk2>y20+_&|YR?s~v6KpgW3Vwt8FD!mnFp0K*8d_ue*|J2o z?K;3K0?Qoh#ZKTCjDWuqxp|A^e~EIDP*C`cdlr{LLkqoakpP9tOAMWGr1y(XO@)LO z@|&V=r!b_=gb9mIRfOCz}Q~EsFI53>6&YH - - - - - - - - - - Python Module Index - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    - -
    -

    Python Module Index

    - -
    - - - - - - - - - - - -
     
    d
    - doctr -
    - -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.3.1/search.html b/v0.3.1/search.html index 73772822d2..d050f5eac7 100644 --- a/v0.3.1/search.html +++ b/v0.3.1/search.html @@ -14,7 +14,7 @@ - + Search - docTR documentation @@ -226,20 +226,42 @@ - - + + diff --git a/v0.3.1/searchindex.js b/v0.3.1/searchindex.js index 803f4f4bcf..6f154115ab 100644 --- a/v0.3.1/searchindex.js +++ b/v0.3.1/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles": {"Artefact": [[2, "artefact"]], "Available Datasets": [[1, "available-datasets"]], "Block": [[2, "block"]], "Build & train your predictor": [[3, "build-train-your-predictor"]], "Changelog": [[0, null]], "Composing transformations": [[6, "composing-transformations"]], "Data Loading": [[1, "data-loading"]], "Detection models": [[5, "detection-models"]], "Detection predictors": [[5, "detection-predictors"]], "DocTR Vocabs": [[1, "id1"]], "DocTR: Document Text Recognition": [[3, null]], "Document": [[2, "document"]], "Document structure": [[2, "document-structure"]], "End-to-End OCR": [[5, "end-to-end-ocr"]], "File reading": [[2, "file-reading"]], "Getting Started": [[3, "getting-started"]], "Installation": [[4, null]], "Line": [[2, "line"]], "Main Features": [[3, "main-features"]], "Model compression": [[5, "model-compression"]], "Model export": [[5, "model-export"]], "Model zoo": [[3, "model-zoo"]], "Notes": [[3, null]], "Package Reference": [[3, null]], "Page": [[2, "page"]], "Pre-processing for detection": [[5, "pre-processing-for-detection"]], "Pre-processing for recognition": [[5, "pre-processing-for-recognition"]], "Prerequisites": [[4, "prerequisites"]], "Recognition models": [[5, "recognition-models"]], "Recognition predictors": [[5, "recognition-predictors"]], "Supported Vocabs": [[1, "supported-vocabs"]], "Supported datasets": [[3, "supported-datasets"]], "Supported transformations": [[6, "supported-transformations"]], "Task evaluation": [[7, "task-evaluation"]], "Text Detection": [[5, "text-detection"]], "Text Recognition": [[5, "text-recognition"]], "Text detection models": [[3, "text-detection-models"]], "Text recognition model zoo": [[5, "id2"]], "Text recognition models": [[3, "text-recognition-models"]], "Two-stage approaches": [[5, "two-stage-approaches"]], "Using SavedModel": [[5, "using-savedmodel"]], "Via Git": [[4, "via-git"]], "Via Python Package": [[4, "via-python-package"]], "Visualization": [[7, "visualization"]], "Word": [[2, "word"]], "doctr.datasets": [[1, null]], "doctr.documents": [[2, null]], "doctr.models": [[5, null]], "doctr.transforms": [[6, null]], "doctr.utils": [[7, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]]}, "docnames": ["changelog", "datasets", "documents", "index", "installing", "models", "transforms", "utils"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "datasets.rst", "documents.rst", "index.rst", "installing.rst", "models.rst", "transforms.rst", "utils.rst"], "indexentries": {"artefact (class in doctr.documents)": [[2, "doctr.documents.Artefact", false]], "as_images() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.as_images", false]], "block (class in doctr.documents)": [[2, "doctr.documents.Block", false]], "colorinversion (class in doctr.transforms)": [[6, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[6, "doctr.transforms.Compose", false]], "convert_to_fp16() (in module doctr.models.export)": [[5, "doctr.models.export.convert_to_fp16", false]], "convert_to_tflite() (in module doctr.models.export)": [[5, "doctr.models.export.convert_to_tflite", false]], "cord (class in doctr.datasets)": [[1, "doctr.datasets.CORD", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.crnn_vgg16_bn", false]], "dataloader (class in doctr.datasets.loader)": [[1, "doctr.datasets.loader.DataLoader", false]], "db_resnet50() (in module doctr.models.detection)": [[5, "doctr.models.detection.db_resnet50", false]], "detection_predictor() (in module doctr.models.detection)": [[5, "doctr.models.detection.detection_predictor", false]], "document (class in doctr.documents)": [[2, "doctr.documents.Document", false]], "documentfile (class in doctr.documents)": [[2, "doctr.documents.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[1, "doctr.datasets.encode_sequences", false]], "from_images() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_images", false]], "from_pdf() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_pdf", false]], "from_url() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[1, "doctr.datasets.FUNSD", false]], "get_artefacts() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.get_artefacts", false]], "get_words() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.get_words", false]], "lambdatransformation (class in doctr.transforms)": [[6, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.documents)": [[2, "doctr.documents.Line", false]], "linknet16() (in module doctr.models.detection)": [[5, "doctr.models.detection.linknet16", false]], "localizationconfusion (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.LocalizationConfusion", false]], "master() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.master", false]], "normalize (class in doctr.transforms)": [[6, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models.zoo)": [[5, "doctr.models.zoo.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[1, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[6, "doctr.transforms.OneOf", false]], "page (class in doctr.documents)": [[2, "doctr.documents.Page", false]], "pdf (class in doctr.documents)": [[2, "doctr.documents.PDF", false]], "quantize_model() (in module doctr.models.export)": [[5, "doctr.models.export.quantize_model", false]], "randomapply (class in doctr.transforms)": [[6, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[6, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[6, "doctr.transforms.RandomContrast", false]], "randomgamma (class in doctr.transforms)": [[6, "doctr.transforms.RandomGamma", false]], "randomhue (class in doctr.transforms)": [[6, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[6, "doctr.transforms.RandomJpegQuality", false]], "randomsaturation (class in doctr.transforms)": [[6, "doctr.transforms.RandomSaturation", false]], "read_html() (in module doctr.documents)": [[2, "doctr.documents.read_html", false]], "read_img() (in module doctr.documents)": [[2, "doctr.documents.read_img", false]], "read_pdf() (in module doctr.documents)": [[2, "doctr.documents.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.recognition_predictor", false]], "resize (class in doctr.transforms)": [[6, "doctr.transforms.Resize", false]], "sar_resnet31() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.sar_resnet31", false]], "sar_vgg16_bn() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.sar_vgg16_bn", false]], "show() (doctr.documents.document method)": [[2, "doctr.documents.Document.show", false]], "show() (doctr.documents.page method)": [[2, "doctr.documents.Page.show", false]], "sroie (class in doctr.datasets)": [[1, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[7, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[7, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[7, "doctr.utils.metrics.TextMatch.summary", false]], "textmatch (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.TextMatch", false]], "togray (class in doctr.transforms)": [[6, "doctr.transforms.ToGray", false]], "visiondataset (class in doctr.datasets.datasets)": [[1, "doctr.datasets.datasets.VisionDataset", false]], "visualize_page() (in module doctr.utils.visualization)": [[7, "doctr.utils.visualization.visualize_page", false]], "word (class in doctr.documents)": [[2, "doctr.documents.Word", false]]}, "objects": {"doctr.datasets": [[1, 0, 1, "", "CORD"], [1, 0, 1, "", "FUNSD"], [1, 0, 1, "", "OCRDataset"], [1, 0, 1, "", "SROIE"], [1, 1, 1, "", "encode_sequences"]], "doctr.datasets.datasets": [[1, 0, 1, "", "VisionDataset"]], "doctr.datasets.loader": [[1, 0, 1, "", "DataLoader"]], "doctr.documents": [[2, 0, 1, "", "Artefact"], [2, 0, 1, "", "Block"], [2, 0, 1, "", "Document"], [2, 0, 1, "", "DocumentFile"], [2, 0, 1, "", "Line"], [2, 0, 1, "", "PDF"], [2, 0, 1, "", "Page"], [2, 0, 1, "", "Word"], [2, 1, 1, "", "read_html"], [2, 1, 1, "", "read_img"], [2, 1, 1, "", "read_pdf"]], "doctr.documents.Document": [[2, 2, 1, "", "show"]], "doctr.documents.DocumentFile": [[2, 2, 1, "", "from_images"], [2, 2, 1, "", "from_pdf"], [2, 2, 1, "", "from_url"]], "doctr.documents.PDF": [[2, 2, 1, "", "as_images"], [2, 2, 1, "", "get_artefacts"], [2, 2, 1, "", "get_words"]], "doctr.documents.Page": [[2, 2, 1, "", "show"]], "doctr.models.detection": [[5, 1, 1, "", "db_resnet50"], [5, 1, 1, "", "detection_predictor"], [5, 1, 1, "", "linknet16"]], "doctr.models.export": [[5, 1, 1, "", "convert_to_fp16"], [5, 1, 1, "", "convert_to_tflite"], [5, 1, 1, "", "quantize_model"]], "doctr.models.recognition": [[5, 1, 1, "", "crnn_vgg16_bn"], [5, 1, 1, "", "master"], [5, 1, 1, "", "recognition_predictor"], [5, 1, 1, "", "sar_resnet31"], [5, 1, 1, "", "sar_vgg16_bn"]], "doctr.models.zoo": [[5, 1, 1, "", "ocr_predictor"]], "doctr.transforms": [[6, 0, 1, "", "ColorInversion"], [6, 0, 1, "", "Compose"], [6, 0, 1, "", "LambdaTransformation"], [6, 0, 1, "", "Normalize"], [6, 0, 1, "", "OneOf"], [6, 0, 1, "", "RandomApply"], [6, 0, 1, "", "RandomBrightness"], [6, 0, 1, "", "RandomContrast"], [6, 0, 1, "", "RandomGamma"], [6, 0, 1, "", "RandomHue"], [6, 0, 1, "", "RandomJpegQuality"], [6, 0, 1, "", "RandomSaturation"], [6, 0, 1, "", "Resize"], [6, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[7, 0, 1, "", "LocalizationConfusion"], [7, 0, 1, "", "OCRMetric"], [7, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.LocalizationConfusion": [[7, 2, 1, "", "summary"]], "doctr.utils.metrics.OCRMetric": [[7, 2, 1, "", "summary"]], "doctr.utils.metrics.TextMatch": [[7, 2, 1, "", "summary"]], "doctr.utils.visualization": [[7, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [2, 7], "0": [1, 3, 5, 6, 7], "00": 5, "01": 5, "0123456789": 1, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 1, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": [], "02": 5, "02562": 5, "03": 3, "035": [], "0361328125": [], "04": [], "05": 3, "06": [], "06640625": [], "07": [], "08": 5, "09": [], "0966796875": [], "1": [1, 3, 5, 6, 7], "10": [1, 5, 7], "100": [5, 6, 7], "1000": 5, "101": [], "1024": [5, 7], "104": [], "106": [], "108": [], "1095": [], "11": 3, "110": 7, "1107": [], "114": [], "115": [], "1156": [], "116": [], "118": [], "11800h": [], "11th": [], "12": 5, "120": [], "123": [], "126": [], "1268": [], "128": 5, "13": 5, "130": [], "13068": [], "131": [], "1337891": [], "1357421875": [], "1396484375": [], "14": 5, "1420": [], "14470v1": [], "149": [], "15": 5, "150": 7, "154": 1, "1552": [], "16": 5, "160": 5, "1630859375": [], "1684": [], "16x16": [], "17": [], "1778": [], "1782": [], "18": 3, "185546875": [], "19": 5, "1900": [], "1910": 5, "19342": [], "19370": [], "195": [], "19598": [], "199": 5, "1999": [], "1m": 5, "2": [3, 5, 6], "20": 5, "200": 7, "2000": [], "2003": [], "2012": [], "2013": [], "2015": [], "2019": 3, "2021": 3, "2023": [], "207901": [], "21": 5, "2103": [], "2186": [], "21888": [], "22": [], "224": [5, 6], "225": 6, "22672": [], "229": 6, "23": [], "233": [], "236": [], "24": [], "246": [], "249": [], "25": 5, "2504": [], "255": [5, 6, 7], "256": 5, "257": [], "26": [], "26032": [], "264": [], "27": 5, "2700": [], "2710": [], "2749": [], "28": 3, "287": [], "29": 5, "296": [], "299": [], "2d": [], "3": [2, 3, 4, 5, 6, 7], "30": 5, "300": [], "3000": [], "301": [], "30595": 5, "30ghz": [], "31": 5, "32": [1, 5, 6], "3232421875": [], "33": [], "33402": [], "33608": [], "34": [], "340": [], "3456": [], "3515625": [], "36": [], "360": [], "37": [], "38": [], "39": 5, "4": [], "40": [], "406": 6, "41": [], "42": [], "43": 5, "44": [], "45": [], "456": 6, "46": 5, "47": 5, "472": [], "48": 5, "485": 6, "49": 5, "49377": [], "5": [1, 6, 7], "50": 5, "51": [], "51171875": [], "512": [], "52": [1, 5], "529": [], "53": 5, "533": [], "54": [], "540": [], "5478515625": [], "55": [], "56": [], "57": [], "58": [], "580": [], "5810546875": [], "583": [], "59": 5, "595": [], "597": [], "5k": [], "5m": 5, "6": [4, 5, 6], "60": 6, "600": [5, 7], "61": 5, "611": [], "62": 5, "625": [], "626": [], "629": [], "63": 5, "630": [], "64": [5, 6], "640": [], "641": [], "647": [], "65": 5, "66": 5, "660": [], "664": [], "666": [], "67": 5, "672": [], "68": 5, "689": [], "69": 5, "693": [], "694": [], "695": [], "6m": [], "7": 5, "70": [5, 7], "700": [], "701": [], "702": [], "707470": [], "71": [], "7100000": [], "713": [], "7141797": [], "7149": [], "72": [], "72dpi": [], "73": [], "73257": [], "733": [], "74": 5, "745": [], "75": 5, "753": [], "7581382": [], "76": [], "77": 5, "772": [], "772875": [], "78": 5, "780": [], "781": [], "783": [], "785": [], "789": [], "79": 5, "793533": [], "796": [], "798": [], "7m": [], "8": [5, 6], "80": [], "800": [5, 7], "81": 5, "817": [], "82": 5, "8275l": 5, "83": 5, "830": [], "84": [], "849": [], "85": 5, "8564453125": [], "857": [], "85875": [], "86": 5, "860": [], "8603515625": [], "862": [], "863": [], "87": 5, "8707": [], "875": [], "88": [], "89": 5, "8m": 5, "9": [], "90": 5, "90k": [], "90kdict32px": [], "91": 5, "913": [], "914085328578949": [], "917": [], "92": 5, "921": [], "93": [], "94": [], "95": 7, "9578408598899841": [], "96": 1, "97": [], "98": [], "99": [], "9949972033500671": [], "A": [1, 2, 3, 5], "And": 5, "As": [], "Be": [], "Being": [], "By": [], "For": [4, 5], "If": [2, 4, 5], "In": [1, 5], "It": 6, "Its": 5, "No": [], "Of": 1, "Or": [], "The": [1, 2, 5, 7], "Then": 5, "To": [], "_": [1, 5], "__call__": [], "_build": [], "_i": 7, "ab": [], "abc": [], "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 1, "abdef": [], "abl": [], "about": 5, "abov": 5, "abstract": 1, "abstractdataset": [], "abus": [], "accent": [], "accept": [], "access": [1, 2, 3], "account": [], "accur": [], "accuraci": 7, "achiev": [], "act": [], "action": [], "activ": [], "ad": 6, "adapt": [], "add": [6, 7], "add_hook": [], "add_label": 7, "addit": [], "addition": 5, "address": 2, "adjust": 6, "advanc": [], "advantag": [], "advis": [], "aesthet": [], "affect": [], "after": [], "ag": [], "again": [], "aggreg": [1, 7], "aggress": [], "align": 2, "all": [1, 2, 3, 5, 6, 7], "allow": [], "along": 5, "alreadi": [], "also": [], "alwai": [], "an": [1, 2, 3, 5, 7], "analysi": [2, 5], "ancient_greek": [], "andrej": [], "angl": 2, "ani": [1, 2, 3, 5, 6, 7], "annot": 2, "anot": [], "anoth": [1, 4, 5], "answer": [], "anyascii": [], "anyon": 3, "anyth": [], "api": [], "apolog": [], "apologi": [], "app": [], "appear": [], "appli": [1, 6], "applic": 5, "appoint": [], "appreci": [], "appropri": [], "ar": [1, 2, 4, 5, 6, 7], "arab": [], "arabic_diacrit": [], "arabic_lett": [], "arabic_punctu": [], "arbitrarili": [], "arch": 5, "architectur": [3, 5], "archiv": [], "area": [], "argument": [1, 2], "around": 5, "arrai": [2, 7], "art": 3, "artefact": 7, "artefact_typ": 2, "articl": [], "artifici": [], "arxiv": 5, "as_imag": 2, "asarrai": 7, "ascii_lett": 1, "aspect": [3, 6], "assess": 7, "assign": 7, "associ": 2, "assum": [], "assume_straight_pag": [], "astyp": [5, 7], "attack": [], "attend": [3, 5], "attent": [], "autoclass": [], "autom": 3, "automat": [], "autoregress": [], "avail": [3, 5, 6], "averag": [5, 6], "avoid": [], "aw": [3, 5], "awar": [], "azur": [], "b": 7, "b_j": 7, "back": [], "backbon": 5, "backend": 5, "background": [], "bangla": [], "bar": [], "bar_cod": [], "baranovskij": [], "base": 5, "baselin": 5, "batch": [1, 5, 6], "batch_siz": 1, "bblanchon": [], "bbox": [], "becaus": [], "been": [5, 7], "befor": 1, "begin": 7, "behavior": [], "being": [5, 7], "belong": [], "benchmark": [], "best": [], "beta": 3, "better": [], "between": [6, 7], "bgr": 2, "bilinear": [5, 6], "bin_thresh": [], "binar": [3, 5], "binari": 2, "bit": [], "block": [5, 7], "block_1_1": [], "blur": [], "bmvc": [], "bn": [], "bodi": [], "bool": [1, 2, 5, 6, 7], "boolean": [], "both": [3, 5, 6], "bottom": [], "bound": [1, 2, 6, 7], "box": [1, 2, 7], "box_thresh": [], "brew": 4, "bright": 6, "browser": [], "build": [], "built": [], "byte": [2, 5], "c": [], "c5": 5, "c_j": [], "cach": [], "cache_sampl": [], "cairo": 4, "call": [], "callabl": [1, 6], "can": [1, 4, 5], "capabl": 5, "case": [1, 7], "cf": 5, "cfg": [], "challeng": [], "challenge2_test_task12_imag": [], "challenge2_test_task1_gt": [], "challenge2_training_task12_imag": [], "challenge2_training_task1_gt": [], "chang": [], "changelog": 3, "channel": [2, 5, 6], "channel_prior": [], "channelshuffl": [], "charact": [1, 2, 3, 5, 7], "charactergener": [], "characterist": [], "charg": 5, "charset": [], "chart": 2, "check": [], "checkpoint": [], "chip": [], "christian": [], "ci": [], "clarifi": [], "clariti": [], "class": [1, 2, 6, 7], "class_nam": [], "classif": [], "classmethod": 2, "clear": [], "clone": 4, "close": [], "co": [], "code": [2, 3], "codecov": [], "colab": [], "collate_fn": [], "collect": 2, "color": 6, "colorinvers": 6, "column": 2, "com": [2, 4], "combin": 5, "command": [], "comment": [], "commit": [], "common": [6, 7], "commun": [], "compar": 3, "comparison": 7, "competit": 1, "compil": [], "complaint": [], "complementari": 7, "complet": [], "compon": 5, "compos": [1, 3, 5], "comprehens": [], "comput": [5, 7], "conf_threshold": [], "confid": 2, "config": [], "configur": [], "confus": 7, "consecut": [5, 6], "consequ": [], "consid": [1, 2, 7], "consist": [], "consolid": [1, 3], "constant": 6, "construct": [], "contact": [], "contain": [], "content": [1, 2], "context": [], "contib": [], "continu": [], "contrast": 6, "contrast_factor": 6, "contrib": [], "contribut": [], "contributor": [], "conv_sequ": 5, "convers": 2, "convert": [2, 5, 6], "convert_page_to_numpi": 2, "convert_to_fp16": 5, "convert_to_tflit": 5, "convolut": 3, "cool": [], "coordin": 2, "cord": [1, 3, 5], "core": 7, "corner": [], "correct": 6, "correspond": [4, 5], "could": [], "counterpart": 7, "cover": [], "coverag": [], "cpu": [3, 5], "creat": [], "crnn": [3, 5], "crnn_mobilenet_v3_larg": [], "crnn_mobilenet_v3_smal": [], "crnn_resnet31": 5, "crnn_vgg16_bn": 5, "crop": 5, "crop_orient": [], "crop_orientation_predictor": [], "crop_param": [], "cuda": [], "currenc": 1, "current": [], "custom": [], "custom_crop_orientation_model": [], "custom_page_orientation_model": [], "customhook": [], "cvit": [], "czczup": [], "czech": [], "d": [], "daili": 3, "danish": [], "data": [2, 3, 5, 6, 7], "dataload": 1, "dataset": 5, "dataset_info": [], "date": [], "db": [], "db_crnn_resnet": 5, "db_crnn_vgg": 5, "db_mobilenet_v3_larg": [], "db_resnet34": [], "db_resnet50": 5, "db_sar_resnet": 5, "db_sar_vgg": 5, "dbnet": [3, 5], "deal": [], "decis": [], "decod": 2, "decode_img_as_tensor": [], "dedic": [], "deem": [], "deep": 5, "def": [], "default": [2, 5], "defer": 1, "defin": 7, "deform": 5, "degre": [], "degress": 2, "delet": [], "delimit": [], "delta": 6, "demo": [], "demonstr": [], "depend": [3, 4], "deploi": [], "deploy": [], "derogatori": [], "describ": 5, "descript": [], "design": 6, "desir": [], "det_arch": 5, "det_b": [], "det_model": [], "det_param": [], "det_predictor": [], "detail": [], "detect": [], "detect_languag": [], "detect_orient": [], "detection_predictor": 5, "detection_task": [], "detectiondataset": [], "detectionmetr": [], "detectionpredictor": 5, "detector": [], "deterior": [], "determin": [], "dev": [], "develop": [], "developp": 4, "deviat": 6, "devic": [], "dict": [2, 7], "dictionari": [2, 7], "differ": [], "differenti": [3, 5], "digit": 1, "dimens": [2, 5, 7], "dimension": 6, "direct": [], "directli": 5, "directori": [], "disabl": [], "disable_crop_orient": [], "disable_page_orient": [], "disclaim": 5, "discuss": [], "disk": [], "disparag": [], "displai": [2, 7], "display_artefact": 7, "distanc": [], "distribut": 6, "div": [], "divers": [], "divid": [], "do": 4, "doc": [2, 5], "docartefact": [], "docstr": [], "doctr": 4, "doctr_cache_dir": [], "doctr_multiprocessing_dis": [], "document": [1, 5, 7], "documentbuild": [], "documentfil": 2, "doesn": [], "don": [], "done": 6, "download": 1, "downsiz": [], "draw": 6, "drop": 1, "drop_last": 1, "dtype": 5, "dual": [], "dummi": [], "dummy_img": [], "dummy_input": [], "dure": [], "dutch": [], "dynam": [], "dynamic_seq_length": [], "e": [2, 4], "each": [1, 2, 3, 5, 6, 7], "eas": [], "easi": [3, 7], "easier": 5, "easili": [2, 5, 7], "econom": [], "edit": [], "educ": [], "effect": [], "effici": [1, 5], "either": 5, "element": [1, 2, 5], "els": [], "email": [], "empathi": [], "en": [], "enabl": 2, "enclos": 2, "encod": [1, 2, 5], "encode_sequ": 1, "encount": [], "encrypt": [], "end": [1, 3, 7], "english": [], "enough": 5, "ensur": [], "entir": 2, "entri": [], "environ": [], "eo": 1, "equiv": [], "error": [], "estim": [], "etc": 2, "ethnic": [], "evalu": [1, 3, 5], "event": [], "everyon": [], "everyth": [], "exact": 7, "exactmatch": [], "exampl": [1, 2, 5, 6, 7], "exchang": [], "exclud": 5, "execut": [], "exist": [], "expand": [], "expect": [2, 5, 6], "experi": 5, "explan": 5, "explicit": [], "exploit": 5, "export": [2, 3, 7], "export_as_straight_box": [], "export_as_xml": [], "export_model_to_onnx": [], "express": 6, "extens": 2, "extern": [], "extra": 4, "extract": [1, 3], "extract_arch": 1, "extractor": 5, "f_": 7, "f_a": 7, "factor": 6, "fair": [], "fairli": [], "fals": [1, 5, 6, 7], "faq": [], "fascan": [], "fast": 1, "fast_bas": [], "fast_smal": [], "fast_tini": [], "faster": [], "fasterrcnn_mobilenet_v3_large_fpn": [], "favorit": [], "featur": [5, 7], "feed": 5, "feedback": [], "feel": [], "felix92": [], "few": 4, "figsiz": 7, "figur": 7, "file": [1, 3], "file_hash": 1, "file_nam": 1, "final": [], "find": 4, "fine": 3, "finnish": [], "first": [], "firsthand": [], "fit": [], "fitz": 2, "flag": [], "flexibl": 7, "flip": [], "float": [2, 6, 7], "float32": 5, "fn": 6, "focu": [], "focus": [], "folder": [1, 5], "follow": [1, 4, 5, 6, 7], "font": [], "font_famili": [], "foral": 7, "forc": [], "forg": [], "form": [1, 3], "format": [2, 5], "forpost": [1, 3], "forum": [], "found": [], "fp": 5, "fp16": 5, "frac": 7, "frame": 5, "framework": 1, "free": [], "french": [1, 5], "friendli": 3, "from": [1, 2, 3, 5, 6, 7], "from_hub": [], "from_imag": 2, "from_pdf": 2, "from_url": 2, "full": [1, 5, 7], "fulli": [], "function": [5, 6, 7], "funsd": [1, 3, 5], "further": [], "futur": [], "g": 2, "g_": 7, "g_x": 7, "gallagh": [], "gamma": 6, "gaussian": 6, "gaussianblur": [], "gaussiannois": [], "gdk": 4, "gen": [], "gender": [], "gener": [], "generic_cyrillic_lett": [], "geometri": 2, "geq": 7, "german": [], "get": 2, "get_artefact": 2, "get_word": 2, "gettextword": 2, "git": 3, "github": 4, "give": [], "given": [1, 2, 5, 7], "global": [], "go": [], "good": [], "googl": [], "googlevis": 3, "gpu": 3, "gracefulli": [], "graph": 2, "grayscal": 6, "ground": 7, "groung": [], "group": [], "gt": [], "gt_box": [], "gt_label": [], "gtk": 4, "guid": [], "guidanc": [], "gvision": 5, "h": 2, "h_": 7, "ha": [1, 7], "half": 5, "handl": 1, "handwrit": [], "handwritten": [], "harass": [], "hardwar": [], "harm": [], "hat": 7, "have": [1, 5, 7], "head": [], "healthi": [], "hebrew": [], "height": 2, "hello": 7, "help": [], "here": [1, 4, 6], "hf": [], "hf_hub_download": [], "high": 2, "higher": 4, "hindi": [], "hindi_digit": [], "hocr": [], "hook": [], "horizont": 2, "hous": [], "how": [], "howev": [], "hsv": 6, "html": [], "http": [2, 4, 5], "hub": [], "hue": 6, "huggingfac": [], "hw": [], "i": [1, 2, 5, 6, 7], "i7": [], "ibrahimov": [], "ic03": [], "ic13": [], "icdar": 3, "icdar2019": 1, "id": 5, "ident": [], "identifi": [3, 5], "ignor": [], "ignore_acc": [], "ignore_cas": [], "iiit": [], "iiit5k": [], "iiithw": [], "imag": [1, 2, 5, 6, 7], "imagenet": [], "imageri": [], "images_90k_norm": [], "img": [1, 6], "img_cont": [], "img_fold": 1, "img_path": [], "img_transform": [], "imgur5k": [], "imgur5k_annot": [], "imlist": [], "impact": [], "implement": [1, 2, 5, 6, 7], "import": [1, 2, 5, 6, 7], "improv": [], "inappropri": [], "incid": [], "includ": [4, 5], "inclus": [], "increas": 6, "independ": [], "index": 2, "indic": 7, "individu": [], "infer": [3, 6], "inform": [1, 3, 5], "inherit": [1, 5], "input": [2, 5, 6], "input_crop": [], "input_pag": [5, 7], "input_shap": 5, "input_t": 5, "input_tensor": 5, "inspir": 6, "instal": 3, "instanc": 5, "instanti": 5, "instead": [1, 2], "insult": [], "int": [1, 2, 5, 6, 7], "int64": [], "integ": 7, "integr": 3, "intel": [], "interact": [2, 7], "interfac": [], "interoper": [], "interpol": [5, 6], "interpret": [1, 2], "intersect": 7, "invert": 6, "investig": [], "invis": [], "invoic": 5, "involv": 5, "io": [], "iou": 7, "iou_thresh": 7, "iou_threshold": [], "irregular": 5, "isn": 1, "issu": [], "italian": [], "iter": 1, "its": [1, 2, 5, 7], "itself": [], "j": 7, "jame": [], "job": [], "join": [], "jpeg": 6, "jpegqual": 6, "jpg": [1, 2], "json": [], "json_output": [], "jump": [], "just": 5, "kei": [], "kera": 5, "kernel": [], "kernel_s": 5, "kernel_shap": [], "keywoard": [], "keyword": [1, 2], "kie": [], "kie_predictor": [], "kiepredictor": [], "kind": [], "know": [], "kwarg": [1, 2, 5, 7], "l": 7, "l_j": 7, "label": [1, 7], "label_fil": 1, "label_fold": [], "label_path": [], "labels_path": [], "ladder": [], "lambda": 6, "lambdatransform": 6, "lang": [], "languag": [2, 3], "larg": [], "largest": 7, "last": [1, 4, 5], "latenc": [], "later": [], "latest": 4, "latin": 1, "layer": [], "layout": [], "lead": [], "leader": [], "learn": 5, "least": 4, "left": 7, "legacy_french": [], "length": 1, "less": [], "let": 5, "letter": [], "level": [5, 7], "levenshtein": [], "leverag": [], "lf": [], "libffi": 4, "librari": 4, "light": 3, "lightweight": [], "like": [], "limits_": 7, "line": [3, 7], "line_1_1": [], "link": [], "linknet": [3, 5], "linknet16": 5, "linknet_resnet18": [], "linknet_resnet34": [], "linknet_resnet50": [], "linux": 4, "list": [1, 2, 6], "ll": 7, "load": [3, 5], "load_state_dict": [], "load_weight": [], "loader": 1, "loc_pr": [], "local": [1, 3, 5, 7], "localis": [], "localizationconfus": 7, "locat": [], "login": [], "login_to_hub": [], "logo": 2, "love": [], "lower": [6, 7], "m": [5, 7], "m1": [], "macbook": [], "machin": [], "maco": 4, "made": 3, "magc_resnet31": [], "mai": [], "mail": [], "main": [], "maintain": 3, "mainten": [], "make": [5, 7], "mani": [], "manipul": [], "map": 1, "map_loc": [], "mask_shap": 7, "master": [3, 5], "match": [3, 7], "mathcal": 7, "matplotlib": 7, "max": 7, "max_angl": [], "max_area": [], "max_char": [], "max_delta": 6, "max_dist": [], "max_gain": 6, "max_gamma": 6, "max_qual": 6, "max_ratio": [], "maximum": 1, "maxval": [5, 6], "mbox": 7, "mean": [6, 7], "meaniou": 7, "meant": 2, "measur": 5, "media": [], "median": [], "meet": [], "member": [], "memori": [], "mention": [], "merg": [], "messag": [], "meta": [], "metadata": [], "metal": [], "method": 6, "metric": [5, 7], "middl": [], "might": 5, "min": [], "min_area": [], "min_char": [], "min_gain": 6, "min_gamma": 6, "min_qual": 6, "min_ratio": [], "min_val": 6, "minde": 4, "minim": [], "minimalist": [], "minimum": 7, "minval": 6, "miss": [], "mistak": [], "mix": 3, "mixed_float16": [], "mixed_precis": [], "mjsynth": [], "mnt": [], "mobilenet": [], "mobilenet_v3_larg": [], "mobilenet_v3_large_r": [], "mobilenet_v3_smal": [], "mobilenet_v3_small_crop_orient": [], "mobilenet_v3_small_page_orient": [], "mobilenet_v3_small_r": [], "mobilenetv3": [], "modal": [], "mode": 4, "model": [1, 7], "model_nam": [], "model_path": [], "moder": [], "modif": [], "modifi": [], "modul": [2, 5, 6, 7], "more": [], "moscardi": [], "most": 5, "mozilla": [], "multi": 3, "multilingu": [], "multipl": [1, 2, 6], "multipli": 6, "multiprocess": [], "my": [], "my_awesome_model": [], "my_hook": [], "n": [1, 5, 7], "na": [], "name": [1, 5], "nation": [], "natur": 3, "ndarrai": [1, 2, 7], "necessari": [], "need": [4, 7], "neg": 6, "nest": [], "nestedobject": [], "netraj": [], "network": [3, 5], "neural": [3, 5], "new": [], "newer": [], "next": 1, "nois": [], "noisi": [1, 3], "non": [2, 3, 6, 7], "none": [1, 2, 7], "normal": [5, 6], "norwegian": [], "note": 0, "now": 3, "np": [5, 7], "num_output_channel": [], "num_sampl": [], "number": [1, 6, 7], "numpi": [2, 5, 7], "o": 4, "obb": [], "obj_detect": [], "object": 1, "objectness_scor": [], "oblig": [], "obtain": [], "occupi": [], "ocr": [1, 3, 7], "ocr_carea": [], "ocr_db_crnn": 7, "ocr_lin": [], "ocr_pag": [], "ocr_par": [], "ocr_predictor": 5, "ocrdataset": 1, "ocrmetr": 7, "ocrpredictor": 5, "ocrx_word": [], "offens": [], "offici": [], "offlin": [], "offset": 6, "onc": 5, "one": [1, 5, 6], "oneof": 6, "ones": 1, "onli": [6, 7], "onlin": [], "onnx": [], "onnxruntim": [], "onnxtr": [], "opac": [], "opacity_rang": [], "open": [], "opinion": [], "optic": [3, 5], "optim": 3, "option": 1, "order": [1, 2, 5], "org": 5, "organ": 2, "orient": 2, "orientationpredictor": [], "other": [], "otherwis": 7, "our": 5, "out": [5, 6, 7], "outpout": [], "output": [2, 5, 6], "output_s": [2, 6], "outsid": [], "over": [4, 7], "overal": [], "overlai": 2, "overview": [], "overwrit": 1, "overwritten": [], "own": 3, "p": 6, "packag": 7, "pad": [1, 5, 6], "page": [4, 5, 7], "page1": 2, "page2": 2, "page_1": [], "page_idx": 2, "page_orientation_predictor": [], "page_param": [], "pair": 7, "pango": 4, "paper": 5, "par_1_1": [], "paragraph": [], "paragraph_break": [], "parallel": [], "param": [5, 6], "paramet": [1, 2, 3, 5, 6, 7], "pars": [1, 3], "parseq": [], "part": 6, "parti": [], "partial": [], "particip": [], "pass": [1, 5], "password": [], "patch": [], "path": [1, 2, 5], "path_to_checkpoint": [], "path_to_custom_model": [], "path_to_pt": [], "patil": [], "pattern": [], "pdf": [2, 5], "pdfpage": [], "peopl": [], "per": [5, 6], "perform": [2, 3, 5, 6, 7], "period": [], "permiss": [], "permut": [], "persian_lett": [], "person": [], "phase": [], "photo": [], "physic": 2, "pick": 6, "pictur": 2, "pip": 4, "pipelin": [], "pixbuf": 4, "pixel": [2, 6], "platinum": 5, "pleas": [], "plot": 7, "plt": 7, "plug": [], "plugin": [], "png": 2, "point": [], "polici": [], "polish": [], "polit": [], "polygon": 1, "pool": [], "portugues": [], "posit": 7, "possibl": 7, "post": 5, "postprocessor": [], "potenti": 5, "power": 3, "ppageno": [], "pre": [], "precis": [5, 7], "pred": [], "pred_box": [], "pred_label": [], "predefin": 1, "predict": [2, 7], "predictor": [], "prefer": 1, "preinstal": [], "preprocessor": 5, "prerequisit": 3, "present": [], "preserv": 6, "preserve_aspect_ratio": 6, "pretrain": [3, 5, 7], "pretrained_backbon": [], "print": [], "prior": [], "privaci": [], "privat": 5, "probabl": 6, "problem": [], "procedur": 6, "process": [2, 3], "processor": 5, "produc": 5, "product": [], "profession": [], "project": [], "promptli": [], "proper": [], "properli": 1, "properti": 5, "provid": [3, 5], "public": 3, "publicli": [], "publish": [], "pull": [], "punctuat": 1, "pure": [], "purpos": [], "push_to_hf_hub": [], "py": [], "pypdfium2": [], "pyplot": 7, "python": 3, "python3": [], "pytorch": [3, 4], "q": [], "qr": 2, "qr_code": [], "qualiti": 6, "quantiz": 5, "quantize_model": 5, "question": [], "quickli": 3, "quicktour": [], "r": [], "race": [], "ramdisk": [], "rand": [5, 7], "random": [5, 6, 7], "randomappli": 6, "randombright": 6, "randomcontrast": 6, "randomcrop": [], "randomgamma": 6, "randomhorizontalflip": [], "randomhu": 6, "randomjpegqu": 6, "randomli": 6, "randomres": [], "randomrot": [], "randomsatur": 6, "randomshadow": [], "rang": 6, "rassi": [], "ratio": 6, "raw": [2, 7], "re": [], "read": [3, 5], "read_html": 2, "read_img": 2, "read_img_as_numpi": [], "read_img_as_tensor": [], "read_pdf": 2, "readi": [], "real": [5, 6], "realli": [], "reason": [], "rebuild": [], "rebuilt": [], "recal": [5, 7], "receipt": [1, 3, 5], "reco_arch": 5, "reco_b": [], "reco_model": [], "reco_param": [], "reco_predictor": [], "recogn": [], "recognit": 7, "recognition_predictor": 5, "recognition_task": [], "recognitiondataset": [], "recognitionpredictor": 5, "rectangular": [], "recurr": 3, "reduc": 6, "refer": 4, "regardless": [], "region": [], "regroup": 7, "regular": [], "reject": [], "rel": 2, "relat": [], "releas": [0, 4], "relev": [], "religion": [], "relu": 5, "remov": [], "render": [], "repo": [], "repo_id": [], "report": [], "repositori": [], "repres": [2, 5], "represent": 5, "request": [], "requir": [4, 6], "research": 3, "residu": [], "resiz": [5, 6], "resnet": 5, "resnet18": [], "resnet31": [], "resnet34": [], "resnet50": [], "resolv": 2, "resolve_block": [], "resolve_lin": [], "resourc": [], "respect": [], "rest": [6, 7], "restrict": [], "result": [2, 5], "return": [1, 2, 5, 7], "reusabl": 5, "review": [], "rgb": [2, 6], "rgb_mode": [], "rgb_output": 2, "right": [5, 7], "roboflow": [], "robust": 3, "root": 1, "rotat": [1, 2], "rotated_bbox": [1, 7], "run": 4, "same": [2, 7], "sampl": 1, "sample_transform": 1, "sanjin": [], "sar": [3, 5], "sar_resnet31": 5, "sar_vgg16_bn": 5, "satur": 6, "save": [1, 5], "saved_model": 5, "scale": 7, "scale_rang": [], "scan": [1, 3], "scene": [3, 5], "scheme": 5, "score": 7, "scratch": 3, "script": [], "seamless": 3, "seamlessli": [], "search": [], "searchabl": [], "sec": [], "second": 5, "section": [], "secur": [], "see": [], "seemlessli": 3, "seen": 5, "segment": 5, "self": [], "semant": 5, "send": [], "sens": 7, "sensit": [], "separ": 5, "sequenc": [1, 2, 5, 7], "sequenti": [5, 6], "seri": [], "serial": 5, "serialized_model": 5, "seriou": [], "set": [1, 5, 7], "set_global_polici": [], "sever": [2, 6], "sex": [], "sexual": [], "sha256": [], "shade": [], "shape": [2, 5, 6, 7], "share": [], "shift": 6, "shm": [], "should": [1, 2, 7], "show": [2, 3, 5, 7], "showcas": [], "shuffl": 1, "side": 7, "signatur": 2, "signific": 1, "simpl": 5, "simpler": [], "sinc": 1, "singl": [], "single_img_doc": [], "size": [1, 2, 5, 6], "skew": [], "slack": [], "slightli": [], "small": 3, "smallest": 2, "snapshot_download": [], "snippet": [], "so": [1, 4], "social": [], "socio": [], "some": [], "someth": [], "somewher": [], "sort": [], "sourc": [1, 2, 5, 6, 7], "space": [], "span": [], "spanish": [], "spatial": 2, "special": 3, "specif": [1, 5, 7], "specifi": 2, "speed": [3, 5], "sphinx": [], "sroie": [1, 3], "stabl": 4, "stackoverflow": [], "stage": 3, "standalon": [], "standard": 6, "start": 1, "state": 3, "static": 7, "statist": 5, "statu": [], "std": 6, "step": [], "still": [], "str": [1, 2, 5, 6, 7], "straight": 1, "straighten": [], "straighten_pag": [], "straigten_pag": [], "stream": 2, "street": [], "strict": [], "strictli": 7, "string": [1, 2, 5, 7], "strive": [], "strong": 5, "structur": [3, 5], "subset": [1, 5], "suggest": [], "sum": 7, "summari": 7, "support": 5, "sustain": [], "svhn": [], "svt": [], "swedish": [], "symbol": [], "symmetr": 6, "symmetric_pad": 6, "synthet": [], "synthtext": [], "system": [], "t": 1, "tabl": [], "take": [], "target": [1, 2, 5, 6], "target_s": 1, "task": [1, 3, 5], "task2": [], "team": [], "techminde": [], "templat": 2, "tensor": [1, 5, 6], "tensorflow": [3, 4, 5, 6], "tensorspec": [], "term": [], "test": [], "test_set": [], "text": [2, 7], "text_output": [], "textmatch": 7, "textnet": [], "textnet_bas": [], "textnet_smal": [], "textnet_tini": [], "textract": [3, 5], "textstylebrush": [], "textual": [1, 2, 3], "tf": [5, 6], "tf_model": 5, "tflite": 5, "than": [4, 7], "thank": [], "thei": [], "them": [1, 4], "thi": [4, 5, 7], "thing": [], "third": [], "those": [2, 4, 5], "threaten": [], "threshold": [], "through": [1, 6], "tilman": [], "time": [1, 5, 7], "tini": [], "titl": 2, "tm": [], "tmp": [], "togeth": [2, 5], "tograi": 6, "tool": [], "top": 7, "topic": [], "torch": [], "torchvis": 6, "total": [], "toward": [], "train": [1, 5, 6], "train_it": 1, "train_load": 1, "train_pytorch": [], "train_set": 1, "train_tensorflow": [], "trainabl": 5, "tranform": 6, "transcrib": [], "transfer": [], "transfo": 6, "transform": [1, 3], "translat": [], "troll": [], "true": [1, 2, 5, 6, 7], "truth": 7, "tune": 3, "tupl": [2, 5, 6, 7], "turn": [], "two": 2, "txt": [], "type": [2, 5], "typic": [], "u": [], "ucsd": [], "udac": [], "uint8": [2, 5, 7], "ukrainian": [], "unaccept": [], "underli": 1, "underneath": 2, "understand": [1, 3], "unidecod": 7, "uniform": [5, 6], "uniformli": [], "uninterrupt": 2, "union": 7, "unit": [], "unittest": [], "unlock": [], "unoffici": [], "unprofession": [], "unsolicit": [], "unsupervis": [], "unwelcom": [], "up": 5, "updat": 7, "upgrad": [], "upper": 6, "uppercas": [], "url": [1, 2], "us": [1, 4, 7], "usabl": 5, "usag": 5, "use_polygon": [], "useabl": [], "user": [2, 3, 4], "utf": [], "util": [3, 5], "v0": 3, "v1": [], "v3": [], "valid": [], "valu": [2, 6], "valuabl": 3, "variabl": [], "varieti": [], "veri": [], "verifi": 1, "verma": [], "version": 5, "vgg": 5, "vgg16": 5, "vgg16_bn_r": [], "via": 3, "video": [], "vietnames": [], "view": [], "viewpoint": [], "violat": [], "visibl": [], "vision": [], "visiondataset": 1, "visiontransform": [], "visual": 3, "visualize_pag": 7, "vit_": [], "vit_b": [], "vitstr": [], "vitstr_bas": [], "vitstr_smal": [], "viz": [], "vocab": [3, 5], "vocabulari": [], "w": [2, 7], "w3": [], "wa": [], "wai": [1, 3, 5], "want": [], "warm": 5, "warmup": [], "wasn": [], "we": [2, 3, 5, 6], "weasyprint": [], "web": 2, "websit": [], "welcom": 3, "well": [], "were": 2, "what": [], "when": [], "whenev": [], "where": [2, 7], "whether": [1, 2, 7], "which": 5, "whichev": 4, "while": 6, "why": [], "width": 2, "wiki": [], "wildreceipt": [], "window": [4, 7], "wish": [], "within": [], "without": 5, "wonder": [], "word": [3, 5, 7], "word_1_1": [], "word_1_2": [], "word_1_3": [], "wordgener": [], "words_onli": 7, "work": [], "worker": 1, "workflow": [], "worklow": [], "world": 7, "worth": [], "wrap": [], "wrapper": [1, 6], "write": [], "written": 2, "www": 2, "x": [2, 6, 7], "x12larg": 5, "x_ascend": [], "x_descend": [], "x_i": 7, "x_size": [], "x_wconf": [], "xeon": 5, "xhtml": [], "xmax": 2, "xmin": 2, "xml": [], "xml_bytes_str": [], "xml_element": [], "xml_output": [], "xmln": [], "y": 7, "y_i": 7, "y_j": 7, "yet": [], "ymax": 2, "ymin": 2, "yolov8": [], "you": [4, 5], "your": [1, 2, 5, 7], "yoursit": 2, "yugesh": [], "zero": [5, 6], "zoo": [], "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 1, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": [], "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": [], "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": [], "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": [], "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": [], "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": [], "\u00e4\u00f6\u00e4\u00f6": [], "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": [], "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": [], "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": [], "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": [], "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": [], "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": [], "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": [], "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": [], "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": [], "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": [], "\u067e\u0686\u06a2\u06a4\u06af": [], "\u0905": [], "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": [], "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": [], "\u0950": [], "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": [], "\u09bd": [], "\u09ce": [], "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": []}, "titles": ["Changelog", "doctr.datasets", "doctr.documents", "DocTR: Document Text Recognition", "Installation", "doctr.models", "doctr.transforms", "doctr.utils"], "titleterms": {"": [], "0": 0, "01": [], "02": [], "03": 0, "04": [], "05": 0, "07": [], "08": [], "09": [], "1": 0, "10": [], "11": 0, "12": [], "18": 0, "2": 0, "2021": 0, "2022": [], "2023": [], "2024": [], "21": [], "22": [], "27": [], "28": 0, "29": [], "3": [], "31": [], "4": [], "5": [], "6": [], "7": [], "8": [], "9": [], "advanc": [], "approach": 5, "architectur": [], "arg": [], "artefact": 2, "artefactdetect": [], "attribut": [], "avail": 1, "aw": [], "ban": [], "block": 2, "bug": [], "build": 3, "changelog": 0, "choos": [], "classif": [], "code": [], "codebas": [], "commit": [], "commun": [], "compos": 6, "compress": 5, "conda": [], "conduct": [], "connect": [], "content": [], "continu": [], "contrib": [], "contribut": [], "contributor": [], "convent": [], "correct": [], "coven": [], "custom": [], "data": 1, "dataload": [], "dataset": [1, 3], "detect": [3, 5], "develop": [], "do": [], "doctr": [1, 2, 3, 5, 6, 7], "document": [2, 3], "end": 5, "enforc": [], "evalu": 7, "export": 5, "factori": [], "featur": 3, "feedback": [], "file": 2, "from": [], "gener": [], "get": 3, "git": 4, "guidelin": [], "half": [], "hub": [], "huggingfac": [], "i": [], "implement": [], "infer": [], "instal": 4, "integr": [], "io": [], "lambda": [], "let": [], "line": 2, "linux": [], "load": 1, "loader": [], "main": 3, "mode": [], "model": [3, 5], "modifi": [], "modul": [], "name": [], "note": 3, "notebook": [], "object": [], "ocr": 5, "onli": [], "onnx": [], "optim": [], "option": [], "orient": [], "our": [], "output": [], "own": [], "packag": [3, 4], "page": 2, "perman": [], "pipelin": [], "pledg": [], "post": [], "pre": 5, "precis": [], "predictor": [3, 5], "prepar": [], "prerequisit": 4, "pretrain": [], "process": 5, "push": [], "python": 4, "qualiti": [], "question": [], "read": 2, "readi": [], "recognit": [3, 5], "refer": 3, "report": [], "request": [], "resourc": [], "respons": [], "return": [], "right": [], "savedmodel": 5, "scope": [], "share": [], "should": [], "stage": 5, "standard": [], "start": 3, "structur": 2, "style": [], "support": [1, 3, 6], "synthet": [], "task": 7, "temporari": [], "test": [], "text": [3, 5], "train": 3, "transform": 6, "two": 5, "unit": [], "us": 5, "util": 7, "v0": 0, "verif": [], "via": 4, "visual": 7, "vocab": 1, "warn": [], "what": [], "word": 2, "your": 3, "zoo": [3, 5]}}) \ No newline at end of file +Search.setIndex({"alltitles": {"1. Correction": [[2, "correction"]], "2. Warning": [[2, "warning"]], "3. Temporary Ban": [[2, "temporary-ban"]], "4. Permanent Ban": [[2, "permanent-ban"]], "AWS Lambda": [[14, null]], "Advanced options": [[19, "advanced-options"]], "Args:": [[7, "args"], [7, "id4"], [7, "id7"], [7, "id10"], [7, "id13"], [7, "id16"], [7, "id19"], [7, "id22"], [7, "id25"], [7, "id29"], [7, "id32"], [7, "id37"], [7, "id40"], [7, "id46"], [7, "id49"], [7, "id50"], [7, "id51"], [7, "id54"], [7, "id57"], [7, "id60"], [7, "id61"], [8, "args"], [8, "id2"], [8, "id3"], [8, "id4"], [8, "id5"], [8, "id6"], [8, "id7"], [8, "id10"], [8, "id12"], [8, "id14"], [8, "id16"], [8, "id20"], [8, "id24"], [8, "id28"], [9, "args"], [9, "id3"], [9, "id8"], [9, "id13"], [9, "id17"], [9, "id21"], [9, "id26"], [9, "id31"], [9, "id36"], [9, "id41"], [9, "id46"], [9, "id50"], [9, "id54"], [9, "id59"], [9, "id63"], [9, "id68"], [9, "id73"], [9, "id77"], [9, "id81"], [9, "id85"], [9, "id90"], [9, "id95"], [9, "id99"], [9, "id104"], [9, "id109"], [9, "id114"], [9, "id119"], [9, "id123"], [9, "id127"], [9, "id132"], [9, "id137"], [9, "id142"], [9, "id146"], [9, "id150"], [9, "id155"], [9, "id159"], [9, "id163"], [9, "id167"], [9, "id169"], [9, "id171"], [9, "id173"], [10, "args"], [10, "id1"], [10, "id2"], [10, "id3"], [10, "id4"], [10, "id5"], [10, "id6"], [10, "id7"], [10, "id8"], [10, "id9"], [10, "id10"], [10, "id11"], [10, "id12"], [10, "id13"], [10, "id14"], [10, "id15"], [10, "id16"], [10, "id17"], [10, "id18"], [10, "id19"], [11, "args"], [11, "id3"], [11, "id4"], [11, "id5"], [11, "id6"], [11, "id7"], [11, "id8"], [11, "id9"]], "Artefact": [[8, "artefact"]], "ArtefactDetection": [[16, "artefactdetection"]], "Attribution": [[2, "attribution"]], "Available Datasets": [[17, "available-datasets"]], "Available architectures": [[19, "available-architectures"], [19, "id1"], [19, "id2"]], "Available contribution modules": [[16, "available-contribution-modules"]], "Block": [[8, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[17, null]], "Choosing the right model": [[19, null]], "Classification": [[15, "classification"]], "Code quality": [[3, "code-quality"]], "Code style verification": [[3, "code-style-verification"]], "Codebase structure": [[3, "codebase-structure"]], "Commits": [[3, "commits"]], "Community resources": [[1, null]], "Composing transformations": [[10, "composing-transformations"]], "Continuous Integration": [[3, "continuous-integration"]], "Contributing to docTR": [[3, null]], "Contributor Covenant Code of Conduct": [[2, null]], "Custom dataset loader": [[7, "custom-dataset-loader"]], "Custom orientation classification models": [[13, "custom-orientation-classification-models"]], "Data Loading": [[17, "data-loading"]], "Dataloader": [[7, "dataloader"]], "Detection": [[15, "detection"], [17, "detection"]], "Detection predictors": [[19, "detection-predictors"]], "Developer mode installation": [[3, "developer-mode-installation"]], "Developing docTR": [[3, "developing-doctr"]], "Document": [[8, "document"]], "Document structure": [[8, "document-structure"]], "End-to-End OCR": [[19, "end-to-end-ocr"]], "Enforcement": [[2, "enforcement"]], "Enforcement Guidelines": [[2, "enforcement-guidelines"]], "Enforcement Responsibilities": [[2, "enforcement-responsibilities"]], "Export to ONNX": [[18, "export-to-onnx"]], "Feature requests & bug report": [[3, "feature-requests-bug-report"]], "Feedback": [[3, "feedback"]], "File reading": [[8, "file-reading"]], "Half-precision": [[18, "half-precision"]], "Installation": [[4, null]], "Integrate contributions into your pipeline": [[16, null]], "Let\u2019s connect": [[3, "let-s-connect"]], "Line": [[8, "line"]], "Loading from Huggingface Hub": [[15, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[13, "loading-your-custom-trained-model"]], "Loading your custom trained orientation classification model": [[13, "loading-your-custom-trained-orientation-classification-model"]], "Main Features": [[5, "main-features"]], "Model optimization": [[18, "model-optimization"]], "Model zoo": [[5, "model-zoo"]], "Modifying the documentation": [[3, "modifying-the-documentation"]], "Naming conventions": [[15, "naming-conventions"]], "OCR": [[17, "ocr"]], "Object Detection": [[17, "object-detection"]], "Our Pledge": [[2, "our-pledge"]], "Our Standards": [[2, "our-standards"]], "Page": [[8, "page"]], "Preparing your model for inference": [[18, null]], "Prerequisites": [[4, "prerequisites"]], "Pretrained community models": [[15, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[15, "pushing-to-the-huggingface-hub"]], "Questions": [[3, "questions"]], "Recognition": [[15, "recognition"], [17, "recognition"]], "Recognition predictors": [[19, "recognition-predictors"]], "Returns:": [[7, "returns"], [8, "returns"], [8, "id11"], [8, "id13"], [8, "id15"], [8, "id19"], [8, "id23"], [8, "id27"], [8, "id31"], [9, "returns"], [9, "id6"], [9, "id11"], [9, "id16"], [9, "id20"], [9, "id24"], [9, "id29"], [9, "id34"], [9, "id39"], [9, "id44"], [9, "id49"], [9, "id53"], [9, "id57"], [9, "id62"], [9, "id66"], [9, "id71"], [9, "id76"], [9, "id80"], [9, "id84"], [9, "id88"], [9, "id93"], [9, "id98"], [9, "id102"], [9, "id107"], [9, "id112"], [9, "id117"], [9, "id122"], [9, "id126"], [9, "id130"], [9, "id135"], [9, "id140"], [9, "id145"], [9, "id149"], [9, "id153"], [9, "id158"], [9, "id162"], [9, "id166"], [9, "id168"], [9, "id170"], [9, "id172"], [11, "returns"]], "Scope": [[2, "scope"]], "Share your model with the community": [[15, null]], "Supported Vocabs": [[7, "supported-vocabs"]], "Supported contribution modules": [[6, "supported-contribution-modules"]], "Supported datasets": [[5, "supported-datasets"]], "Supported transformations": [[10, "supported-transformations"]], "Synthetic dataset generator": [[7, "synthetic-dataset-generator"], [17, "synthetic-dataset-generator"]], "Task evaluation": [[11, "task-evaluation"]], "Text Detection": [[19, "text-detection"]], "Text Recognition": [[19, "text-recognition"]], "Text detection models": [[5, "text-detection-models"]], "Text recognition models": [[5, "text-recognition-models"]], "Train your own model": [[13, null]], "Two-stage approaches": [[19, "two-stage-approaches"]], "Unit tests": [[3, "unit-tests"]], "Use your own datasets": [[17, "use-your-own-datasets"]], "Using your ONNX exported model": [[18, "using-your-onnx-exported-model"]], "Via Conda (Only for Linux)": [[4, "via-conda-only-for-linux"]], "Via Git": [[4, "via-git"]], "Via Python Package": [[4, "via-python-package"]], "Visualization": [[11, "visualization"]], "What should I do with the output?": [[19, "what-should-i-do-with-the-output"]], "Word": [[8, "word"]], "docTR Notebooks": [[12, null]], "docTR Vocabs": [[7, "id62"]], "docTR: Document Text Recognition": [[5, null]], "doctr.contrib": [[6, null]], "doctr.datasets": [[7, null], [7, "datasets"]], "doctr.io": [[8, null]], "doctr.models": [[9, null]], "doctr.models.classification": [[9, "doctr-models-classification"]], "doctr.models.detection": [[9, "doctr-models-detection"]], "doctr.models.factory": [[9, "doctr-models-factory"]], "doctr.models.recognition": [[9, "doctr-models-recognition"]], "doctr.models.zoo": [[9, "doctr-models-zoo"]], "doctr.transforms": [[10, null]], "doctr.utils": [[11, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.10.0 (2024-10-21)": [[0, "v0-10-0-2024-10-21"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]], "v0.7.0 (2023-09-09)": [[0, "v0-7-0-2023-09-09"]], "v0.8.0 (2024-02-28)": [[0, "v0-8-0-2024-02-28"]], "v0.8.1 (2024-03-04)": [[0, "v0-8-1-2024-03-04"]], "v0.9.0 (2024-08-08)": [[0, "v0-9-0-2024-08-08"]]}, "docnames": ["changelog", "community/resources", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/contrib", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_contrib_modules", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "community/resources.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/contrib.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_contrib_modules.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[8, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[8, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[10, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[7, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[10, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[10, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[7, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[7, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[9, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[7, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[7, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[8, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[8, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[7, "doctr.datasets.encode_sequences", false]], "fast_base() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_base", false]], "fast_small() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_small", false]], "fast_tiny() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_tiny", false]], "from_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[7, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[10, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[10, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[7, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[7, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[7, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[7, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[7, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[9, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[10, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[8, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet34() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[7, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_crop_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_crop_orientation", false]], "mobilenet_v3_small_page_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_page_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[10, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[9, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[7, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[10, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[8, "doctr.io.Page", false]], "page_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.page_orientation_predictor", false]], "parseq() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[10, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[10, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[10, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[10, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[10, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[10, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[10, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[10, "doctr.transforms.RandomJpegQuality", false]], "randomresize (class in doctr.transforms)": [[10, "doctr.transforms.RandomResize", false]], "randomrotate (class in doctr.transforms)": [[10, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[10, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[10, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[8, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[8, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[8, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[7, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[10, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[8, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[8, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[7, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[7, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[7, "doctr.datasets.SVT", false]], "synthtext (class in doctr.datasets)": [[7, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.TextMatch", false]], "textnet_base() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_base", false]], "textnet_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_small", false]], "textnet_tiny() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_tiny", false]], "togray (class in doctr.transforms)": [[10, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[11, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_small", false]], "wildreceipt (class in doctr.datasets)": [[7, "doctr.datasets.WILDRECEIPT", false]], "word (class in doctr.io)": [[8, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[7, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[7, 0, 1, "", "CORD"], [7, 0, 1, "", "CharacterGenerator"], [7, 0, 1, "", "DetectionDataset"], [7, 0, 1, "", "DocArtefacts"], [7, 0, 1, "", "FUNSD"], [7, 0, 1, "", "IC03"], [7, 0, 1, "", "IC13"], [7, 0, 1, "", "IIIT5K"], [7, 0, 1, "", "IIITHWS"], [7, 0, 1, "", "IMGUR5K"], [7, 0, 1, "", "MJSynth"], [7, 0, 1, "", "OCRDataset"], [7, 0, 1, "", "RecognitionDataset"], [7, 0, 1, "", "SROIE"], [7, 0, 1, "", "SVHN"], [7, 0, 1, "", "SVT"], [7, 0, 1, "", "SynthText"], [7, 0, 1, "", "WILDRECEIPT"], [7, 0, 1, "", "WordGenerator"], [7, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[7, 0, 1, "", "DataLoader"]], "doctr.io": [[8, 0, 1, "", "Artefact"], [8, 0, 1, "", "Block"], [8, 0, 1, "", "Document"], [8, 0, 1, "", "DocumentFile"], [8, 0, 1, "", "Line"], [8, 0, 1, "", "Page"], [8, 0, 1, "", "Word"], [8, 1, 1, "", "decode_img_as_tensor"], [8, 1, 1, "", "read_html"], [8, 1, 1, "", "read_img_as_numpy"], [8, 1, 1, "", "read_img_as_tensor"], [8, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[8, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[8, 2, 1, "", "from_images"], [8, 2, 1, "", "from_pdf"], [8, 2, 1, "", "from_url"]], "doctr.io.Page": [[8, 2, 1, "", "show"]], "doctr.models": [[9, 1, 1, "", "kie_predictor"], [9, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[9, 1, 1, "", "crop_orientation_predictor"], [9, 1, 1, "", "magc_resnet31"], [9, 1, 1, "", "mobilenet_v3_large"], [9, 1, 1, "", "mobilenet_v3_large_r"], [9, 1, 1, "", "mobilenet_v3_small"], [9, 1, 1, "", "mobilenet_v3_small_crop_orientation"], [9, 1, 1, "", "mobilenet_v3_small_page_orientation"], [9, 1, 1, "", "mobilenet_v3_small_r"], [9, 1, 1, "", "page_orientation_predictor"], [9, 1, 1, "", "resnet18"], [9, 1, 1, "", "resnet31"], [9, 1, 1, "", "resnet34"], [9, 1, 1, "", "resnet50"], [9, 1, 1, "", "textnet_base"], [9, 1, 1, "", "textnet_small"], [9, 1, 1, "", "textnet_tiny"], [9, 1, 1, "", "vgg16_bn_r"], [9, 1, 1, "", "vit_b"], [9, 1, 1, "", "vit_s"]], "doctr.models.detection": [[9, 1, 1, "", "db_mobilenet_v3_large"], [9, 1, 1, "", "db_resnet50"], [9, 1, 1, "", "detection_predictor"], [9, 1, 1, "", "fast_base"], [9, 1, 1, "", "fast_small"], [9, 1, 1, "", "fast_tiny"], [9, 1, 1, "", "linknet_resnet18"], [9, 1, 1, "", "linknet_resnet34"], [9, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[9, 1, 1, "", "from_hub"], [9, 1, 1, "", "login_to_hub"], [9, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[9, 1, 1, "", "crnn_mobilenet_v3_large"], [9, 1, 1, "", "crnn_mobilenet_v3_small"], [9, 1, 1, "", "crnn_vgg16_bn"], [9, 1, 1, "", "master"], [9, 1, 1, "", "parseq"], [9, 1, 1, "", "recognition_predictor"], [9, 1, 1, "", "sar_resnet31"], [9, 1, 1, "", "vitstr_base"], [9, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[10, 0, 1, "", "ChannelShuffle"], [10, 0, 1, "", "ColorInversion"], [10, 0, 1, "", "Compose"], [10, 0, 1, "", "GaussianBlur"], [10, 0, 1, "", "GaussianNoise"], [10, 0, 1, "", "LambdaTransformation"], [10, 0, 1, "", "Normalize"], [10, 0, 1, "", "OneOf"], [10, 0, 1, "", "RandomApply"], [10, 0, 1, "", "RandomBrightness"], [10, 0, 1, "", "RandomContrast"], [10, 0, 1, "", "RandomCrop"], [10, 0, 1, "", "RandomGamma"], [10, 0, 1, "", "RandomHorizontalFlip"], [10, 0, 1, "", "RandomHue"], [10, 0, 1, "", "RandomJpegQuality"], [10, 0, 1, "", "RandomResize"], [10, 0, 1, "", "RandomRotate"], [10, 0, 1, "", "RandomSaturation"], [10, 0, 1, "", "RandomShadow"], [10, 0, 1, "", "Resize"], [10, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[11, 0, 1, "", "DetectionMetric"], [11, 0, 1, "", "LocalizationConfusion"], [11, 0, 1, "", "OCRMetric"], [11, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.visualization": [[11, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [2, 8, 9, 11, 15, 18], "0": [2, 4, 7, 10, 11, 13, 16, 17, 19], "00": 19, "01": 19, "0123456789": 7, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "02562": 9, "03": 19, "035": 19, "0361328125": 19, "04": 19, "05": 19, "06": 19, "06640625": 19, "07": 19, "08": [10, 19], "09": 19, "0966796875": 19, "1": [7, 8, 9, 10, 11, 13, 17, 19], "10": [4, 7, 11, 19], "100": [7, 10, 11, 17, 19], "1000": 19, "101": 7, "1024": [9, 13, 19], "104": 7, "106": 7, "108": 7, "1095": 17, "11": 19, "110": 11, "1107": 17, "114": 7, "115": 7, "1156": 17, "116": 7, "118": 7, "11800h": 19, "11th": 19, "12": 19, "120": 7, "123": 7, "126": 7, "1268": 17, "128": [9, 13, 18, 19], "13": 19, "130": 7, "13068": 17, "131": 7, "1337891": 17, "1357421875": 19, "1396484375": 19, "14": 19, "1420": 19, "14470v1": 7, "149": 17, "15": 19, "150": [11, 19], "1552": 19, "16": [9, 18, 19], "1630859375": 19, "1684": 19, "16x16": 9, "17": 19, "1778": 19, "1782": 19, "18": [9, 19], "185546875": 19, "1900": 19, "1910": 9, "19342": 17, "19370": 17, "195": 7, "19598": 17, "199": 19, "1999": 19, "2": [4, 5, 7, 8, 9, 10, 16, 19], "20": 19, "200": 11, "2000": 17, "2003": [5, 7], "2012": 7, "2013": [5, 7], "2015": 7, "2019": 5, "2023": 1, "207901": 17, "21": 19, "2103": 7, "2186": 17, "21888": 17, "22": 19, "224": [9, 10], "225": 10, "22672": 17, "229": [10, 17], "23": 19, "233": 17, "236": 7, "24": 19, "246": 17, "249": 17, "25": 19, "2504": 19, "255": [8, 9, 10, 11, 19], "256": 9, "257": 17, "26": 19, "26032": 17, "264": 13, "27": 19, "2700": 17, "2710": 19, "2749": 13, "28": 19, "287": 13, "29": 19, "296": 13, "299": 13, "2d": 19, "3": [4, 5, 8, 9, 10, 11, 18, 19], "30": 19, "300": 17, "3000": 17, "301": 13, "30595": 19, "30ghz": 19, "31": 9, "32": [7, 9, 10, 13, 17, 18, 19], "3232421875": 19, "33": [10, 19], "33402": 17, "33608": 17, "34": [9, 19], "340": 19, "3456": 19, "3515625": 19, "36": 19, "360": 17, "37": [7, 19], "38": 19, "39": 19, "4": [9, 10, 11, 19], "40": 19, "406": 10, "41": 19, "42": 19, "43": 19, "44": 19, "45": 19, "456": 10, "46": 19, "47": 19, "472": 17, "48": [7, 19], "485": 10, "49": 19, "49377": 17, "5": [7, 10, 11, 16, 19], "50": [9, 17, 19], "51": 19, "51171875": 19, "512": 9, "52": [7, 19], "529": 19, "53": 19, "54": 19, "540": 19, "5478515625": 19, "55": 19, "56": 19, "57": 19, "58": [7, 19], "580": 19, "5810546875": 19, "583": 19, "59": 19, "597": 19, "5k": [5, 7], "5m": 19, "6": [10, 19], "60": 10, "600": [9, 11, 19], "61": 19, "62": 19, "626": 17, "63": 19, "64": [9, 10, 19], "641": 19, "647": 17, "65": 19, "66": 19, "67": 19, "68": 19, "69": 19, "693": 13, "694": 13, "695": 13, "6m": 19, "7": 19, "70": [7, 11, 19], "707470": 17, "71": [7, 19], "7100000": 17, "7141797": 17, "7149": 17, "72": 19, "72dpi": 8, "73": 19, "73257": 17, "74": 19, "75": [10, 19], "7581382": 17, "76": 19, "77": 19, "772": 13, "772875": 17, "78": 19, "785": 13, "79": 19, "793533": 17, "796": 17, "798": 13, "7m": 19, "8": [9, 10, 19], "80": 19, "800": [9, 11, 17, 19], "81": 19, "82": 19, "83": 19, "84": 19, "849": 17, "85": 19, "8564453125": 19, "857": 19, "85875": 17, "86": 19, "8603515625": 19, "87": 19, "8707": 17, "88": 19, "89": 19, "9": [10, 19], "90": 19, "90k": 7, "90kdict32px": 7, "91": 19, "914085328578949": 19, "92": 19, "93": 19, "94": [7, 19], "95": [11, 19], "9578408598899841": 19, "96": 19, "97": 19, "98": 19, "99": 19, "9949972033500671": 19, "A": [2, 3, 5, 7, 8, 9, 12, 18], "As": 3, "Be": 19, "Being": 2, "By": 14, "For": [2, 3, 4, 13, 19], "If": [3, 8, 9, 13, 19], "In": [3, 7, 17], "It": [10, 15, 16, 18], "Its": [5, 9], "No": [2, 19], "Of": 7, "Or": [16, 18], "The": [2, 3, 7, 8, 11, 14, 16, 17, 18, 19], "Then": 9, "To": [3, 4, 14, 15, 16, 18, 19], "_": [2, 7, 9], "__call__": 19, "_build": 3, "_i": 11, "ab": 7, "abc": 18, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "abdef": [7, 17], "abl": [17, 19], "about": [2, 17, 19], "abov": 19, "abstract": 1, "abstractdataset": 7, "abus": 2, "accept": 2, "access": [5, 8, 17, 19], "account": [2, 15], "accur": 19, "accuraci": 11, "achiev": 18, "act": 2, "action": 2, "activ": 5, "ad": [3, 9, 10], "adapt": 2, "add": [10, 11, 15, 19], "add_hook": 19, "add_label": 11, "addit": [3, 4, 8, 16, 19], "addition": [3, 19], "address": [2, 8], "adjust": 10, "advanc": 2, "advantag": 18, "advis": 3, "aesthet": [5, 7], "affect": 2, "after": [15, 19], "ag": 2, "again": 9, "aggreg": [11, 17], "aggress": 2, "align": [2, 8, 10], "all": [2, 3, 6, 7, 8, 10, 11, 16, 17, 19], "allow": [2, 18], "along": 19, "alreadi": [3, 18], "also": [2, 9, 15, 16, 17, 19], "alwai": 17, "an": [2, 3, 5, 7, 8, 9, 11, 16, 18, 19], "analysi": [8, 16], "ancient_greek": 7, "andrej": 1, "angl": [8, 10], "ani": [2, 7, 8, 9, 10, 11, 18, 19], "annot": 7, "anot": 17, "anoth": [9, 13, 17], "answer": 2, "anyascii": 11, "anyon": 5, "anyth": 16, "api": [3, 5], "apolog": 2, "apologi": 2, "app": 3, "appear": 2, "appli": [2, 7, 10], "applic": [5, 9], "appoint": 2, "appreci": 15, "appropri": [2, 3, 19], "ar": [2, 3, 4, 6, 7, 8, 10, 11, 12, 16, 17, 19], "arab": 7, "arabic_diacrit": 7, "arabic_lett": 7, "arabic_punctu": 7, "arbitrarili": [5, 9], "arch": [9, 15], "architectur": [5, 9, 15, 16], "area": 19, "argument": [7, 8, 9, 11, 13, 19], "around": 2, "arrai": [8, 10, 11], "art": [5, 16], "artefact": [11, 16, 19], "artefact_typ": 8, "articl": 1, "artifici": [5, 7], "arxiv": [7, 9], "asarrai": 11, "ascii_lett": 7, "aspect": [5, 9, 10, 19], "assess": 11, "assign": 11, "associ": 8, "assum": 9, "assume_straight_pag": [9, 13, 19], "astyp": [9, 11, 19], "attack": 2, "attend": [5, 9], "attent": [2, 9], "autom": 5, "automat": 19, "autoregress": [5, 9], "avail": [2, 5, 6, 10], "averag": [10, 19], "avoid": [2, 4], "aw": [5, 19], "awar": 19, "azur": 19, "b": [9, 11, 19], "b_j": 11, "back": 3, "backbon": 9, "backend": 19, "background": 17, "bangla": 7, "bar": 16, "bar_cod": 17, "baranovskij": 1, "base": [5, 9, 16], "baselin": [5, 9, 19], "batch": [7, 9, 10, 16, 17, 19], "batch_siz": [7, 9, 13, 16, 17, 18], "bblanchon": 4, "bbox": 19, "becaus": 14, "been": [3, 11, 17, 19], "befor": [7, 9, 10, 19], "begin": 11, "behavior": [2, 19], "being": [11, 19], "belong": 19, "benchmark": 19, "best": [1, 2], "better": [12, 19], "between": [10, 11, 19], "bgr": 8, "bilinear": 10, "bin_thresh": 19, "binar": [5, 9, 19], "binari": [8, 18, 19], "bit": 18, "block": [11, 19], "block_1_1": 19, "blur": 10, "bmvc": 7, "bn": 15, "bodi": [2, 19], "bool": [7, 8, 9, 10, 11], "boolean": [9, 19], "both": [5, 7, 10, 17, 19], "bottom": [9, 19], "bound": [7, 8, 9, 10, 11, 16, 17, 19], "box": [7, 8, 9, 10, 11, 16, 17, 19], "box_thresh": 19, "bright": 10, "browser": [3, 5], "build": [3, 4, 18], "built": 3, "byte": [8, 19], "c": [4, 8, 11], "c_j": 11, "cach": [3, 7, 14], "cache_sampl": 7, "call": 18, "callabl": [7, 10], "can": [3, 4, 13, 14, 15, 16, 17, 19], "capabl": [3, 12, 19], "case": [7, 11], "cf": 19, "cfg": 19, "challeng": 7, "challenge2_test_task12_imag": 7, "challenge2_test_task1_gt": 7, "challenge2_training_task12_imag": 7, "challenge2_training_task1_gt": 7, "chang": [14, 19], "channel": [2, 3, 8, 10], "channel_prior": 4, "channelshuffl": 10, "charact": [5, 7, 8, 11, 17, 19], "charactergener": [7, 17], "characterist": 2, "charg": 19, "charset": 19, "chart": 8, "check": [3, 15, 19], "checkpoint": 9, "chip": 4, "christian": 1, "ci": 3, "clarifi": 2, "clariti": 2, "class": [2, 7, 8, 10, 11, 19], "class_nam": 13, "classif": [17, 19], "classmethod": 8, "clear": 3, "clone": 4, "close": 3, "co": 15, "code": [5, 8, 16], "codecov": 3, "colab": 12, "collate_fn": 7, "collect": [8, 16], "color": 10, "colorinvers": 10, "column": 8, "com": [2, 4, 8, 9, 15], "combin": 19, "command": [3, 16], "comment": 2, "commit": 2, "common": [2, 10, 11, 18], "commun": 2, "compar": 5, "comparison": [11, 19], "competit": 7, "compil": [12, 19], "complaint": 2, "complementari": 11, "complet": 3, "compon": 19, "compos": [7, 19], "comprehens": 19, "comput": [7, 11, 18, 19], "conf_threshold": 16, "confid": [8, 19], "config": [4, 9], "configur": 9, "confus": 11, "consecut": [10, 19], "consequ": 2, "consid": [2, 3, 7, 8, 11, 19], "consist": 19, "consolid": [5, 7], "constant": 10, "construct": 2, "contact": 2, "contain": [1, 6, 7, 12, 17, 19], "content": [7, 8, 19], "context": 9, "contib": 4, "continu": 2, "contrast": 10, "contrast_factor": 10, "contrib": [4, 16], "contribut": 2, "contributor": 3, "convers": 8, "convert": [8, 10], "convolut": 9, "cool": 1, "coordin": [8, 19], "cord": [5, 7, 17, 19], "core": [11, 19], "corner": 19, "correct": 10, "correspond": [4, 8, 10, 19], "could": [2, 16], "counterpart": 11, "cover": 3, "coverag": 3, "cpu": [5, 13, 18], "creat": [1, 15], "crnn": [5, 9, 15], "crnn_mobilenet_v3_larg": [9, 15, 19], "crnn_mobilenet_v3_smal": [9, 18, 19], "crnn_vgg16_bn": [9, 13, 15, 19], "crop": [8, 9, 10, 13, 17, 19], "crop_orient": [8, 19], "crop_orientation_predictor": [9, 13], "crop_param": 13, "cuda": 18, "currenc": 7, "current": [3, 13, 19], "custom": [15, 16, 18, 19], "custom_crop_orientation_model": 13, "custom_page_orientation_model": 13, "customhook": 19, "cvit": 5, "czczup": 9, "czech": 7, "d": [7, 17], "danish": 7, "data": [5, 7, 8, 10, 11, 13, 15], "dataload": 17, "dataset": [9, 13, 19], "dataset_info": 7, "date": [13, 19], "db": 15, "db_mobilenet_v3_larg": [9, 15, 19], "db_resnet34": 19, "db_resnet50": [9, 13, 15, 19], "dbnet": [5, 9], "deal": [12, 19], "decis": 2, "decod": 8, "decode_img_as_tensor": 8, "dedic": 18, "deem": 2, "deep": [9, 19], "def": 19, "default": [4, 8, 13, 14, 19], "defer": 17, "defin": [11, 18], "degre": [8, 10, 19], "degress": 8, "delet": 3, "delimit": 19, "delta": 10, "demo": [3, 5], "demonstr": 2, "depend": [3, 4, 5, 19], "deploi": 3, "deploy": 5, "derogatori": 2, "describ": 9, "descript": 12, "design": 10, "desir": 8, "det_arch": [9, 13, 15, 18], "det_b": 19, "det_model": [13, 15, 18], "det_param": 13, "det_predictor": [13, 19], "detail": [13, 19], "detect": [1, 7, 8, 11, 12, 13, 16], "detect_languag": 9, "detect_orient": [9, 13, 19], "detection_predictor": [9, 19], "detection_task": [7, 17], "detectiondataset": [7, 17], "detectionmetr": 11, "detectionpredictor": [9, 13], "detector": [5, 9, 16], "deterior": 9, "determin": 2, "dev": [3, 14], "develop": 4, "deviat": 10, "devic": 18, "dict": [8, 11, 19], "dictionari": [8, 11], "differ": 2, "differenti": [5, 9], "digit": [5, 7, 17], "dimens": [8, 11, 19], "dimension": 10, "direct": 7, "directli": [15, 19], "directori": [3, 14], "disabl": [2, 14, 19], "disable_crop_orient": 19, "disable_page_orient": 19, "disclaim": 19, "discuss": 3, "disparag": 2, "displai": [8, 11], "display_artefact": 11, "distribut": 10, "div": 19, "divers": 2, "divid": 8, "do": [3, 4, 9], "doc": [3, 8, 16, 18, 19], "docartefact": [7, 17], "docstr": 3, "doctr": [1, 4, 13, 14, 15, 16, 17, 18, 19], "doctr_cache_dir": 14, "doctr_multiprocessing_dis": 14, "document": [1, 7, 9, 11, 12, 13, 16, 17, 18, 19], "documentbuild": 19, "documentfil": [8, 13, 15, 16, 18], "doesn": 18, "don": [13, 19], "done": 10, "download": [7, 17], "downsiz": 9, "draw": 10, "drop": 7, "drop_last": 7, "dtype": [8, 9, 10, 11, 18], "dual": [5, 7], "dummi": 15, "dummy_img": 19, "dummy_input": 18, "dure": 2, "dutch": 7, "dynam": [7, 16], "dynamic_seq_length": 7, "e": [2, 3, 4, 8, 9], "each": [5, 7, 8, 9, 10, 11, 17, 19], "eas": 3, "easi": [5, 11, 15, 18], "easili": [8, 11, 13, 15, 17, 19], "econom": 2, "edit": 2, "educ": 2, "effect": 19, "effici": [3, 5, 7, 9], "either": [11, 19], "element": [7, 8, 9, 19], "els": [3, 16], "email": 2, "empathi": 2, "en": 19, "enabl": [7, 8], "enclos": 8, "encod": [5, 7, 8, 9, 19], "encode_sequ": 7, "encount": 3, "encrypt": 8, "end": [5, 7, 9, 11], "english": [7, 17], "enough": [3, 19], "ensur": 3, "entri": 7, "environ": [2, 14], "eo": 7, "equiv": 19, "estim": 9, "etc": [8, 16], "ethnic": 2, "evalu": [17, 19], "event": 2, "everyon": 2, "everyth": [3, 19], "exact": [11, 19], "exampl": [2, 3, 5, 7, 9, 15, 19], "exchang": 18, "execut": 19, "exist": 15, "expand": 10, "expect": [8, 10, 11], "experi": 2, "explan": [2, 19], "explicit": 2, "exploit": [5, 9], "export": [8, 9, 11, 12, 16, 19], "export_as_straight_box": [9, 19], "export_as_xml": 19, "export_model_to_onnx": 18, "express": [2, 10], "extens": 8, "extern": [2, 17], "extract": [1, 5, 7], "extractor": 9, "f_": 11, "f_a": 11, "factor": 10, "fair": 2, "fairli": 2, "fals": [7, 8, 9, 10, 11, 13, 19], "faq": 2, "fascan": 15, "fast": [5, 7, 9], "fast_bas": [9, 19], "fast_smal": [9, 19], "fast_tini": [9, 19], "faster": [5, 9, 18], "fasterrcnn_mobilenet_v3_large_fpn": 9, "favorit": 19, "featur": [4, 9, 11, 12, 13, 16], "feedback": 2, "feel": [3, 15], "felix92": 15, "few": [18, 19], "figsiz": 11, "figur": [11, 16], "file": [3, 7], "final": 9, "find": [3, 17], "fine": 1, "finnish": 7, "first": [3, 7], "firsthand": 7, "fit": [9, 19], "flag": 19, "flip": 10, "float": [8, 10, 11, 18], "float32": [8, 9, 10, 18], "fn": 10, "focu": 15, "focus": [2, 7], "folder": 7, "follow": [2, 3, 4, 7, 10, 11, 13, 14, 15, 16, 19], "font": 7, "font_famili": 7, "foral": 11, "forc": 3, "forg": 4, "form": [5, 7, 19], "format": [8, 11, 13, 17, 18, 19], "forpost": [5, 7], "forum": 3, "found": 1, "fp16": 18, "frac": 11, "framework": [4, 15, 17, 19], "free": [2, 3, 15], "french": [7, 13, 15, 19], "friendli": 5, "from": [1, 2, 5, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19], "from_hub": [9, 15], "from_imag": [8, 15, 16, 18], "from_pdf": 8, "from_url": 8, "full": [7, 11, 19], "function": [7, 10, 11, 16], "funsd": [5, 7, 17, 19], "further": 17, "futur": 7, "g": [8, 9], "g_": 11, "g_x": 11, "gallagh": 1, "gamma": 10, "gaussian": 10, "gaussianblur": 10, "gaussiannois": 10, "gen": 19, "gender": 2, "gener": [3, 5, 8, 9], "generic_cyrillic_lett": 7, "geometri": [5, 8, 19], "geq": 11, "german": [7, 13, 15], "get": [18, 19], "git": 15, "github": [3, 4, 9, 15], "give": [2, 16], "given": [7, 8, 10, 11, 19], "global": 9, "go": 19, "good": 18, "googl": 3, "googlevis": 5, "gpu": [5, 16, 18], "gracefulli": 2, "graph": [5, 7, 8], "grayscal": 10, "ground": 11, "groung": 11, "group": [5, 19], "gt": 11, "gt_box": 11, "gt_label": 11, "guid": 3, "guidanc": 17, "gvision": 19, "h": [8, 9, 10], "h_": 11, "ha": [3, 7, 11, 17], "handl": [12, 17, 19], "handwrit": 7, "handwritten": 17, "harass": 2, "hardwar": 19, "harm": 2, "hat": 11, "have": [2, 3, 11, 13, 15, 17, 18, 19], "head": [9, 19], "healthi": 2, "hebrew": 7, "height": [8, 10], "hello": [11, 19], "help": 18, "here": [6, 10, 12, 16, 17, 19], "hf": 9, "hf_hub_download": 9, "high": 8, "higher": [4, 7, 19], "hindi": 7, "hindi_digit": 7, "hocr": 19, "hook": 19, "horizont": [8, 10, 19], "hous": 7, "how": [1, 3, 12, 13, 15, 17], "howev": 17, "hsv": 10, "html": [2, 3, 4, 8, 19], "http": [2, 4, 7, 8, 9, 15, 19], "hub": 9, "hue": 10, "huggingfac": 9, "hw": 7, "i": [2, 3, 7, 8, 9, 10, 11, 14, 15, 16, 17, 18], "i7": 19, "ibrahimov": 1, "ic03": [5, 7, 17], "ic13": [5, 7, 17], "icdar": [5, 7], "icdar2019": 7, "id": 19, "ident": 2, "identifi": 5, "iiit": [5, 7], "iiit5k": [7, 17], "iiithw": [5, 7, 17], "imag": [1, 5, 7, 8, 9, 10, 11, 15, 16, 17, 19], "imagenet": 9, "imageri": 2, "images_90k_norm": 7, "img": [7, 10, 17, 18], "img_cont": 8, "img_fold": [7, 17], "img_path": 8, "img_transform": 7, "imgur5k": [5, 7, 17], "imgur5k_annot": 7, "imlist": 7, "impact": 2, "implement": [7, 8, 9, 10, 11, 19], "import": [7, 8, 9, 10, 11, 13, 15, 16, 17, 18, 19], "improv": 9, "inappropri": 2, "incid": 2, "includ": [2, 7, 17, 18], "inclus": 2, "increas": 10, "independ": 10, "index": [3, 8], "indic": 11, "individu": 2, "infer": [5, 9, 10, 16, 19], "inform": [1, 2, 3, 5, 7, 17], "input": [3, 8, 9, 10, 18, 19], "input_crop": 9, "input_pag": [9, 11, 19], "input_shap": 18, "input_tensor": 9, "inspir": [2, 10], "instal": [15, 16, 18], "instanc": [2, 19], "instanti": [9, 19], "instead": [7, 8, 9], "insult": 2, "int": [7, 8, 9, 10], "int64": 11, "integ": 11, "integr": [1, 5, 15, 17], "intel": 19, "interact": [2, 8, 11], "interfac": [15, 18], "interoper": 18, "interpol": 10, "interpret": [7, 8], "intersect": 11, "invert": 10, "investig": 2, "invis": 2, "involv": [2, 19], "io": [13, 15, 16, 18], "iou": 11, "iou_thresh": 11, "iou_threshold": 16, "irregular": [5, 9, 17], "isn": 7, "issu": [2, 3, 15], "italian": 7, "iter": [7, 10, 17, 19], "its": [8, 9, 10, 11, 17, 19], "itself": [9, 15], "j": 11, "jame": 1, "job": 3, "join": 3, "jpeg": 10, "jpegqual": 10, "jpg": [7, 8, 15, 18], "json": [7, 17, 19], "json_output": 19, "jump": 3, "just": 2, "kei": [5, 7], "kera": [9, 18], "kernel": [5, 9, 10], "kernel_shap": 10, "keywoard": 9, "keyword": [7, 8, 9, 11], "kie": [9, 13], "kie_predictor": [9, 13], "kiepredictor": 9, "kind": 2, "know": [3, 18], "kwarg": [7, 8, 9, 11], "l": 11, "l_j": 11, "label": [7, 11, 16, 17], "label_fil": [7, 17], "label_fold": 7, "label_path": [7, 17], "labels_path": [7, 17], "ladder": 2, "lambda": 10, "lambdatransform": 10, "lang": 19, "languag": [2, 5, 7, 8, 9, 15, 19], "larg": [9, 15], "largest": 11, "last": [4, 7], "latenc": 9, "later": 3, "latest": 19, "latin": 7, "layer": 18, "layout": 19, "lead": 2, "leader": 2, "learn": [2, 5, 9, 18, 19], "least": 4, "left": [11, 19], "legacy_french": 7, "length": [7, 19], "less": [18, 19], "level": [2, 7, 11, 19], "leverag": 12, "lf": 15, "librari": [3, 4, 12, 13], "light": 5, "lightweight": 18, "like": 2, "limits_": 11, "line": [5, 9, 11, 19], "line_1_1": 19, "link": 13, "linknet": [5, 9], "linknet_resnet18": [9, 13, 18, 19], "linknet_resnet34": [9, 18, 19], "linknet_resnet50": [9, 19], "list": [7, 8, 10, 11, 15], "ll": 11, "load": [5, 7, 9, 16, 18], "load_state_dict": 13, "load_weight": 13, "loc_pr": 19, "local": [3, 5, 7, 9, 11, 17, 19], "localis": 7, "localizationconfus": 11, "locat": [3, 8, 19], "login": 9, "login_to_hub": [9, 15], "logo": [8, 16, 17], "love": 15, "lower": [10, 11, 19], "m": [3, 11, 19], "m1": 4, "macbook": 4, "machin": 18, "made": 5, "magc_resnet31": 9, "mai": [2, 3], "mail": 2, "main": 12, "maintain": 5, "mainten": 3, "make": [2, 3, 11, 13, 14, 15, 18, 19], "mani": [17, 19], "manipul": 19, "map": [7, 9], "map_loc": 13, "master": [5, 9, 19], "match": [11, 19], "mathcal": 11, "matplotlib": [8, 11], "max": [7, 10, 11], "max_angl": 10, "max_area": 10, "max_char": [7, 17], "max_delta": 10, "max_gain": 10, "max_gamma": 10, "max_qual": 10, "max_ratio": 10, "maximum": [7, 10], "maxval": [9, 10], "mbox": 11, "mean": [10, 11, 13], "meaniou": 11, "meant": [8, 18], "measur": 19, "media": 2, "median": 9, "meet": 13, "member": 2, "memori": [14, 18], "mention": 19, "merg": 7, "messag": 3, "meta": 19, "metadata": 18, "metal": 4, "method": [8, 10, 19], "metric": [11, 19], "middl": 19, "might": [18, 19], "min": 10, "min_area": 10, "min_char": [7, 17], "min_gain": 10, "min_gamma": 10, "min_qual": 10, "min_ratio": 10, "min_val": 10, "minde": [1, 2, 4, 5, 9], "minim": [3, 5], "minimalist": [5, 9], "minimum": [4, 7, 10, 11, 19], "minval": 10, "miss": 4, "mistak": 2, "mixed_float16": 18, "mixed_precis": 18, "mjsynth": [5, 7, 17], "mnt": 7, "mobilenet": [9, 15], "mobilenet_v3_larg": 9, "mobilenet_v3_large_r": 9, "mobilenet_v3_smal": [9, 13], "mobilenet_v3_small_crop_orient": [9, 13], "mobilenet_v3_small_page_orient": [9, 13], "mobilenet_v3_small_r": 9, "mobilenetv3": 9, "modal": [5, 7], "mode": 4, "model": [7, 11, 14, 16, 17], "model_nam": [9, 15, 18], "model_path": [16, 18], "moder": 2, "modif": 3, "modifi": [9, 14, 19], "modul": [4, 8, 9, 10, 11, 19], "more": [3, 17, 19], "moscardi": 1, "most": 19, "mozilla": 2, "multi": [5, 9], "multilingu": [7, 15], "multipl": [7, 8, 10, 19], "multipli": 10, "multiprocess": 14, "my": 9, "my_awesome_model": 15, "my_hook": 19, "n": [7, 11], "name": [7, 9, 18, 19], "nation": 2, "natur": [2, 5, 7], "ndarrai": [7, 8, 10, 11], "necessari": [4, 13, 14], "need": [3, 4, 7, 11, 13, 14, 15, 16, 19], "neg": 10, "nest": 19, "netraj": 1, "network": [5, 7, 9, 18], "neural": [5, 7, 9, 18], "new": [3, 11], "next": [7, 17], "nois": 10, "noisi": [5, 7], "non": [5, 7, 8, 9, 10, 11], "none": [7, 8, 9, 10, 11, 19], "normal": [9, 10], "norwegian": 7, "note": [0, 3, 7, 9, 13, 15, 16, 18], "now": 3, "np": [9, 10, 11, 19], "num_output_channel": 10, "num_sampl": [7, 17], "number": [7, 9, 10, 11, 19], "numpi": [8, 9, 11, 19], "o": 4, "obb": 16, "obj_detect": 15, "object": [7, 8, 11, 16, 19], "objectness_scor": [8, 19], "oblig": 2, "obtain": 19, "occupi": 18, "ocr": [1, 5, 7, 9, 11, 15], "ocr_carea": 19, "ocr_db_crnn": 11, "ocr_lin": 19, "ocr_pag": 19, "ocr_par": 19, "ocr_predictor": [9, 13, 15, 18, 19], "ocrdataset": [7, 17], "ocrmetr": 11, "ocrpredictor": [9, 13], "ocrx_word": 19, "offens": 2, "offici": [2, 9], "offlin": 2, "offset": 10, "onc": 19, "one": [3, 7, 9, 10, 13, 15, 19], "oneof": 10, "ones": [7, 11], "onli": [3, 9, 10, 11, 13, 15, 17, 18, 19], "onlin": 2, "onnx": 16, "onnxruntim": [16, 18], "onnxtr": 18, "opac": 10, "opacity_rang": 10, "open": [1, 2, 3, 15, 18], "opinion": 2, "optic": [5, 19], "optim": [5, 19], "option": [7, 9, 13], "order": [3, 7, 8, 10], "org": [2, 7, 9, 19], "organ": 8, "orient": [2, 8, 9, 12, 16, 19], "orientationpredictor": 9, "other": [2, 3], "otherwis": [2, 8, 11], "our": [1, 3, 9, 19], "out": [3, 9, 10, 11, 19], "outpout": 19, "output": [8, 10, 18], "output_s": [8, 10], "outsid": 14, "over": [7, 11, 19], "overal": [2, 9], "overlai": 8, "overview": 16, "overwrit": 13, "overwritten": 15, "own": 5, "p": [10, 19], "packag": [3, 5, 11, 14, 16, 17, 18], "pad": [7, 9, 10, 19], "page": [4, 7, 9, 11, 13, 19], "page1": 8, "page2": 8, "page_1": 19, "page_idx": [8, 19], "page_orientation_predictor": [9, 13], "page_param": 13, "pair": 11, "paper": 9, "par_1_1": 19, "paragraph": 19, "paragraph_break": 19, "parallel": 9, "param": [10, 19], "paramet": [5, 8, 9, 18], "pars": [5, 7], "parseq": [5, 9, 15, 18, 19], "part": [7, 10, 19], "parti": 4, "partial": 19, "particip": 2, "pass": [7, 8, 9, 13, 19], "password": 8, "patch": [9, 11], "path": [7, 8, 16, 17, 18], "path_to_checkpoint": 13, "path_to_custom_model": 18, "path_to_pt": 13, "patil": 1, "pattern": 2, "pdf": [8, 9, 12], "pdfpage": 8, "peopl": 2, "per": [10, 19], "perform": [5, 8, 9, 10, 11, 14, 18, 19], "period": 2, "permiss": 2, "permut": [5, 9], "persian_lett": 7, "person": [2, 17], "phase": 19, "photo": 17, "physic": [2, 8], "pick": 10, "pictur": 8, "pip": [3, 4, 16, 18], "pipelin": 19, "pixel": [8, 10, 19], "pleas": 3, "plot": 11, "plt": 11, "plug": 15, "plugin": 4, "png": 8, "point": 18, "polici": 14, "polish": 7, "polit": 2, "polygon": [7, 11, 19], "pool": 9, "portugues": 7, "posit": [2, 11], "possibl": [3, 11, 15, 19], "post": [2, 19], "postprocessor": 19, "potenti": 9, "power": 5, "ppageno": 19, "pre": [3, 9, 18], "precis": [11, 19], "pred": 11, "pred_box": 11, "pred_label": 11, "predefin": 17, "predict": [8, 9, 11, 19], "predictor": [5, 8, 9, 12, 13, 15, 18], "prefer": 17, "preinstal": 4, "preprocessor": [13, 19], "prerequisit": 15, "present": 12, "preserv": [9, 10, 19], "preserve_aspect_ratio": [8, 9, 10, 13, 19], "pretrain": [5, 9, 11, 13, 18, 19], "pretrained_backbon": [9, 13], "print": 19, "prior": 7, "privaci": 2, "privat": 2, "probabl": [1, 10], "problem": 3, "procedur": 10, "process": [3, 5, 8, 9, 13, 19], "processor": 19, "produc": [12, 19], "product": 18, "profession": 2, "project": [3, 17], "promptli": 2, "proper": 3, "properli": 7, "provid": [2, 3, 5, 15, 16, 17, 19], "public": [2, 5], "publicli": 19, "publish": 2, "pull": 15, "punctuat": 7, "pure": 7, "purpos": 3, "push_to_hf_hub": [9, 15], "py": 15, "pypdfium2": [4, 8], "pyplot": [8, 11], "python": [1, 3, 16], "python3": 15, "pytorch": [4, 5, 9, 10, 13, 15, 18, 19], "q": 3, "qr": [8, 16], "qr_code": 17, "qualiti": 10, "question": 2, "quickli": 5, "quicktour": 12, "r": 19, "race": 2, "ramdisk": 7, "rand": [9, 10, 11, 18, 19], "random": [9, 10, 11, 19], "randomappli": 10, "randombright": 10, "randomcontrast": 10, "randomcrop": 10, "randomgamma": 10, "randomhorizontalflip": 10, "randomhu": 10, "randomjpegqu": 10, "randomli": 10, "randomres": 10, "randomrot": 10, "randomsatur": 10, "randomshadow": 10, "rang": 10, "rassi": 15, "ratio": [9, 10, 19], "raw": [8, 11], "re": 18, "read": [5, 7, 9], "read_html": 8, "read_img_as_numpi": 8, "read_img_as_tensor": 8, "read_pdf": 8, "readi": 18, "real": [1, 5, 9, 10], "realli": 1, "reason": [2, 5, 7], "rebuild": 3, "rebuilt": 3, "recal": [11, 19], "receipt": [5, 7, 19], "reco_arch": [9, 13, 15, 18], "reco_b": 19, "reco_model": [13, 15, 18], "reco_param": 13, "reco_predictor": 13, "recogn": 19, "recognit": [7, 11, 12, 13], "recognition_predictor": [9, 19], "recognition_task": [7, 17], "recognitiondataset": [7, 17], "recognitionpredictor": [9, 13], "rectangular": 9, "reduc": [4, 10], "refer": [3, 4, 13, 15, 16, 17, 19], "regardless": 2, "region": 19, "regroup": 11, "regular": 17, "reject": 2, "rel": [8, 10, 11, 19], "relat": 8, "releas": [0, 4], "relev": 16, "religion": 2, "remov": 2, "render": [8, 19], "repo": 9, "repo_id": [9, 15], "report": 2, "repositori": [7, 9, 15], "repres": [2, 18, 19], "represent": [5, 9], "request": [2, 15], "requir": [4, 10, 18], "research": 5, "residu": 9, "resiz": [10, 19], "resnet": 9, "resnet18": [9, 15], "resnet31": 9, "resnet34": 9, "resnet50": [9, 15], "resolv": 8, "resolve_block": 19, "resolve_lin": 19, "resourc": 17, "respect": 2, "rest": [3, 10, 11], "restrict": 14, "result": [3, 7, 8, 12, 15, 18, 19], "return": 19, "reusabl": 19, "review": 2, "rgb": [8, 10], "rgb_mode": 8, "rgb_output": 8, "right": [2, 9, 11], "roboflow": 1, "robust": [5, 7], "root": 7, "rotat": [7, 8, 9, 10, 11, 12, 13, 17, 19], "run": [3, 4, 9], "same": [3, 8, 11, 17, 18, 19], "sampl": [7, 9, 17, 19], "sample_transform": 7, "sanjin": 1, "sar": [5, 9], "sar_resnet31": [9, 19], "satur": 10, "save": [9, 17], "scale": [8, 9, 10, 11], "scale_rang": 10, "scan": [5, 7], "scene": [5, 7, 9], "score": [8, 11], "script": [3, 17], "seamless": 5, "seamlessli": [5, 19], "search": [1, 9], "searchabl": 12, "sec": 19, "second": 19, "section": [1, 13, 15, 16, 18, 19], "secur": [2, 14], "see": [2, 3], "seen": 19, "segment": [5, 9, 19], "self": 19, "semant": [5, 9], "send": 19, "sens": 11, "sensit": 17, "separ": 19, "sequenc": [5, 7, 8, 9, 11, 19], "sequenti": [10, 19], "seri": 2, "seriou": 2, "set": [2, 4, 7, 9, 11, 14, 16, 19], "set_global_polici": 18, "sever": [8, 10, 19], "sex": 2, "sexual": 2, "shade": 10, "shape": [5, 8, 9, 10, 11, 19], "share": [14, 17], "shift": 10, "shm": 14, "should": [3, 7, 8, 10, 11], "show": [5, 8, 9, 11, 13, 15, 16], "showcas": [3, 12], "shuffl": [7, 10], "side": 11, "signatur": 8, "signific": 17, "simpl": [5, 9, 18], "simpler": 9, "sinc": [7, 17], "singl": [2, 3, 5, 7], "single_img_doc": 18, "size": [2, 7, 8, 10, 16, 19], "skew": 19, "slack": 3, "slightli": 9, "small": [3, 9, 19], "smallest": 8, "snapshot_download": 9, "snippet": 19, "so": [3, 4, 7, 9, 15, 17], "social": 2, "socio": 2, "some": [1, 4, 12, 15, 17], "someth": 3, "somewher": 3, "sort": 2, "sourc": [1, 7, 8, 9, 10, 11, 15], "space": [2, 19], "span": 19, "spanish": 7, "spatial": [5, 7, 8], "specif": [3, 4, 11, 13, 17, 19], "specifi": [2, 7, 8], "speed": [5, 9, 19], "sphinx": 3, "sroie": [5, 7, 17], "stabl": 4, "stackoverflow": 3, "stage": 5, "standalon": 12, "standard": 10, "start": 7, "state": [1, 5, 11, 16], "static": 11, "statist": 1, "statu": 2, "std": [10, 13], "step": 14, "still": 19, "str": [7, 8, 9, 10, 11], "straight": [7, 9, 17, 19], "straighten": 19, "straighten_pag": [9, 13, 19], "straigten_pag": 13, "stream": 8, "street": [5, 7], "strict": 4, "strictli": 11, "string": [7, 8, 11, 19], "strive": 4, "strong": [5, 9], "structur": [18, 19], "subset": [7, 19], "suggest": [3, 15], "sum": 11, "summari": 11, "support": [4, 13, 16, 18, 19], "sustain": 2, "svhn": [5, 7, 17], "svt": [7, 17], "swedish": 7, "symmetr": [9, 10, 19], "symmetric_pad": [9, 10, 19], "synthet": 5, "synthtext": [5, 7, 17], "system": 19, "t": [3, 7, 13, 18, 19], "tabl": [15, 16, 17], "take": [2, 7, 19], "target": [7, 8, 10, 11, 17], "target_s": 7, "task": [5, 7, 9, 15, 17, 19], "task2": 7, "team": 4, "techminde": 4, "templat": [3, 5], "tensor": [7, 8, 10, 19], "tensorflow": [4, 5, 8, 9, 10, 13, 15, 18, 19], "tensorspec": 18, "term": 2, "test": [7, 17], "test_set": 7, "text": [1, 7, 8, 9, 11, 17], "text_output": 19, "textmatch": 11, "textnet": 9, "textnet_bas": 9, "textnet_smal": 9, "textnet_tini": 9, "textract": [5, 19], "textstylebrush": [5, 7], "textual": [5, 7, 8, 9, 19], "tf": [4, 8, 9, 10, 15, 18], "than": [3, 11, 15], "thank": 3, "thei": [2, 11], "them": [7, 19], "thi": [1, 2, 3, 4, 6, 7, 10, 11, 13, 14, 15, 17, 18, 19], "thing": [18, 19], "third": 4, "those": [2, 8, 19], "threaten": 2, "threshold": 19, "through": [2, 10, 16, 17], "tilman": 15, "time": [1, 2, 5, 9, 11, 17], "tini": 9, "titl": [8, 19], "tm": 19, "tmp": 14, "togeth": [3, 8], "tograi": 10, "tool": [1, 17], "top": [11, 18, 19], "topic": 3, "torch": [4, 10, 13, 15, 18], "torchvis": 10, "total": 13, "toward": [2, 4], "train": [3, 7, 9, 10, 15, 16, 17, 18, 19], "train_it": [7, 17], "train_load": [7, 17], "train_pytorch": 15, "train_set": [7, 17], "train_tensorflow": 15, "trainabl": [5, 9], "tranform": 10, "transcrib": 19, "transfer": [5, 7], "transfo": 10, "transform": [5, 7, 9], "translat": 2, "troll": 2, "true": [7, 8, 9, 10, 11, 13, 14, 15, 17, 18, 19], "truth": 11, "tune": [1, 18], "tupl": [7, 8, 10, 11], "two": [8, 14], "txt": 7, "type": [8, 11, 15, 18, 19], "typic": 19, "u": [2, 3], "ucsd": 7, "udac": 3, "uint8": [8, 9, 11, 19], "ukrainian": 7, "unaccept": 2, "underli": [17, 19], "underneath": 8, "understand": [5, 7, 19], "uniform": [9, 10], "uniformli": 10, "uninterrupt": [8, 19], "union": 11, "unit": 1, "unittest": 3, "unlock": 8, "unoffici": 9, "unprofession": 2, "unsolicit": 2, "unsupervis": 5, "unwelcom": 2, "up": [9, 19], "updat": 11, "upgrad": 3, "upper": [7, 10], "uppercas": 17, "url": 8, "us": [2, 3, 4, 7, 9, 11, 12, 13, 14, 15, 16, 19], "usabl": 19, "usag": [14, 18], "use_polygon": [7, 11, 17], "useabl": 19, "user": [5, 8, 12], "utf": 19, "util": 18, "v1": 15, "v3": [9, 15, 19], "valid": 17, "valu": [3, 8, 10, 19], "valuabl": 5, "variabl": 14, "varieti": 7, "veri": 9, "verma": 1, "version": [2, 3, 4, 18, 19], "vgg": 9, "vgg16": 15, "vgg16_bn_r": 9, "via": 2, "video": 1, "vietnames": 7, "view": [5, 7], "viewpoint": 2, "violat": 2, "visibl": 2, "vision": [5, 7, 9], "visiondataset": 7, "visiontransform": 9, "visual": [4, 5, 16], "visualize_pag": 11, "vit_": 9, "vit_b": 9, "vitstr": [5, 9, 18], "vitstr_bas": [9, 19], "vitstr_smal": [9, 13, 18, 19], "viz": 4, "vocab": [13, 15, 17, 18, 19], "vocabulari": [7, 13, 15], "w": [8, 9, 10, 11], "w3": 19, "wa": 2, "wai": [2, 5, 17], "want": [3, 18, 19], "warmup": 19, "wasn": 3, "we": [1, 2, 3, 4, 5, 8, 10, 13, 15, 17, 18, 19], "weasyprint": 8, "web": [3, 8], "websit": 7, "welcom": 2, "well": [1, 2, 18], "were": [2, 8, 19], "what": [1, 2], "when": [2, 3, 9], "whenev": 3, "where": [3, 8, 10, 11], "whether": [3, 7, 8, 10, 11, 17, 19], "which": [2, 9, 14, 16, 17, 19], "whichev": 4, "while": [10, 19], "why": 2, "width": [8, 10], "wiki": 2, "wildreceipt": [5, 7, 17], "window": [9, 11], "wish": 3, "within": 2, "without": [2, 7, 9], "wonder": 3, "word": [5, 7, 9, 11, 19], "word_1_1": 19, "word_1_2": 19, "word_1_3": 19, "wordgener": [7, 17], "words_onli": 11, "work": [1, 13, 14, 19], "workflow": 3, "worklow": 3, "world": [11, 19], "worth": 9, "wrap": 19, "wrapper": [7, 10], "write": 14, "written": [2, 8], "www": [2, 8, 19], "x": [8, 10, 11], "x_ascend": 19, "x_descend": 19, "x_i": 11, "x_size": 19, "x_wconf": 19, "xhtml": 19, "xmax": 8, "xmin": 8, "xml": 19, "xml_bytes_str": 19, "xml_element": 19, "xml_output": 19, "xmln": 19, "y": 11, "y_i": 11, "y_j": 11, "yet": 16, "ymax": 8, "ymin": 8, "yolov8": 16, "you": [3, 4, 7, 8, 9, 13, 14, 15, 16, 17, 18, 19], "your": [3, 5, 8, 11, 19], "yoursit": 8, "yugesh": 1, "zero": [10, 11], "zoo": 13, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 7, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 7, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": 7, "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 7, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 7, "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 7, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 7, "\u00e4\u00f6\u00e4\u00f6": 7, "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 7, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": 7, "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": 7, "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": 7, "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": 7, "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": 7, "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0905": 7, "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": 7, "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": 7, "\u0950": 7, "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": 7, "\u09bd": 7, "\u09ce": 7, "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": 7}, "titles": ["Changelog", "Community resources", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.contrib", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Integrate contributions into your pipeline", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 3, "0": 0, "01": 0, "02": 0, "03": 0, "04": 0, "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 2], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 2], "2021": 0, "2022": 0, "2023": 0, "2024": 0, "21": 0, "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 2], "31": 0, "4": [0, 2], "5": 0, "6": 0, "7": 0, "8": 0, "9": 0, "advanc": 19, "approach": 19, "architectur": 19, "arg": [7, 8, 9, 10, 11], "artefact": 8, "artefactdetect": 16, "attribut": 2, "avail": [16, 17, 19], "aw": 14, "ban": 2, "block": 8, "bug": 3, "changelog": 0, "choos": [17, 19], "classif": [9, 13, 15], "code": [2, 3], "codebas": 3, "commit": 3, "commun": [1, 15], "compos": 10, "conda": 4, "conduct": 2, "connect": 3, "continu": 3, "contrib": 6, "contribut": [3, 6, 16], "contributor": 2, "convent": 15, "correct": 2, "coven": 2, "custom": [7, 13], "data": 17, "dataload": 7, "dataset": [5, 7, 17], "detect": [5, 9, 15, 17, 19], "develop": 3, "do": 19, "doctr": [3, 5, 6, 7, 8, 9, 10, 11, 12], "document": [3, 5, 8], "end": 19, "enforc": 2, "evalu": 11, "export": 18, "factori": 9, "featur": [3, 5], "feedback": 3, "file": 8, "from": 15, "gener": [7, 17], "git": 4, "guidelin": 2, "half": 18, "hub": 15, "huggingfac": 15, "i": 19, "infer": 18, "instal": [3, 4], "integr": [3, 16], "io": 8, "lambda": 14, "let": 3, "line": 8, "linux": 4, "load": [13, 15, 17], "loader": 7, "main": 5, "mode": 3, "model": [5, 9, 13, 15, 18, 19], "modifi": 3, "modul": [6, 16], "name": 15, "notebook": 12, "object": 17, "ocr": [17, 19], "onli": 4, "onnx": 18, "optim": 18, "option": 19, "orient": 13, "our": 2, "output": 19, "own": [13, 17], "packag": 4, "page": 8, "perman": 2, "pipelin": 16, "pledg": 2, "precis": 18, "predictor": 19, "prepar": 18, "prerequisit": 4, "pretrain": 15, "push": 15, "python": 4, "qualiti": 3, "question": 3, "read": 8, "readi": 17, "recognit": [5, 9, 15, 17, 19], "report": 3, "request": 3, "resourc": 1, "respons": 2, "return": [7, 8, 9, 11], "right": 19, "scope": 2, "share": 15, "should": 19, "stage": 19, "standard": 2, "structur": [3, 8], "style": 3, "support": [5, 6, 7, 10], "synthet": [7, 17], "task": 11, "temporari": 2, "test": 3, "text": [5, 19], "train": 13, "transform": 10, "two": 19, "unit": 3, "us": [17, 18], "util": 11, "v0": 0, "verif": 3, "via": 4, "visual": 11, "vocab": 7, "warn": 2, "what": 19, "word": 8, "your": [13, 15, 16, 17, 18], "zoo": [5, 9]}}) \ No newline at end of file diff --git a/v0.3.1/transforms.html b/v0.3.1/transforms.html deleted file mode 100644 index 85e94d8a76..0000000000 --- a/v0.3.1/transforms.html +++ /dev/null @@ -1,684 +0,0 @@ - - - - - - - - - - - - - doctr.transforms - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    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:

    -
    -
    -class doctr.transforms.Resize(output_size: Tuple[int, int], method: str = 'bilinear', preserve_aspect_ratio: bool = False, symmetric_pad: bool = False)[source]
    -

    Resizes a tensor to a target size

    -
    -
    Example::
    >>> from doctr.transforms import Resize
    ->>> import tensorflow as tf
    ->>> transfo = Resize((32, 32))
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • output_size – expected output size

    • -
    • method – interpolation method

    • -
    • preserve_aspect_ratio – if True, preserve aspect ratio and pad the rest with zeros

    • -
    • symmetric_pad – if True while preserving aspect ratio, the padding will be done symmetrically

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.Normalize(mean: Tuple[float, float, float], std: Tuple[float, float, float])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • mean – average value per channel

    • -
    • std – standard deviation per channel

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.LambdaTransformation(fn: Callable[[Tensor], Tensor])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import LambdaTransformation
    ->>> import tensorflow as tf
    ->>> transfo = LambdaTransformation(lambda x: x/ 255.)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    fn – the function to be applied to the input tensor

    -
    -
    -
    - -
    -
    -class doctr.transforms.ToGray[source]
    -

    Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ToGray()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    - -
    -
    -class doctr.transforms.ColorInversion(min_val: float = 0.5)[source]
    -

    Applies the following tranformation to a tensor (image or batch of images): -convert to grayscale, colorize (shift 0-values randomly), and then invert colors

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ColorInversion(min_val=0.6)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    min_val – range [min_val, 1] to colorize RGB pixels

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomBrightness(max_delta: float = 0.3)[source]
    -

    Randomly adjust brightness of a tensor (batch of images or image) by adding a delta -to all pixels

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Brightness()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    • -
    • p – probability to apply transformation

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomContrast(delta: float = 0.3)[source]
    -

    Randomly adjust contrast of a tensor (batch of images or image) by adjusting -each pixel: (img - mean) * contrast_factor + mean.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Contrast()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomSaturation(delta: float = 0.5)[source]
    -

    Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and -increasing saturation by a factor.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Saturation()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomHue(max_delta: float = 0.3)[source]
    -

    Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Hue()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomGamma(min_gamma: float = 0.5, max_gamma: float = 1.5, min_gain: float = 0.8, max_gain: float = 1.2)[source]
    -

    randomly performs gamma correction for a tensor (batch of images or image)

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Gamma()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_gamma – non-negative real number, lower bound for gamma param

    • -
    • max_gamma – non-negative real number, upper bound for gamma

    • -
    • min_gain – lower bound for constant multiplier

    • -
    • max_gain – upper bound for constant multiplier

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomJpegQuality(min_quality: int = 60, max_quality: int = 100)[source]
    -

    Randomly adjust jpeg quality of a 3 dimensional RGB image

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = JpegQuality()
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_quality – int between [0, 100]

    • -
    • max_quality – int between [0, 100]

    • -
    -
    -
    -
    - -
    -
    -

    Composing transformations

    -

    It is common to require several transformations to be performed consecutively.

    -
    -
    -class doctr.transforms.Compose(transforms: List[Callable[[Any], Any]])[source]
    -

    Implements a wrapper that will apply transformations sequentially

    -
    -
    Example::
    >>> from doctr.transforms import Compose, Resize
    ->>> import tensorflow as tf
    ->>> transfos = Compose([Resize((32, 32))])
    ->>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformation modules

    -
    -
    -
    - -
    -
    -class doctr.transforms.OneOf(transforms: List[Callable[[Any], Any]])[source]
    -

    Randomly apply one of the input transformations

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = OneOf([JpegQuality(), Gamma()])
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformations, one only will be picked

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomApply(transform: Callable[[Any], Any], p: float = 0.5)[source]
    -

    Apply with a probability p the input transformation

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = RandomApply(Gamma(), p=.5)
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • transform – transformation to apply

    • -
    • p – probability to apply

    • -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.3.1/using_doctr/custom_models_training.html b/v0.3.1/using_doctr/custom_models_training.html index df39d8d568..b714c1f971 100644 --- a/v0.3.1/using_doctr/custom_models_training.html +++ b/v0.3.1/using_doctr/custom_models_training.html @@ -14,7 +14,7 @@ - + Train your own model - docTR documentation @@ -619,7 +619,7 @@

    Loading your custom trained orientation classification model - + diff --git a/v0.3.1/using_doctr/running_on_aws.html b/v0.3.1/using_doctr/running_on_aws.html index 16ceaca7a1..808ea541cd 100644 --- a/v0.3.1/using_doctr/running_on_aws.html +++ b/v0.3.1/using_doctr/running_on_aws.html @@ -14,7 +14,7 @@ - + AWS Lambda - docTR documentation @@ -362,7 +362,7 @@

    AWS Lambda - + diff --git a/v0.3.1/using_doctr/sharing_models.html b/v0.3.1/using_doctr/sharing_models.html index d76b4017f4..c9e978400a 100644 --- a/v0.3.1/using_doctr/sharing_models.html +++ b/v0.3.1/using_doctr/sharing_models.html @@ -14,7 +14,7 @@ - + Share your model with the community - docTR documentation @@ -544,7 +544,7 @@

    Recognition - + diff --git a/v0.3.1/using_doctr/using_contrib_modules.html b/v0.3.1/using_doctr/using_contrib_modules.html index 50598dae5d..0c5fffdf9f 100644 --- a/v0.3.1/using_doctr/using_contrib_modules.html +++ b/v0.3.1/using_doctr/using_contrib_modules.html @@ -14,7 +14,7 @@ - + Integrate contributions into your pipeline - docTR documentation @@ -415,7 +415,7 @@

    ArtefactDetection - + diff --git a/v0.3.1/using_doctr/using_datasets.html b/v0.3.1/using_doctr/using_datasets.html index 460476dbbf..8a7d4f0a64 100644 --- a/v0.3.1/using_doctr/using_datasets.html +++ b/v0.3.1/using_doctr/using_datasets.html @@ -14,7 +14,7 @@ - + Choose a ready to use dataset - docTR documentation @@ -642,7 +642,7 @@

    Data Loading - + diff --git a/v0.3.1/using_doctr/using_model_export.html b/v0.3.1/using_doctr/using_model_export.html index 6124c00ebe..6790dd0642 100644 --- a/v0.3.1/using_doctr/using_model_export.html +++ b/v0.3.1/using_doctr/using_model_export.html @@ -14,7 +14,7 @@ - + Preparing your model for inference - docTR documentation @@ -467,7 +467,7 @@

    Using your ONNX exported model - + diff --git a/v0.3.1/using_doctr/using_models.html b/v0.3.1/using_doctr/using_models.html index 61f1f5ab7a..9ead8498e1 100644 --- a/v0.3.1/using_doctr/using_models.html +++ b/v0.3.1/using_doctr/using_models.html @@ -14,7 +14,7 @@ - + Choosing the right model - docTR documentation @@ -1253,7 +1253,7 @@

    Advanced options - + diff --git a/v0.3.1/utils.html b/v0.3.1/utils.html deleted file mode 100644 index e2f223f06a..0000000000 --- a/v0.3.1/utils.html +++ /dev/null @@ -1,574 +0,0 @@ - - - - - - - - - - - - - doctr.utils - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.utils

    -

    This module regroups non-core features that are complementary to the rest of the package.

    -
    -

    Visualization

    -

    Easy-to-use functions to make sense of your model’s predictions.

    -
    -
    -doctr.utils.visualization.visualize_page(page: Dict[str, Any], image: ndarray, words_only: bool = True, display_artefacts: bool = True, scale: float = 10, interactive: bool = True, add_labels: bool = True, **kwargs: Any) Figure[source]
    -

    Visualize a full page with predicted blocks, lines and words

    -
    -
    Example::
    >>> import numpy as np
    ->>> import matplotlib.pyplot as plt
    ->>> from doctr.utils.visualization import visualize_page
    ->>> from doctr.models import ocr_db_crnn
    ->>> model = ocr_db_crnn(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([[input_page]])
    ->>> visualize_page(out[0].pages[0].export(), input_page)
    ->>> plt.show()
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • page – the exported Page of a Document

    • -
    • image – np array of the page, needs to have the same shape than page[‘dimensions’]

    • -
    • words_only – whether only words should be displayed

    • -
    • display_artefacts – whether artefacts should be displayed

    • -
    • scale – figsize of the largest windows side

    • -
    • interactive – whether the plot should be interactive

    • -
    • add_labels – for static plot, adds text labels on top of bounding box

    • -
    -
    -
    -
    - -
    -
    -

    Task evaluation

    -

    Implementations of task-specific metrics to easily assess your model performances.

    -
    -
    -class doctr.utils.metrics.TextMatch[source]
    -

    Implements text match metric (word-level accuracy) for recognition task.

    -

    The raw aggregated metric is computed as follows:

    -
    -
    -\[\forall X, Y \in \mathcal{W}^N, -TextMatch(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N f_{Y_i}(X_i)\]
    -
    -

    with the indicator function \(f_{a}\) defined as:

    -
    -
    -\[\begin{split}\forall a, x \in \mathcal{W}, -f_a(x) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } x = a \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{W}\) is the set of all possible character sequences, -\(N\) is a strictly positive integer.

    -
    -
    Example::
    >>> from doctr.utils import TextMatch
    ->>> metric = TextMatch()
    ->>> metric.update(['Hello', 'world'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    -summary() Dict[str, float][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a dictionary with the exact match score for the raw data, its lower-case counterpart, its unidecode -counterpart and its lower-case unidecode counterpart

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.LocalizationConfusion(iou_thresh: float = 0.5, rotated_bbox: bool = False, mask_shape: Tuple[int, int] = (1024, 1024))[source]
    -

    Implements common confusion metrics and mean IoU for localization evaluation.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall Y \in \mathcal{B}^N, \forall X \in \mathcal{B}^M, \\ -Recall(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -Precision(X, Y) = \frac{1}{M} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -meanIoU(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(X_i, Y_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(g_{X}\) defined as:

    -
    -
    -\[\begin{split}\forall y \in \mathcal{B}, -g_X(y) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } y\mbox{ has been assigned to any }(X_i)_i\mbox{ with an }IoU \geq 0.5 \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import LocalizationConfusion
    ->>> metric = LocalizationConfusion(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]))
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[float | None, float | None, float | None][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall, precision and meanIoU scores

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.OCRMetric(iou_thresh: float = 0.5, rotated_bbox: bool = False, mask_shape: Tuple[int, int] = (1024, 1024))[source]
    -

    Implements end-to-end OCR metric.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall (B, L) \in \mathcal{B}^N \times \mathcal{L}^N, -\forall (\hat{B}, \hat{L}) \in \mathcal{B}^M \times \mathcal{L}^M, \\ -Recall(B, \hat{B}, L, \hat{L}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -Precision(B, \hat{B}, L, \hat{L}) = \frac{1}{M} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(h_{B, L}\) defined as:

    -
    -
    -\[\begin{split}\forall (b, l) \in \mathcal{B} \times \mathcal{L}, -h_{B,L}(b, l) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ - & IoU \geq 0.5 \mbox{ and that for this assignment, } l = L_j\\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(\mathcal{L}\) is the set of possible character sequences, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import OCRMetric
    ->>> metric = OCRMetric(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]),
    -['hello'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[Dict[str, float | None], Dict[str, float | None], float | None][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall & precision for each string comparison flexibility and the mean IoU

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.4.0/_modules/doctr/datasets/cord.html b/v0.4.0/_modules/doctr/datasets/cord.html index f98ee6901c..55b0584830 100644 --- a/v0.4.0/_modules/doctr/datasets/cord.html +++ b/v0.4.0/_modules/doctr/datasets/cord.html @@ -13,7 +13,7 @@ - + doctr.datasets.cord - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.cord

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
     from .datasets import VisionDataset
    -from doctr.utils.geometry import fit_rbbox
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['CORD']
    +__all__ = ["CORD"]
     
     
     
    -[docs] +[docs] class CORD(VisionDataset): """CORD dataset from `"CORD: A Consolidated Receipt Dataset forPost-OCR Parsing" <https://openreview.net/pdf?id=SJl3z659UH>`_. - Example:: - >>> from doctr.datasets import CORD - >>> train_set = CORD(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/cord-grid.png&src=0 + :align: center + + >>> from doctr.datasets import CORD + >>> train_set = CORD(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - TRAIN = ('https://github.com/mindee/doctr/releases/download/v0.1.1/cord_train.zip', - '45f9dc77f126490f3e52d7cb4f70ef3c57e649ea86d19d862a2757c9c455d7f8') - TEST = ('https://github.com/mindee/doctr/releases/download/v0.1.1/cord_test.zip', - '8c895e3d6f7e1161c5b7245e3723ce15c04d84be89eaa6093949b75a66fb3c58') + TRAIN = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/cord_train.zip&src=0", + "45f9dc77f126490f3e52d7cb4f70ef3c57e649ea86d19d862a2757c9c455d7f8", + "cord_train.zip", + ) + + TEST = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/cord_test.zip&src=0", + "8c895e3d6f7e1161c5b7245e3723ce15c04d84be89eaa6093949b75a66fb3c58", + "cord_test.zip", + ) def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + url, sha256, name = self.TRAIN if train else self.TEST + super().__init__( + url, + name, + sha256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - url, sha256 = self.TRAIN if train else self.TEST - super().__init__(url, None, sha256, True, **kwargs) - - # # List images - self.root = os.path.join(self._root, 'image') - self.data: List[Tuple[str, Dict[str, Any]]] = [] + # List images + tmp_root = os.path.join(self.root, "image") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] self.train = train - self.sample_transforms = sample_transforms - for img_path in os.listdir(self.root): + np_dtype = np.float32 + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking CORD", total=len(os.listdir(tmp_root))): # File existence check - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem _targets = [] - with open(os.path.join(self._root, 'json', f"{stem}.json"), 'rb') as f: + with open(os.path.join(self.root, "json", f"{stem}.json"), "rb") as f: label = json.load(f) for line in label["valid_line"]: for word in line["words"]: if len(word["text"]) > 0: x = word["quad"]["x1"], word["quad"]["x2"], word["quad"]["x3"], word["quad"]["x4"] y = word["quad"]["y1"], word["quad"]["y2"], word["quad"]["y3"], word["quad"]["y4"] - if rotated_bbox: - box = list(fit_rbbox(np.array([ - [x[0], y[0]], - [x[1], y[1]], - [x[2], y[2]], - [x[3], y[3]], - ], dtype=np.float32))) + box: Union[List[float], np.ndarray] + if use_polygons: + # (x, y) coordinates of top left, top right, bottom right, bottom left corners + box = np.array( + [ + [x[0], y[0]], + [x[1], y[1]], + [x[2], y[2]], + [x[3], y[3]], + ], + dtype=np_dtype, + ) else: - # Reduce 8 coords to 4 + # Reduce 8 coords to 4 -> xmin, ymin, xmax, ymax box = [min(x), min(y), max(x), max(y)] - _targets.append((word['text'], box)) + _targets.append((word["text"], box)) text_targets, box_targets = zip(*_targets) - self.data.append(( - img_path, - dict(boxes=np.asarray(box_targets, dtype=int).clip(min=0), labels=text_targets) - )) + if recognition_task: + crops = crop_bboxes_from_image( + img_path=os.path.join(tmp_root, img_path), geoms=np.asarray(box_targets, dtype=int).clip(min=0) + ) + for crop, label in zip(crops, list(text_targets)): + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, np.asarray(box_targets, dtype=int).clip(min=0))) + else: + self.data.append(( + img_path, + dict(boxes=np.asarray(box_targets, dtype=int).clip(min=0), labels=list(text_targets)), + )) + + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -397,8 +461,8 @@

    Source code for doctr.datasets.cord

           
         
       
    -
    - + + diff --git a/v0.4.0/_modules/doctr/datasets/core.html b/v0.4.0/_modules/doctr/datasets/core.html deleted file mode 100644 index b3dcc29ff9..0000000000 --- a/v0.4.0/_modules/doctr/datasets/core.html +++ /dev/null @@ -1,417 +0,0 @@ - - - - - - - - - - - - doctr.datasets.core - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.core

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from pathlib import Path
    -from zipfile import ZipFile
    -from typing import List, Any, Optional, Tuple
    -import tensorflow as tf
    -
    -from doctr.models.utils import download_from_url
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset:
    -
    -    data: List[Any] = []
    -
    -    def __len__(self):
    -        return len(self.data)
    -
    -    def __getitem__(
    -        self,
    -        index: int
    -    ) -> Tuple[tf.Tensor, Any]:
    -
    -        img_name, target = self.data[index]
    -        # Read image
    -        img = tf.io.read_file(os.path.join(self.root, img_name))
    -        img = tf.image.decode_jpeg(img, channels=3)
    -        if self.sample_transforms is not None:
    -            img = self.sample_transforms(img)
    -
    -        return img, target
    -
    -    def extra_repr(self) -> str:
    -        return ""
    -
    -    def __repr__(self) -> str:
    -        return f"{self.__class__.__name__}({self.extra_repr()})"
    -
    -    @staticmethod
    -    def collate_fn(samples: List[Tuple[tf.Tensor, Any]]) -> Tuple[tf.Tensor, List[Any]]:
    -
    -        images, targets = zip(*samples)
    -        images = tf.stack(images, axis=0)
    -
    -        return images, list(targets)
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset): - """Implements an abstract dataset - - Args: - url: URL of the dataset - file_name: name of the file once downloaded - file_hash: expected SHA256 of the file - extract_archive: whether the downloaded file is an archive to be extracted - download: whether the dataset should be downloaded if not present on disk - overwrite: whether the archive should be re-extracted - """ - - def __init__( - self, - url: str, - file_name: Optional[str] = None, - file_hash: Optional[str] = None, - extract_archive: bool = False, - download: bool = False, - overwrite: bool = False, - ) -> None: - - dataset_cache = os.path.join(os.path.expanduser('~'), '.cache', 'doctr', 'datasets') - - file_name = file_name if isinstance(file_name, str) else os.path.basename(url) - # Download the file if not present - archive_path = os.path.join(dataset_cache, file_name) - - if not os.path.exists(archive_path) and not download: - raise ValueError("the dataset needs to be downloaded first with download=True") - - archive_path = download_from_url(url, file_name, file_hash, cache_subdir='datasets') - - # Extract the archive - if extract_archive: - archive_path = Path(archive_path) - dataset_path = archive_path.parent.joinpath(archive_path.stem) - if not dataset_path.is_dir() or overwrite: - with ZipFile(archive_path, 'r') as f: - f.extractall(path=dataset_path) - - # List images - self._root = dataset_path if extract_archive else archive_path - self.data: List[Any] = []
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.0/_modules/doctr/datasets/datasets/tensorflow.html b/v0.4.0/_modules/doctr/datasets/datasets/tensorflow.html deleted file mode 100644 index a236abd9fe..0000000000 --- a/v0.4.0/_modules/doctr/datasets/datasets/tensorflow.html +++ /dev/null @@ -1,356 +0,0 @@ - - - - - - - - - - - - doctr.datasets.datasets.tensorflow - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.datasets.tensorflow

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from typing import List, Any, Tuple
    -import tensorflow as tf
    -
    -from .base import _AbstractDataset, _VisionDataset
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset(_AbstractDataset):
    -
    -    def _read_sample(self, index: int) -> Tuple[tf.Tensor, Any]:
    -        img_name, target = self.data[index]
    -        # Read image
    -        img = tf.io.read_file(os.path.join(self.root, img_name))
    -        img = tf.image.decode_jpeg(img, channels=3)
    -
    -        return img, target
    -
    -    @staticmethod
    -    def collate_fn(samples: List[Tuple[tf.Tensor, Any]]) -> Tuple[tf.Tensor, List[Any]]:
    -
    -        images, targets = zip(*samples)
    -        images = tf.stack(images, axis=0)
    -
    -        return images, list(targets)
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset, _VisionDataset): - pass
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.0/_modules/doctr/datasets/detection.html b/v0.4.0/_modules/doctr/datasets/detection.html index 739563e466..718001e4cf 100644 --- a/v0.4.0/_modules/doctr/datasets/detection.html +++ b/v0.4.0/_modules/doctr/datasets/detection.html @@ -13,7 +13,7 @@ - + doctr.datasets.detection - docTR documentation @@ -430,7 +430,7 @@

    Source code for doctr.datasets.detection

         
       
    - + diff --git a/v0.4.0/_modules/doctr/datasets/doc_artefacts.html b/v0.4.0/_modules/doctr/datasets/doc_artefacts.html index 3313ae4660..94c32aaa0f 100644 --- a/v0.4.0/_modules/doctr/datasets/doc_artefacts.html +++ b/v0.4.0/_modules/doctr/datasets/doc_artefacts.html @@ -13,7 +13,7 @@ - + doctr.datasets.doc_artefacts - docTR documentation @@ -414,7 +414,7 @@

    Source code for doctr.datasets.doc_artefacts

       
    - + diff --git a/v0.4.0/_modules/doctr/datasets/funsd.html b/v0.4.0/_modules/doctr/datasets/funsd.html index 35d7ad4cf5..f08612f9fa 100644 --- a/v0.4.0/_modules/doctr/datasets/funsd.html +++ b/v0.4.0/_modules/doctr/datasets/funsd.html @@ -13,7 +13,7 @@ - + doctr.datasets.funsd - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.funsd

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
     from .datasets import VisionDataset
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['FUNSD']
    +__all__ = ["FUNSD"]
     
     
     
    -[docs] +[docs] class FUNSD(VisionDataset): """FUNSD dataset from `"FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents" <https://arxiv.org/pdf/1905.13538.pdf>`_. - Example:: - >>> from doctr.datasets import FUNSD - >>> train_set = FUNSD(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/funsd-grid.png&src=0 + :align: center + + >>> from doctr.datasets import FUNSD + >>> train_set = FUNSD(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - URL = 'https://guillaumejaume.github.io/FUNSD/dataset.zip' - SHA256 = 'c31735649e4f441bcbb4fd0f379574f7520b42286e80b01d80b445649d54761f' - FILE_NAME = 'funsd.zip' + URL = "https://guillaumejaume.github.io/FUNSD/dataset.zip" + SHA256 = "c31735649e4f441bcbb4fd0f379574f7520b42286e80b01d80b445649d54761f" + FILE_NAME = "funsd.zip" def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + super().__init__( + self.URL, + self.FILE_NAME, + self.SHA256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - super().__init__(self.URL, self.FILE_NAME, self.SHA256, True, **kwargs) self.train = train - self.sample_transforms = sample_transforms + np_dtype = np.float32 # Use the subset - subfolder = os.path.join('dataset', 'training_data' if train else 'testing_data') + subfolder = os.path.join("dataset", "training_data" if train else "testing_data") # # List images - self.root = os.path.join(self._root, subfolder, 'images') - self.data: List[Tuple[str, Dict[str, Any]]] = [] - for img_path in os.listdir(self.root): + tmp_root = os.path.join(self.root, subfolder, "images") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking FUNSD", total=len(os.listdir(tmp_root))): # File existence check - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem - with open(os.path.join(self._root, subfolder, 'annotations', f"{stem}.json"), 'rb') as f: + with open(os.path.join(self.root, subfolder, "annotations", f"{stem}.json"), "rb") as f: data = json.load(f) - _targets = [(word['text'], word['box']) for block in data['form'] - for word in block['words'] if len(word['text']) > 0] + _targets = [ + (word["text"], word["box"]) + for block in data["form"] + for word in block["words"] + if len(word["text"]) > 0 + ] text_targets, box_targets = zip(*_targets) - if rotated_bbox: - # box_targets: xmin, ymin, xmax, ymax -> x, y, w, h, alpha = 0 - box_targets = [ + if use_polygons: + # xmin, ymin, xmax, ymax -> (x, y) coordinates of top left, top right, bottom right, bottom left corners + box_targets = [ # type: ignore[assignment] [ - (box[0] + box[2]) / 2, (box[1] + box[3]) / 2, box[2] - box[0], box[3] - box[1], 0 - ] for box in box_targets + [box[0], box[1]], + [box[2], box[1]], + [box[2], box[3]], + [box[0], box[3]], + ] + for box in box_targets ] - self.data.append((img_path, dict(boxes=np.asarray(box_targets, dtype=int), labels=text_targets))) + if recognition_task: + crops = crop_bboxes_from_image( + img_path=os.path.join(tmp_root, img_path), geoms=np.asarray(box_targets, dtype=np_dtype) + ) + for crop, label in zip(crops, list(text_targets)): + # filter labels with unknown characters + if not any(char in label for char in ["☑", "☐", "\uf703", "\uf702"]): + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, np.asarray(box_targets, dtype=np_dtype))) + else: + self.data.append(( + img_path, + dict(boxes=np.asarray(box_targets, dtype=np_dtype), labels=list(text_targets)), + )) + + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -386,8 +453,8 @@

    Source code for doctr.datasets.funsd

           
         
       
    -
    - + + diff --git a/v0.4.0/_modules/doctr/datasets/generator/tensorflow.html b/v0.4.0/_modules/doctr/datasets/generator/tensorflow.html index 9f562582d9..a3e619f720 100644 --- a/v0.4.0/_modules/doctr/datasets/generator/tensorflow.html +++ b/v0.4.0/_modules/doctr/datasets/generator/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.datasets.generator.tensorflow - docTR documentation @@ -395,7 +395,7 @@

    Source code for doctr.datasets.generator.tensorflow

    - + diff --git a/v0.4.0/_modules/doctr/datasets/ic03.html b/v0.4.0/_modules/doctr/datasets/ic03.html index 3d221d07de..60e54a8a4b 100644 --- a/v0.4.0/_modules/doctr/datasets/ic03.html +++ b/v0.4.0/_modules/doctr/datasets/ic03.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic03 - docTR documentation @@ -468,7 +468,7 @@

    Source code for doctr.datasets.ic03

         
       
    - + diff --git a/v0.4.0/_modules/doctr/datasets/ic13.html b/v0.4.0/_modules/doctr/datasets/ic13.html index 8137e08e9f..219c98dcd1 100644 --- a/v0.4.0/_modules/doctr/datasets/ic13.html +++ b/v0.4.0/_modules/doctr/datasets/ic13.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic13 - docTR documentation @@ -440,7 +440,7 @@

    Source code for doctr.datasets.ic13

         
       
    - + diff --git a/v0.4.0/_modules/doctr/datasets/iiit5k.html b/v0.4.0/_modules/doctr/datasets/iiit5k.html index 1fc8ecfb27..b49c80fe18 100644 --- a/v0.4.0/_modules/doctr/datasets/iiit5k.html +++ b/v0.4.0/_modules/doctr/datasets/iiit5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiit5k - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.datasets.iiit5k

         
       
    - + diff --git a/v0.4.0/_modules/doctr/datasets/iiithws.html b/v0.4.0/_modules/doctr/datasets/iiithws.html index 07f5b13685..f7220afbc7 100644 --- a/v0.4.0/_modules/doctr/datasets/iiithws.html +++ b/v0.4.0/_modules/doctr/datasets/iiithws.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiithws - docTR documentation @@ -407,7 +407,7 @@

    Source code for doctr.datasets.iiithws

         
       
    - + diff --git a/v0.4.0/_modules/doctr/datasets/imgur5k.html b/v0.4.0/_modules/doctr/datasets/imgur5k.html index 68d433ca62..51c6545db4 100644 --- a/v0.4.0/_modules/doctr/datasets/imgur5k.html +++ b/v0.4.0/_modules/doctr/datasets/imgur5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.imgur5k - docTR documentation @@ -488,7 +488,7 @@

    Source code for doctr.datasets.imgur5k

         
       
    - + diff --git a/v0.4.0/_modules/doctr/datasets/loader.html b/v0.4.0/_modules/doctr/datasets/loader.html index d32e6da298..ed80350ef0 100644 --- a/v0.4.0/_modules/doctr/datasets/loader.html +++ b/v0.4.0/_modules/doctr/datasets/loader.html @@ -13,7 +13,7 @@ - + doctr.datasets.loader - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.loader

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import math
    -import tensorflow as tf
    -import numpy as np
    -from typing import Optional
    +from typing import Callable, Optional
     
    -from .multithreading import multithread_exec
    +import numpy as np
    +import tensorflow as tf
     
     __all__ = ["DataLoader"]
     
    @@ -293,12 +314,13 @@ 

    Source code for doctr.datasets.loader

         """Collate multiple elements into batches
     
         Args:
    +    ----
             samples: list of N tuples containing M elements
     
         Returns:
    +    -------
             Tuple of M sequences contianing N elements each
         """
    -
         batch_data = zip(*samples)
     
         tf_data = tuple(tf.stack(elt, axis=0) for elt in batch_data)
    @@ -307,23 +329,23 @@ 

    Source code for doctr.datasets.loader

     
     
     
    -[docs] +[docs] class DataLoader: """Implements a dataset wrapper for fast data loading - Example:: - >>> from doctr.datasets import FUNSD, 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) + >>> 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) Args: + ---- dataset: the dataset shuffle: whether the samples should be shuffled before passing it to the iterator batch_size: number of elements in each batch drop_last: if `True`, drops the last batch if it isn't full - workers: number of workers to use for data loading + collate_fn: function to merge samples into a batch """ def __init__( @@ -332,17 +354,22 @@

    Source code for doctr.datasets.loader

             shuffle: bool = True,
             batch_size: int = 1,
             drop_last: bool = False,
    -        workers: Optional[int] = None,
    +        collate_fn: Optional[Callable] = None,
         ) -> None:
             self.dataset = dataset
             self.shuffle = shuffle
             self.batch_size = batch_size
             nb = len(self.dataset) / batch_size
             self.num_batches = math.floor(nb) if drop_last else math.ceil(nb)
    -        self.collate_fn = self.dataset.collate_fn if hasattr(self.dataset, 'collate_fn') else default_collate
    -        self.workers = workers
    +        if collate_fn is None:
    +            self.collate_fn = self.dataset.collate_fn if hasattr(self.dataset, "collate_fn") else default_collate
    +        else:
    +            self.collate_fn = collate_fn
             self.reset()
     
    +    def __len__(self) -> int:
    +        return self.num_batches
    +
         def reset(self) -> None:
             # Updates indices after each epoch
             self._num_yielded = 0
    @@ -358,9 +385,9 @@ 

    Source code for doctr.datasets.loader

             if self._num_yielded < self.num_batches:
                 # Get next indices
                 idx = self._num_yielded * self.batch_size
    -            indices = self.indices[idx: min(len(self.dataset), idx + self.batch_size)]
    +            indices = self.indices[idx : min(len(self.dataset), idx + self.batch_size)]
     
    -            samples = multithread_exec(self.dataset.__getitem__, indices, threads=self.workers)
    +            samples = list(map(self.dataset.__getitem__, indices))
     
                 batch_data = self.collate_fn(samples)
     
    @@ -401,8 +428,8 @@ 

    Source code for doctr.datasets.loader

           
         
       
    -
    - +
    + diff --git a/v0.4.0/_modules/doctr/datasets/mjsynth.html b/v0.4.0/_modules/doctr/datasets/mjsynth.html index 77bb01d523..df34e49cf9 100644 --- a/v0.4.0/_modules/doctr/datasets/mjsynth.html +++ b/v0.4.0/_modules/doctr/datasets/mjsynth.html @@ -13,7 +13,7 @@ - + doctr.datasets.mjsynth - docTR documentation @@ -438,7 +438,7 @@

    Source code for doctr.datasets.mjsynth

         
       
    - + diff --git a/v0.4.0/_modules/doctr/datasets/ocr.html b/v0.4.0/_modules/doctr/datasets/ocr.html index 11297d5952..ce1ed8b0d4 100644 --- a/v0.4.0/_modules/doctr/datasets/ocr.html +++ b/v0.4.0/_modules/doctr/datasets/ocr.html @@ -13,7 +13,7 @@ - + doctr.datasets.ocr - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.ocr

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple
     
    -from .datasets import AbstractDataset
    -from doctr.utils.geometry import fit_rbbox
    +import numpy as np
     
    +from .datasets import AbstractDataset
     
    -__all__ = ['OCRDataset']
    +__all__ = ["OCRDataset"]
     
     
     
    -[docs] +[docs] class OCRDataset(AbstractDataset): """Implements an OCR dataset + >>> from doctr.datasets import OCRDataset + >>> train_set = OCRDataset(img_folder="/path/to/images", + >>> label_file="/path/to/labels.json") + >>> img, target = train_set[0] + Args: + ---- img_folder: local path to image folder (all jpg at the root) label_file: local path to the label file - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) - **kwargs: keyword arguments from `VisionDataset`. + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + **kwargs: keyword arguments from `AbstractDataset`. """ def __init__( self, img_folder: str, label_file: str, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, **kwargs: Any, ) -> None: - - self.sample_transforms = sample_transforms - self.root = img_folder + super().__init__(img_folder, **kwargs) # List images self.data: List[Tuple[str, Dict[str, Any]]] = [] - with open(label_file, 'rb') as f: + np_dtype = np.float32 + with open(label_file, "rb") as f: data = json.load(f) - for file_dic in data: + for img_name, annotations in data.items(): # Get image path - img_name = Path(os.path.basename(file_dic["raw-archive-filepath"])).stem + '.jpg' + img_name = Path(img_name) # File existence check if not os.path.exists(os.path.join(self.root, img_name)): raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_name)}") # handle empty images - if (len(file_dic["coordinates"]) == 0 or - (len(file_dic["coordinates"]) == 1 and file_dic["coordinates"][0] == "N/A")): - self.data.append((img_name, dict(boxes=np.zeros((0, 4), dtype=np.float32), labels=[]))) + if len(annotations["typed_words"]) == 0: + self.data.append((img_name, dict(boxes=np.zeros((0, 4), dtype=np_dtype), labels=[]))) continue - is_valid: List[bool] = [] - box_targets: List[List[float]] = [] - for box in file_dic["coordinates"]: - if rotated_bbox: - x, y, w, h, alpha = fit_rbbox(np.asarray(box, dtype=np.float32)) - box = [x, y, w, h, alpha] - is_valid.append(w > 0 and h > 0) - else: - xs, ys = zip(*box) - box = [min(xs), min(ys), max(xs), max(ys)] - is_valid.append(box[0] < box[2] and box[1] < box[3]) - if is_valid[-1]: - box_targets.append(box) + # Unpack the straight boxes (xmin, ymin, xmax, ymax) + geoms = [list(map(float, obj["geometry"][:4])) for obj in annotations["typed_words"]] + if use_polygons: + # (x, y) coordinates of top left, top right, bottom right, bottom left corners + geoms = [ + [geom[:2], [geom[2], geom[1]], geom[2:], [geom[0], geom[3]]] # type: ignore[list-item] + for geom in geoms + ] + + text_targets = [obj["value"] for obj in annotations["typed_words"]] - text_targets = [word for word, _valid in zip(file_dic["string"], is_valid) if _valid] - self.data.append((img_name, dict(boxes=np.asarray(box_targets, dtype=np.float32), labels=text_targets)))
    + self.data.append((img_name, dict(boxes=np.asarray(geoms, dtype=np_dtype), labels=text_targets)))
    @@ -383,8 +402,8 @@

    Source code for doctr.datasets.ocr

           
         
       
    - - + + diff --git a/v0.4.0/_modules/doctr/datasets/recognition.html b/v0.4.0/_modules/doctr/datasets/recognition.html index 512c70c308..1754789364 100644 --- a/v0.4.0/_modules/doctr/datasets/recognition.html +++ b/v0.4.0/_modules/doctr/datasets/recognition.html @@ -13,7 +13,7 @@ - + doctr.datasets.recognition - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.datasets.recognition

         
       
    - + diff --git a/v0.4.0/_modules/doctr/datasets/sroie.html b/v0.4.0/_modules/doctr/datasets/sroie.html index 66fd4ca3e0..04cf10bda2 100644 --- a/v0.4.0/_modules/doctr/datasets/sroie.html +++ b/v0.4.0/_modules/doctr/datasets/sroie.html @@ -13,7 +13,7 @@ - + doctr.datasets.sroie - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.sroie

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import csv
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
     from .datasets import VisionDataset
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['SROIE']
    +__all__ = ["SROIE"]
     
     
     
    -[docs] +[docs] class SROIE(VisionDataset): """SROIE dataset from `"ICDAR2019 Competition on Scanned Receipt OCR and Information Extraction" <https://arxiv.org/pdf/2103.10213.pdf>`_. - Example:: - >>> from doctr.datasets import SROIE - >>> train_set = SROIE(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/sroie-grid.png&src=0 + :align: center + + >>> from doctr.datasets import SROIE + >>> train_set = SROIE(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - TRAIN = ('https://github.com/mindee/doctr/releases/download/v0.1.1/sroie2019_train_task1.zip', - 'd4fa9e60abb03500d83299c845b9c87fd9c9430d1aeac96b83c5d0bb0ab27f6f') - TEST = ('https://github.com/mindee/doctr/releases/download/v0.1.1/sroie2019_test.zip', - '41b3c746a20226fddc80d86d4b2a903d43b5be4f521dd1bbe759dbf8844745e2') + TRAIN = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/sroie2019_train_task1.zip&src=0", + "d4fa9e60abb03500d83299c845b9c87fd9c9430d1aeac96b83c5d0bb0ab27f6f", + "sroie2019_train_task1.zip", + ) + TEST = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/sroie2019_test.zip&src=0", + "41b3c746a20226fddc80d86d4b2a903d43b5be4f521dd1bbe759dbf8844745e2", + "sroie2019_test.zip", + ) def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + url, sha256, name = self.TRAIN if train else self.TEST + super().__init__( + url, + name, + sha256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - url, sha256 = self.TRAIN if train else self.TEST - super().__init__(url, None, sha256, True, **kwargs) - self.sample_transforms = sample_transforms self.train = train - if rotated_bbox: - raise NotImplementedError + tmp_root = os.path.join(self.root, "images") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] + np_dtype = np.float32 - # # List images - self.root = os.path.join(self._root, 'images') - self.data: List[Tuple[str, Dict[str, Any]]] = [] - for img_path in os.listdir(self.root): + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking SROIE", total=len(os.listdir(tmp_root))): # File existence check - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem - _targets = [] - with open(os.path.join(self._root, 'annotations', f"{stem}.txt"), encoding='latin') as f: - for row in csv.reader(f, delimiter=','): - # Safeguard for blank lines - if len(row) > 0: - # Label may contain commas - label = ",".join(row[8:]) - # Reduce 8 coords to 4 - p1_x, p1_y, p2_x, p2_y, p3_x, p3_y, p4_x, p4_y = map(int, row[:8]) - left, right = min(p1_x, p2_x, p3_x, p4_x), max(p1_x, p2_x, p3_x, p4_x) - top, bot = min(p1_y, p2_y, p3_y, p4_y), max(p1_y, p2_y, p3_y, p4_y) - if len(label) > 0: - _targets.append((label, [left, top, right, bot])) - - text_targets, box_targets = zip(*_targets) - - self.data.append((img_path, dict(boxes=np.asarray(box_targets, dtype=np.float32), labels=text_targets))) + with open(os.path.join(self.root, "annotations", f"{stem}.txt"), encoding="latin") as f: + _rows = [row for row in list(csv.reader(f, delimiter=",")) if len(row) > 0] + + labels = [",".join(row[8:]) for row in _rows] + # reorder coordinates (8 -> (4,2) -> + # (x, y) coordinates of top left, top right, bottom right, bottom left corners) and filter empty lines + coords: np.ndarray = np.stack( + [np.array(list(map(int, row[:8])), dtype=np_dtype).reshape((4, 2)) for row in _rows], axis=0 + ) + + if not use_polygons: + # xmin, ymin, xmax, ymax + coords = np.concatenate((coords.min(axis=1), coords.max(axis=1)), axis=1) + + if recognition_task: + crops = crop_bboxes_from_image(img_path=os.path.join(tmp_root, img_path), geoms=coords) + for crop, label in zip(crops, labels): + if crop.shape[0] > 0 and crop.shape[1] > 0 and len(label) > 0: + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, coords)) + else: + self.data.append((img_path, dict(boxes=coords, labels=labels))) + + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -390,8 +444,8 @@

    Source code for doctr.datasets.sroie

           
         
       
    -
    - + + diff --git a/v0.4.0/_modules/doctr/datasets/svhn.html b/v0.4.0/_modules/doctr/datasets/svhn.html index 48e4e4d210..60e02b1b3b 100644 --- a/v0.4.0/_modules/doctr/datasets/svhn.html +++ b/v0.4.0/_modules/doctr/datasets/svhn.html @@ -13,7 +13,7 @@ - + doctr.datasets.svhn - docTR documentation @@ -473,7 +473,7 @@

    Source code for doctr.datasets.svhn

         
       
    - + diff --git a/v0.4.0/_modules/doctr/datasets/svt.html b/v0.4.0/_modules/doctr/datasets/svt.html index 4144dc6b9b..a997fcbb50 100644 --- a/v0.4.0/_modules/doctr/datasets/svt.html +++ b/v0.4.0/_modules/doctr/datasets/svt.html @@ -13,7 +13,7 @@ - + doctr.datasets.svt - docTR documentation @@ -459,7 +459,7 @@

    Source code for doctr.datasets.svt

         
       
    - + diff --git a/v0.4.0/_modules/doctr/datasets/synthtext.html b/v0.4.0/_modules/doctr/datasets/synthtext.html index 3b9de506a7..c776e1d673 100644 --- a/v0.4.0/_modules/doctr/datasets/synthtext.html +++ b/v0.4.0/_modules/doctr/datasets/synthtext.html @@ -13,7 +13,7 @@ - + doctr.datasets.synthtext - docTR documentation @@ -470,7 +470,7 @@

    Source code for doctr.datasets.synthtext

         
       
    - + diff --git a/v0.4.0/_modules/doctr/datasets/utils.html b/v0.4.0/_modules/doctr/datasets/utils.html index 2259698c0f..bde9304597 100644 --- a/v0.4.0/_modules/doctr/datasets/utils.html +++ b/v0.4.0/_modules/doctr/datasets/utils.html @@ -13,7 +13,7 @@ - + doctr.datasets.utils - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.utils

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import string
     import unicodedata
    +from collections.abc import Sequence
    +from functools import partial
    +from pathlib import Path
    +from typing import Any, Dict, List, Optional, Tuple, TypeVar, Union
    +from typing import Sequence as SequenceType
    +
     import numpy as np
    -from typing import List, Optional, Any
    +from PIL import Image
    +
    +from doctr.io.image import get_img_shape
    +from doctr.utils.geometry import convert_to_relative_coords, extract_crops, extract_rcrops
     
     from .vocabs import VOCABS
     
    -__all__ = ['translate', 'encode_sequence', 'decode_sequence', 'encode_sequences']
    +__all__ = ["translate", "encode_string", "decode_sequence", "encode_sequences", "pre_transform_multiclass"]
    +
    +ImageTensor = TypeVar("ImageTensor")
     
     
     def translate(
         input_string: str,
         vocab_name: str,
    -    unknown_char: str = '■',
    +    unknown_char: str = "■",
     ) -> str:
         """Translate a string input in a given vocabulary
     
         Args:
    +    ----
             input_string: input string to translate
             vocab_name: vocabulary to use (french, latin, ...)
             unknown_char: unknown character for non-translatable characters
     
         Returns:
    -        A string translated in a given vocab"""
    -
    +    -------
    +        A string translated in a given vocab
    +    """
         if VOCABS.get(vocab_name) is None:
             raise KeyError("output vocabulary must be in vocabs dictionnary")
     
    -    translated = ''
    +    translated = ""
         for char in input_string:
             if char not in VOCABS[vocab_name]:
                 # we need to translate char into a vocab char
    @@ -315,51 +350,63 @@ 

    Source code for doctr.datasets.utils

                     # remove whitespaces
                     continue
                 # normalize character if it is not in vocab
    -            char = unicodedata.normalize('NFD', char).encode('ascii', 'ignore').decode('ascii')
    -            if char == '' or char not in VOCABS[vocab_name]:
    +            char = unicodedata.normalize("NFD", char).encode("ascii", "ignore").decode("ascii")
    +            if char == "" or char not in VOCABS[vocab_name]:
                     # if normalization fails or char still not in vocab, return unknown character)
                     char = unknown_char
             translated += char
         return translated
     
     
    -def encode_sequence(
    +def encode_string(
         input_string: str,
         vocab: str,
     ) -> List[int]:
         """Given a predefined mapping, encode the string to a sequence of numbers
     
         Args:
    +    ----
             input_string: string to encode
             vocab: vocabulary (string), the encoding is given by the indexing of the character sequence
     
         Returns:
    -        A list encoding the input_string"""
    -
    -    return list(map(vocab.index, input_string))  # type: ignore[arg-type]
    +    -------
    +        A list encoding the input_string
    +    """
    +    try:
    +        return list(map(vocab.index, input_string))
    +    except ValueError:
    +        raise ValueError(
    +            f"some characters cannot be found in 'vocab'. \
    +                         Please check the input string {input_string} and the vocabulary {vocab}"
    +        )
     
     
     def decode_sequence(
    -    input_array: np.array,
    +    input_seq: Union[np.ndarray, SequenceType[int]],
         mapping: str,
     ) -> str:
         """Given a predefined mapping, decode the sequence of numbers to a string
     
         Args:
    -        input_array: array to decode
    +    ----
    +        input_seq: array to decode
             mapping: vocabulary (string), the encoding is given by the indexing of the character sequence
     
         Returns:
    -        A string, decoded from input_array"""
    -
    -    if not input_array.dtype == np.int_ or input_array.max() >= len(mapping):
    +    -------
    +        A string, decoded from input_seq
    +    """
    +    if not isinstance(input_seq, (Sequence, np.ndarray)):
    +        raise TypeError("Invalid sequence type")
    +    if isinstance(input_seq, np.ndarray) and (input_seq.dtype != np.int_ or input_seq.max() >= len(mapping)):
             raise AssertionError("Input must be an array of int, with max less than mapping size")
    -    decoded = ''.join(mapping[idx] for idx in input_array)
    -    return decoded
    +
    +    return "".join(map(mapping.__getitem__, input_seq))
     
     
     
    -[docs] +[docs] def encode_sequences( sequences: List[str], vocab: str, @@ -367,48 +414,53 @@

    Source code for doctr.datasets.utils

         eos: int = -1,
         sos: Optional[int] = None,
         pad: Optional[int] = None,
    -    **kwargs: Any,
    +    dynamic_seq_length: bool = False,
     ) -> np.ndarray:
         """Encode character sequences using a given vocab as mapping
     
         Args:
    +    ----
             sequences: the list of character sequences of size N
             vocab: the ordered vocab to use for encoding
             target_size: maximum length of the encoded data
             eos: encoding of End Of String
             sos: optional encoding of Start Of String
             pad: optional encoding for padding. In case of padding, all sequences are followed by 1 EOS then PAD
    +        dynamic_seq_length: if `target_size` is specified, uses it as upper bound and enables dynamic sequence size
     
         Returns:
    +    -------
             the padded encoded data as a tensor
         """
    -
         if 0 <= eos < len(vocab):
             raise ValueError("argument 'eos' needs to be outside of vocab possible indices")
     
    -    if not isinstance(target_size, int):
    -        target_size = max(len(w) for w in sequences)
    -        if sos:
    -            target_size += 1
    -        if pad:
    -            target_size += 1
    +    if not isinstance(target_size, int) or dynamic_seq_length:
    +        # Maximum string length + EOS
    +        max_length = max(len(w) for w in sequences) + 1
    +        if isinstance(sos, int):
    +            max_length += 1
    +        if isinstance(pad, int):
    +            max_length += 1
    +        target_size = max_length if not isinstance(target_size, int) else min(max_length, target_size)
     
         # Pad all sequences
    -    if pad:  # pad with padding symbol
    +    if isinstance(pad, int):  # pad with padding symbol
             if 0 <= pad < len(vocab):
                 raise ValueError("argument 'pad' needs to be outside of vocab possible indices")
             # In that case, add EOS at the end of the word before padding
    -        encoded_data = np.full([len(sequences), target_size], pad, dtype=np.int32)
    +        default_symbol = pad
         else:  # pad with eos symbol
    -        encoded_data = np.full([len(sequences), target_size], eos, dtype=np.int32)
    +        default_symbol = eos
    +    encoded_data: np.ndarray = np.full([len(sequences), target_size], default_symbol, dtype=np.int32)
     
    -    for idx, seq in enumerate(sequences):
    -        encoded_seq = encode_sequence(seq, vocab)
    -        if pad:  # add eos at the end of the sequence
    -            encoded_seq.append(eos)
    -        encoded_data[idx, :min(len(encoded_seq), target_size)] = encoded_seq[:min(len(encoded_seq), target_size)]
    +    # Encode the strings
    +    for idx, seq in enumerate(map(partial(encode_string, vocab=vocab), sequences)):
    +        if isinstance(pad, int):  # add eos at the end of the sequence
    +            seq.append(eos)
    +        encoded_data[idx, : min(len(seq), target_size)] = seq[: min(len(seq), target_size)]
     
    -    if sos:  # place eos symbol at the beginning of each sequence
    +    if isinstance(sos, int):  # place sos symbol at the beginning of each sequence
             if 0 <= sos < len(vocab):
                 raise ValueError("argument 'sos' needs to be outside of vocab possible indices")
             encoded_data = np.roll(encoded_data, 1)
    @@ -416,6 +468,59 @@ 

    Source code for doctr.datasets.utils

     
         return encoded_data
    + + +def convert_target_to_relative( + img: ImageTensor, target: Union[np.ndarray, Dict[str, Any]] +) -> Tuple[ImageTensor, Union[Dict[str, Any], np.ndarray]]: + if isinstance(target, np.ndarray): + target = convert_to_relative_coords(target, get_img_shape(img)) + else: + target["boxes"] = convert_to_relative_coords(target["boxes"], get_img_shape(img)) + return img, target + + +def crop_bboxes_from_image(img_path: Union[str, Path], geoms: np.ndarray) -> List[np.ndarray]: + """Crop a set of bounding boxes from an image + + Args: + ---- + img_path: path to the image + geoms: a array of polygons of shape (N, 4, 2) or of straight boxes of shape (N, 4) + + Returns: + ------- + a list of cropped images + """ + with Image.open(img_path) as pil_img: + img: np.ndarray = np.array(pil_img.convert("RGB")) + # Polygon + if geoms.ndim == 3 and geoms.shape[1:] == (4, 2): + return extract_rcrops(img, geoms.astype(dtype=int)) + if geoms.ndim == 2 and geoms.shape[1] == 4: + return extract_crops(img, geoms.astype(dtype=int)) + raise ValueError("Invalid geometry format") + + +def pre_transform_multiclass(img, target: Tuple[np.ndarray, List]) -> Tuple[np.ndarray, Dict[str, List]]: + """Converts multiclass target to relative coordinates. + + Args: + ---- + img: Image + target: tuple of target polygons and their classes names + + Returns: + ------- + Image and dictionary of boxes, with class names as keys + """ + boxes = convert_to_relative_coords(target[0], get_img_shape(img)) + boxes_classes = target[1] + boxes_dict: Dict = {k: [] for k in sorted(set(boxes_classes))} + for k, poly in zip(boxes_classes, boxes): + boxes_dict[k].append(poly) + boxes_dict = {k: np.stack(v, axis=0) for k, v in boxes_dict.items()} + return img, boxes_dict
    @@ -448,8 +553,8 @@

    Source code for doctr.datasets.utils

           
         
       
    - - + + diff --git a/v0.4.0/_modules/doctr/datasets/wildreceipt.html b/v0.4.0/_modules/doctr/datasets/wildreceipt.html index c543ee7cac..12c6aebd14 100644 --- a/v0.4.0/_modules/doctr/datasets/wildreceipt.html +++ b/v0.4.0/_modules/doctr/datasets/wildreceipt.html @@ -13,7 +13,7 @@ - + doctr.datasets.wildreceipt - docTR documentation @@ -454,7 +454,7 @@

    Source code for doctr.datasets.wildreceipt

         
       
    - + diff --git a/v0.4.0/_modules/doctr/documents/elements.html b/v0.4.0/_modules/doctr/documents/elements.html deleted file mode 100644 index 10c1e142d2..0000000000 --- a/v0.4.0/_modules/doctr/documents/elements.html +++ /dev/null @@ -1,577 +0,0 @@ - - - - - - - - - - - - doctr.documents.elements - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.elements

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import matplotlib.pyplot as plt
    -from typing import Tuple, Dict, List, Any, Optional, Union
    -
    -from doctr.utils.geometry import resolve_enclosing_bbox, resolve_enclosing_rbbox
    -from doctr.utils.visualization import visualize_page
    -from doctr.utils.common_types import BoundingBox, RotatedBbox
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['Element', 'Word', 'Artefact', 'Line', 'Block', 'Page', 'Document']
    -
    -
    -class Element(NestedObject):
    -    """Implements an abstract document element with exporting and text rendering capabilities"""
    -
    -    _exported_keys: List[str] = []
    -
    -    def __init__(self, **kwargs: Any) -> None:
    -        self._children_names: List[str] = []
    -        for k, v in kwargs.items():
    -            setattr(self, k, v)
    -            self._children_names.append(k)
    -
    -    def export(self) -> Dict[str, Any]:
    -        """Exports the object into a nested dict format"""
    -
    -        export_dict = {k: getattr(self, k) for k in self._exported_keys}
    -        for children_name in self._children_names:
    -            export_dict[children_name] = [c.export() for c in getattr(self, children_name)]
    -
    -        return export_dict
    -
    -    def render(self) -> str:
    -        raise NotImplementedError
    -
    -
    -
    -[docs] -class Word(Element): - """Implements a word element - - Args: - value: the text string of the word - confidence: the confidence associated with the text prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size - """ - - _exported_keys: List[str] = ["value", "confidence", "geometry"] - - def __init__(self, value: str, confidence: float, geometry: Union[BoundingBox, RotatedBbox]) -> None: - super().__init__() - self.value = value - self.confidence = confidence - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return self.value - - def extra_repr(self) -> str: - return f"value='{self.value}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Artefact(Element): - """Implements a non-textual element - - Args: - artefact_type: the type of artefact - confidence: the confidence of the type prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. - """ - - _exported_keys: List[str] = ["geometry", "type", "confidence"] - - def __init__(self, artefact_type: str, confidence: float, geometry: BoundingBox) -> None: - super().__init__() - self.geometry = geometry - self.type = artefact_type - self.confidence = confidence - - def render(self) -> str: - """Renders the full text of the element""" - return f"[{self.type.upper()}]" - - def extra_repr(self) -> str: - return f"type='{self.type}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Line(Element): - """Implements a line element as a collection of words - - Args: - words: list of word elements - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all words in it. - """ - - _exported_keys: List[str] = ["geometry"] - words: List[Word] = [] - - def __init__( - self, - words: List[Word], - geometry: Optional[Union[BoundingBox, RotatedBbox]] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - # Check whether this is a rotated or straight box - box_resolution_fn = resolve_enclosing_rbbox if len(words[0].geometry) == 5 else resolve_enclosing_bbox - geometry = box_resolution_fn([w.geometry for w in words]) # type: ignore[operator, misc] - - super().__init__(words=words) - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return " ".join(w.render() for w in self.words)
    - - - -
    -[docs] -class Block(Element): - """Implements a block element as a collection of lines and artefacts - - Args: - lines: list of line elements - artefacts: list of artefacts - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all lines and artefacts in it. - """ - - _exported_keys: List[str] = ["geometry"] - lines: List[Line] = [] - artefacts: List[Artefact] = [] - - def __init__( - self, - lines: List[Line] = [], - artefacts: List[Artefact] = [], - geometry: Optional[Union[BoundingBox, RotatedBbox]] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - line_boxes = [word.geometry for line in lines for word in line.words] - artefact_boxes = [artefact.geometry for artefact in artefacts] - box_resolution_fn = resolve_enclosing_rbbox if len(lines[0].geometry) == 5 else resolve_enclosing_bbox - geometry = box_resolution_fn(line_boxes + artefact_boxes) # type: ignore[operator, arg-type] - - super().__init__(lines=lines, artefacts=artefacts) - self.geometry = geometry - - def render(self, line_break: str = '\n') -> str: - """Renders the full text of the element""" - return line_break.join(line.render() for line in self.lines)
    - - - -
    -[docs] -class Page(Element): - """Implements a page element as a collection of blocks - - Args: - blocks: list of block elements - page_idx: the index of the page in the input raw document - dimensions: the page size in pixels in format (width, height) - orientation: a dictionary with the value of the rotation angle in degress and confidence of the prediction - language: a dictionary with the language value and confidence of the prediction - """ - - _exported_keys: List[str] = ["page_idx", "dimensions", "orientation", "language"] - blocks: List[Block] = [] - - def __init__( - self, - blocks: List[Block], - page_idx: int, - dimensions: Tuple[int, int], - orientation: Optional[Dict[str, Any]] = None, - language: Optional[Dict[str, Any]] = None, - ) -> None: - super().__init__(blocks=blocks) - self.page_idx = page_idx - self.dimensions = dimensions - self.orientation = orientation if isinstance(orientation, dict) else dict(value=None, confidence=None) - self.language = language if isinstance(language, dict) else dict(value=None, confidence=None) - - def render(self, block_break: str = '\n\n') -> str: - """Renders the full text of the element""" - return block_break.join(b.render() for b in self.blocks) - - def extra_repr(self) -> str: - return f"dimensions={self.dimensions}" - -
    -[docs] - def show( - self, page: np.ndarray, interactive: bool = True, **kwargs - ) -> None: - """Overlay the result on a given image - - Args: - page: image encoded as a numpy array in uint8 - interactive: whether the display should be interactive - """ - visualize_page(self.export(), page, interactive=interactive) - plt.show(**kwargs)
    -
    - - - -
    -[docs] -class Document(Element): - """Implements a document element as a collection of pages - - Args: - pages: list of page elements - """ - - pages: List[Page] = [] - - def __init__( - self, - pages: List[Page], - ) -> None: - super().__init__(pages=pages) - - def render(self, page_break: str = '\n\n\n\n') -> str: - """Renders the full text of the element""" - return page_break.join(p.render() for p in self.pages) - -
    -[docs] - def show(self, pages: List[np.ndarray], **kwargs) -> None: - """Overlay the result on a given image - - Args: - pages: list of images encoded as numpy arrays in uint8 - """ - for img, result in zip(pages, self.pages): - result.show(img, **kwargs)
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.0/_modules/doctr/documents/reader.html b/v0.4.0/_modules/doctr/documents/reader.html deleted file mode 100644 index cdcd814b6c..0000000000 --- a/v0.4.0/_modules/doctr/documents/reader.html +++ /dev/null @@ -1,612 +0,0 @@ - - - - - - - - - - - - doctr.documents.reader - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.reader

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import cv2
    -from pathlib import Path
    -import fitz
    -from weasyprint import HTML
    -from typing import List, Tuple, Optional, Any, Union, Sequence, Dict
    -
    -__all__ = ['read_pdf', 'read_img', 'read_html', 'DocumentFile', 'PDF']
    -
    -
    -AbstractPath = Union[str, Path]
    -AbstractFile = Union[AbstractPath, bytes]
    -Bbox = Tuple[float, float, float, float]
    -
    -
    -
    -[docs] -def read_img( - file: AbstractFile, - output_size: Optional[Tuple[int, int]] = None, - rgb_output: bool = True, -) -> np.ndarray: - """Read an image file into numpy format - - Example:: - >>> from doctr.documents import read_img - >>> page = read_img("path/to/your/doc.jpg") - - Args: - file: the path to the image file - output_size: the expected output size of each page in format H x W - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - Returns: - the page decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)): - if not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - img = cv2.imread(str(file), cv2.IMREAD_COLOR) - elif isinstance(file, bytes): - file = np.frombuffer(file, np.uint8) - img = cv2.imdecode(file, cv2.IMREAD_COLOR) - else: - raise TypeError("unsupported object type for argument 'file'") - - # Validity check - if img is None: - raise ValueError("unable to read file.") - # Resizing - if isinstance(output_size, tuple): - img = cv2.resize(img, output_size[::-1], interpolation=cv2.INTER_LINEAR) - # Switch the channel order - if rgb_output: - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - return img
    - - - -
    -[docs] -def read_pdf(file: AbstractFile, **kwargs: Any) -> fitz.Document: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_pdf - >>> doc = read_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)) and not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - - fitz_args: Dict[str, AbstractFile] = {} - - if isinstance(file, (str, Path)): - fitz_args['filename'] = file - elif isinstance(file, bytes): - fitz_args['stream'] = file - else: - raise TypeError("unsupported object type for argument 'file'") - - # Read pages with fitz and convert them to numpy ndarrays - return fitz.open(**fitz_args, filetype="pdf", **kwargs)
    - - - -def convert_page_to_numpy( - page: fitz.fitz.Page, - output_size: Optional[Tuple[int, int]] = None, - bgr_output: bool = False, - default_scales: Tuple[float, float] = (2, 2), -) -> np.ndarray: - """Convert a fitz page to a numpy-formatted image - - Args: - page: the page of a file read with PyMuPDF - output_size: the expected output size of each page in format H x W. Default goes to 840 x 595 for A4 pdf, - if you want to increase the resolution while preserving the original A4 aspect ratio can pass (1024, 726) - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - default_scales: spatial scaling to be applied when output_size is not specified where (1, 1) - corresponds to 72 dpi rendering. - - Returns: - the rendered image in numpy format - """ - - # If no output size is specified, keep the origin one - if output_size is not None: - scales = (output_size[1] / page.MediaBox[2], output_size[0] / page.MediaBox[3]) - else: - # Default 72 DPI (scales of (1, 1)) is unnecessarily low - scales = default_scales - - transform_matrix = fitz.Matrix(*scales) - - # Generate the pixel map using the transformation matrix - pixmap = page.getPixmap(matrix=transform_matrix) - # Decode it into a numpy - img = np.frombuffer(pixmap.samples, dtype=np.uint8).reshape(pixmap.height, pixmap.width, 3) - - # Switch the channel order - if bgr_output: - img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) - - return img - - -
    -[docs] -def read_html(url: str, **kwargs: Any) -> bytes: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_html - >>> doc = read_html("https://www.yoursite.com") - - Args: - url: URL of the target web page - Returns: - decoded PDF file as a bytes stream - """ - - return HTML(url, **kwargs).write_pdf()
    - - - -
    -[docs] -class PDF: - """PDF document template - - Args: - doc: input PDF document - """ - def __init__(self, doc: fitz.Document) -> None: - self.doc = doc - -
    -[docs] - def as_images(self, **kwargs) -> List[np.ndarray]: - """Convert all document pages to images - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images() - - Args: - kwargs: keyword arguments of `convert_page_to_numpy` - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - return [convert_page_to_numpy(page, **kwargs) for page in self.doc]
    - - - def get_page_words(self, idx, **kwargs) -> List[Tuple[Bbox, str]]: - """Get the annotations for all words of a given page""" - - # xmin, ymin, xmax, ymax, value, block_idx, line_idx, word_idx - return [(info[:4], info[4]) for info in self.doc[idx].getTextWords(**kwargs)] - -
    -[docs] - def get_words(self, **kwargs) -> List[List[Tuple[Bbox, str]]]: - """Get the annotations for all words in the document - - Example:: - >>> from doctr.documents import DocumentFile - >>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words() - - Args: - kwargs: keyword arguments of `fitz.Page.getTextWords` - Returns: - the list of pages annotations, represented as a list of tuple (bounding box, value) - """ - return [self.get_page_words(idx, **kwargs) for idx in range(len(self.doc))]
    - - - def get_page_artefacts(self, idx) -> List[Tuple[float, float, float, float]]: - return [tuple(self.doc[idx].getImageBbox(artefact)) # type: ignore[misc] - for artefact in self.doc[idx].get_images(full=True)] - -
    -[docs] - def get_artefacts(self) -> List[List[Tuple[float, float, float, float]]]: - """Get the artefacts for the entire document - - Example:: - >>> from doctr.documents import DocumentFile - >>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts() - - Returns: - the list of pages artefacts, represented as a list of bounding boxes - """ - - return [self.get_page_artefacts(idx) for idx in range(len(self.doc))]
    -
    - - - -
    -[docs] -class DocumentFile: - """Read a document from multiple extensions""" - -
    -[docs] - @classmethod - def from_pdf(cls, file: AbstractFile, **kwargs) -> PDF: - """Read a PDF file - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file or a binary stream - Returns: - a PDF document - """ - - doc = read_pdf(file, **kwargs) - - return PDF(doc)
    - - -
    -[docs] - @classmethod - def from_url(cls, url: str, **kwargs) -> PDF: - """Interpret a web page as a PDF document - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_url("https://www.yoursite.com") - - Args: - url: the URL of the target web page - Returns: - a PDF document - """ - pdf_stream = read_html(url) - return cls.from_pdf(pdf_stream, **kwargs)
    - - -
    -[docs] - @classmethod - def from_images(cls, files: Union[Sequence[AbstractFile], AbstractFile], **kwargs) -> List[np.ndarray]: - """Read an image file (or a collection of image files) and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"]) - - Args: - files: the path to the image file or a binary stream, or a collection of those - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - if isinstance(files, (str, Path, bytes)): - files = [files] - - return [read_img(file, **kwargs) for file in files]
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.0/_modules/doctr/io/elements.html b/v0.4.0/_modules/doctr/io/elements.html index 753a47455c..e049d6ce30 100644 --- a/v0.4.0/_modules/doctr/io/elements.html +++ b/v0.4.0/_modules/doctr/io/elements.html @@ -13,7 +13,7 @@ - + doctr.io.elements - docTR documentation @@ -1008,7 +1008,7 @@

    Source code for doctr.io.elements

         
       
    - + diff --git a/v0.4.0/_modules/doctr/io/html.html b/v0.4.0/_modules/doctr/io/html.html index 7ad5b97031..be73631500 100644 --- a/v0.4.0/_modules/doctr/io/html.html +++ b/v0.4.0/_modules/doctr/io/html.html @@ -13,7 +13,7 @@ - + doctr.io.html - docTR documentation @@ -360,7 +360,7 @@

    Source code for doctr.io.html

         
       
    - + diff --git a/v0.4.0/_modules/doctr/io/image/base.html b/v0.4.0/_modules/doctr/io/image/base.html index 336b4bff0e..a50c95d595 100644 --- a/v0.4.0/_modules/doctr/io/image/base.html +++ b/v0.4.0/_modules/doctr/io/image/base.html @@ -13,7 +13,7 @@ - + doctr.io.image.base - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.io.image.base

         
       
    - + diff --git a/v0.4.0/_modules/doctr/io/image/tensorflow.html b/v0.4.0/_modules/doctr/io/image/tensorflow.html index f1846820a3..3b9e731756 100644 --- a/v0.4.0/_modules/doctr/io/image/tensorflow.html +++ b/v0.4.0/_modules/doctr/io/image/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.io.image.tensorflow - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.io.image.tensorflow

         
       
    - + diff --git a/v0.4.0/_modules/doctr/io/pdf.html b/v0.4.0/_modules/doctr/io/pdf.html index e3abf6960b..e5b94811c3 100644 --- a/v0.4.0/_modules/doctr/io/pdf.html +++ b/v0.4.0/_modules/doctr/io/pdf.html @@ -13,7 +13,7 @@ - + doctr.io.pdf - docTR documentation @@ -377,7 +377,7 @@

    Source code for doctr.io.pdf

         
       
    - + diff --git a/v0.4.0/_modules/doctr/io/reader.html b/v0.4.0/_modules/doctr/io/reader.html index c1ddc26edd..d36e5bb553 100644 --- a/v0.4.0/_modules/doctr/io/reader.html +++ b/v0.4.0/_modules/doctr/io/reader.html @@ -13,7 +13,7 @@ - + doctr.io.reader - docTR documentation @@ -426,7 +426,7 @@

    Source code for doctr.io.reader

         
       
    - + diff --git a/v0.4.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html b/v0.4.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html index 9f074805c1..61a010d548 100644 --- a/v0.4.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html +++ b/v0.4.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.magc_resnet.tensorflow - docTR documentation @@ -531,7 +531,7 @@

    Source code for doctr.models.classification.magc_resnet.tensorflow

    - + diff --git a/v0.4.0/_modules/doctr/models/classification/mobilenet/tensorflow.html b/v0.4.0/_modules/doctr/models/classification/mobilenet/tensorflow.html index 6a63851276..7c448394ad 100644 --- a/v0.4.0/_modules/doctr/models/classification/mobilenet/tensorflow.html +++ b/v0.4.0/_modules/doctr/models/classification/mobilenet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.mobilenet.tensorflow - docTR documentation @@ -793,7 +793,7 @@

    Source code for doctr.models.classification.mobilenet.tensorflow

    - + diff --git a/v0.4.0/_modules/doctr/models/classification/resnet/tensorflow.html b/v0.4.0/_modules/doctr/models/classification/resnet/tensorflow.html index 095d377f31..aed4343741 100644 --- a/v0.4.0/_modules/doctr/models/classification/resnet/tensorflow.html +++ b/v0.4.0/_modules/doctr/models/classification/resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.resnet.tensorflow - docTR documentation @@ -749,7 +749,7 @@

    Source code for doctr.models.classification.resnet.tensorflow

    - + diff --git a/v0.4.0/_modules/doctr/models/classification/textnet/tensorflow.html b/v0.4.0/_modules/doctr/models/classification/textnet/tensorflow.html index ad254ebbfb..c5567d7d67 100644 --- a/v0.4.0/_modules/doctr/models/classification/textnet/tensorflow.html +++ b/v0.4.0/_modules/doctr/models/classification/textnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.textnet.tensorflow - docTR documentation @@ -611,7 +611,7 @@

    Source code for doctr.models.classification.textnet.tensorflow

    - + diff --git a/v0.4.0/_modules/doctr/models/classification/vgg/tensorflow.html b/v0.4.0/_modules/doctr/models/classification/vgg/tensorflow.html index 01ae452624..788111ae87 100644 --- a/v0.4.0/_modules/doctr/models/classification/vgg/tensorflow.html +++ b/v0.4.0/_modules/doctr/models/classification/vgg/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vgg.tensorflow - docTR documentation @@ -451,7 +451,7 @@

    Source code for doctr.models.classification.vgg.tensorflow

    - + diff --git a/v0.4.0/_modules/doctr/models/classification/vit/tensorflow.html b/v0.4.0/_modules/doctr/models/classification/vit/tensorflow.html index 1333cf6045..971ba5abe9 100644 --- a/v0.4.0/_modules/doctr/models/classification/vit/tensorflow.html +++ b/v0.4.0/_modules/doctr/models/classification/vit/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vit.tensorflow - docTR documentation @@ -533,7 +533,7 @@

    Source code for doctr.models.classification.vit.tensorflow

    - + diff --git a/v0.4.0/_modules/doctr/models/classification/zoo.html b/v0.4.0/_modules/doctr/models/classification/zoo.html index f7796a7522..3eb2a3ec4e 100644 --- a/v0.4.0/_modules/doctr/models/classification/zoo.html +++ b/v0.4.0/_modules/doctr/models/classification/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.classification.zoo - docTR documentation @@ -447,7 +447,7 @@

    Source code for doctr.models.classification.zoo

    <
    - + diff --git a/v0.4.0/_modules/doctr/models/detection/differentiable_binarization.html b/v0.4.0/_modules/doctr/models/detection/differentiable_binarization.html deleted file mode 100644 index 38e9b36ec2..0000000000 --- a/v0.4.0/_modules/doctr/models/detection/differentiable_binarization.html +++ /dev/null @@ -1,879 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.differentiable_binarization - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.differentiable_binarization

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -import cv2
    -from copy import deepcopy
    -import numpy as np
    -from shapely.geometry import Polygon
    -import pyclipper
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers
    -from typing import Union, List, Tuple, Optional, Any, Dict
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..utils import IntermediateLayerGetter, load_pretrained_params, conv_sequence
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['DBPostProcessor', 'DBNet', 'db_resnet50']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'db_resnet50': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'backbone': 'ResNet50',
    -        'fpn_layers': ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"],
    -        'fpn_channels': 128,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'DBPostProcessor',
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.2.0/db_resnet50-adcafc63.zip',
    -    },
    -}
    -
    -
    -class DBPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for DBNet adapted from the implementation of `xuannianz
    -    <https://github.com/xuannianz/DifferentiableBinarization>`_.
    -
    -    Args:
    -        unclip ratio: ratio used to unshrink polygons
    -        min_size_box: minimal length (pix) to keep a box
    -        max_candidates: maximum boxes to consider in a single page
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        unclip_ratio: Union[float, int] = 1.5,
    -        max_candidates: int = 1000,
    -        box_thresh: float = 0.1,
    -        bin_thresh: float = 0.3,
    -    ) -> None:
    -
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -        self.unclip_ratio = unclip_ratio
    -        self.max_candidates = max_candidates
    -
    -    def polygon_to_box(
    -        self,
    -        points: np.ndarray,
    -    ) -> Optional[Tuple[int, int, int, int]]:
    -        """Expand a polygon (points) by a factor unclip_ratio, and returns a 4-points box
    -
    -        Args:
    -            points: The first parameter.
    -
    -        Returns:
    -            a box in absolute coordinates (x, y, w, h)
    -        """
    -        poly = Polygon(points)
    -        distance = poly.area * self.unclip_ratio / poly.length  # compute distance to expand polygon
    -        offset = pyclipper.PyclipperOffset()
    -        offset.AddPath(points, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        _points = offset.Execute(distance)
    -        # Take biggest stack of points
    -        idx = 0
    -        if len(_points) > 1:
    -            max_size = 0
    -            for _idx, p in enumerate(_points):
    -                if len(p) > max_size:
    -                    idx = _idx
    -                    max_size = len(p)
    -            # We ensure that _points can be correctly casted to a ndarray
    -            _points = [_points[idx]]
    -        expanded_points = np.asarray(_points)  # expand polygon
    -        if len(expanded_points) < 1:
    -            return None
    -        x, y, w, h = cv2.boundingRect(expanded_points)  # compute a 4-points box from expanded polygon
    -        return x, y, w, h
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map
    -
    -        Args:
    -            pred: Pred map from differentiable binarization output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        # get contours from connected components on the bitmap
    -        contours, _ = cv2.findContours(bitmap.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    -        for contour in contours[:self.max_candidates]:
    -            # Check whether smallest enclosing bounding box is not too small
    -            if np.any(contour[:, 0].max(axis=0) - contour[:, 0].min(axis=0) < min_size_box):
    -                continue
    -            x, y, w, h = cv2.boundingRect(contour)
    -            points = np.array([[x, y], [x, y + h], [x + w, y + h], [x + w, y]])
    -            # Compute objectness
    -            score = self.box_score(pred, points)
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            _box = self.polygon_to_box(points)
    -
    -            if _box is None or _box[2] < min_size_box or _box[3] < min_size_box:  # remove to small boxes
    -                continue
    -            x, y, w, h = _box
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -class FeaturePyramidNetwork(layers.Layer, NestedObject):
    -    """Feature Pyramid Network as described in `"Feature Pyramid Networks for Object Detection"
    -    <https://arxiv.org/pdf/1612.03144.pdf>`_.
    -
    -    Args:
    -        channels: number of channel to output
    -    """
    -
    -    def __init__(
    -        self,
    -        channels: int,
    -    ) -> None:
    -        super().__init__()
    -        self.channels = channels
    -        self.upsample = layers.UpSampling2D(size=(2, 2), interpolation='nearest')
    -        self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer='he_normal') for _ in range(4)]
    -        self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2 ** idx) for idx in range(4)]
    -
    -    @staticmethod
    -    def build_upsampling(
    -        channels: int,
    -        dilation_factor: int = 1,
    -    ) -> layers.Layer:
    -        """Module which performs a 3x3 convolution followed by up-sampling
    -
    -        Args:
    -            channels: number of output channels
    -            dilation_factor (int): dilation factor to scale the convolution output before concatenation
    -
    -        Returns:
    -            a keras.layers.Layer object, wrapping these operations in a sequential module
    -
    -        """
    -
    -        _layers = conv_sequence(channels, 'relu', True, kernel_size=3)
    -
    -        if dilation_factor > 1:
    -            _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation='nearest'))
    -
    -        module = keras.Sequential(_layers)
    -
    -        return module
    -
    -    def extra_repr(self) -> str:
    -        return f"channels={self.channels}"
    -
    -    def call(
    -        self,
    -        x: List[tf.Tensor],
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # Channel mapping
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.inner_blocks, x)]
    -        # Upsample & sum
    -        for idx in range(len(results) - 1, -1):
    -            results[idx] += self.upsample(results[idx + 1])
    -        # Conv & upsample
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.layer_blocks, results)]
    -
    -        return layers.concatenate(results)
    -
    -
    -class DBNet(DetectionModel, NestedObject):
    -    """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization"
    -    <https://arxiv.org/pdf/1911.08947.pdf>`_.
    -
    -    Args:
    -        feature extractor: the backbone serving as feature extractor
    -        fpn_channels: number of channels each extracted feature maps is mapped to
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'fpn', 'probability_head', 'threshold_head', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: IntermediateLayerGetter,
    -        fpn_channels: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(cfg=cfg)
    -
    -        self.shrink_ratio = 0.4
    -        self.thresh_min = 0.3
    -        self.thresh_max = 0.7
    -        self.min_size_box = 3
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.fpn = FeaturePyramidNetwork(channels=fpn_channels)
    -        # Initialize kernels
    -        _inputs = [layers.Input(shape=in_shape[1:]) for in_shape in self.feat_extractor.output_shape]
    -        output_shape = tuple(self.fpn(_inputs).shape)
    -
    -        self.probability_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -        self.threshold_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -
    -        self.postprocessor = DBPostProcessor()
    -
    -    @staticmethod
    -    def compute_distance(
    -        xs: np.array,
    -        ys: np.array,
    -        a: np.array,
    -        b: np.array,
    -        eps: float = 1e-7,
    -    ) -> float:
    -        """Compute the distance for each point of the map (xs, ys) to the (a, b) segment
    -
    -        Args:
    -            xs : map of x coordinates (height, width)
    -            ys : map of y coordinates (height, width)
    -            a: first point defining the [ab] segment
    -            b: second point defining the [ab] segment
    -
    -        Returns:
    -            The computed distance
    -
    -        """
    -        square_dist_1 = np.square(xs - a[0]) + np.square(ys - a[1])
    -        square_dist_2 = np.square(xs - b[0]) + np.square(ys - b[1])
    -        square_dist = np.square(a[0] - b[0]) + np.square(a[1] - b[1])
    -        cosin = (square_dist - square_dist_1 - square_dist_2) / (2 * np.sqrt(square_dist_1 * square_dist_2) + eps)
    -        square_sin = 1 - np.square(cosin)
    -        square_sin = np.nan_to_num(square_sin)
    -        result = np.sqrt(square_dist_1 * square_dist_2 * square_sin / square_dist)
    -        result[cosin < 0] = np.sqrt(np.fmin(square_dist_1, square_dist_2))[cosin < 0]
    -        return result
    -
    -    def draw_thresh_map(
    -        self,
    -        polygon: np.array,
    -        canvas: np.array,
    -        mask: np.array,
    -    ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    -        """Draw a polygon treshold map on a canvas, as described in the DB paper
    -
    -        Args:
    -            polygon : array of coord., to draw the boundary of the polygon
    -            canvas : threshold map to fill with polygons
    -            mask : mask for training on threshold polygons
    -        """
    -        if polygon.ndim != 2 or polygon.shape[1] != 2:
    -            raise AttributeError("polygon should be a 2 dimensional array of coords")
    -
    -        # Augment polygon by shrink_ratio
    -        polygon_shape = Polygon(polygon)
    -        distance = polygon_shape.area * (1 - np.power(self.shrink_ratio, 2)) / polygon_shape.length
    -        subject = [tuple(coor) for coor in polygon]  # Get coord as list of tuples
    -        padding = pyclipper.PyclipperOffset()
    -        padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        padded_polygon = np.array(padding.Execute(distance)[0])
    -
    -        # Fill the mask with 1 on the new padded polygon
    -        cv2.fillPoly(mask, [padded_polygon.astype(np.int32)], 1.0)
    -
    -        # Get min/max to recover polygon after distance computation
    -        xmin = padded_polygon[:, 0].min()
    -        xmax = padded_polygon[:, 0].max()
    -        ymin = padded_polygon[:, 1].min()
    -        ymax = padded_polygon[:, 1].max()
    -        width = xmax - xmin + 1
    -        height = ymax - ymin + 1
    -        # Get absolute polygon for distance computation
    -        polygon[:, 0] = polygon[:, 0] - xmin
    -        polygon[:, 1] = polygon[:, 1] - ymin
    -        # Get absolute padded polygon
    -        xs = np.broadcast_to(np.linspace(0, width - 1, num=width).reshape(1, width), (height, width))
    -        ys = np.broadcast_to(np.linspace(0, height - 1, num=height).reshape(height, 1), (height, width))
    -
    -        # Compute distance map to fill the padded polygon
    -        distance_map = np.zeros((polygon.shape[0], height, width), dtype=np.float32)
    -        for i in range(polygon.shape[0]):
    -            j = (i + 1) % polygon.shape[0]
    -            absolute_distance = self.compute_distance(xs, ys, polygon[i], polygon[j])
    -            distance_map[i] = np.clip(absolute_distance / distance, 0, 1)
    -        distance_map = np.min(distance_map, axis=0)
    -
    -        # Clip the padded polygon inside the canvas
    -        xmin_valid = min(max(0, xmin), canvas.shape[1] - 1)
    -        xmax_valid = min(max(0, xmax), canvas.shape[1] - 1)
    -        ymin_valid = min(max(0, ymin), canvas.shape[0] - 1)
    -        ymax_valid = min(max(0, ymax), canvas.shape[0] - 1)
    -
    -        # Fill the canvas with the distances computed inside the valid padded polygon
    -        canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1] = np.fmax(
    -            1 - distance_map[
    -                ymin_valid - ymin:ymax_valid - ymin + 1,
    -                xmin_valid - xmin:xmax_valid - xmin + 1
    -            ],
    -            canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1]
    -        )
    -
    -        return polygon, canvas, mask
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor, tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.uint8)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -        thresh_target = np.zeros(output_shape, dtype=np.uint8)
    -        thresh_mask = np.ones(output_shape, dtype=np.uint8)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            polys = np.stack([
    -                abs_boxes[:, [0, 1]],
    -                abs_boxes[:, [0, 3]],
    -                abs_boxes[:, [2, 3]],
    -                abs_boxes[:, [2, 1]],
    -            ], axis=1)
    -
    -            for box, box_size, poly, is_ambiguous in zip(abs_boxes, boxes_size, polys, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -
    -                # Negative shrink for gt, as described in paper
    -                polygon = Polygon(poly)
    -                distance = polygon.area * (1 - np.power(self.shrink_ratio, 2)) / polygon.length
    -                subject = [tuple(coor) for coor in poly]
    -                padding = pyclipper.PyclipperOffset()
    -                padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -                shrinked = padding.Execute(-distance)
    -
    -                # Draw polygon on gt if it is valid
    -                if len(shrinked) == 0:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                shrinked = np.array(shrinked[0]).reshape(-1, 2)
    -                if shrinked.shape[0] <= 2 or not Polygon(shrinked).is_valid:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                cv2.fillPoly(seg_target[idx], [shrinked.astype(np.int32)], 1)
    -
    -                # Draw on both thresh map and thresh mask
    -                poly, thresh_target[idx], thresh_mask[idx] = self.draw_thresh_map(poly, thresh_target[idx],
    -                                                                                  thresh_mask[idx])
    -
    -        thresh_target = thresh_target.astype(np.float32) * (self.thresh_max - self.thresh_min) + self.thresh_min
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -        thresh_target = tf.convert_to_tensor(thresh_target, dtype=tf.float32)
    -        thresh_mask = tf.convert_to_tensor(thresh_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask, thresh_target, thresh_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        thresh_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts, masks, thresh_gts, thresh_masks from a list of boxes
    -        and a list of masks for each image. From there it computes the loss with the model output
    -
    -        Args:
    -            out_map: output feature map of the model of shape (N, H, W, C)
    -            thresh_map: threshold map of shape (N, H, W, C)
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -
    -        prob_map = tf.math.sigmoid(tf.squeeze(out_map, axis=[-1]))
    -        thresh_map = tf.math.sigmoid(tf.squeeze(thresh_map, axis=[-1]))
    -
    -        seg_target, seg_mask, thresh_target, thresh_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute balanced BCE loss for proba_map
    -        bce_scale = 5.
    -        bce_loss = tf.keras.losses.binary_crossentropy(seg_target[..., None], out_map, from_logits=True)[seg_mask]
    -
    -        neg_target = 1 - seg_target[seg_mask]
    -        positive_count = tf.math.reduce_sum(seg_target[seg_mask])
    -        negative_count = tf.math.reduce_min([tf.math.reduce_sum(neg_target), 3. * positive_count])
    -        negative_loss = bce_loss * neg_target
    -        negative_loss, _ = tf.nn.top_k(negative_loss, tf.cast(negative_count, tf.int32))
    -        sum_losses = tf.math.reduce_sum(bce_loss * seg_target[seg_mask]) + tf.math.reduce_sum(negative_loss)
    -        balanced_bce_loss = sum_losses / (positive_count + negative_count + 1e-6)
    -
    -        # Compute dice loss for approxbin_map
    -        bin_map = 1 / (1 + tf.exp(-50. * (prob_map[seg_mask] - thresh_map[seg_mask])))
    -
    -        bce_min = tf.math.reduce_min(bce_loss)
    -        weights = (bce_loss - bce_min) / (tf.math.reduce_max(bce_loss) - bce_min) + 1.
    -        inter = tf.math.reduce_sum(bin_map * seg_target[seg_mask] * weights)
    -        union = tf.math.reduce_sum(bin_map) + tf.math.reduce_sum(seg_target[seg_mask]) + 1e-8
    -        dice_loss = 1 - 2.0 * inter / union
    -
    -        # Compute l1 loss for thresh_map
    -        l1_scale = 10.
    -        if tf.reduce_any(thresh_mask):
    -            l1_loss = tf.math.reduce_mean(tf.math.abs(thresh_map[thresh_mask] - thresh_target[thresh_mask]))
    -        else:
    -            l1_loss = tf.constant(0.)
    -
    -        return l1_scale * l1_loss + bce_scale * balanced_bce_loss + dice_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        feat_maps = self.feat_extractor(x, **kwargs)
    -        feat_concat = self.fpn(feat_maps, **kwargs)
    -        logits = self.probability_head(feat_concat, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            thresh_map = self.threshold_head(feat_concat, **kwargs)
    -            loss = self.compute_loss(logits, thresh_map, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _db_resnet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> DBNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['fpn_channels'] = kwargs.get('fpn_channels', _cfg['fpn_channels'])
    -
    -    # Feature extractor
    -    resnet = tf.keras.applications.__dict__[_cfg['backbone']](
    -        include_top=False,
    -        weights=None,
    -        input_shape=_cfg['input_shape'],
    -        pooling=None,
    -    )
    -
    -    feat_extractor = IntermediateLayerGetter(
    -        resnet,
    -        _cfg['fpn_layers'],
    -    )
    -
    -    kwargs['fpn_channels'] = _cfg['fpn_channels']
    -
    -    # Build the model
    -    model = DBNet(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def db_resnet50(pretrained: bool = False, **kwargs: Any) -> DBNet: - """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" - <https://arxiv.org/pdf/1911.08947.pdf>`_, using a ResNet-50 backbone. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _db_resnet('db_resnet50', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html b/v0.4.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html index 9145c7c3fd..66cef8663d 100644 --- a/v0.4.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html +++ b/v0.4.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.differentiable_binarization.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     # Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
     
     from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers
    -from typing import List, Tuple, Optional, Any, Dict
    +from typing import Any, Dict, List, Optional, Tuple
     
    +import numpy as np
    +import tensorflow as tf
    +from tensorflow.keras import Model, Sequential, layers, losses
    +from tensorflow.keras.applications import ResNet50
    +
    +from doctr.file_utils import CLASS_NAME
    +from doctr.models.utils import (
    +    IntermediateLayerGetter,
    +    _bf16_to_float32,
    +    _build_model,
    +    conv_sequence,
    +    load_pretrained_params,
    +)
     from doctr.utils.repr import NestedObject
    -from doctr.models.utils import IntermediateLayerGetter, load_pretrained_params, conv_sequence
    +
    +from ...classification import mobilenet_v3_large
     from .base import DBPostProcessor, _DBNet
     
    -__all__ = ['DBNet', 'db_resnet50']
    +__all__ = ["DBNet", "db_resnet50", "db_mobilenet_v3_large"]
     
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'db_resnet50': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'backbone': 'ResNet50',
    -        'fpn_layers': ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"],
    -        'fpn_channels': 128,
    -        'input_shape': (1024, 1024, 3),
    -        'rotated_bbox': False,
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.2.0/db_resnet50-adcafc63.zip',
    +    "db_resnet50": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/db_resnet50-649fa22b.weights.h5&src=0",
    +    },
    +    "db_mobilenet_v3_large": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/db_mobilenet_v3_large-ee2e1dbe.weights.h5&src=0",
         },
     }
     
    @@ -313,6 +348,7 @@ 

    Source code for doctr.models.detection.differentiable_binarization.tensorflo <https://arxiv.org/pdf/1612.03144.pdf>`_. Args: + ---- channels: number of channel to output """ @@ -322,9 +358,9 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo ) -> None: super().__init__() self.channels = channels - self.upsample = layers.UpSampling2D(size=(2, 2), interpolation='nearest') - self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer='he_normal') for _ in range(4)] - self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2 ** idx) for idx in range(4)] + self.upsample = layers.UpSampling2D(size=(2, 2), interpolation="nearest") + self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer="he_normal") for _ in range(4)] + self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2**idx) for idx in range(4)] @staticmethod def build_upsampling( @@ -334,20 +370,21 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo """Module which performs a 3x3 convolution followed by up-sampling Args: + ---- channels: number of output channels dilation_factor (int): dilation factor to scale the convolution output before concatenation Returns: + ------- a keras.layers.Layer object, wrapping these operations in a sequential module """ - - _layers = conv_sequence(channels, 'relu', True, kernel_size=3) + _layers = conv_sequence(channels, "relu", True, kernel_size=3) if dilation_factor > 1: - _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation='nearest')) + _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation="nearest")) - module = keras.Sequential(_layers) + module = Sequential(_layers) return module @@ -359,7 +396,6 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo x: List[tf.Tensor], **kwargs: Any, ) -> tf.Tensor: - # Channel mapping results = [block(fmap, **kwargs) for block, fmap in zip(self.inner_blocks, x)] # Upsample & sum @@ -371,200 +407,324 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo return layers.concatenate(results) -class DBNet(_DBNet, keras.Model, NestedObject): +class DBNet(_DBNet, Model, NestedObject): """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" <https://arxiv.org/pdf/1911.08947.pdf>`_. Args: + ---- feature extractor: the backbone serving as feature extractor fpn_channels: number of channels each extracted feature maps is mapped to + bin_thresh: threshold for binarization + box_thresh: minimal objectness score to consider a box + assume_straight_pages: if True, fit straight bounding boxes only + exportable: onnx exportable returns only logits + cfg: the configuration dict of the model + class_names: list of class names """ - _children_names: List[str] = ['feat_extractor', 'fpn', 'probability_head', 'threshold_head', 'postprocessor'] + _children_names: List[str] = ["feat_extractor", "fpn", "probability_head", "threshold_head", "postprocessor"] def __init__( self, feature_extractor: IntermediateLayerGetter, - fpn_channels: int = 128, - rotated_bbox: bool = False, + fpn_channels: int = 128, # to be set to 256 to represent the author's initial idea + bin_thresh: float = 0.3, + box_thresh: float = 0.1, + assume_straight_pages: bool = True, + exportable: bool = False, cfg: Optional[Dict[str, Any]] = None, + class_names: List[str] = [CLASS_NAME], ) -> None: - super().__init__() + self.class_names = class_names + num_classes: int = len(self.class_names) self.cfg = cfg self.feat_extractor = feature_extractor - self.rotated_bbox = rotated_bbox + self.exportable = exportable + self.assume_straight_pages = assume_straight_pages self.fpn = FeaturePyramidNetwork(channels=fpn_channels) # Initialize kernels _inputs = [layers.Input(shape=in_shape[1:]) for in_shape in self.feat_extractor.output_shape] output_shape = tuple(self.fpn(_inputs).shape) - self.probability_head = keras.Sequential( - [ - *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]), - layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'), - layers.BatchNormalization(), - layers.Activation('relu'), - layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'), - ] + self.probability_head = Sequential([ + *conv_sequence(64, "relu", True, kernel_size=3, input_shape=output_shape[1:]), + layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer="he_normal"), + layers.BatchNormalization(), + layers.Activation("relu"), + layers.Conv2DTranspose(num_classes, 2, strides=2, kernel_initializer="he_normal"), + ]) + self.threshold_head = Sequential([ + *conv_sequence(64, "relu", True, kernel_size=3, input_shape=output_shape[1:]), + layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer="he_normal"), + layers.BatchNormalization(), + layers.Activation("relu"), + layers.Conv2DTranspose(num_classes, 2, strides=2, kernel_initializer="he_normal"), + ]) + + self.postprocessor = DBPostProcessor( + assume_straight_pages=assume_straight_pages, bin_thresh=bin_thresh, box_thresh=box_thresh ) - self.threshold_head = keras.Sequential( - [ - *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]), - layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'), - layers.BatchNormalization(), - layers.Activation('relu'), - layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'), - ] - ) - - self.postprocessor = DBPostProcessor(rotated_bbox=rotated_bbox) def compute_loss( self, out_map: tf.Tensor, thresh_map: tf.Tensor, - target: List[Dict[str, Any]] + target: List[Dict[str, np.ndarray]], + gamma: float = 2.0, + alpha: float = 0.5, + eps: float = 1e-8, ) -> tf.Tensor: """Compute a batch of gts, masks, thresh_gts, thresh_masks from a list of boxes and a list of masks for each image. From there it computes the loss with the model output Args: + ---- out_map: output feature map of the model of shape (N, H, W, C) thresh_map: threshold map of shape (N, H, W, C) target: list of dictionary where each dict has a `boxes` and a `flags` entry + gamma: modulating factor in the focal loss formula + alpha: balancing factor in the focal loss formula + eps: epsilon factor in dice loss Returns: + ------- A loss tensor """ + if gamma < 0: + raise ValueError("Value of gamma should be greater than or equal to zero.") - prob_map = tf.math.sigmoid(tf.squeeze(out_map, axis=[-1])) - thresh_map = tf.math.sigmoid(tf.squeeze(thresh_map, axis=[-1])) + prob_map = tf.math.sigmoid(out_map) + thresh_map = tf.math.sigmoid(thresh_map) - seg_target, seg_mask, thresh_target, thresh_mask = self.compute_target(target, out_map.shape[:3]) - seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32) + seg_target, seg_mask, thresh_target, thresh_mask = self.build_target(target, out_map.shape[1:], True) + seg_target = tf.convert_to_tensor(seg_target, dtype=out_map.dtype) seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool) - thresh_target = tf.convert_to_tensor(thresh_target, dtype=tf.float32) + seg_mask = tf.cast(seg_mask, tf.float32) + thresh_target = tf.convert_to_tensor(thresh_target, dtype=out_map.dtype) thresh_mask = tf.convert_to_tensor(thresh_mask, dtype=tf.bool) - # Compute balanced BCE loss for proba_map - bce_scale = 5. - bce_loss = tf.keras.losses.binary_crossentropy(seg_target[..., None], out_map, from_logits=True)[seg_mask] - - neg_target = 1 - seg_target[seg_mask] - positive_count = tf.math.reduce_sum(seg_target[seg_mask]) - negative_count = tf.math.reduce_min([tf.math.reduce_sum(neg_target), 3. * positive_count]) - negative_loss = bce_loss * neg_target - negative_loss, _ = tf.nn.top_k(negative_loss, tf.cast(negative_count, tf.int32)) - sum_losses = tf.math.reduce_sum(bce_loss * seg_target[seg_mask]) + tf.math.reduce_sum(negative_loss) - balanced_bce_loss = sum_losses / (positive_count + negative_count + 1e-6) - - # Compute dice loss for approxbin_map - bin_map = 1 / (1 + tf.exp(-50. * (prob_map[seg_mask] - thresh_map[seg_mask]))) - - bce_min = tf.math.reduce_min(bce_loss) - weights = (bce_loss - bce_min) / (tf.math.reduce_max(bce_loss) - bce_min) + 1. - inter = tf.math.reduce_sum(bin_map * seg_target[seg_mask] * weights) - union = tf.math.reduce_sum(bin_map) + tf.math.reduce_sum(seg_target[seg_mask]) + 1e-8 - dice_loss = 1 - 2.0 * inter / union + # Focal loss + focal_scale = 10.0 + bce_loss = losses.binary_crossentropy(seg_target[..., None], out_map[..., None], from_logits=True) + + # Convert logits to prob, compute gamma factor + p_t = (seg_target * prob_map) + ((1 - seg_target) * (1 - prob_map)) + alpha_t = seg_target * alpha + (1 - seg_target) * (1 - alpha) + # Unreduced loss + focal_loss = alpha_t * (1 - p_t) ** gamma * bce_loss + # Class reduced + focal_loss = tf.reduce_sum(seg_mask * focal_loss, (0, 1, 2, 3)) / tf.reduce_sum(seg_mask, (0, 1, 2, 3)) + + # Compute dice loss for each class or for approx binary_map + if len(self.class_names) > 1: + dice_map = tf.nn.softmax(out_map, axis=-1) + else: + # compute binary map instead + dice_map = 1.0 / (1.0 + tf.exp(-50 * (prob_map - thresh_map))) + # Class-reduced dice loss + inter = tf.reduce_sum(seg_mask * dice_map * seg_target, axis=[0, 1, 2]) + cardinality = tf.reduce_sum(seg_mask * (dice_map + seg_target), axis=[0, 1, 2]) + dice_loss = tf.reduce_mean(1 - 2 * inter / (cardinality + eps)) # Compute l1 loss for thresh_map - l1_scale = 10. if tf.reduce_any(thresh_mask): - l1_loss = tf.math.reduce_mean(tf.math.abs(thresh_map[thresh_mask] - thresh_target[thresh_mask])) + thresh_mask = tf.cast(thresh_mask, tf.float32) + l1_loss = tf.reduce_sum(tf.abs(thresh_map - thresh_target) * thresh_mask) / ( + tf.reduce_sum(thresh_mask) + eps + ) else: - l1_loss = tf.constant(0.) + l1_loss = tf.constant(0.0) - return l1_scale * l1_loss + bce_scale * balanced_bce_loss + dice_loss + return l1_loss + focal_scale * focal_loss + dice_loss def call( self, x: tf.Tensor, - target: Optional[List[Dict[str, Any]]] = None, + target: Optional[List[Dict[str, np.ndarray]]] = None, return_model_output: bool = False, - return_boxes: bool = False, + return_preds: bool = False, **kwargs: Any, ) -> Dict[str, Any]: - feat_maps = self.feat_extractor(x, **kwargs) feat_concat = self.fpn(feat_maps, **kwargs) logits = self.probability_head(feat_concat, **kwargs) out: Dict[str, tf.Tensor] = {} - if return_model_output or target is None or return_boxes: - prob_map = tf.math.sigmoid(logits) + if self.exportable: + out["logits"] = logits + return out + + if return_model_output or target is None or return_preds: + prob_map = _bf16_to_float32(tf.math.sigmoid(logits)) if return_model_output: out["out_map"] = prob_map - if target is None or return_boxes: - # Post-process boxes - out["preds"] = self.postprocessor(tf.squeeze(prob_map, axis=-1).numpy()) + if target is None or return_preds: + # Post-process boxes (keep only text predictions) + out["preds"] = [dict(zip(self.class_names, preds)) for preds in self.postprocessor(prob_map.numpy())] if target is not None: thresh_map = self.threshold_head(feat_concat, **kwargs) loss = self.compute_loss(logits, thresh_map, target) - out['loss'] = loss + out["loss"] = loss return out -def _db_resnet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> DBNet: +def _db_resnet( + arch: str, + pretrained: bool, + backbone_fn, + fpn_layers: List[str], + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> DBNet: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['fpn_channels'] = kwargs.get('fpn_channels', _cfg['fpn_channels']) - _cfg['rotated_bbox'] = kwargs.get('rotated_bbox', _cfg['rotated_bbox']) + _cfg["input_shape"] = input_shape or _cfg["input_shape"] + if not kwargs.get("class_names", None): + kwargs["class_names"] = _cfg.get("class_names", [CLASS_NAME]) + else: + kwargs["class_names"] = sorted(kwargs["class_names"]) # Feature extractor - resnet = tf.keras.applications.__dict__[_cfg['backbone']]( - include_top=False, - weights=None, - input_shape=_cfg['input_shape'], - pooling=None, + feat_extractor = IntermediateLayerGetter( + backbone_fn( + weights="imagenet" if pretrained_backbone else None, + include_top=False, + pooling=None, + input_shape=_cfg["input_shape"], + ), + fpn_layers, ) + # Build the model + model = DBNet(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) + + # Load pretrained parameters + if pretrained: + # The given class_names differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, + _cfg["url"], + skip_mismatch=kwargs["class_names"] != default_cfgs[arch].get("class_names", [CLASS_NAME]), + ) + + return model + + +def _db_mobilenet( + arch: str, + pretrained: bool, + backbone_fn, + fpn_layers: List[str], + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> DBNet: + pretrained_backbone = pretrained_backbone and not pretrained + + # Patch the config + _cfg = deepcopy(default_cfgs[arch]) + _cfg["input_shape"] = input_shape or _cfg["input_shape"] + if not kwargs.get("class_names", None): + kwargs["class_names"] = default_cfgs[arch].get("class_names", [CLASS_NAME]) + else: + kwargs["class_names"] = sorted(kwargs["class_names"]) + + # Feature extractor feat_extractor = IntermediateLayerGetter( - resnet, - _cfg['fpn_layers'], + backbone_fn( + input_shape=_cfg["input_shape"], + include_top=False, + pretrained=pretrained_backbone, + ), + fpn_layers, ) - kwargs['fpn_channels'] = _cfg['fpn_channels'] - kwargs['rotated_bbox'] = _cfg['rotated_bbox'] - # Build the model model = DBNet(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) # Load pretrained parameters if pretrained: - load_pretrained_params(model, _cfg['url']) + # The given class_names differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, + _cfg["url"], + skip_mismatch=kwargs["class_names"] != default_cfgs[arch].get("class_names", [CLASS_NAME]), + ) return model
    -[docs] +[docs] def db_resnet50(pretrained: bool = False, **kwargs: Any) -> DBNet: """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" <https://arxiv.org/pdf/1911.08947.pdf>`_, using a ResNet-50 backbone. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import db_resnet50 + >>> model = db_resnet50(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the DBNet architecture Returns: + ------- text detection architecture """ + return _db_resnet( + "db_resnet50", + pretrained, + ResNet50, + ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"], + **kwargs, + )
    + + + +
    +[docs] +def db_mobilenet_v3_large(pretrained: bool = False, **kwargs: Any) -> DBNet: + """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" + <https://arxiv.org/pdf/1911.08947.pdf>`_, using a mobilenet v3 large backbone. + + >>> import tensorflow as tf + >>> from doctr.models import db_mobilenet_v3_large + >>> model = db_mobilenet_v3_large(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) - return _db_resnet('db_resnet50', pretrained, **kwargs)
    + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the DBNet architecture + + Returns: + ------- + text detection architecture + """ + return _db_mobilenet( + "db_mobilenet_v3_large", + pretrained, + mobilenet_v3_large, + ["inverted_2", "inverted_5", "inverted_11", "final_block"], + **kwargs, + )

    @@ -598,8 +758,8 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo - - + + diff --git a/v0.4.0/_modules/doctr/models/detection/fast/tensorflow.html b/v0.4.0/_modules/doctr/models/detection/fast/tensorflow.html index 5b84d2dea1..65e1a77af8 100644 --- a/v0.4.0/_modules/doctr/models/detection/fast/tensorflow.html +++ b/v0.4.0/_modules/doctr/models/detection/fast/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.fast.tensorflow - docTR documentation @@ -769,7 +769,7 @@

    Source code for doctr.models.detection.fast.tensorflow

    - + diff --git a/v0.4.0/_modules/doctr/models/detection/linknet.html b/v0.4.0/_modules/doctr/models/detection/linknet.html deleted file mode 100644 index 129cfdce8b..0000000000 --- a/v0.4.0/_modules/doctr/models/detection/linknet.html +++ /dev/null @@ -1,644 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.linknet - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.linknet

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -import numpy as np
    -import cv2
    -from tensorflow.keras import layers, Sequential
    -from typing import Dict, Any, Tuple, Optional, List
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..backbones import ResnetStage
    -from ..utils import conv_sequence, load_pretrained_params
    -from ...utils.repr import NestedObject
    -
    -__all__ = ['LinkNet', 'linknet', 'LinkNetPostProcessor']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'linknet': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'out_chan': 1,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'LinkNetPostProcessor',
    -        'url': None,
    -    },
    -}
    -
    -
    -class LinkNetPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for LinkNet model.
    -
    -    Args:
    -        min_size_box: minimal length (pix) to keep a box
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        min_size_box: int = 3,
    -        bin_thresh: float = 0.15,
    -        box_thresh: float = 0.1,
    -    ) -> None:
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map: find connected components then filter boxes
    -
    -        Args:
    -            pred: Pred map from differentiable linknet output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        label_num, labelimage = cv2.connectedComponents(bitmap.astype(np.uint8), connectivity=4)
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        for label in range(1, label_num + 1):
    -            points = np.array(np.where(labelimage == label)[::-1]).T
    -            if points.shape[0] < 4:  # remove polygons with 3 points or less
    -                continue
    -            score = self.box_score(pred, points.reshape(-1, 2))
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            x, y, w, h = cv2.boundingRect(points)
    -            if min(w, h) < min_size_box:  # filter too small boxes
    -                continue
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -def decoder_block(in_chan: int, out_chan: int) -> Sequential:
    -    """Creates a LinkNet decoder block"""
    -
    -    return Sequential([
    -        *conv_sequence(in_chan // 4, 'relu', True, kernel_size=1),
    -        layers.Conv2DTranspose(
    -            filters=in_chan // 4,
    -            kernel_size=3,
    -            strides=2,
    -            padding="same",
    -            use_bias=False,
    -            kernel_initializer='he_normal'
    -        ),
    -        layers.BatchNormalization(),
    -        layers.Activation('relu'),
    -        *conv_sequence(out_chan, 'relu', True, kernel_size=1),
    -    ])
    -
    -
    -class LinkNetFPN(layers.Layer, NestedObject):
    -    """LinkNet Encoder-Decoder module
    -
    -    """
    -
    -    def __init__(
    -        self,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.encoder_1 = ResnetStage(num_blocks=2, output_channels=64, downsample=True)
    -        self.encoder_2 = ResnetStage(num_blocks=2, output_channels=128, downsample=True)
    -        self.encoder_3 = ResnetStage(num_blocks=2, output_channels=256, downsample=True)
    -        self.encoder_4 = ResnetStage(num_blocks=2, output_channels=512, downsample=True)
    -        self.decoder_1 = decoder_block(in_chan=64, out_chan=64)
    -        self.decoder_2 = decoder_block(in_chan=128, out_chan=64)
    -        self.decoder_3 = decoder_block(in_chan=256, out_chan=128)
    -        self.decoder_4 = decoder_block(in_chan=512, out_chan=256)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor
    -    ) -> tf.Tensor:
    -        x_1 = self.encoder_1(x)
    -        x_2 = self.encoder_2(x_1)
    -        x_3 = self.encoder_3(x_2)
    -        x_4 = self.encoder_4(x_3)
    -        y_4 = self.decoder_4(x_4)
    -        y_3 = self.decoder_3(y_4 + x_3)
    -        y_2 = self.decoder_2(y_3 + x_2)
    -        y_1 = self.decoder_1(y_2 + x_1)
    -        return y_1
    -
    -
    -class LinkNet(DetectionModel, NestedObject):
    -    """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation"
    -    <https://arxiv.org/pdf/1707.03718.pdf>`_.
    -
    -    Args:
    -        out_chan: number of channels for the output
    -    """
    -
    -    _children_names: List[str] = ['stem', 'fpn', 'classifier', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        out_chan: int = 1,
    -        input_shape: Tuple[int, int, int] = (512, 512, 3),
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(cfg=cfg)
    -
    -        self.stem = Sequential([
    -            *conv_sequence(64, 'relu', True, strides=2, kernel_size=7, input_shape=input_shape),
    -            layers.MaxPool2D(pool_size=(3, 3), strides=2, padding='same'),
    -        ])
    -
    -        self.fpn = LinkNetFPN()
    -
    -        self.classifier = Sequential([
    -            layers.Conv2DTranspose(
    -                filters=32,
    -                kernel_size=3,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -            layers.BatchNormalization(),
    -            layers.Activation('relu'),
    -            *conv_sequence(32, 'relu', True, strides=1, kernel_size=3),
    -            layers.Conv2DTranspose(
    -                filters=out_chan,
    -                kernel_size=2,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -        ])
    -
    -        self.min_size_box = 3
    -
    -        self.postprocessor = LinkNetPostProcessor()
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.bool)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            for box, box_size, is_ambiguous in zip(abs_boxes, boxes_size, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Fill polygon with 1
    -                seg_target[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = True
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts and masks from a list of boxes and a list of masks for each image
    -        Then, it computes the loss function with proba_map, gts and masks
    -
    -        Args:
    -            out_map: output feature map of the model of shape N x H x W x 1
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -        seg_target, seg_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute BCE loss
    -        return tf.math.reduce_mean(tf.keras.losses.binary_crossentropy(
    -            seg_target[seg_mask],
    -            tf.squeeze(out_map, axis=[-1])[seg_mask],
    -            from_logits=True
    -        ))
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        logits = self.stem(x)
    -        logits = self.fpn(logits)
    -        logits = self.classifier(logits)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            loss = self.compute_loss(logits, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _linknet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> LinkNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['out_chan'] = kwargs.get('out_chan', _cfg['out_chan'])
    -
    -    kwargs['out_chan'] = _cfg['out_chan']
    -    kwargs['input_shape'] = _cfg['input_shape']
    -    # Build the model
    -    model = LinkNet(cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def linknet(pretrained: bool = False, **kwargs: Any) -> LinkNet: - """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" - <https://arxiv.org/pdf/1707.03718.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import linknet - >>> model = linknet(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _linknet('linknet', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.0/_modules/doctr/models/detection/linknet/tensorflow.html b/v0.4.0/_modules/doctr/models/detection/linknet/tensorflow.html index cd4f446673..ce995f99d4 100644 --- a/v0.4.0/_modules/doctr/models/detection/linknet/tensorflow.html +++ b/v0.4.0/_modules/doctr/models/detection/linknet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.linknet.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.detection.linknet.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     # Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
     
     from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers, Sequential
    -from typing import Dict, Any, Tuple, Optional, List
    +from typing import Any, Dict, List, Optional, Tuple
     
    +import numpy as np
    +import tensorflow as tf
    +from tensorflow.keras import Model, Sequential, layers, losses
    +
    +from doctr.file_utils import CLASS_NAME
    +from doctr.models.classification import resnet18, resnet34, resnet50
    +from doctr.models.utils import (
    +    IntermediateLayerGetter,
    +    _bf16_to_float32,
    +    _build_model,
    +    conv_sequence,
    +    load_pretrained_params,
    +)
     from doctr.utils.repr import NestedObject
    -from doctr.models.backbones import ResnetStage
    -from doctr.models.utils import conv_sequence, load_pretrained_params
    -from .base import LinkNetPostProcessor, _LinkNet
     
    -__all__ = ['LinkNet', 'linknet16']
    +from .base import LinkNetPostProcessor, _LinkNet
     
    +__all__ = ["LinkNet", "linknet_resnet18", "linknet_resnet34", "linknet_resnet50"]
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'linknet16': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'num_classes': 1,
    -        'input_shape': (1024, 1024, 3),
    -        'rotated_bbox': False,
    -        'url': None,
    +    "linknet_resnet18": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/linknet_resnet18-615a82c5.weights.h5&src=0",
    +    },
    +    "linknet_resnet34": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/linknet_resnet34-9d772be5.weights.h5&src=0",
    +    },
    +    "linknet_resnet50": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/linknet_resnet50-6bf6c8b5.weights.h5&src=0",
         },
     }
     
     
    -def decoder_block(in_chan: int, out_chan: int) -> Sequential:
    +def decoder_block(in_chan: int, out_chan: int, stride: int, **kwargs: Any) -> Sequential:
         """Creates a LinkNet decoder block"""
    -
         return Sequential([
    -        *conv_sequence(in_chan // 4, 'relu', True, kernel_size=1),
    +        *conv_sequence(in_chan // 4, "relu", True, kernel_size=1, **kwargs),
             layers.Conv2DTranspose(
                 filters=in_chan // 4,
                 kernel_size=3,
    -            strides=2,
    +            strides=stride,
                 padding="same",
                 use_bias=False,
    -            kernel_initializer='he_normal'
    +            kernel_initializer="he_normal",
             ),
             layers.BatchNormalization(),
    -        layers.Activation('relu'),
    -        *conv_sequence(out_chan, 'relu', True, kernel_size=1),
    +        layers.Activation("relu"),
    +        *conv_sequence(out_chan, "relu", True, kernel_size=1),
         ])
     
     
    -class LinkNetFPN(layers.Layer, NestedObject):
    -    """LinkNet Encoder-Decoder module"""
    +class LinkNetFPN(Model, NestedObject):
    +    """LinkNet Decoder module"""
     
         def __init__(
             self,
    +        out_chans: int,
    +        in_shapes: List[Tuple[int, ...]],
         ) -> None:
    -
             super().__init__()
    -        self.encoder_1 = ResnetStage(num_blocks=2, output_channels=64, downsample=True)
    -        self.encoder_2 = ResnetStage(num_blocks=2, output_channels=128, downsample=True)
    -        self.encoder_3 = ResnetStage(num_blocks=2, output_channels=256, downsample=True)
    -        self.encoder_4 = ResnetStage(num_blocks=2, output_channels=512, downsample=True)
    -        self.decoder_1 = decoder_block(in_chan=64, out_chan=64)
    -        self.decoder_2 = decoder_block(in_chan=128, out_chan=64)
    -        self.decoder_3 = decoder_block(in_chan=256, out_chan=128)
    -        self.decoder_4 = decoder_block(in_chan=512, out_chan=256)
    +        self.out_chans = out_chans
    +        strides = [2] * (len(in_shapes) - 1) + [1]
    +        i_chans = [s[-1] for s in in_shapes[::-1]]
    +        o_chans = i_chans[1:] + [out_chans]
    +        self.decoders = [
    +            decoder_block(in_chan, out_chan, s, input_shape=in_shape)
    +            for in_chan, out_chan, s, in_shape in zip(i_chans, o_chans, strides, in_shapes[::-1])
    +        ]
    +
    +    def call(self, x: List[tf.Tensor], **kwargs: Any) -> tf.Tensor:
    +        out = 0
    +        for decoder, fmap in zip(self.decoders, x[::-1]):
    +            out = decoder(out + fmap, **kwargs)
    +        return out
     
    -    def call(
    -        self,
    -        x: tf.Tensor
    -    ) -> tf.Tensor:
    -        x_1 = self.encoder_1(x)
    -        x_2 = self.encoder_2(x_1)
    -        x_3 = self.encoder_3(x_2)
    -        x_4 = self.encoder_4(x_3)
    -        y_4 = self.decoder_4(x_4)
    -        y_3 = self.decoder_3(y_4 + x_3)
    -        y_2 = self.decoder_2(y_3 + x_2)
    -        y_1 = self.decoder_1(y_2 + x_1)
    -        return y_1
    -
    -
    -class LinkNet(_LinkNet, keras.Model):
    +    def extra_repr(self) -> str:
    +        return f"out_chans={self.out_chans}"
    +
    +
    +class LinkNet(_LinkNet, Model):
         """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation"
         <https://arxiv.org/pdf/1707.03718.pdf>`_.
     
         Args:
    -        num_classes: number of channels for the output
    +    ----
    +        feature extractor: the backbone serving as feature extractor
    +        fpn_channels: number of channels each extracted feature maps is mapped to
    +        bin_thresh: threshold for binarization of the output feature map
    +        box_thresh: minimal objectness score to consider a box
    +        assume_straight_pages: if True, fit straight bounding boxes only
    +        exportable: onnx exportable returns only logits
    +        cfg: the configuration dict of the model
    +        class_names: list of class names
         """
     
    -    _children_names: List[str] = ['stem', 'fpn', 'classifier', 'postprocessor']
    +    _children_names: List[str] = ["feat_extractor", "fpn", "classifier", "postprocessor"]
     
         def __init__(
             self,
    -        num_classes: int = 1,
    -        input_shape: Tuple[int, int, int] = (512, 512, 3),
    -        rotated_bbox: bool = False,
    +        feat_extractor: IntermediateLayerGetter,
    +        fpn_channels: int = 64,
    +        bin_thresh: float = 0.1,
    +        box_thresh: float = 0.1,
    +        assume_straight_pages: bool = True,
    +        exportable: bool = False,
             cfg: Optional[Dict[str, Any]] = None,
    +        class_names: List[str] = [CLASS_NAME],
         ) -> None:
             super().__init__(cfg=cfg)
     
    -        self.rotated_bbox = rotated_bbox
    +        self.class_names = class_names
    +        num_classes: int = len(self.class_names)
     
    -        self.stem = Sequential([
    -            *conv_sequence(64, 'relu', True, strides=2, kernel_size=7, input_shape=input_shape),
    -            layers.MaxPool2D(pool_size=(3, 3), strides=2, padding='same'),
    -        ])
    +        self.exportable = exportable
    +        self.assume_straight_pages = assume_straight_pages
    +
    +        self.feat_extractor = feat_extractor
     
    -        self.fpn = LinkNetFPN()
    +        self.fpn = LinkNetFPN(fpn_channels, [_shape[1:] for _shape in self.feat_extractor.output_shape])
    +        self.fpn.build(self.feat_extractor.output_shape)
     
             self.classifier = Sequential([
                 layers.Conv2DTranspose(
    @@ -393,154 +442,246 @@ 

    Source code for doctr.models.detection.linknet.tensorflow

    strides=2, padding="same", use_bias=False, - kernel_initializer='he_normal' + kernel_initializer="he_normal", + input_shape=self.fpn.decoders[-1].output_shape[1:], ), layers.BatchNormalization(), - layers.Activation('relu'), - *conv_sequence(32, 'relu', True, strides=1, kernel_size=3), + layers.Activation("relu"), + *conv_sequence(32, "relu", True, kernel_size=3, strides=1), layers.Conv2DTranspose( filters=num_classes, kernel_size=2, strides=2, padding="same", - use_bias=False, - kernel_initializer='he_normal' + use_bias=True, + kernel_initializer="he_normal", ), ]) - self.postprocessor = LinkNetPostProcessor(rotated_bbox=rotated_bbox) + self.postprocessor = LinkNetPostProcessor( + assume_straight_pages=assume_straight_pages, bin_thresh=bin_thresh, box_thresh=box_thresh + ) def compute_loss( self, out_map: tf.Tensor, - target: List[Dict[str, Any]], - focal_loss: bool = False, - alpha: float = .5, - gamma: float = 2., - edge_factor: float = 2., + target: List[Dict[str, np.ndarray]], + gamma: float = 2.0, + alpha: float = 0.5, + eps: float = 1e-8, ) -> tf.Tensor: """Compute linknet loss, BCE with boosted box edges or focal loss. Focal loss implementation based on <https://github.com/tensorflow/addons/>`_. Args: + ---- out_map: output feature map of the model of shape N x H x W x 1 target: list of dictionary where each dict has a `boxes` and a `flags` entry - focal_loss: if True, use focal loss instead of BCE - edge_factor: boost factor for box edges (in case of BCE) + gamma: modulating factor in the focal loss formula alpha: balancing factor in the focal loss formula - gammma: modulating factor in the focal loss formula + eps: epsilon factor in dice loss Returns: + ------- A loss tensor """ - seg_target, seg_mask, edge_mask = self.compute_target(target, out_map.shape[:3]) - seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32) - edge_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool) + seg_target, seg_mask = self.build_target(target, out_map.shape[1:], True) + seg_target = tf.convert_to_tensor(seg_target, dtype=out_map.dtype) seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool) - - # Get the cross_entropy for each entry - bce = tf.keras.losses.binary_crossentropy( - seg_target[seg_mask], - tf.squeeze(out_map, axis=[-1])[seg_mask], - from_logits=True) - - if focal_loss: - if gamma and gamma < 0: - raise ValueError("Value of gamma should be greater than or equal to zero.") - - # Convert logits to prob, compute gamma factor - pred_prob = tf.sigmoid(tf.squeeze(out_map, axis=[-1])[seg_mask]) - p_t = (seg_target[seg_mask] * pred_prob) + ((1 - seg_target[seg_mask]) * (1 - pred_prob)) - modulating_factor = tf.pow((1.0 - p_t), gamma) - - # Compute alpha factor - alpha_factor = seg_target[seg_mask] * alpha + (1 - seg_target[seg_mask]) * (1 - alpha) - - # compute the final loss - loss = tf.reduce_mean(alpha_factor * modulating_factor * bce) - - else: - # Compute BCE loss with highlighted edges - loss = tf.math.multiply( - 1 + (edge_factor - 1) * tf.cast(edge_mask, tf.float32), - bce - ) - loss = tf.reduce_mean(loss) - - return loss + seg_mask = tf.cast(seg_mask, tf.float32) + + bce_loss = losses.binary_crossentropy(seg_target[..., None], out_map[..., None], from_logits=True) + proba_map = tf.sigmoid(out_map) + + # Focal loss + if gamma < 0: + raise ValueError("Value of gamma should be greater than or equal to zero.") + # Convert logits to prob, compute gamma factor + p_t = (seg_target * proba_map) + ((1 - seg_target) * (1 - proba_map)) + alpha_t = seg_target * alpha + (1 - seg_target) * (1 - alpha) + # Unreduced loss + focal_loss = alpha_t * (1 - p_t) ** gamma * bce_loss + # Class reduced + focal_loss = tf.reduce_sum(seg_mask * focal_loss, (0, 1, 2, 3)) / tf.reduce_sum(seg_mask, (0, 1, 2, 3)) + + # Compute dice loss for each class + dice_map = tf.nn.softmax(out_map, axis=-1) if len(self.class_names) > 1 else proba_map + # Class-reduced dice loss + inter = tf.reduce_sum(seg_mask * dice_map * seg_target, axis=[0, 1, 2]) + cardinality = tf.reduce_sum(seg_mask * (dice_map + seg_target), axis=[0, 1, 2]) + dice_loss = tf.reduce_mean(1 - 2 * inter / (cardinality + eps)) + + return focal_loss + dice_loss def call( self, x: tf.Tensor, - target: Optional[List[Dict[str, Any]]] = None, + target: Optional[List[Dict[str, np.ndarray]]] = None, return_model_output: bool = False, - return_boxes: bool = False, - focal_loss: bool = True, + return_preds: bool = False, **kwargs: Any, ) -> Dict[str, Any]: - - logits = self.stem(x) - logits = self.fpn(logits) - logits = self.classifier(logits) + feat_maps = self.feat_extractor(x, **kwargs) + logits = self.fpn(feat_maps, **kwargs) + logits = self.classifier(logits, **kwargs) out: Dict[str, tf.Tensor] = {} - if return_model_output or target is None or return_boxes: - prob_map = tf.math.sigmoid(logits) + if self.exportable: + out["logits"] = logits + return out + + if return_model_output or target is None or return_preds: + prob_map = _bf16_to_float32(tf.math.sigmoid(logits)) + if return_model_output: out["out_map"] = prob_map - if target is None or return_boxes: + if target is None or return_preds: # Post-process boxes - out["preds"] = self.postprocessor(tf.squeeze(prob_map, axis=-1).numpy()) + out["preds"] = [dict(zip(self.class_names, preds)) for preds in self.postprocessor(prob_map.numpy())] if target is not None: - loss = self.compute_loss(logits, target, focal_loss) - out['loss'] = loss + loss = self.compute_loss(logits, target) + out["loss"] = loss return out -def _linknet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> LinkNet: +def _linknet( + arch: str, + pretrained: bool, + backbone_fn, + fpn_layers: List[str], + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> LinkNet: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['num_classes'] = kwargs.get('num_classes', _cfg['num_classes']) - _cfg['rotated_bbox'] = kwargs.get('rotated_bbox', _cfg['rotated_bbox']) + _cfg["input_shape"] = input_shape or default_cfgs[arch]["input_shape"] + if not kwargs.get("class_names", None): + kwargs["class_names"] = _cfg.get("class_names", [CLASS_NAME]) + else: + kwargs["class_names"] = sorted(kwargs["class_names"]) + + # Feature extractor + feat_extractor = IntermediateLayerGetter( + backbone_fn( + pretrained=pretrained_backbone, + include_top=False, + input_shape=_cfg["input_shape"], + ), + fpn_layers, + ) - kwargs['num_classes'] = _cfg['num_classes'] - kwargs['input_shape'] = _cfg['input_shape'] - kwargs['rotated_bbox'] = _cfg['rotated_bbox'] # Build the model - model = LinkNet(cfg=_cfg, **kwargs) + model = LinkNet(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) + # Load pretrained parameters if pretrained: - load_pretrained_params(model, _cfg['url']) + # The given class_names differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, + _cfg["url"], + skip_mismatch=kwargs["class_names"] != default_cfgs[arch].get("class_names", [CLASS_NAME]), + ) return model -
    -[docs] -def linknet16(pretrained: bool = False, **kwargs: Any) -> LinkNet: +
    +[docs] +def linknet_resnet18(pretrained: bool = False, **kwargs: Any) -> LinkNet: + """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" + <https://arxiv.org/pdf/1707.03718.pdf>`_. + + >>> import tensorflow as tf + >>> from doctr.models import linknet_resnet18 + >>> model = linknet_resnet18(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the LinkNet architecture + + Returns: + ------- + text detection architecture + """ + return _linknet( + "linknet_resnet18", + pretrained, + resnet18, + ["resnet_block_1", "resnet_block_3", "resnet_block_5", "resnet_block_7"], + **kwargs, + )
    + + + +
    +[docs] +def linknet_resnet34(pretrained: bool = False, **kwargs: Any) -> LinkNet: """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" <https://arxiv.org/pdf/1707.03718.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import linknet16 - >>> model = linknet16(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import linknet_resnet34 + >>> model = linknet_resnet34(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the LinkNet architecture Returns: + ------- text detection architecture """ + return _linknet( + "linknet_resnet34", + pretrained, + resnet34, + ["resnet_block_2", "resnet_block_6", "resnet_block_12", "resnet_block_15"], + **kwargs, + )
    + + + +
    +[docs] +def linknet_resnet50(pretrained: bool = False, **kwargs: Any) -> LinkNet: + """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" + <https://arxiv.org/pdf/1707.03718.pdf>`_. + + >>> import tensorflow as tf + >>> from doctr.models import linknet_resnet50 + >>> model = linknet_resnet50(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the LinkNet architecture - return _linknet('linknet16', pretrained, **kwargs)
    + Returns: + ------- + text detection architecture + """ + return _linknet( + "linknet_resnet50", + pretrained, + resnet50, + ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"], + **kwargs, + )
    @@ -574,8 +715,8 @@

    Source code for doctr.models.detection.linknet.tensorflow

    - +
    + diff --git a/v0.4.0/_modules/doctr/models/detection/zoo.html b/v0.4.0/_modules/doctr/models/detection/zoo.html index d3128b8d14..3651c4e2d3 100644 --- a/v0.4.0/_modules/doctr/models/detection/zoo.html +++ b/v0.4.0/_modules/doctr/models/detection/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.detection.zoo - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.detection.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -from typing import Any
    +from typing import Any, List
     
     from doctr.file_utils import is_tf_available, is_torch_available
    -from .core import DetectionPredictor
    -from ..preprocessor import PreProcessor
    -from .. import detection
     
    +from .. import detection
    +from ..detection.fast import reparameterize
    +from ..preprocessor import PreProcessor
    +from .predictor import DetectionPredictor
     
     __all__ = ["detection_predictor"]
     
    +ARCHS: List[str]
    +
     
     if is_tf_available():
    -    ARCHS = ['db_resnet50', 'linknet16']
    +    ARCHS = [
    +        "db_resnet50",
    +        "db_mobilenet_v3_large",
    +        "linknet_resnet18",
    +        "linknet_resnet34",
    +        "linknet_resnet50",
    +        "fast_tiny",
    +        "fast_small",
    +        "fast_base",
    +    ]
     elif is_torch_available():
    -    ARCHS = ['db_resnet34', 'db_resnet50', 'db_mobilenet_v3', 'linknet16']
    +    ARCHS = [
    +        "db_resnet34",
    +        "db_resnet50",
    +        "db_mobilenet_v3_large",
    +        "linknet_resnet18",
    +        "linknet_resnet34",
    +        "linknet_resnet50",
    +        "fast_tiny",
    +        "fast_small",
    +        "fast_base",
    +    ]
    +
     
    +def _predictor(arch: Any, pretrained: bool, assume_straight_pages: bool = True, **kwargs: Any) -> DetectionPredictor:
    +    if isinstance(arch, str):
    +        if arch not in ARCHS:
    +            raise ValueError(f"unknown architecture '{arch}'")
     
    -def _predictor(arch: str, pretrained: bool, **kwargs: Any) -> DetectionPredictor:
    +        _model = detection.__dict__[arch](
    +            pretrained=pretrained,
    +            pretrained_backbone=kwargs.get("pretrained_backbone", True),
    +            assume_straight_pages=assume_straight_pages,
    +        )
    +        # Reparameterize FAST models by default to lower inference latency and memory usage
    +        if isinstance(_model, detection.FAST):
    +            _model = reparameterize(_model)
    +    else:
    +        if not isinstance(arch, (detection.DBNet, detection.LinkNet, detection.FAST)):
    +            raise ValueError(f"unknown architecture: {type(arch)}")
     
    -    if arch not in ARCHS:
    -        raise ValueError(f"unknown architecture '{arch}'")
    +        _model = arch
    +        _model.assume_straight_pages = assume_straight_pages
    +        _model.postprocessor.assume_straight_pages = assume_straight_pages
     
    -    # Detection
    -    _model = detection.__dict__[arch](pretrained=pretrained)
    -    kwargs['mean'] = kwargs.get('mean', _model.cfg['mean'])
    -    kwargs['std'] = kwargs.get('std', _model.cfg['std'])
    -    kwargs['batch_size'] = kwargs.get('batch_size', 1)
    +    kwargs.pop("pretrained_backbone", None)
    +
    +    kwargs["mean"] = kwargs.get("mean", _model.cfg["mean"])
    +    kwargs["std"] = kwargs.get("std", _model.cfg["std"])
    +    kwargs["batch_size"] = kwargs.get("batch_size", 2)
         predictor = DetectionPredictor(
    -        PreProcessor(_model.cfg['input_shape'][:2], **kwargs),
    -        _model
    +        PreProcessor(_model.cfg["input_shape"][:-1] if is_tf_available() else _model.cfg["input_shape"][1:], **kwargs),
    +        _model,
         )
         return predictor
     
     
     
    -[docs] -def detection_predictor(arch: str = 'db_resnet50', pretrained: bool = False, **kwargs: Any) -> DetectionPredictor: +[docs] +def detection_predictor( + arch: Any = "fast_base", + pretrained: bool = False, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + batch_size: int = 2, + **kwargs: Any, +) -> DetectionPredictor: """Text detection architecture. - Example:: - >>> import numpy as np - >>> from doctr.models import detection_predictor - >>> model = detection_predictor(pretrained=True) - >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) - >>> out = model([input_page]) + >>> import numpy as np + >>> from doctr.models import detection_predictor + >>> model = detection_predictor(arch='db_resnet50', pretrained=True) + >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) Args: - arch: name of the architecture to use ('db_resnet50') + ---- + arch: name of the architecture or model itself to use (e.g. 'db_resnet50') pretrained: If True, returns a model pre-trained on our text detection dataset + assume_straight_pages: If True, fit straight boxes to the page + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right + batch_size: number of samples the model processes in parallel + **kwargs: optional keyword arguments passed to the architecture Returns: + ------- Detection predictor """ - - return _predictor(arch, pretrained, **kwargs)
    + return _predictor( + arch=arch, + pretrained=pretrained, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + batch_size=batch_size, + **kwargs, + )
    @@ -367,8 +449,8 @@

    Source code for doctr.models.detection.zoo

           
         
       
    - - + + diff --git a/v0.4.0/_modules/doctr/models/export.html b/v0.4.0/_modules/doctr/models/export.html deleted file mode 100644 index f25a81aa21..0000000000 --- a/v0.4.0/_modules/doctr/models/export.html +++ /dev/null @@ -1,411 +0,0 @@ - - - - - - - - - - - - doctr.models.export - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.export

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import logging
    -import numpy as np
    -import tensorflow as tf
    -from tensorflow.keras import Model
    -from typing import Tuple
    -
    -logging.getLogger("tensorflow").setLevel(logging.DEBUG)
    -
    -
    -__all__ = ['convert_to_tflite', 'convert_to_fp16', 'quantize_model']
    -
    -
    -
    -[docs] -def convert_to_tflite(tf_model: Model) -> bytes: - """Converts a model to TFLite format - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_tflite, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_tflite(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - return converter.convert()
    - - - -
    -[docs] -def convert_to_fp16(tf_model: Model) -> bytes: - """Converts a model to half precision - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_fp16, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_fp16(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the serialized FP16 model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - converter.target_spec.supported_types = [tf.float16] - return converter.convert()
    - - - -
    -[docs] -def quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) -> bytes: - """Quantize a Tensorflow model - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import quantize_model, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = quantize_model(model, (224, 224, 3)) - - Args: - tf_model: a keras model - input_shape: shape of the expected input tensor (excluding batch dimension) with channel last order - - Returns: - bytes: the serialized quantized model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - - # Float fallback for operators that do not have an integer implementation - def representative_dataset(): - for _ in range(100): - data = np.random.rand(1, *input_shape) - yield [data.astype(np.float32)] - - converter.representative_dataset = representative_dataset - converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] - converter.inference_input_type = tf.int8 - converter.inference_output_type = tf.int8 - - return converter.convert()
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.0/_modules/doctr/models/factory/hub.html b/v0.4.0/_modules/doctr/models/factory/hub.html index 8274a809f5..756b2c7a17 100644 --- a/v0.4.0/_modules/doctr/models/factory/hub.html +++ b/v0.4.0/_modules/doctr/models/factory/hub.html @@ -13,7 +13,7 @@ - + doctr.models.factory.hub - docTR documentation @@ -568,7 +568,7 @@

    Source code for doctr.models.factory.hub

         
       
    - + diff --git a/v0.4.0/_modules/doctr/models/recognition/crnn.html b/v0.4.0/_modules/doctr/models/recognition/crnn.html deleted file mode 100644 index daa2393439..0000000000 --- a/v0.4.0/_modules/doctr/models/recognition/crnn.html +++ /dev/null @@ -1,565 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.crnn - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.crnn

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import layers
    -from tensorflow.keras.models import Sequential
    -from typing import Tuple, Dict, Any, Optional, List
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel, RecognitionPostProcessor
    -
    -__all__ = ['CRNN', 'crnn_vgg16_bn', 'crnn_resnet31', 'CTCPostProcessor']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'crnn_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/crnn_vgg16_bn-748c855f.zip',
    -    },
    -    'crnn_resnet31': {
    -        'mean': (0.694, 0.695, 0.693),
    -        'std': (0.299, 0.296, 0.301),
    -        'backbone': 'resnet31', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.1/crnn_resnet31-69ab71db.zip',
    -    },
    -}
    -
    -
    -class CTCPostProcessor(RecognitionPostProcessor):
    -    """
    -    Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor
    -    ) -> List[Tuple[str, float]]:
    -        """
    -        Performs decoding of raw output with CTC and decoding of CTC predictions
    -        with label_to_idx mapping dictionnary
    -
    -        Args:
    -            logits: raw output of the model, shape BATCH_SIZE X SEQ_LEN X NUM_CLASSES + 1
    -
    -        Returns:
    -            A list of decoded words of length BATCH_SIZE
    -
    -        """
    -        # Decode CTC
    -        _decoded, _log_prob = tf.nn.ctc_beam_search_decoder(
    -            tf.transpose(logits, perm=[1, 0, 2]),
    -            tf.fill(logits.shape[0], logits.shape[1]),
    -            beam_width=1, top_paths=1,
    -        )
    -        out_idxs = tf.sparse.to_dense(_decoded[0], default_value=len(self.vocab))
    -        probs = tf.math.exp(tf.squeeze(_log_prob, axis=1))
    -
    -        # Map it to characters
    -        _decoded_strings_pred = tf.strings.reduce_join(
    -            inputs=tf.nn.embedding_lookup(self._embedding, out_idxs),
    -            axis=-1
    -        )
    -        _decoded_strings_pred = tf.strings.split(_decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -class CRNN(RecognitionModel):
    -    """Implements a CRNN architecture as described in `"An End-to-End Trainable Neural Network for Image-based
    -    Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of units in the LSTM layers
    -        cfg: configuration dictionary
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: tf.keras.Model,
    -        vocab: str,
    -        rnn_units: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(vocab=vocab, cfg=cfg)
    -        self.feat_extractor = feature_extractor
    -
    -        # Initialize kernels
    -        h, w, c = self.feat_extractor.output_shape[1:]
    -        self.max_length = w
    -
    -        self.decoder = Sequential(
    -            [
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Dense(units=len(vocab) + 1)
    -            ]
    -        )
    -        self.decoder.build(input_shape=(None, w, h * c))
    -
    -        self.postprocessor = CTCPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        target: List[str],
    -    ) -> tf.Tensor:
    -        """Compute CTC loss for the model.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        gt, seq_len = self.compute_target(target)
    -        batch_len = model_output.shape[0]
    -        input_length = model_output.shape[1] * tf.ones(shape=(batch_len))
    -        ctc_loss = tf.nn.ctc_loss(
    -            gt, model_output, seq_len, input_length, logits_time_major=False, blank_index=len(self.vocab)
    -        )
    -        return ctc_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        # B x H x W x C --> B x W x H x C
    -        transposed_feat = tf.transpose(features, perm=[0, 2, 1, 3])
    -        w, h, c = transposed_feat.get_shape().as_list()[1:]
    -        # B x W x H x C --> B x W x H * C
    -        features_seq = tf.reshape(transposed_feat, shape=(-1, w, h * c))
    -        logits = self.decoder(features_seq, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = logits
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(logits)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(logits, target)
    -
    -        return out
    -
    -
    -def _crnn(arch: str, pretrained: bool, input_shape: Optional[Tuple[int, int, int]] = None, **kwargs: Any) -> CRNN:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[_cfg['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -
    -    # Build the model
    -    model = CRNN(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a VGG-16 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_vgg16_bn - >>> model = crnn_vgg16_bn(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_vgg16_bn', pretrained, **kwargs)
    - - - -def crnn_resnet31(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a resnet31 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_resnet31 - >>> model = crnn_resnet31(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_resnet31', pretrained, **kwargs) -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.0/_modules/doctr/models/recognition/crnn/tensorflow.html b/v0.4.0/_modules/doctr/models/recognition/crnn/tensorflow.html index 41cc93dd23..bc64da9a1b 100644 --- a/v0.4.0/_modules/doctr/models/recognition/crnn/tensorflow.html +++ b/v0.4.0/_modules/doctr/models/recognition/crnn/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.crnn.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple, Union
    +
     import tensorflow as tf
     from tensorflow.keras import layers
    -from tensorflow.keras.models import Sequential, Model
    -from typing import Tuple, Dict, Any, Optional, List
    +from tensorflow.keras.models import Model, Sequential
    +
    +from doctr.datasets import VOCABS
     
    -from ... import backbones
    -from ...utils import load_pretrained_params
    +from ...classification import mobilenet_v3_large_r, mobilenet_v3_small_r, vgg16_bn_r
    +from ...utils.tensorflow import _bf16_to_float32, _build_model, load_pretrained_params
     from ..core import RecognitionModel, RecognitionPostProcessor
     
    -__all__ = ['CRNN', 'crnn_vgg16_bn', 'crnn_resnet31', 'CTCPostProcessor']
    +__all__ = ["CRNN", "crnn_vgg16_bn", "crnn_mobilenet_v3_small", "crnn_mobilenet_v3_large"]
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'crnn_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/crnn_vgg16_bn-748c855f.zip',
    +    "crnn_vgg16_bn": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["legacy_french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/crnn_vgg16_bn-9c188f45.weights.h5&src=0",
         },
    -    'crnn_resnet31': {
    -        'mean': (0.694, 0.695, 0.693),
    -        'std': (0.299, 0.296, 0.301),
    -        'backbone': 'resnet31', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.1/crnn_resnet31-69ab71db.zip',
    +    "crnn_mobilenet_v3_small": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/crnn_mobilenet_v3_small-54850265.weights.h5&src=0",
    +    },
    +    "crnn_mobilenet_v3_large": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/crnn_mobilenet_v3_large-c64045e5.weights.h5&src=0",
         },
     }
     
     
     class CTCPostProcessor(RecognitionPostProcessor):
    -    """
    -    Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
    +    """Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
     
         Args:
    +    ----
             vocab: string containing the ordered sequence of supported characters
             ignore_case: if True, ignore case of letters
             ignore_accents: if True, ignore accents of letters
    @@ -325,37 +353,57 @@ 

    Source code for doctr.models.recognition.crnn.tensorflow

    def __call__( self, - logits: tf.Tensor - ) -> List[Tuple[str, float]]: - """ - Performs decoding of raw output with CTC and decoding of CTC predictions + logits: tf.Tensor, + beam_width: int = 1, + top_paths: int = 1, + ) -> Union[List[Tuple[str, float]], List[Tuple[List[str], List[float]]]]: + """Performs decoding of raw output with CTC and decoding of CTC predictions with label_to_idx mapping dictionnary Args: + ---- logits: raw output of the model, shape BATCH_SIZE X SEQ_LEN X NUM_CLASSES + 1 + beam_width: An int scalar >= 0 (beam search beam width). + top_paths: An int scalar >= 0, <= beam_width (controls output size). Returns: + ------- A list of decoded words of length BATCH_SIZE + """ # Decode CTC _decoded, _log_prob = tf.nn.ctc_beam_search_decoder( tf.transpose(logits, perm=[1, 0, 2]), - tf.fill(logits.shape[0], logits.shape[1]), - beam_width=1, top_paths=1, + tf.fill(tf.shape(logits)[:1], tf.shape(logits)[1]), + beam_width=beam_width, + top_paths=top_paths, ) - out_idxs = tf.sparse.to_dense(_decoded[0], default_value=len(self.vocab)) - probs = tf.math.exp(tf.squeeze(_log_prob, axis=1)) + + _decoded = tf.sparse.concat( + 1, + [tf.sparse.expand_dims(dec, axis=1) for dec in _decoded], + expand_nonconcat_dims=True, + ) # dim : batchsize x beamwidth x actual_max_len_predictions + out_idxs = tf.sparse.to_dense(_decoded, default_value=len(self.vocab)) # Map it to characters _decoded_strings_pred = tf.strings.reduce_join( inputs=tf.nn.embedding_lookup(tf.constant(self._embedding, dtype=tf.string), out_idxs), - axis=-1 + axis=-1, ) _decoded_strings_pred = tf.strings.split(_decoded_strings_pred, "<eos>") - decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0] - word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] - + decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value="not valid")[ + :, :, 0 + ] # dim : batch_size x beam_width + + if top_paths == 1: + probs = tf.math.exp(tf.squeeze(_log_prob, axis=1)) # dim : batchsize + decoded_strings_pred = tf.squeeze(decoded_strings_pred, axis=1) + word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] + else: + probs = tf.math.exp(_log_prob) # dim : batchsize x beamwidth + word_values = [[word.decode() for word in words] for words in decoded_strings_pred.numpy().tolist()] return list(zip(word_values, probs.numpy().tolist())) @@ -364,19 +412,26 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. Args: + ---- feature_extractor: the backbone serving as feature extractor vocab: vocabulary used for encoding rnn_units: number of units in the LSTM layers + exportable: onnx exportable returns only logits + beam_width: beam width for beam search decoding + top_paths: number of top paths for beam search decoding cfg: configuration dictionary """ - _children_names: List[str] = ['feat_extractor', 'decoder', 'postprocessor'] + _children_names: List[str] = ["feat_extractor", "decoder", "postprocessor"] def __init__( self, - feature_extractor: tf.keras.Model, + feature_extractor: Model, vocab: str, rnn_units: int = 128, + exportable: bool = False, + beam_width: int = 1, + top_paths: int = 1, cfg: Optional[Dict[str, Any]] = None, ) -> None: # Initialize kernels @@ -386,19 +441,21 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    self.vocab = vocab self.max_length = w self.cfg = cfg + self.exportable = exportable self.feat_extractor = feature_extractor - self.decoder = Sequential( - [ - layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), - layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), - layers.Dense(units=len(vocab) + 1) - ] - ) + self.decoder = Sequential([ + layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), + layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), + layers.Dense(units=len(vocab) + 1), + ]) self.decoder.build(input_shape=(None, w, h * c)) self.postprocessor = CTCPostProcessor(vocab=vocab) + self.beam_width = beam_width + self.top_paths = top_paths + def compute_loss( self, model_output: tf.Tensor, @@ -407,16 +464,17 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    """Compute CTC loss for the model. Args: - gt: the encoded tensor with gt labels + ---- model_output: predicted logits of the model - seq_len: lengths of each gt word inside the batch + target: lengths of each gt word inside the batch Returns: + ------- The loss of the model on the batch """ - gt, seq_len = self.compute_target(target) + gt, seq_len = self.build_target(target) batch_len = model_output.shape[0] - input_length = model_output.shape[1] * tf.ones(shape=(batch_len)) + input_length = tf.fill((batch_len,), model_output.shape[1]) ctc_loss = tf.nn.ctc_loss( gt, model_output, seq_len, input_length, logits_time_major=False, blank_index=len(self.vocab) ) @@ -428,8 +486,12 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    target: Optional[List[str]] = None, return_model_output: bool = False, return_preds: bool = False, + beam_width: int = 1, + top_paths: int = 1, **kwargs: Any, ) -> Dict[str, Any]: + if kwargs.get("training", False) and target is None: + raise ValueError("Need to provide labels during training") features = self.feat_extractor(x, **kwargs) # B x H x W x C --> B x W x H x C @@ -437,91 +499,132 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    w, h, c = transposed_feat.get_shape().as_list()[1:] # B x W x H x C --> B x W x H * C features_seq = tf.reshape(transposed_feat, shape=(-1, w, h * c)) - logits = self.decoder(features_seq, **kwargs) + logits = _bf16_to_float32(self.decoder(features_seq, **kwargs)) out: Dict[str, tf.Tensor] = {} + if self.exportable: + out["logits"] = logits + return out + if return_model_output: out["out_map"] = logits if target is None or return_preds: # Post-process boxes - out["preds"] = self.postprocessor(logits) + out["preds"] = self.postprocessor(logits, beam_width=beam_width, top_paths=top_paths) if target is not None: - out['loss'] = self.compute_loss(logits, target) + out["loss"] = self.compute_loss(logits, target) return out -def _crnn(arch: str, pretrained: bool, input_shape: Optional[Tuple[int, int, int]] = None, **kwargs: Any) -> CRNN: +def _crnn( + arch: str, + pretrained: bool, + backbone_fn, + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> CRNN: + pretrained_backbone = pretrained_backbone and not pretrained + + kwargs["vocab"] = kwargs.get("vocab", default_cfgs[arch]["vocab"]) - # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab']) - _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units']) + _cfg["vocab"] = kwargs["vocab"] + _cfg["input_shape"] = input_shape or default_cfgs[arch]["input_shape"] - # Feature extractor - feat_extractor = backbones.__dict__[_cfg['backbone']]( - input_shape=_cfg['input_shape'], + feat_extractor = backbone_fn( + input_shape=_cfg["input_shape"], include_top=False, + pretrained=pretrained_backbone, ) - kwargs['vocab'] = _cfg['vocab'] - kwargs['rnn_units'] = _cfg['rnn_units'] - # Build the model model = CRNN(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) # Load pretrained parameters if pretrained: - load_pretrained_params(model, _cfg['url']) + # The given vocab differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params(model, _cfg["url"], skip_mismatch=kwargs["vocab"] != default_cfgs[arch]["vocab"]) return model
    -[docs] +[docs] def crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> CRNN: """CRNN with a VGG-16 backbone as described in `"An End-to-End Trainable Neural Network for Image-based Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_vgg16_bn - >>> model = crnn_vgg16_bn(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import crnn_vgg16_bn + >>> model = crnn_vgg16_bn(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the CRNN architecture Returns: + ------- text recognition architecture """ + return _crnn("crnn_vgg16_bn", pretrained, vgg16_bn_r, **kwargs)
    + + + +
    +[docs] +def crnn_mobilenet_v3_small(pretrained: bool = False, **kwargs: Any) -> CRNN: + """CRNN with a MobileNet V3 Small backbone as described in `"An End-to-End Trainable Neural Network for Image-based + Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. + + >>> import tensorflow as tf + >>> from doctr.models import crnn_mobilenet_v3_small + >>> model = crnn_mobilenet_v3_small(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the CRNN architecture - return _crnn('crnn_vgg16_bn', pretrained, **kwargs)
    + Returns: + ------- + text recognition architecture + """ + return _crnn("crnn_mobilenet_v3_small", pretrained, mobilenet_v3_small_r, **kwargs)
    -def crnn_resnet31(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a resnet31 backbone as described in `"An End-to-End Trainable Neural Network for Image-based +
    +[docs] +def crnn_mobilenet_v3_large(pretrained: bool = False, **kwargs: Any) -> CRNN: + """CRNN with a MobileNet V3 Large backbone as described in `"An End-to-End Trainable Neural Network for Image-based Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_resnet31 - >>> model = crnn_resnet31(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import crnn_mobilenet_v3_large + >>> model = crnn_mobilenet_v3_large(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the CRNN architecture Returns: + ------- text recognition architecture """ + return _crnn("crnn_mobilenet_v3_large", pretrained, mobilenet_v3_large_r, **kwargs)
    - return _crnn('crnn_resnet31', pretrained, **kwargs)
    @@ -554,8 +657,8 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    - +
    + diff --git a/v0.4.0/_modules/doctr/models/recognition/master/tensorflow.html b/v0.4.0/_modules/doctr/models/recognition/master/tensorflow.html index 2dc5a27717..aa6aa69325 100644 --- a/v0.4.0/_modules/doctr/models/recognition/master/tensorflow.html +++ b/v0.4.0/_modules/doctr/models/recognition/master/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.master.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.master.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import tensorflow as tf
    -from tensorflow.keras import layers, Sequential, Model
    -from typing import Tuple, List, Dict, Any, Optional
     from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple
     
    -from ..core import RecognitionPostProcessor
    -from ...backbones.resnet import ResnetStage
    -from ...utils import conv_sequence, load_pretrained_params
    -from ..transformer import Decoder, positional_encoding, create_look_ahead_mask, create_padding_mask
    -from ....datasets import VOCABS
    -from .base import _MASTER, _MASTERPostProcessor
    +import tensorflow as tf
    +from tensorflow.keras import Model, layers
    +
    +from doctr.datasets import VOCABS
    +from doctr.models.classification import magc_resnet31
    +from doctr.models.modules.transformer import Decoder, PositionalEncoding
     
    +from ...utils.tensorflow import _bf16_to_float32, _build_model, load_pretrained_params
    +from .base import _MASTER, _MASTERPostProcessor
     
    -__all__ = ['MASTER', 'master', 'MASTERPostProcessor']
    +__all__ = ["MASTER", "master"]
     
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'master': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'input_shape': (48, 160, 3),
    -        'vocab': VOCABS['french'],
    -        'url': None,
    +    "master": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/master-d7fdaeff.weights.h5&src=0",
         },
     }
     
     
    -class MAGC(layers.Layer):
    -
    -    """Implements the Multi-Aspect Global Context Attention, as described in
    -    <https://arxiv.org/pdf/1910.02562.pdf>`_.
    -
    -    Args:
    -        inplanes: input channels
    -        headers: number of headers to split channels
    -        att_scale: if True, re-scale attention to counteract the variance distibutions
    -        **kwargs
    -    """
    -
    -    def __init__(
    -        self,
    -        inplanes: int,
    -        headers: int = 1,
    -        att_scale: bool = False,
    -        **kwargs
    -    ) -> None:
    -        super().__init__(**kwargs)
    -
    -        self.headers = headers  # h
    -        self.inplanes = inplanes  # C
    -        self.att_scale = att_scale
    -
    -        self.single_header_inplanes = int(inplanes / headers)  # C / h
    -
    -        self.conv_mask = tf.keras.layers.Conv2D(
    -            filters=1,
    -            kernel_size=1,
    -            kernel_initializer=tf.initializers.he_normal()
    -        )
    -
    -        self.transform = tf.keras.Sequential(
    -            [
    -                tf.keras.layers.Conv2D(
    -                    filters=self.inplanes,
    -                    kernel_size=1,
    -                    kernel_initializer=tf.initializers.he_normal()
    -                ),
    -                tf.keras.layers.LayerNormalization([1, 2, 3]),
    -                tf.keras.layers.ReLU(),
    -                tf.keras.layers.Conv2D(
    -                    filters=self.inplanes,
    -                    kernel_size=1,
    -                    kernel_initializer=tf.initializers.he_normal()
    -                ),
    -            ],
    -            name='transform'
    -        )
    -
    -    @tf.function
    -    def context_modeling(self, inputs: tf.Tensor) -> tf.Tensor:
    -        b, h, w, c = (tf.shape(inputs)[i] for i in range(4))
    -
    -        # B, H, W, C -->> B*h, H, W, C/h
    -        x = tf.reshape(inputs, shape=(b, h, w, self.headers, self.single_header_inplanes))
    -        x = tf.transpose(x, perm=(0, 3, 1, 2, 4))
    -        x = tf.reshape(x, shape=(b * self.headers, h, w, self.single_header_inplanes))
    -
    -        # Compute shorcut
    -        shortcut = x
    -        # B*h, 1, H*W, C/h
    -        shortcut = tf.reshape(shortcut, shape=(b * self.headers, 1, h * w, self.single_header_inplanes))
    -        # B*h, 1, C/h, H*W
    -        shortcut = tf.transpose(shortcut, perm=[0, 1, 3, 2])
    -
    -        # Compute context mask
    -        # B*h, H, W, 1,
    -        context_mask = self.conv_mask(x)
    -        # B*h, 1, H*W, 1
    -        context_mask = tf.reshape(context_mask, shape=(b * self.headers, 1, h * w, 1))
    -        # scale variance
    -        if self.att_scale and self.headers > 1:
    -            context_mask = context_mask / tf.sqrt(self.single_header_inplanes)
    -        # B*h, 1, H*W, 1
    -        context_mask = tf.keras.activations.softmax(context_mask, axis=2)
    -
    -        # Compute context
    -        # B*h, 1, C/h, 1
    -        context = tf.matmul(shortcut, context_mask)
    -        context = tf.reshape(context, shape=(b, 1, c, 1))
    -        # B, 1, 1, C
    -        context = tf.transpose(context, perm=(0, 1, 3, 2))
    -        # Set shape to resolve shape when calling this module in the Sequential MAGCResnet
    -        batch, chan = inputs.get_shape().as_list()[0], inputs.get_shape().as_list()[-1]
    -        context.set_shape([batch, 1, 1, chan])
    -        return context
    -
    -    def call(self, inputs: tf.Tensor, **kwargs) -> tf.Tensor:
    -        # Context modeling: B, H, W, C  ->  B, 1, 1, C
    -        context = self.context_modeling(inputs)
    -        # Transform: B, 1, 1, C  ->  B, 1, 1, C
    -        transformed = self.transform(context)
    -        return inputs + transformed
    -
    -
    -class MAGCResnet(Sequential):
    -
    -    """Implements the modified resnet with MAGC layers, as described in paper.
    -
    -    Args:
    -        headers: number of header to split channels in MAGC layers
    -        input_shape: shape of the model input (without batch dim)
    -    """
    -
    -    def __init__(
    -        self,
    -        headers: int = 1,
    -        input_shape: Tuple[int, int, int] = (48, 160, 3),
    -    ) -> None:
    -        _layers = [
    -            # conv_1x
    -            *conv_sequence(out_channels=64, activation='relu', bn=True, kernel_size=3, input_shape=input_shape),
    -            *conv_sequence(out_channels=128, activation='relu', bn=True, kernel_size=3),
    -            layers.MaxPooling2D((2, 2), (2, 2)),
    -            # conv_2x
    -            ResnetStage(num_blocks=1, output_channels=256),
    -            MAGC(inplanes=256, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=256, activation='relu', bn=True, kernel_size=3),
    -            layers.MaxPooling2D((2, 2), (2, 2)),
    -            # conv_3x
    -            ResnetStage(num_blocks=2, output_channels=512),
    -            MAGC(inplanes=512, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=512, activation='relu', bn=True, kernel_size=3),
    -            layers.MaxPooling2D((2, 1), (2, 1)),
    -            # conv_4x
    -            ResnetStage(num_blocks=5, output_channels=512),
    -            MAGC(inplanes=512, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=512, activation='relu', bn=True, kernel_size=3),
    -            # conv_5x
    -            ResnetStage(num_blocks=3, output_channels=512),
    -            MAGC(inplanes=512, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=512, activation='relu', bn=True, kernel_size=3),
    -        ]
    -        super().__init__(_layers)
    -
    -
     class MASTER(_MASTER, Model):
    -
         """Implements MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_.
         Implementation based on the official TF implementation: <https://github.com/jiangxiluning/MASTER-TF>`_.
     
         Args:
    +    ----
    +        feature_extractor: the backbone serving as feature extractor
             vocab: vocabulary, (without EOS, SOS, PAD)
             d_model: d parameter for the transformer decoder
    -        headers: headers for the MAGC module
             dff: depth of the pointwise feed-forward layer
             num_heads: number of heads for the mutli-head attention module
             num_layers: number of decoder layers to stack
             max_length: maximum length of character sequence handled by the model
    -        input_size: size of the image inputs
    +        dropout: dropout probability of the decoder
    +        input_shape: size of the image inputs
    +        exportable: onnx exportable returns only logits
    +        cfg: dictionary containing information about the model
         """
     
         def __init__(
             self,
    +        feature_extractor: Model,
             vocab: str,
             d_model: int = 512,
    -        headers: int = 1,
             dff: int = 2048,
    -        num_heads: int = 8,
    +        num_heads: int = 8,  # number of heads in the transformer decoder
             num_layers: int = 3,
             max_length: int = 50,
    -        input_shape: Tuple[int, int, int] = (48, 160, 3),
    +        dropout: float = 0.2,
    +        input_shape: Tuple[int, int, int] = (32, 128, 3),  # different from the paper
    +        exportable: bool = False,
             cfg: Optional[Dict[str, Any]] = None,
         ) -> None:
             super().__init__()
     
    -        self.vocab = vocab
    +        self.exportable = exportable
             self.max_length = max_length
    +        self.d_model = d_model
    +        self.vocab = vocab
             self.cfg = cfg
             self.vocab_size = len(vocab)
     
    -        self.feature_extractor = MAGCResnet(headers=headers, input_shape=input_shape)
    -        self.seq_embedding = layers.Embedding(self.vocab_size + 3, d_model)  # 3 more classes: EOS/PAD/SOS
    +        self.feat_extractor = feature_extractor
    +        self.positional_encoding = PositionalEncoding(self.d_model, dropout, max_len=input_shape[0] * input_shape[1])
     
             self.decoder = Decoder(
                 num_layers=num_layers,
    -            d_model=d_model,
    +            d_model=self.d_model,
                 num_heads=num_heads,
    +            vocab_size=self.vocab_size + 3,  # EOS, SOS, PAD
                 dff=dff,
    -            vocab_size=self.vocab_size,
    -            maximum_position_encoding=max_length,
    +            dropout=dropout,
    +            maximum_position_encoding=self.max_length,
             )
    -        self.feature_pe = positional_encoding(input_shape[0] * input_shape[1], d_model)
    -        self.linear = layers.Dense(self.vocab_size + 3, kernel_initializer=tf.initializers.he_uniform())
     
    +        self.linear = layers.Dense(self.vocab_size + 3, kernel_initializer=tf.initializers.he_uniform())
             self.postprocessor = MASTERPostProcessor(vocab=self.vocab)
     
         @tf.function
    -    def make_mask(self, target: tf.Tensor) -> tf.Tensor:
    -        look_ahead_mask = create_look_ahead_mask(tf.shape(target)[1])
    -        target_padding_mask = create_padding_mask(target, self.vocab_size + 2)  # Pad symbol
    -        combined_mask = tf.maximum(target_padding_mask, look_ahead_mask)
    -        return combined_mask
    +    def make_source_and_target_mask(self, source: tf.Tensor, target: tf.Tensor) -> Tuple[tf.Tensor, tf.Tensor]:
    +        # [1, 1, 1, ..., 0, 0, 0] -> 0 is masked
    +        # (N, 1, 1, max_length)
    +        target_pad_mask = tf.cast(tf.math.not_equal(target, self.vocab_size + 2), dtype=tf.uint8)
    +        target_pad_mask = target_pad_mask[:, tf.newaxis, tf.newaxis, :]
    +        target_length = target.shape[1]
    +        # sub mask filled diagonal with 1 = see 0 = masked (max_length, max_length)
    +        target_sub_mask = tf.linalg.band_part(tf.ones((target_length, target_length)), -1, 0)
    +        # source mask filled with ones (max_length, positional_encoded_seq_len)
    +        source_mask = tf.ones((target_length, source.shape[1]))
    +        # combine the two masks into one boolean mask where False is masked (N, 1, max_length, max_length)
    +        target_mask = tf.math.logical_and(
    +            tf.cast(target_sub_mask, dtype=tf.bool), tf.cast(target_pad_mask, dtype=tf.bool)
    +        )
    +        return source_mask, target_mask
     
    +    @staticmethod
         def compute_loss(
    -        self,
             model_output: tf.Tensor,
             gt: tf.Tensor,
             seq_len: List[int],
    @@ -512,11 +413,13 @@ 

    Source code for doctr.models.recognition.master.tensorflow

    Sequences are masked after the EOS character. Args: + ---- gt: the encoded tensor with gt labels model_output: predicted logits of the model seq_len: lengths of each gt word inside the batch Returns: + ------- The loss of the model on the batch """ # Input length : number of timesteps @@ -532,7 +435,7 @@

    Source code for doctr.models.recognition.master.tensorflow

    mask_values = tf.zeros_like(cce) mask_2d = tf.sequence_mask(seq_len, input_len - 1) # delete the last mask timestep as well masked_loss = tf.where(mask_2d, cce, mask_values) - ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32)) + ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, model_output.dtype)) return tf.expand_dims(ce_loss, axis=1) @@ -547,94 +450,103 @@

    Source code for doctr.models.recognition.master.tensorflow

    """Call function for training Args: + ---- x: images target: list of str labels return_model_output: if True, return logits return_preds: if True, decode logits + **kwargs: keyword arguments passed to the decoder - Return: + Returns: + ------- A dictionnary containing eventually loss, logits and predictions. """ - # Encode - feature = self.feature_extractor(x, **kwargs) - b, h, w, c = (tf.shape(feature)[i] for i in range(4)) + feature = self.feat_extractor(x, **kwargs) + b, h, w, c = feature.get_shape() + # (N, H, W, C) --> (N, H * W, C) feature = tf.reshape(feature, shape=(b, h * w, c)) - encoded = feature + self.feature_pe[:, :h * w, :] + # add positional encoding to features + encoded = self.positional_encoding(feature, **kwargs) out: Dict[str, tf.Tensor] = {} + if kwargs.get("training", False) and target is None: + raise ValueError("Need to provide labels during training") + if target is not None: # Compute target: tensor of gts and sequence lengths - gt, seq_len = self.compute_target(target) - - if kwargs.get('training', False): - if target is None: - raise AssertionError("In training mode, you need to pass a value to 'target'") - tgt_mask = self.make_mask(gt) + gt, seq_len = self.build_target(target) + # Compute decoder masks + source_mask, target_mask = self.make_source_and_target_mask(encoded, gt) # Compute logits - output = self.decoder(gt, encoded, tgt_mask, None, **kwargs) + output = self.decoder(gt, encoded, source_mask, target_mask, **kwargs) logits = self.linear(output, **kwargs) - else: - # When not training, we want to compute logits in with the decoder, although - # we have access to gts (we need gts to compute the loss, but not in the decoder) logits = self.decode(encoded, **kwargs) + logits = _bf16_to_float32(logits) + + if self.exportable: + out["logits"] = logits + return out + if target is not None: - out['loss'] = self.compute_loss(logits, gt, seq_len) + out["loss"] = self.compute_loss(logits, gt, seq_len) if return_model_output: - out['out_map'] = logits + out["out_map"] = logits if return_preds: - predictions = self.postprocessor(logits) - out['preds'] = predictions + out["preds"] = self.postprocessor(logits) return out + @tf.function def decode(self, encoded: tf.Tensor, **kwargs: Any) -> tf.Tensor: """Decode function for prediction Args: + ---- encoded: encoded features + **kwargs: keyword arguments passed to the decoder - Return: + Returns: + ------- A Tuple of tf.Tensor: predictions, logits """ - b = tf.shape(encoded)[0] - max_len = tf.constant(self.max_length, dtype=tf.int32) + b = encoded.shape[0] + start_symbol = tf.constant(self.vocab_size + 1, dtype=tf.int32) # SOS padding_symbol = tf.constant(self.vocab_size + 2, dtype=tf.int32) # PAD - ys = tf.fill(dims=(b, max_len - 1), value=padding_symbol) + ys = tf.fill(dims=(b, self.max_length - 1), value=padding_symbol) start_vector = tf.fill(dims=(b, 1), value=start_symbol) ys = tf.concat([start_vector, ys], axis=-1) - logits = tf.zeros(shape=(b, max_len - 1, self.vocab_size + 3), dtype=tf.float32) # 3 symbols - # max_len = len + 2 (sos + eos) + # Final dimension include EOS/SOS/PAD for i in range(self.max_length - 1): - ys_mask = self.make_mask(ys) - output = self.decoder(ys, encoded, ys_mask, None, **kwargs) + source_mask, target_mask = self.make_source_and_target_mask(encoded, ys) + output = self.decoder(ys, encoded, source_mask, target_mask, **kwargs) logits = self.linear(output, **kwargs) prob = tf.nn.softmax(logits, axis=-1) - next_word = tf.argmax(prob, axis=-1, output_type=ys.dtype) - # ys.shape = B, T - i_mesh, j_mesh = tf.meshgrid(tf.range(b), tf.range(max_len), indexing='ij') + next_token = tf.argmax(prob, axis=-1, output_type=ys.dtype) + # update ys with the next token and ignore the first token (SOS) + i_mesh, j_mesh = tf.meshgrid(tf.range(b), tf.range(self.max_length), indexing="ij") indices = tf.stack([i_mesh[:, i + 1], j_mesh[:, i + 1]], axis=1) - ys = tf.tensor_scatter_nd_update(ys, indices, next_word[:, i + 1]) + ys = tf.tensor_scatter_nd_update(ys, indices, next_token[:, i]) - # final_logits of shape (N, max_length - 1, vocab_size + 1) (whithout sos) + # Shape (N, max_length, vocab_size + 1) return logits class MASTERPostProcessor(_MASTERPostProcessor): """Post processor for MASTER architectures + Args: + ---- vocab: string containing the ordered sequence of supported characters - ignore_case: if True, ignore case of letters - ignore_accents: if True, ignore accents of letters """ def __call__( @@ -649,51 +561,66 @@

    Source code for doctr.models.recognition.master.tensorflow

    probs = tf.math.reduce_min(probs, axis=1) # decode raw output of the model with tf_label_to_idx - out_idxs = tf.cast(out_idxs, dtype='int32') + out_idxs = tf.cast(out_idxs, dtype="int32") embedding = tf.constant(self._embedding, dtype=tf.string) decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(embedding, out_idxs), axis=-1) decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>") - decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0] + decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value="not valid")[:, 0] word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] - return list(zip(word_values, probs.numpy().tolist())) + return list(zip(word_values, probs.numpy().clip(0, 1).tolist())) -def _master(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> MASTER: +def _master(arch: str, pretrained: bool, backbone_fn, pretrained_backbone: bool = True, **kwargs: Any) -> MASTER: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab']) + _cfg["input_shape"] = kwargs.get("input_shape", _cfg["input_shape"]) + _cfg["vocab"] = kwargs.get("vocab", _cfg["vocab"]) - kwargs['vocab'] = _cfg['vocab'] + kwargs["vocab"] = _cfg["vocab"] + kwargs["input_shape"] = _cfg["input_shape"] # Build the model - model = MASTER(cfg=_cfg, **kwargs) + model = MASTER( + backbone_fn(pretrained=pretrained_backbone, input_shape=_cfg["input_shape"], include_top=False), + cfg=_cfg, + **kwargs, + ) + _build_model(model) + # Load pretrained parameters if pretrained: - load_pretrained_params(model, default_cfgs[arch]['url']) + # The given vocab differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, default_cfgs[arch]["url"], skip_mismatch=kwargs["vocab"] != default_cfgs[arch]["vocab"] + ) return model
    -[docs] +[docs] def master(pretrained: bool = False, **kwargs: Any) -> MASTER: """MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import master - >>> model = master(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 48, 160, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + + >>> import tensorflow as tf + >>> from doctr.models import master + >>> model = master(pretrained=False) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keywoard arguments passed to the MASTER architecture + Returns: + ------- text recognition architecture """ - - return _master('master', pretrained, **kwargs)
    + return _master("master", pretrained, magc_resnet31, **kwargs)
    @@ -727,8 +654,8 @@

    Source code for doctr.models.recognition.master.tensorflow

    - +
    + diff --git a/v0.4.0/_modules/doctr/models/recognition/parseq/tensorflow.html b/v0.4.0/_modules/doctr/models/recognition/parseq/tensorflow.html index 0819737dfc..b181acef53 100644 --- a/v0.4.0/_modules/doctr/models/recognition/parseq/tensorflow.html +++ b/v0.4.0/_modules/doctr/models/recognition/parseq/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.parseq.tensorflow - docTR documentation @@ -845,7 +845,7 @@

    Source code for doctr.models.recognition.parseq.tensorflow

    - + diff --git a/v0.4.0/_modules/doctr/models/recognition/sar.html b/v0.4.0/_modules/doctr/models/recognition/sar.html deleted file mode 100644 index 2482e9f156..0000000000 --- a/v0.4.0/_modules/doctr/models/recognition/sar.html +++ /dev/null @@ -1,712 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.sar - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.sar

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import Sequential, layers
    -from typing import Tuple, Dict, List, Any, Optional
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel
    -from .core import RecognitionPostProcessor
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['SAR', 'SARPostProcessor', 'sar_vgg16_bn', 'sar_resnet31']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'sar_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1-models/sar_vgg16bn-0d7e2c26.zip',
    -    },
    -    'sar_resnet31': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'resnet31', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/sar_resnet31-ea202587.zip',
    -    },
    -}
    -
    -
    -class AttentionModule(layers.Layer, NestedObject):
    -    """Implements attention module of the SAR model
    -
    -    Args:
    -        attention_units: number of hidden attention units
    -
    -    """
    -    def __init__(
    -        self,
    -        attention_units: int
    -    ) -> None:
    -
    -        super().__init__()
    -        self.hidden_state_projector = layers.Conv2D(
    -            attention_units, 1, strides=1, use_bias=False, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.features_projector = layers.Conv2D(
    -            attention_units, 3, strides=1, use_bias=True, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.attention_projector = layers.Conv2D(
    -            1, 1, strides=1, use_bias=False, padding="same", kernel_initializer='he_normal',
    -        )
    -        self.flatten = layers.Flatten()
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        hidden_state: tf.Tensor,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        [H, W] = features.get_shape().as_list()[1:3]
    -        # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units)
    -        hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs)
    -        # shape (N, H, W, vgg_units) -> (N, H, W, attention_units)
    -        features_projection = self.features_projector(features, **kwargs)
    -        projection = tf.math.tanh(hidden_state_projection + features_projection)
    -        # shape (N, H, W, attention_units) -> (N, H, W, 1)
    -        attention = self.attention_projector(projection, **kwargs)
    -        # shape (N, H, W, 1) -> (N, H * W)
    -        attention = self.flatten(attention)
    -        attention = tf.nn.softmax(attention)
    -        # shape (N, H * W) -> (N, H, W, 1)
    -        attention_map = tf.reshape(attention, [-1, H, W, 1])
    -        glimpse = tf.math.multiply(features, attention_map)
    -        # shape (N, H * W) -> (N, 1)
    -        glimpse = tf.reduce_sum(glimpse, axis=[1, 2])
    -        return glimpse
    -
    -
    -class SARDecoder(layers.Layer, NestedObject):
    -    """Implements decoder module of the SAR model
    -
    -    Args:
    -        rnn_units: number of hidden units in recurrent cells
    -        max_length: maximum length of a sequence
    -        vocab_size: number of classes in the model alphabet
    -        embedding_units: number of hidden embedding units
    -        attention_units: number of hidden attention units
    -        num_decoder_layers: number of LSTM layers to stack
    -
    -    """
    -    def __init__(
    -        self,
    -        rnn_units: int,
    -        max_length: int,
    -        vocab_size: int,
    -        embedding_units: int,
    -        attention_units: int,
    -        num_decoder_layers: int = 2,
    -        input_shape: Optional[List[Tuple[Optional[int]]]] = None,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.vocab_size = vocab_size
    -        self.lstm_decoder = layers.StackedRNNCells(
    -            [layers.LSTMCell(rnn_units, dtype=tf.float32, implementation=1) for _ in range(num_decoder_layers)]
    -        )
    -        self.embed = layers.Dense(embedding_units, use_bias=False, input_shape=(None, self.vocab_size + 1))
    -        self.attention_module = AttentionModule(attention_units)
    -        self.output_dense = layers.Dense(vocab_size + 1, use_bias=True, input_shape=(None, 2 * rnn_units))
    -        self.max_length = max_length
    -
    -        # Initialize kernels
    -        if input_shape is not None:
    -            self.attention_module.call(layers.Input(input_shape[0][1:]), layers.Input((1, 1, rnn_units)))
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        holistic: tf.Tensor,
    -        gt: Optional[tf.Tensor] = None,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # initialize states (each of shape (N, rnn_units))
    -        states = self.lstm_decoder.get_initial_state(
    -            inputs=None, batch_size=features.shape[0], dtype=tf.float32
    -        )
    -        # run first step of lstm
    -        # holistic: shape (N, rnn_units)
    -        _, states = self.lstm_decoder(holistic, states, **kwargs)
    -        # Initialize with the index of virtual START symbol (placed after <eos>)
    -        symbol = tf.fill(features.shape[0], self.vocab_size + 1)
    -        logits_list = []
    -        if kwargs.get('training') and gt is None:
    -            raise ValueError('Need to provide labels during training for teacher forcing')
    -        for t in range(self.max_length + 1):  # keep 1 step for <eos>
    -            # one-hot symbol with depth vocab_size + 1
    -            # embeded_symbol: shape (N, embedding_units)
    -            embeded_symbol = self.embed(tf.one_hot(symbol, depth=self.vocab_size + 1), **kwargs)
    -            logits, states = self.lstm_decoder(embeded_symbol, states, **kwargs)
    -            glimpse = self.attention_module(
    -                features, tf.expand_dims(tf.expand_dims(logits, axis=1), axis=1), **kwargs,
    -            )
    -            # logits: shape (N, rnn_units), glimpse: shape (N, 1)
    -            logits = tf.concat([logits, glimpse], axis=-1)
    -            # shape (N, rnn_units + 1) -> (N, vocab_size + 1)
    -            logits = self.output_dense(logits, **kwargs)
    -            # update symbol with predicted logits for t+1 step
    -            if kwargs.get('training'):
    -                symbol = gt[:, t]
    -            else:
    -                symbol = tf.argmax(logits, axis=-1)
    -            logits_list.append(logits)
    -        outputs = tf.stack(logits_list, axis=1)  # shape (N, max_length + 1, vocab_size + 1)
    -
    -        return outputs
    -
    -
    -class SAR(RecognitionModel):
    -    """Implements a SAR architecture as described in `"Show, Attend and Read:A Simple and Strong Baseline for
    -    Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of hidden units in both encoder and decoder LSTM
    -        embedding_units: number of embedding units
    -        attention_units: number of hidden units in attention module
    -        max_length: maximum word length handled by the model
    -        num_decoders: number of LSTM to stack in decoder layer
    -
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'encoder', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor,
    -        vocab: str,
    -        rnn_units: int = 512,
    -        embedding_units: int = 512,
    -        attention_units: int = 512,
    -        max_length: int = 30,
    -        num_decoders: int = 2,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(vocab=vocab, cfg=cfg)
    -
    -        self.max_length = max_length + 1  # Add 1 timestep for EOS after the longest word
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.encoder = Sequential(
    -            [
    -                layers.LSTM(units=rnn_units, return_sequences=True),
    -                layers.LSTM(units=rnn_units, return_sequences=False)
    -            ]
    -        )
    -        # Initialize the kernels (watch out for reduce_max)
    -        self.encoder.build(input_shape=(None,) + self.feat_extractor.output_shape[2:])
    -
    -        self.decoder = SARDecoder(
    -            rnn_units, max_length, len(vocab), embedding_units, attention_units, num_decoders,
    -            input_shape=[self.feat_extractor.output_shape, self.encoder.output_shape]
    -        )
    -
    -        self.postprocessor = SARPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        gt: tf.Tensor,
    -        seq_len: tf.Tensor,
    -    ) -> tf.Tensor:
    -        """Compute categorical cross-entropy loss for the model.
    -        Sequences are masked after the EOS character.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        # Input length : number of timesteps
    -        input_len = tf.shape(model_output)[1]
    -        # Add one for additional <eos> token
    -        seq_len = seq_len + 1
    -        # One-hot gt labels
    -        oh_gt = tf.one_hot(gt, depth=model_output.shape[2])
    -        # Compute loss
    -        cce = tf.nn.softmax_cross_entropy_with_logits(oh_gt, model_output)
    -        # Compute mask
    -        mask_values = tf.zeros_like(cce)
    -        mask_2d = tf.sequence_mask(seq_len, input_len)
    -        masked_loss = tf.where(mask_2d, cce, mask_values)
    -        ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32))
    -        return tf.expand_dims(ce_loss, axis=1)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        pooled_features = tf.reduce_max(features, axis=1)  # vertical max pooling
    -        encoded = self.encoder(pooled_features, **kwargs)
    -        if target is not None:
    -            gt, seq_len = self.compute_target(target)
    -        decoded_features = self.decoder(features, encoded, gt=None if target is None else gt, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = decoded_features
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(decoded_features)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(decoded_features, gt, seq_len)
    -
    -        return out
    -
    -
    -class SARPostProcessor(RecognitionPostProcessor):
    -    """Post processor for SAR architectures
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor,
    -    ) -> List[Tuple[str, float]]:
    -        # compute pred with argmax for attention models
    -        out_idxs = tf.math.argmax(logits, axis=2)
    -        # N x L
    -        probs = tf.gather(tf.nn.softmax(logits, axis=-1), out_idxs, axis=-1, batch_dims=2)
    -        # Take the minimum confidence of the sequence
    -        probs = tf.math.reduce_min(probs, axis=1)
    -
    -        # decode raw output of the model with tf_label_to_idx
    -        out_idxs = tf.cast(out_idxs, dtype='int32')
    -        decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(self._embedding, out_idxs), axis=-1)
    -        decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -def _sar(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> SAR:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -    _cfg['embedding_units'] = kwargs.get('embedding_units', _cfg['rnn_units'])
    -    _cfg['attention_units'] = kwargs.get('attention_units', _cfg['rnn_units'])
    -    _cfg['max_length'] = kwargs.get('max_length', _cfg['max_length'])
    -    _cfg['num_decoders'] = kwargs.get('num_decoders', _cfg['num_decoders'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[default_cfgs[arch]['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -    kwargs['embedding_units'] = _cfg['embedding_units']
    -    kwargs['attention_units'] = _cfg['attention_units']
    -    kwargs['max_length'] = _cfg['max_length']
    -    kwargs['num_decoders'] = _cfg['num_decoders']
    -
    -    # Build the model
    -    model = SAR(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, default_cfgs[arch]['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a VGG16 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import sar_vgg16_bn - >>> model = sar_vgg16_bn(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_vgg16_bn', pretrained, **kwargs)
    - - - -
    -[docs] -def sar_resnet31(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a resnet-31 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example: - >>> import tensorflow as tf - >>> from doctr.models import sar_resnet31 - >>> model = sar_resnet31(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_resnet31', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.0/_modules/doctr/models/recognition/sar/tensorflow.html b/v0.4.0/_modules/doctr/models/recognition/sar/tensorflow.html index e514e4f0c4..4a591e6451 100644 --- a/v0.4.0/_modules/doctr/models/recognition/sar/tensorflow.html +++ b/v0.4.0/_modules/doctr/models/recognition/sar/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.sar.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.sar.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple
    +
     import tensorflow as tf
    -from tensorflow.keras import Sequential, layers, Model
    -from typing import Tuple, Dict, List, Any, Optional
    +from tensorflow.keras import Model, Sequential, layers
     
    -from ... import backbones
    -from ...utils import load_pretrained_params
    -from ..core import RecognitionModel, RecognitionPostProcessor
    +from doctr.datasets import VOCABS
     from doctr.utils.repr import NestedObject
     
    -__all__ = ['SAR', 'SARPostProcessor', 'sar_vgg16_bn', 'sar_resnet31']
    +from ...classification import resnet31
    +from ...utils.tensorflow import _bf16_to_float32, _build_model, load_pretrained_params
    +from ..core import RecognitionModel, RecognitionPostProcessor
    +
    +__all__ = ["SAR", "sar_resnet31"]
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'sar_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1-models/sar_vgg16bn-0d7e2c26.zip',
    -    },
    -    'sar_resnet31': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'resnet31', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/sar_resnet31-ea202587.zip',
    +    "sar_resnet31": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/sar_resnet31-5a58806c.weights.h5&src=0",
         },
     }
     
     
    +class SAREncoder(layers.Layer, NestedObject):
    +    """Implements encoder module of the SAR model
    +
    +    Args:
    +    ----
    +        rnn_units: number of hidden rnn units
    +        dropout_prob: dropout probability
    +    """
    +
    +    def __init__(self, rnn_units: int, dropout_prob: float = 0.0) -> None:
    +        super().__init__()
    +        self.rnn = Sequential([
    +            layers.LSTM(units=rnn_units, return_sequences=True, recurrent_dropout=dropout_prob),
    +            layers.LSTM(units=rnn_units, return_sequences=False, recurrent_dropout=dropout_prob),
    +        ])
    +
    +    def call(
    +        self,
    +        x: tf.Tensor,
    +        **kwargs: Any,
    +    ) -> tf.Tensor:
    +        # (N, C)
    +        return self.rnn(x, **kwargs)
    +
    +
     class AttentionModule(layers.Layer, NestedObject):
         """Implements attention module of the SAR model
     
         Args:
    +    ----
             attention_units: number of hidden attention units
     
         """
    -    def __init__(
    -        self,
    -        attention_units: int
    -    ) -> None:
     
    +    def __init__(self, attention_units: int) -> None:
             super().__init__()
             self.hidden_state_projector = layers.Conv2D(
    -            attention_units, 1, strides=1, use_bias=False, padding='same', kernel_initializer='he_normal',
    +            attention_units,
    +            1,
    +            strides=1,
    +            use_bias=False,
    +            padding="same",
    +            kernel_initializer="he_normal",
             )
             self.features_projector = layers.Conv2D(
    -            attention_units, 3, strides=1, use_bias=True, padding='same', kernel_initializer='he_normal',
    +            attention_units,
    +            3,
    +            strides=1,
    +            use_bias=True,
    +            padding="same",
    +            kernel_initializer="he_normal",
             )
             self.attention_projector = layers.Conv2D(
    -            1, 1, strides=1, use_bias=False, padding="same", kernel_initializer='he_normal',
    +            1,
    +            1,
    +            strides=1,
    +            use_bias=False,
    +            padding="same",
    +            kernel_initializer="he_normal",
             )
             self.flatten = layers.Flatten()
     
    @@ -343,12 +395,12 @@ 

    Source code for doctr.models.recognition.sar.tensorflow

    hidden_state: tf.Tensor, **kwargs: Any, ) -> tf.Tensor: - [H, W] = features.get_shape().as_list()[1:3] - # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units) - hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs) # shape (N, H, W, vgg_units) -> (N, H, W, attention_units) features_projection = self.features_projector(features, **kwargs) + # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units) + hidden_state = tf.expand_dims(tf.expand_dims(hidden_state, axis=1), axis=1) + hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs) projection = tf.math.tanh(hidden_state_projection + features_projection) # shape (N, H, W, attention_units) -> (N, H, W, 1) attention = self.attention_projector(projection, **kwargs) @@ -358,23 +410,25 @@

    Source code for doctr.models.recognition.sar.tensorflow

    # shape (N, H * W) -> (N, H, W, 1) attention_map = tf.reshape(attention, [-1, H, W, 1]) glimpse = tf.math.multiply(features, attention_map) - # shape (N, H * W) -> (N, 1) - glimpse = tf.reduce_sum(glimpse, axis=[1, 2]) - return glimpse + # shape (N, H * W) -> (N, C) + return tf.reduce_sum(glimpse, axis=[1, 2]) class SARDecoder(layers.Layer, NestedObject): """Implements decoder module of the SAR model Args: + ---- rnn_units: number of hidden units in recurrent cells max_length: maximum length of a sequence vocab_size: number of classes in the model alphabet embedding_units: number of hidden embedding units attention_units: number of hidden attention units - num_decoder_layers: number of LSTM layers to stack + num_decoder_cells: number of LSTMCell layers to stack + dropout_prob: dropout probability """ + def __init__( self, rnn_units: int, @@ -382,23 +436,22 @@

    Source code for doctr.models.recognition.sar.tensorflow

    vocab_size: int, embedding_units: int, attention_units: int, - num_decoder_layers: int = 2, - input_shape: Optional[List[Tuple[Optional[int]]]] = None, + num_decoder_cells: int = 2, + dropout_prob: float = 0.0, ) -> None: - super().__init__() self.vocab_size = vocab_size - self.lstm_decoder = layers.StackedRNNCells( - [layers.LSTMCell(rnn_units, dtype=tf.float32, implementation=1) for _ in range(num_decoder_layers)] - ) - self.embed = layers.Dense(embedding_units, use_bias=False, input_shape=(None, self.vocab_size + 1)) - self.attention_module = AttentionModule(attention_units) - self.output_dense = layers.Dense(vocab_size + 1, use_bias=True, input_shape=(None, 2 * rnn_units)) self.max_length = max_length - # Initialize kernels - if input_shape is not None: - self.attention_module.call(layers.Input(input_shape[0][1:]), layers.Input((1, 1, rnn_units))) + self.embed = layers.Dense(embedding_units, use_bias=False) + self.embed_tgt = layers.Embedding(embedding_units, self.vocab_size + 1) + + self.lstm_cells = layers.StackedRNNCells([ + layers.LSTMCell(rnn_units, implementation=1) for _ in range(num_decoder_cells) + ]) + self.attention_module = AttentionModule(attention_units) + self.output_dense = layers.Dense(self.vocab_size + 1, use_bias=True) + self.dropout = layers.Dropout(dropout_prob) def call( self, @@ -407,40 +460,47 @@

    Source code for doctr.models.recognition.sar.tensorflow

    gt: Optional[tf.Tensor] = None, **kwargs: Any, ) -> tf.Tensor: - - # initialize states (each of shape (N, rnn_units)) - states = self.lstm_decoder.get_initial_state( - inputs=None, batch_size=features.shape[0], dtype=tf.float32 - ) - # run first step of lstm - # holistic: shape (N, rnn_units) - _, states = self.lstm_decoder(holistic, states, **kwargs) - # Initialize with the index of virtual START symbol (placed after <eos>) - symbol = tf.fill(features.shape[0], self.vocab_size + 1) - logits_list = [] - if kwargs.get('training') and gt is None: - raise ValueError('Need to provide labels during training for teacher forcing') - for t in range(self.max_length + 1): # keep 1 step for <eos> - # one-hot symbol with depth vocab_size + 1 - # embeded_symbol: shape (N, embedding_units) - embeded_symbol = self.embed(tf.one_hot(symbol, depth=self.vocab_size + 1), **kwargs) - logits, states = self.lstm_decoder(embeded_symbol, states, **kwargs) - glimpse = self.attention_module( - features, tf.expand_dims(tf.expand_dims(logits, axis=1), axis=1), **kwargs, - ) - # logits: shape (N, rnn_units), glimpse: shape (N, 1) - logits = tf.concat([logits, glimpse], axis=-1) - # shape (N, rnn_units + 1) -> (N, vocab_size + 1) - logits = self.output_dense(logits, **kwargs) - # update symbol with predicted logits for t+1 step - if kwargs.get('training'): - symbol = gt[:, t] # type: ignore[index] + if gt is not None: + gt_embedding = self.embed_tgt(gt, **kwargs) + + logits_list: List[tf.Tensor] = [] + + for t in range(self.max_length + 1): # 32 + if t == 0: + # step to init the first states of the LSTMCell + states = self.lstm_cells.get_initial_state( + inputs=None, batch_size=features.shape[0], dtype=features.dtype + ) + prev_symbol = holistic + elif t == 1: + # step to init a 'blank' sequence of length vocab_size + 1 filled with zeros + # (N, vocab_size + 1) --> (N, embedding_units) + prev_symbol = tf.zeros([features.shape[0], self.vocab_size + 1], dtype=features.dtype) + prev_symbol = self.embed(prev_symbol, **kwargs) else: - symbol = tf.argmax(logits, axis=-1) - logits_list.append(logits) - outputs = tf.stack(logits_list, axis=1) # shape (N, max_length + 1, vocab_size + 1) - - return outputs + if gt is not None and kwargs.get("training", False): + # (N, embedding_units) -2 because of <bos> and <eos> (same) + prev_symbol = self.embed(gt_embedding[:, t - 2], **kwargs) + else: + # -1 to start at timestep where prev_symbol was initialized + index = tf.argmax(logits_list[t - 1], axis=-1) + # update prev_symbol with ones at the index of the previous logit vector + prev_symbol = self.embed(self.embed_tgt(index, **kwargs), **kwargs) + + # (N, C), (N, C) take the last hidden state and cell state from current timestep + _, states = self.lstm_cells(prev_symbol, states, **kwargs) + # states = (hidden_state, cell_state) + hidden_state = states[0][0] + # (N, H, W, C), (N, C) --> (N, C) + glimpse = self.attention_module(features, hidden_state, **kwargs) + # (N, C), (N, C) --> (N, 2 * C) + logits = tf.concat([hidden_state, glimpse], axis=1) + logits = self.dropout(logits, **kwargs) + # (N, vocab_size + 1) + logits_list.append(self.output_dense(logits, **kwargs)) + + # (max_length + 1, N, vocab_size + 1) --> (N, max_length + 1, vocab_size + 1) + return tf.transpose(tf.stack(logits_list[1:]), (1, 0, 2)) class SAR(Model, RecognitionModel): @@ -448,17 +508,20 @@

    Source code for doctr.models.recognition.sar.tensorflow

    Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. Args: + ---- feature_extractor: the backbone serving as feature extractor vocab: vocabulary used for encoding rnn_units: number of hidden units in both encoder and decoder LSTM embedding_units: number of embedding units attention_units: number of hidden units in attention module max_length: maximum word length handled by the model - num_decoders: number of LSTM to stack in decoder layer - + num_decoder_cells: number of LSTMCell layers to stack + dropout_prob: dropout probability for the encoder and decoder + exportable: onnx exportable returns only logits + cfg: dictionary containing information about the model """ - _children_names: List[str] = ['feat_extractor', 'encoder', 'decoder', 'postprocessor'] + _children_names: List[str] = ["feat_extractor", "encoder", "decoder", "postprocessor"] def __init__( self, @@ -468,36 +531,34 @@

    Source code for doctr.models.recognition.sar.tensorflow

    embedding_units: int = 512, attention_units: int = 512, max_length: int = 30, - num_decoders: int = 2, + num_decoder_cells: int = 2, + dropout_prob: float = 0.0, + exportable: bool = False, cfg: Optional[Dict[str, Any]] = None, ) -> None: - super().__init__() self.vocab = vocab + self.exportable = exportable self.cfg = cfg - self.max_length = max_length + 1 # Add 1 timestep for EOS after the longest word self.feat_extractor = feature_extractor - self.encoder = Sequential( - [ - layers.LSTM(units=rnn_units, return_sequences=True), - layers.LSTM(units=rnn_units, return_sequences=False) - ] - ) - # Initialize the kernels (watch out for reduce_max) - self.encoder.build(input_shape=(None,) + self.feat_extractor.output_shape[2:]) - + self.encoder = SAREncoder(rnn_units, dropout_prob) self.decoder = SARDecoder( - rnn_units, max_length, len(vocab), embedding_units, attention_units, num_decoders, - input_shape=[self.feat_extractor.output_shape, self.encoder.output_shape] + rnn_units, + self.max_length, + len(vocab), + embedding_units, + attention_units, + num_decoder_cells, + dropout_prob, ) self.postprocessor = SARPostProcessor(vocab=vocab) + @staticmethod def compute_loss( - self, model_output: tf.Tensor, gt: tf.Tensor, seq_len: tf.Tensor, @@ -506,11 +567,13 @@

    Source code for doctr.models.recognition.sar.tensorflow

    Sequences are masked after the EOS character. Args: + ---- gt: the encoded tensor with gt labels model_output: predicted logits of the model seq_len: lengths of each gt word inside the batch Returns: + ------- The loss of the model on the batch """ # Input length : number of timesteps @@ -525,7 +588,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    mask_values = tf.zeros_like(cce) mask_2d = tf.sequence_mask(seq_len, input_len) masked_loss = tf.where(mask_2d, cce, mask_values) - ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32)) + ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, model_output.dtype)) return tf.expand_dims(ce_loss, axis=1) def call( @@ -536,16 +599,28 @@

    Source code for doctr.models.recognition.sar.tensorflow

    return_preds: bool = False, **kwargs: Any, ) -> Dict[str, Any]: - features = self.feat_extractor(x, **kwargs) - pooled_features = tf.reduce_max(features, axis=1) # vertical max pooling + # vertical max pooling --> (N, C, W) + pooled_features = tf.reduce_max(features, axis=1) + # holistic (N, C) encoded = self.encoder(pooled_features, **kwargs) + if target is not None: - gt, seq_len = self.compute_target(target) + gt, seq_len = self.build_target(target) seq_len = tf.cast(seq_len, tf.int32) - decoded_features = self.decoder(features, encoded, gt=None if target is None else gt, **kwargs) + + if kwargs.get("training", False) and target is None: + raise ValueError("Need to provide labels during training for teacher forcing") + + decoded_features = _bf16_to_float32( + self.decoder(features, encoded, gt=None if target is None else gt, **kwargs) + ) out: Dict[str, tf.Tensor] = {} + if self.exportable: + out["logits"] = decoded_features + return out + if return_model_output: out["out_map"] = decoded_features @@ -554,7 +629,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    out["preds"] = self.postprocessor(decoded_features) if target is not None: - out['loss'] = self.compute_loss(decoded_features, gt, seq_len) + out["loss"] = self.compute_loss(decoded_features, gt, seq_len) return out @@ -563,9 +638,8 @@

    Source code for doctr.models.recognition.sar.tensorflow

    """Post processor for SAR architectures Args: + ---- vocab: string containing the ordered sequence of supported characters - ignore_case: if True, ignore case of letters - ignore_accents: if True, ignore accents of letters """ def __call__( @@ -580,95 +654,75 @@

    Source code for doctr.models.recognition.sar.tensorflow

    probs = tf.math.reduce_min(probs, axis=1) # decode raw output of the model with tf_label_to_idx - out_idxs = tf.cast(out_idxs, dtype='int32') + out_idxs = tf.cast(out_idxs, dtype="int32") embedding = tf.constant(self._embedding, dtype=tf.string) decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(embedding, out_idxs), axis=-1) decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>") - decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0] + decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value="not valid")[:, 0] word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] - return list(zip(word_values, probs.numpy().tolist())) + return list(zip(word_values, probs.numpy().clip(0, 1).tolist())) -def _sar(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> SAR: +def _sar( + arch: str, + pretrained: bool, + backbone_fn, + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> SAR: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab']) - _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units']) - _cfg['embedding_units'] = kwargs.get('embedding_units', _cfg['rnn_units']) - _cfg['attention_units'] = kwargs.get('attention_units', _cfg['rnn_units']) - _cfg['max_length'] = kwargs.get('max_length', _cfg['max_length']) - _cfg['num_decoders'] = kwargs.get('num_decoders', _cfg['num_decoders']) + _cfg["input_shape"] = input_shape or _cfg["input_shape"] + _cfg["vocab"] = kwargs.get("vocab", _cfg["vocab"]) # Feature extractor - feat_extractor = backbones.__dict__[default_cfgs[arch]['backbone']]( - input_shape=_cfg['input_shape'], + feat_extractor = backbone_fn( + pretrained=pretrained_backbone, + input_shape=_cfg["input_shape"], include_top=False, ) - kwargs['vocab'] = _cfg['vocab'] - kwargs['rnn_units'] = _cfg['rnn_units'] - kwargs['embedding_units'] = _cfg['embedding_units'] - kwargs['attention_units'] = _cfg['attention_units'] - kwargs['max_length'] = _cfg['max_length'] - kwargs['num_decoders'] = _cfg['num_decoders'] + kwargs["vocab"] = _cfg["vocab"] # Build the model model = SAR(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) # Load pretrained parameters if pretrained: - load_pretrained_params(model, default_cfgs[arch]['url']) + # The given vocab differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, default_cfgs[arch]["url"], skip_mismatch=kwargs["vocab"] != default_cfgs[arch]["vocab"] + ) return model -
    -[docs] -def sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a VGG16 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import sar_vgg16_bn - >>> model = sar_vgg16_bn(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_vgg16_bn', pretrained, **kwargs)
    - - -
    -[docs] +[docs] def sar_resnet31(pretrained: bool = False, **kwargs: Any) -> SAR: """SAR with a resnet-31 feature extractor as described in `"Show, Attend and Read:A Simple and Strong Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - Example: - >>> import tensorflow as tf - >>> from doctr.models import sar_resnet31 - >>> model = sar_resnet31(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import sar_resnet31 + >>> model = sar_resnet31(pretrained=False) + >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the SAR architecture Returns: + ------- text recognition architecture """ - - return _sar('sar_resnet31', pretrained, **kwargs)
    + return _sar("sar_resnet31", pretrained, resnet31, **kwargs)
    @@ -702,8 +756,8 @@

    Source code for doctr.models.recognition.sar.tensorflow

    - +
    + diff --git a/v0.4.0/_modules/doctr/models/recognition/vitstr/tensorflow.html b/v0.4.0/_modules/doctr/models/recognition/vitstr/tensorflow.html index 6e101893bf..c594d40a56 100644 --- a/v0.4.0/_modules/doctr/models/recognition/vitstr/tensorflow.html +++ b/v0.4.0/_modules/doctr/models/recognition/vitstr/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.vitstr.tensorflow - docTR documentation @@ -621,7 +621,7 @@

    Source code for doctr.models.recognition.vitstr.tensorflow

    - + diff --git a/v0.4.0/_modules/doctr/models/recognition/zoo.html b/v0.4.0/_modules/doctr/models/recognition/zoo.html index bf0ae6af6e..f664304019 100644 --- a/v0.4.0/_modules/doctr/models/recognition/zoo.html +++ b/v0.4.0/_modules/doctr/models/recognition/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.zoo - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -from typing import Any
    +from typing import Any, List
     
    -from doctr.file_utils import is_tf_available, is_torch_available
    -from .core import RecognitionPredictor
    -from ..preprocessor import PreProcessor
    -from .. import recognition
    +from doctr.file_utils import is_tf_available
    +from doctr.models.preprocessor import PreProcessor
     
    +from .. import recognition
    +from .predictor import RecognitionPredictor
     
     __all__ = ["recognition_predictor"]
     
     
    -if is_tf_available():
    -    ARCHS = ['crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31', 'master']
    -elif is_torch_available():
    -    ARCHS = ['crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31']
    +ARCHS: List[str] = [
    +    "crnn_vgg16_bn",
    +    "crnn_mobilenet_v3_small",
    +    "crnn_mobilenet_v3_large",
    +    "sar_resnet31",
    +    "master",
    +    "vitstr_small",
    +    "vitstr_base",
    +    "parseq",
    +]
    +
     
    +def _predictor(arch: Any, pretrained: bool, **kwargs: Any) -> RecognitionPredictor:
    +    if isinstance(arch, str):
    +        if arch not in ARCHS:
    +            raise ValueError(f"unknown architecture '{arch}'")
     
    -def _predictor(arch: str, pretrained: bool, **kwargs: Any) -> RecognitionPredictor:
    +        _model = recognition.__dict__[arch](
    +            pretrained=pretrained, pretrained_backbone=kwargs.get("pretrained_backbone", True)
    +        )
    +    else:
    +        if not isinstance(
    +            arch, (recognition.CRNN, recognition.SAR, recognition.MASTER, recognition.ViTSTR, recognition.PARSeq)
    +        ):
    +            raise ValueError(f"unknown architecture: {type(arch)}")
    +        _model = arch
     
    -    if arch not in ARCHS:
    -        raise ValueError(f"unknown architecture '{arch}'")
    +    kwargs.pop("pretrained_backbone", None)
     
    -    _model = recognition.__dict__[arch](pretrained=pretrained)
    -    kwargs['mean'] = kwargs.get('mean', _model.cfg['mean'])
    -    kwargs['std'] = kwargs.get('std', _model.cfg['std'])
    -    kwargs['batch_size'] = kwargs.get('batch_size', 32)
    -    predictor = RecognitionPredictor(
    -        PreProcessor(_model.cfg['input_shape'][:2], preserve_aspect_ratio=True, **kwargs),
    -        _model
    -    )
    +    kwargs["mean"] = kwargs.get("mean", _model.cfg["mean"])
    +    kwargs["std"] = kwargs.get("std", _model.cfg["std"])
    +    kwargs["batch_size"] = kwargs.get("batch_size", 128)
    +    input_shape = _model.cfg["input_shape"][:2] if is_tf_available() else _model.cfg["input_shape"][-2:]
    +    predictor = RecognitionPredictor(PreProcessor(input_shape, preserve_aspect_ratio=True, **kwargs), _model)
     
         return predictor
     
     
     
    -[docs] -def recognition_predictor(arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) -> RecognitionPredictor: +[docs] +def recognition_predictor( + arch: Any = "crnn_vgg16_bn", + pretrained: bool = False, + symmetric_pad: bool = False, + batch_size: int = 128, + **kwargs: Any, +) -> RecognitionPredictor: """Text recognition architecture. Example:: @@ -326,14 +369,18 @@

    Source code for doctr.models.recognition.zoo

            >>> out = model([input_page])
     
         Args:
    -        arch: name of the architecture to use ('crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31')
    +    ----
    +        arch: name of the architecture or model itself to use (e.g. 'crnn_vgg16_bn')
             pretrained: If True, returns a model pre-trained on our text recognition dataset
    +        symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right
    +        batch_size: number of samples the model processes in parallel
    +        **kwargs: optional parameters to be passed to the architecture
     
         Returns:
    +    -------
             Recognition predictor
         """
    -
    -    return _predictor(arch, pretrained, **kwargs)
    + return _predictor(arch=arch, pretrained=pretrained, symmetric_pad=symmetric_pad, batch_size=batch_size, **kwargs)
    @@ -367,8 +414,8 @@

    Source code for doctr.models.recognition.zoo

       
    -
    - +
    + diff --git a/v0.4.0/_modules/doctr/models/zoo.html b/v0.4.0/_modules/doctr/models/zoo.html index dec6857019..d459671648 100644 --- a/v0.4.0/_modules/doctr/models/zoo.html +++ b/v0.4.0/_modules/doctr/models/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.zoo - docTR documentation @@ -225,15 +225,42 @@

    Source code for doctr.models.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     from typing import Any
    -from .core import OCRPredictor
    +
     from .detection.zoo import detection_predictor
    +from .kie_predictor import KIEPredictor
    +from .predictor import OCRPredictor
     from .recognition.zoo import recognition_predictor
     
    +__all__ = ["ocr_predictor", "kie_predictor"]
     
    -__all__ = ["ocr_predictor"]
    -
    -
    -def _predictor(det_arch: str, reco_arch: str, pretrained: bool, det_bs=2, reco_bs=128) -> OCRPredictor:
     
    +def _predictor(
    +    det_arch: Any,
    +    reco_arch: Any,
    +    pretrained: bool,
    +    pretrained_backbone: bool = True,
    +    assume_straight_pages: bool = True,
    +    preserve_aspect_ratio: bool = True,
    +    symmetric_pad: bool = True,
    +    det_bs: int = 2,
    +    reco_bs: int = 128,
    +    detect_orientation: bool = False,
    +    straighten_pages: bool = False,
    +    detect_language: bool = False,
    +    **kwargs,
    +) -> OCRPredictor:
         # Detection
    -    det_predictor = detection_predictor(det_arch, pretrained=pretrained, batch_size=det_bs)
    +    det_predictor = detection_predictor(
    +        det_arch,
    +        pretrained=pretrained,
    +        pretrained_backbone=pretrained_backbone,
    +        batch_size=det_bs,
    +        assume_straight_pages=assume_straight_pages,
    +        preserve_aspect_ratio=preserve_aspect_ratio,
    +        symmetric_pad=symmetric_pad,
    +    )
     
         # Recognition
    -    reco_predictor = recognition_predictor(reco_arch, pretrained=pretrained, batch_size=reco_bs)
    +    reco_predictor = recognition_predictor(
    +        reco_arch,
    +        pretrained=pretrained,
    +        pretrained_backbone=pretrained_backbone,
    +        batch_size=reco_bs,
    +    )
     
    -    return OCRPredictor(det_predictor, reco_predictor)
    +    return OCRPredictor(
    +        det_predictor,
    +        reco_predictor,
    +        assume_straight_pages=assume_straight_pages,
    +        preserve_aspect_ratio=preserve_aspect_ratio,
    +        symmetric_pad=symmetric_pad,
    +        detect_orientation=detect_orientation,
    +        straighten_pages=straighten_pages,
    +        detect_language=detect_language,
    +        **kwargs,
    +    )
     
     
     
    -[docs] +[docs] def ocr_predictor( - det_arch: str = 'db_resnet50', - reco_arch: str = 'crnn_vgg16_bn', + det_arch: Any = "fast_base", + reco_arch: Any = "crnn_vgg16_bn", pretrained: bool = False, - **kwargs: Any + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + export_as_straight_boxes: bool = False, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs: Any, ) -> OCRPredictor: """End-to-end OCR architecture using one model for localization, and another for text recognition. - Example:: - >>> import numpy as np - >>> from doctr.models import ocr_predictor - >>> model = ocr_predictor(pretrained=True) - >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) - >>> out = model([input_page]) + >>> 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(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) Args: - arch: name of the architecture to use ('db_sar_vgg', 'db_sar_resnet', 'db_crnn_vgg', 'db_crnn_resnet') + ---- + det_arch: name of the detection architecture or the model itself to use + (e.g. 'db_resnet50', 'db_mobilenet_v3_large') + reco_arch: name of the recognition architecture or the model itself to use + (e.g. 'crnn_vgg16_bn', 'sar_resnet31') pretrained: If True, returns a model pre-trained on our OCR dataset + pretrained_backbone: If True, returns a model with a pretrained backbone + assume_straight_pages: if True, speeds up the inference by assuming you only pass straight pages + without rotated textual elements. + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it. + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right. + export_as_straight_boxes: when assume_straight_pages is set to False, export final predictions + (potentially rotated) as straight bounding boxes. + detect_orientation: if True, the estimated general page orientation will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + straighten_pages: if True, estimates the page general orientation + based on the segmentation map median line orientation. + Then, rotates page before passing it again to the deep learning detection module. + Doing so will improve performances for documents with page-uniform rotations. + detect_language: if True, the language prediction will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + kwargs: keyword args of `OCRPredictor` Returns: + ------- OCR predictor """ + return _predictor( + det_arch, + reco_arch, + pretrained, + pretrained_backbone=pretrained_backbone, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + export_as_straight_boxes=export_as_straight_boxes, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + )
    + + - return _predictor(det_arch, reco_arch, pretrained, **kwargs)
    +def _kie_predictor( + det_arch: Any, + reco_arch: Any, + pretrained: bool, + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + det_bs: int = 2, + reco_bs: int = 128, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs, +) -> KIEPredictor: + # Detection + det_predictor = detection_predictor( + det_arch, + pretrained=pretrained, + pretrained_backbone=pretrained_backbone, + batch_size=det_bs, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + ) + + # Recognition + reco_predictor = recognition_predictor( + reco_arch, + pretrained=pretrained, + pretrained_backbone=pretrained_backbone, + batch_size=reco_bs, + ) + + return KIEPredictor( + det_predictor, + reco_predictor, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + ) + + +
    +[docs] +def kie_predictor( + det_arch: Any = "fast_base", + reco_arch: Any = "crnn_vgg16_bn", + pretrained: bool = False, + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + export_as_straight_boxes: bool = False, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs: Any, +) -> KIEPredictor: + """End-to-end KIE architecture using one model for localization, and another for text recognition. + + >>> 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(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) + + Args: + ---- + det_arch: name of the detection architecture or the model itself to use + (e.g. 'db_resnet50', 'db_mobilenet_v3_large') + reco_arch: name of the recognition architecture or the model itself to use + (e.g. 'crnn_vgg16_bn', 'sar_resnet31') + pretrained: If True, returns a model pre-trained on our OCR dataset + pretrained_backbone: If True, returns a model with a pretrained backbone + assume_straight_pages: if True, speeds up the inference by assuming you only pass straight pages + without rotated textual elements. + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it. + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right. + export_as_straight_boxes: when assume_straight_pages is set to False, export final predictions + (potentially rotated) as straight bounding boxes. + detect_orientation: if True, the estimated general page orientation will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + straighten_pages: if True, estimates the page general orientation + based on the segmentation map median line orientation. + Then, rotates page before passing it again to the deep learning detection module. + Doing so will improve performances for documents with page-uniform rotations. + detect_language: if True, the language prediction will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + kwargs: keyword args of `OCRPredictor` + + Returns: + ------- + KIE predictor + """ + return _kie_predictor( + det_arch, + reco_arch, + pretrained, + pretrained_backbone=pretrained_backbone, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + export_as_straight_boxes=export_as_straight_boxes, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + )
    @@ -353,8 +575,8 @@

    Source code for doctr.models.zoo

           
         
       
    - - + + diff --git a/v0.4.0/_modules/doctr/transforms/modules.html b/v0.4.0/_modules/doctr/transforms/modules.html deleted file mode 100644 index ba8269e7ef..0000000000 --- a/v0.4.0/_modules/doctr/transforms/modules.html +++ /dev/null @@ -1,734 +0,0 @@ - - - - - - - - - - - - doctr.transforms.modules - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.transforms.modules

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import random
    -import tensorflow as tf
    -from typing import List, Any, Tuple, Callable
    -
    -from doctr.utils.repr import NestedObject
    -from . import functional as F
    -
    -
    -__all__ = ['Compose', 'Resize', 'Normalize', 'LambdaTransformation', 'ToGray', 'ColorInversion',
    -           'RandomBrightness', 'RandomContrast', 'RandomSaturation', 'RandomHue', 'RandomGamma', 'RandomJpegQuality',
    -           'OneOf', 'RandomApply']
    -
    -
    -
    -[docs] -class Compose(NestedObject): - """Implements a wrapper that will apply transformations sequentially - - Example:: - >>> from doctr.transforms import Compose, Resize - >>> import tensorflow as tf - >>> transfos = Compose([Resize((32, 32))]) - >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformation modules - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, x: Any) -> Any: - for t in self.transforms: - x = t(x) - - return x
    - - - -
    -[docs] -class Resize(NestedObject): - """Resizes a tensor to a target size - - Example:: - >>> from doctr.transforms import Resize - >>> import tensorflow as tf - >>> transfo = Resize((32, 32)) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - output_size: expected output size - method: interpolation method - preserve_aspect_ratio: if `True`, preserve aspect ratio and pad the rest with zeros - symmetric_pad: if `True` while preserving aspect ratio, the padding will be done symmetrically - """ - def __init__( - self, - output_size: Tuple[int, int], - method: str = 'bilinear', - preserve_aspect_ratio: bool = False, - symmetric_pad: bool = False, - ) -> None: - self.output_size = output_size - self.method = method - self.preserve_aspect_ratio = preserve_aspect_ratio - self.symmetric_pad = symmetric_pad - - def extra_repr(self) -> str: - _repr = f"output_size={self.output_size}, method='{self.method}'" - if self.preserve_aspect_ratio: - _repr += f", preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}" - return _repr - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img = tf.image.resize(img, self.output_size, self.method, self.preserve_aspect_ratio) - if self.preserve_aspect_ratio: - # pad width - if not self.symmetric_pad: - offset = (0, 0) - elif self.output_size[0] == img.shape[0]: - offset = (0, int((self.output_size[1] - img.shape[1]) / 2)) - else: - offset = (int((self.output_size[0] - img.shape[0]) / 2), 0) - img = tf.image.pad_to_bounding_box(img, *offset, *self.output_size) - return img
    - - - -
    -[docs] -class Normalize(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - mean: average value per channel - std: standard deviation per channel - """ - def __init__(self, mean: Tuple[float, float, float], std: Tuple[float, float, float]) -> None: - self.mean = tf.constant(mean, dtype=tf.float32) - self.std = tf.constant(std, dtype=tf.float32) - - def extra_repr(self) -> str: - return f"mean={self.mean.numpy().tolist()}, std={self.std.numpy().tolist()}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img -= self.mean - img /= self.std - return img
    - - - -
    -[docs] -class LambdaTransformation(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import LambdaTransformation - >>> import tensorflow as tf - >>> transfo = LambdaTransformation(lambda x: x/ 255.) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - fn: the function to be applied to the input tensor - """ - def __init__(self, fn: Callable[[tf.Tensor], tf.Tensor]) -> None: - self.fn = fn - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return self.fn(img)
    - - - -
    -[docs] -class ToGray(NestedObject): - """Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ToGray() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - """ - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.rgb_to_grayscale(img)
    - - - -
    -[docs] -class ColorInversion(NestedObject): - """Applies the following tranformation to a tensor (image or batch of images): - convert to grayscale, colorize (shift 0-values randomly), and then invert colors - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ColorInversion(min_val=0.6) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_val: range [min_val, 1] to colorize RGB pixels - """ - def __init__(self, min_val: float = 0.6) -> None: - self.min_val = min_val - - def extra_repr(self) -> str: - return f"min_val={self.min_val}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return F.invert_colors(img, self.min_val)
    - - - -
    -[docs] -class RandomBrightness(NestedObject): - """Randomly adjust brightness of a tensor (batch of images or image) by adding a delta - to all pixels - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Brightness() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - p: probability to apply transformation - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_brightness(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomContrast(NestedObject): - """Randomly adjust contrast of a tensor (batch of images or image) by adjusting - each pixel: (img - mean) * contrast_factor + mean. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Contrast() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1) - """ - def __init__(self, delta: float = .3) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_contrast(img, lower=1 - self.delta, upper=1 / (1 - self.delta))
    - - - -
    -[docs] -class RandomSaturation(NestedObject): - """Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and - increasing saturation by a factor. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Saturation() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1) - """ - def __init__(self, delta: float = .5) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_saturation(img, lower=1 - self.delta, upper=1 + self.delta)
    - - - -
    -[docs] -class RandomHue(NestedObject): - """Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Hue() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_hue(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomGamma(NestedObject): - """randomly performs gamma correction for a tensor (batch of images or image) - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Gamma() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_gamma: non-negative real number, lower bound for gamma param - max_gamma: non-negative real number, upper bound for gamma - min_gain: lower bound for constant multiplier - max_gain: upper bound for constant multiplier - """ - def __init__( - self, - min_gamma: float = 0.5, - max_gamma: float = 1.5, - min_gain: float = 0.8, - max_gain: float = 1.2, - ) -> None: - self.min_gamma = min_gamma - self.max_gamma = max_gamma - self.min_gain = min_gain - self.max_gain = max_gain - - def extra_repr(self) -> str: - return f"""gamma_range=({self.min_gamma}, {self.max_gamma}), - gain_range=({self.min_gain}, {self.max_gain})""" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - gamma = random.uniform(self.min_gamma, self.max_gamma) - gain = random.uniform(self.min_gain, self.max_gain) - return tf.image.adjust_gamma(img, gamma=gamma, gain=gain)
    - - - -
    -[docs] -class RandomJpegQuality(NestedObject): - """Randomly adjust jpeg quality of a 3 dimensional RGB image - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = JpegQuality() - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - min_quality: int between [0, 100] - max_quality: int between [0, 100] - """ - def __init__(self, min_quality: int = 60, max_quality: int = 100) -> None: - self.min_quality = min_quality - self.max_quality = max_quality - - def extra_repr(self) -> str: - return f"min_quality={self.min_quality}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_jpeg_quality( - img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality - )
    - - - -
    -[docs] -class OneOf(NestedObject): - """Randomly apply one of the input transformations - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = OneOf([JpegQuality(), Gamma()]) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformations, one only will be picked - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - # Pick transformation - transfo = self.transforms[int(random.random() * len(self.transforms))] - # Apply - return transfo(img)
    - - - -
    -[docs] -class RandomApply(NestedObject): - """Apply with a probability p the input transformation - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = RandomApply(Gamma(), p=.5) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transform: transformation to apply - p: probability to apply - """ - def __init__(self, transform: NestedObject, p: float = .5) -> None: - self.transform = transform - self.p = p - - def extra_repr(self) -> str: - return f"transform={self.transform}, p={self.p}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - if random.random() < self.p: - return self.transform(img) - return img
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.0/_modules/doctr/transforms/modules/base.html b/v0.4.0/_modules/doctr/transforms/modules/base.html index c42079a8fd..4596df3848 100644 --- a/v0.4.0/_modules/doctr/transforms/modules/base.html +++ b/v0.4.0/_modules/doctr/transforms/modules/base.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.base - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.transforms.modules.base

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    +import math
     import random
    -from typing import List, Any, Callable
    +from typing import Any, Callable, List, Optional, Tuple, Union
    +
    +import numpy as np
     
     from doctr.utils.repr import NestedObject
    +
     from .. import functional as F
     
    +__all__ = ["SampleCompose", "ImageTransform", "ColorInversion", "OneOf", "RandomApply", "RandomRotate", "RandomCrop"]
    +
    +
    +class SampleCompose(NestedObject):
    +    """Implements a wrapper that will apply transformations sequentially on both image and target
    +
    +    .. tabs::
    +
    +        .. tab:: TensorFlow
    +
    +            .. code:: python
    +
    +                >>> import numpy as np
    +                >>> import tensorflow as tf
    +                >>> from doctr.transforms import SampleCompose, ImageTransform, ColorInversion, RandomRotate
    +                >>> transfo = SampleCompose([ImageTransform(ColorInversion((32, 32))), RandomRotate(30)])
    +                >>> out, out_boxes = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1), np.zeros((2, 4)))
    +
    +        .. tab:: PyTorch
    +
    +            .. code:: python
    +
    +                >>> import numpy as np
    +                >>> import torch
    +                >>> from doctr.transforms import SampleCompose, ImageTransform, ColorInversion, RandomRotate
    +                >>> transfos = SampleCompose([ImageTransform(ColorInversion((32, 32))), RandomRotate(30)])
    +                >>> out, out_boxes = transfos(torch.rand(8, 64, 64, 3), np.zeros((2, 4)))
    +
    +    Args:
    +    ----
    +        transforms: list of transformation modules
    +    """
    +
    +    _children_names: List[str] = ["sample_transforms"]
    +
    +    def __init__(self, transforms: List[Callable[[Any, Any], Tuple[Any, Any]]]) -> None:
    +        self.sample_transforms = transforms
    +
    +    def __call__(self, x: Any, target: Any) -> Tuple[Any, Any]:
    +        for t in self.sample_transforms:
    +            x, target = t(x, target)
    +
    +        return x, target
    +
    +
    +class ImageTransform(NestedObject):
    +    """Implements a transform wrapper to turn an image-only transformation into an image+target transform
    +
    +    .. tabs::
    +
    +        .. tab:: TensorFlow
    +
    +            .. code:: python
    +
    +                >>> import tensorflow as tf
    +                >>> from doctr.transforms import ImageTransform, ColorInversion
    +                >>> transfo = ImageTransform(ColorInversion((32, 32)))
    +                >>> out, _ = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1), None)
    +
    +        .. tab:: PyTorch
    +
    +            .. code:: python
    +
    +                >>> import torch
    +                >>> from doctr.transforms import ImageTransform, ColorInversion
    +                >>> transfo = ImageTransform(ColorInversion((32, 32)))
    +                >>> out, _ = transfo(torch.rand(8, 64, 64, 3), None)
    +
    +    Args:
    +    ----
    +        transform: the image transformation module to wrap
    +    """
    +
    +    _children_names: List[str] = ["img_transform"]
    +
    +    def __init__(self, transform: Callable[[Any], Any]) -> None:
    +        self.img_transform = transform
     
    -__all__ = ['ColorInversion', 'OneOf', 'RandomApply']
    +    def __call__(self, img: Any, target: Any) -> Tuple[Any, Any]:
    +        img = self.img_transform(img)
    +        return img, target
     
     
     
    -[docs] +[docs] class ColorInversion(NestedObject): """Applies the following tranformation to a tensor (image or batch of images): convert to grayscale, colorize (shift 0-values randomly), and then invert colors - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ColorInversion(min_val=0.6) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + .. tabs:: + + .. tab:: TensorFlow + + .. code:: python + + >>> import tensorflow as tf + >>> from doctr.transforms import ColorInversion + >>> transfo = ColorInversion(min_val=0.6) + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + + .. tab:: PyTorch + + .. code:: python + + >>> import torch + >>> from doctr.transforms import ColorInversion + >>> transfo = ColorInversion(min_val=0.6) + >>> out = transfo(torch.rand(8, 64, 64, 3)) Args: + ---- min_val: range [min_val, 1] to colorize RGB pixels """ + def __init__(self, min_val: float = 0.5) -> None: self.min_val = min_val @@ -316,59 +437,178 @@

    Source code for doctr.transforms.modules.base

    -[docs] +[docs] class OneOf(NestedObject): """Randomly apply one of the input transformations - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = OneOf([JpegQuality(), Gamma()]) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + .. tabs:: + + .. tab:: TensorFlow + + .. code:: python + + >>> import tensorflow as tf + >>> from doctr.transforms import OneOf + >>> transfo = OneOf([JpegQuality(), Gamma()]) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + .. tab:: PyTorch + + .. code:: python + + >>> import torch + >>> from doctr.transforms import OneOf + >>> transfo = OneOf([JpegQuality(), Gamma()]) + >>> out = transfo(torch.rand(1, 64, 64, 3)) Args: + ---- transforms: list of transformations, one only will be picked """ - _children_names: List[str] = ['transforms'] + _children_names: List[str] = ["transforms"] def __init__(self, transforms: List[Callable[[Any], Any]]) -> None: self.transforms = transforms - def __call__(self, img: Any) -> Any: + def __call__(self, img: Any, target: Optional[np.ndarray] = None) -> Union[Any, Tuple[Any, np.ndarray]]: # Pick transformation transfo = self.transforms[int(random.random() * len(self.transforms))] # Apply - return transfo(img)
    + return transfo(img) if target is None else transfo(img, target) # type: ignore[call-arg]
    -[docs] +[docs] class RandomApply(NestedObject): """Apply with a probability p the input transformation - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = RandomApply(Gamma(), p=.5) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + .. tabs:: + + .. tab:: TensorFlow + + .. code:: python + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomApply + >>> transfo = RandomApply(Gamma(), p=.5) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + .. tab:: PyTorch + + .. code:: python + + >>> import torch + >>> from doctr.transforms import RandomApply + >>> transfo = RandomApply(Gamma(), p=.5) + >>> out = transfo(torch.rand(1, 64, 64, 3)) Args: + ---- transform: transformation to apply p: probability to apply """ - def __init__(self, transform: Callable[[Any], Any], p: float = .5) -> None: + + def __init__(self, transform: Callable[[Any], Any], p: float = 0.5) -> None: self.transform = transform self.p = p def extra_repr(self) -> str: return f"transform={self.transform}, p={self.p}" - def __call__(self, img: Any) -> Any: + def __call__(self, img: Any, target: Optional[np.ndarray] = None) -> Union[Any, Tuple[Any, np.ndarray]]: if random.random() < self.p: - return self.transform(img) - return img
    + return self.transform(img) if target is None else self.transform(img, target) # type: ignore[call-arg] + return img if target is None else (img, target)
    + + + +
    +[docs] +class RandomRotate(NestedObject): + """Randomly rotate a tensor image and its boxes + + .. image:: https://doctr-static.mindee.com/models?id=v0.4.0/rotation_illustration.png&src=0 + :align: center + + Args: + ---- + max_angle: maximum angle for rotation, in degrees. Angles will be uniformly picked in + [-max_angle, max_angle] + expand: whether the image should be padded before the rotation + """ + + def __init__(self, max_angle: float = 5.0, expand: bool = False) -> None: + self.max_angle = max_angle + self.expand = expand + + def extra_repr(self) -> str: + return f"max_angle={self.max_angle}, expand={self.expand}" + + def __call__(self, img: Any, target: np.ndarray) -> Tuple[Any, np.ndarray]: + angle = random.uniform(-self.max_angle, self.max_angle) + r_img, r_polys = F.rotate_sample(img, target, angle, self.expand) + # Removes deleted boxes + is_kept = (r_polys.max(1) > r_polys.min(1)).sum(1) == 2 + return r_img, r_polys[is_kept]
    + + + +
    +[docs] +class RandomCrop(NestedObject): + """Randomly crop a tensor image and its boxes + + Args: + ---- + scale: tuple of floats, relative (min_area, max_area) of the crop + ratio: tuple of float, relative (min_ratio, max_ratio) where ratio = h/w + """ + + def __init__(self, scale: Tuple[float, float] = (0.08, 1.0), ratio: Tuple[float, float] = (0.75, 1.33)) -> None: + self.scale = scale + self.ratio = ratio + + def extra_repr(self) -> str: + return f"scale={self.scale}, ratio={self.ratio}" + + def __call__(self, img: Any, target: np.ndarray) -> Tuple[Any, np.ndarray]: + scale = random.uniform(self.scale[0], self.scale[1]) + ratio = random.uniform(self.ratio[0], self.ratio[1]) + + height, width = img.shape[:2] + + # Calculate crop size + crop_area = scale * width * height + aspect_ratio = ratio * (width / height) + crop_width = int(round(math.sqrt(crop_area * aspect_ratio))) + crop_height = int(round(math.sqrt(crop_area / aspect_ratio))) + + # Ensure crop size does not exceed image dimensions + crop_width = min(crop_width, width) + crop_height = min(crop_height, height) + + # Randomly select crop position + x = random.randint(0, width - crop_width) + y = random.randint(0, height - crop_height) + + # relative crop box + crop_box = (x / width, y / height, (x + crop_width) / width, (y + crop_height) / height) + if target.shape[1:] == (4, 2): + min_xy = np.min(target, axis=1) + max_xy = np.max(target, axis=1) + _target = np.concatenate((min_xy, max_xy), axis=1) + else: + _target = target + + # Crop image and targets + croped_img, crop_boxes = F.crop_detection(img, _target, crop_box) + # hard fallback if no box is kept + if crop_boxes.shape[0] == 0: + return img, target + # clip boxes + return croped_img, np.clip(crop_boxes, 0, 1)
    @@ -402,8 +642,8 @@

    Source code for doctr.transforms.modules.base

    - - + + diff --git a/v0.4.0/_modules/doctr/transforms/modules/tensorflow.html b/v0.4.0/_modules/doctr/transforms/modules/tensorflow.html index 1d192a876b..acbbe96225 100644 --- a/v0.4.0/_modules/doctr/transforms/modules/tensorflow.html +++ b/v0.4.0/_modules/doctr/transforms/modules/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.transforms.modules.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import random
    +from typing import Any, Callable, Iterable, List, Optional, Tuple, Union
    +
    +import numpy as np
     import tensorflow as tf
    -from typing import List, Any, Tuple, Callable
     
     from doctr.utils.repr import NestedObject
     
    -
    -__all__ = ['Compose', 'Resize', 'Normalize', 'LambdaTransformation', 'ToGray', 'RandomBrightness',
    -           'RandomContrast', 'RandomSaturation', 'RandomHue', 'RandomGamma', 'RandomJpegQuality']
    +from ..functional.tensorflow import _gaussian_filter, random_shadow
    +
    +__all__ = [
    +    "Compose",
    +    "Resize",
    +    "Normalize",
    +    "LambdaTransformation",
    +    "ToGray",
    +    "RandomBrightness",
    +    "RandomContrast",
    +    "RandomSaturation",
    +    "RandomHue",
    +    "RandomGamma",
    +    "RandomJpegQuality",
    +    "GaussianBlur",
    +    "ChannelShuffle",
    +    "GaussianNoise",
    +    "RandomHorizontalFlip",
    +    "RandomShadow",
    +    "RandomResize",
    +]
     
     
     
    -[docs] +[docs] class Compose(NestedObject): """Implements a wrapper that will apply transformations sequentially - Example:: - >>> from doctr.transforms import Compose, Resize - >>> import tensorflow as tf - >>> transfos = Compose([Resize((32, 32))]) - >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import Compose, Resize + >>> transfos = Compose([Resize((32, 32))]) + >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) Args: + ---- transforms: list of transformation modules """ - _children_names: List[str] = ['transforms'] + _children_names: List[str] = ["transforms"] def __init__(self, transforms: List[Callable[[Any], Any]]) -> None: self.transforms = transforms @@ -319,26 +361,27 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class Resize(NestedObject): """Resizes a tensor to a target size - Example:: - >>> from doctr.transforms import Resize - >>> import tensorflow as tf - >>> transfo = Resize((32, 32)) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import Resize + >>> transfo = Resize((32, 32)) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) Args: + ---- output_size: expected output size method: interpolation method preserve_aspect_ratio: if `True`, preserve aspect ratio and pad the rest with zeros symmetric_pad: if `True` while preserving aspect ratio, the padding will be done symmetrically """ + def __init__( self, - output_size: Tuple[int, int], - method: str = 'bilinear', + output_size: Union[int, Tuple[int, int]], + method: str = "bilinear", preserve_aspect_ratio: bool = False, symmetric_pad: bool = False, ) -> None: @@ -346,6 +389,14 @@

    Source code for doctr.transforms.modules.tensorflow

    self.method = method self.preserve_aspect_ratio = preserve_aspect_ratio self.symmetric_pad = symmetric_pad + self.antialias = True + + if isinstance(self.output_size, int): + self.wanted_size = (self.output_size, self.output_size) + elif isinstance(self.output_size, (tuple, list)): + self.wanted_size = self.output_size + else: + raise AssertionError("Output size should be either a list, a tuple or an int") def extra_repr(self) -> str: _repr = f"output_size={self.output_size}, method='{self.method}'" @@ -353,64 +404,106 @@

    Source code for doctr.transforms.modules.tensorflow

    _repr += f", preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}" return _repr - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img = tf.image.resize(img, self.output_size, self.method, self.preserve_aspect_ratio) + def __call__( + self, + img: tf.Tensor, + target: Optional[np.ndarray] = None, + ) -> Union[tf.Tensor, Tuple[tf.Tensor, np.ndarray]]: + input_dtype = img.dtype + self.output_size = ( + (self.output_size, self.output_size) if isinstance(self.output_size, int) else self.output_size + ) + + img = tf.image.resize(img, self.wanted_size, self.method, self.preserve_aspect_ratio, self.antialias) + # It will produce an un-padded resized image, with a side shorter than wanted if we preserve aspect ratio + raw_shape = img.shape[:2] + if self.symmetric_pad: + half_pad = (int((self.output_size[0] - img.shape[0]) / 2), 0) if self.preserve_aspect_ratio: - # pad width - if not self.symmetric_pad: - offset = (0, 0) - elif self.output_size[0] == img.shape[0]: - offset = (0, int((self.output_size[1] - img.shape[1]) / 2)) - else: - offset = (int((self.output_size[0] - img.shape[0]) / 2), 0) - img = tf.image.pad_to_bounding_box(img, *offset, *self.output_size) - return img
    + if isinstance(self.output_size, (tuple, list)): + # In that case we need to pad because we want to enforce both width and height + if not self.symmetric_pad: + half_pad = (0, 0) + elif self.output_size[0] == img.shape[0]: + half_pad = (0, int((self.output_size[1] - img.shape[1]) / 2)) + # Pad image + img = tf.image.pad_to_bounding_box(img, *half_pad, *self.output_size) + + # In case boxes are provided, resize boxes if needed (for detection task if preserve aspect ratio) + if target is not None: + if self.symmetric_pad: + offset = half_pad[0] / img.shape[0], half_pad[1] / img.shape[1] + + if self.preserve_aspect_ratio: + # Get absolute coords + if target.shape[1:] == (4,): + if isinstance(self.output_size, (tuple, list)) and self.symmetric_pad: + target[:, [0, 2]] = offset[1] + target[:, [0, 2]] * raw_shape[1] / img.shape[1] + target[:, [1, 3]] = offset[0] + target[:, [1, 3]] * raw_shape[0] / img.shape[0] + else: + target[:, [0, 2]] *= raw_shape[1] / img.shape[1] + target[:, [1, 3]] *= raw_shape[0] / img.shape[0] + elif target.shape[1:] == (4, 2): + if isinstance(self.output_size, (tuple, list)) and self.symmetric_pad: + target[..., 0] = offset[1] + target[..., 0] * raw_shape[1] / img.shape[1] + target[..., 1] = offset[0] + target[..., 1] * raw_shape[0] / img.shape[0] + else: + target[..., 0] *= raw_shape[1] / img.shape[1] + target[..., 1] *= raw_shape[0] / img.shape[0] + else: + raise AssertionError("Boxes should be in the format (n_boxes, 4, 2) or (n_boxes, 4)") + + return tf.cast(img, dtype=input_dtype), np.clip(target, 0, 1) + + return tf.cast(img, dtype=input_dtype)
    -[docs] +[docs] class Normalize(NestedObject): """Normalize a tensor to a Gaussian distribution for each channel - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import Normalize + >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- mean: average value per channel std: standard deviation per channel """ + def __init__(self, mean: Tuple[float, float, float], std: Tuple[float, float, float]) -> None: - self.mean = tf.constant(mean, dtype=tf.float32) - self.std = tf.constant(std, dtype=tf.float32) + self.mean = tf.constant(mean) + self.std = tf.constant(std) def extra_repr(self) -> str: return f"mean={self.mean.numpy().tolist()}, std={self.std.numpy().tolist()}" def __call__(self, img: tf.Tensor) -> tf.Tensor: - img -= self.mean - img /= self.std + img -= tf.cast(self.mean, dtype=img.dtype) + img /= tf.cast(self.std, dtype=img.dtype) return img
    -[docs] +[docs] class LambdaTransformation(NestedObject): """Normalize a tensor to a Gaussian distribution for each channel - Example:: - >>> from doctr.transforms import LambdaTransformation - >>> import tensorflow as tf - >>> transfo = LambdaTransformation(lambda x: x/ 255.) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import LambdaTransformation + >>> transfo = LambdaTransformation(lambda x: x/ 255.) + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- fn: the function to be applied to the input tensor """ + def __init__(self, fn: Callable[[tf.Tensor], tf.Tensor]) -> None: self.fn = fn @@ -420,37 +513,42 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class ToGray(NestedObject): """Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ToGray() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import ToGray + >>> transfo = ToGray() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) """ + + def __init__(self, num_output_channels: int = 1): + self.num_output_channels = num_output_channels + def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.rgb_to_grayscale(img)
    + img = tf.image.rgb_to_grayscale(img) + return img if self.num_output_channels == 1 else tf.repeat(img, self.num_output_channels, axis=-1)
    -[docs] +[docs] class RandomBrightness(NestedObject): """Randomly adjust brightness of a tensor (batch of images or image) by adding a delta to all pixels - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Brightness() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomBrightness + >>> transfo = RandomBrightness() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] p: probability to apply transformation """ + def __init__(self, max_delta: float = 0.3) -> None: self.max_delta = max_delta @@ -463,21 +561,22 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomContrast(NestedObject): """Randomly adjust contrast of a tensor (batch of images or image) by adjusting each pixel: (img - mean) * contrast_factor + mean. - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Contrast() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomContrast + >>> transfo = RandomContrast() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1) """ - def __init__(self, delta: float = .3) -> None: + + def __init__(self, delta: float = 0.3) -> None: self.delta = delta def extra_repr(self) -> str: @@ -489,21 +588,22 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomSaturation(NestedObject): """Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and increasing saturation by a factor. - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Saturation() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomSaturation + >>> transfo = RandomSaturation() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1) """ - def __init__(self, delta: float = .5) -> None: + + def __init__(self, delta: float = 0.5) -> None: self.delta = delta def extra_repr(self) -> str: @@ -515,19 +615,20 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomHue(NestedObject): """Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Hue() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomHue + >>> transfo = RandomHue() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] """ + def __init__(self, max_delta: float = 0.3) -> None: self.max_delta = max_delta @@ -540,22 +641,23 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomGamma(NestedObject): """randomly performs gamma correction for a tensor (batch of images or image) - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Gamma() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomGamma + >>> transfo = RandomGamma() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- min_gamma: non-negative real number, lower bound for gamma param max_gamma: non-negative real number, upper bound for gamma min_gain: lower bound for constant multiplier max_gain: upper bound for constant multiplier """ + def __init__( self, min_gamma: float = 0.5, @@ -580,20 +682,21 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomJpegQuality(NestedObject): """Randomly adjust jpeg quality of a 3 dimensional RGB image - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = JpegQuality() - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomJpegQuality + >>> transfo = RandomJpegQuality() + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) Args: + ---- min_quality: int between [0, 100] max_quality: int between [0, 100] """ + def __init__(self, min_quality: int = 60, max_quality: int = 100) -> None: self.min_quality = min_quality self.max_quality = max_quality @@ -602,10 +705,224 @@

    Source code for doctr.transforms.modules.tensorflow

    return f"min_quality={self.min_quality}" def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_jpeg_quality( - img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality + return tf.image.random_jpeg_quality(img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality)
    + + + +
    +[docs] +class GaussianBlur(NestedObject): + """Randomly adjust jpeg quality of a 3 dimensional RGB image + + >>> import tensorflow as tf + >>> from doctr.transforms import GaussianBlur + >>> transfo = GaussianBlur(3, (.1, 5)) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + kernel_shape: size of the blurring kernel + std: min and max value of the standard deviation + """ + + def __init__(self, kernel_shape: Union[int, Iterable[int]], std: Tuple[float, float]) -> None: + self.kernel_shape = kernel_shape + self.std = std + + def extra_repr(self) -> str: + return f"kernel_shape={self.kernel_shape}, std={self.std}" + + def __call__(self, img: tf.Tensor) -> tf.Tensor: + return tf.squeeze( + _gaussian_filter( + img[tf.newaxis, ...], + kernel_size=self.kernel_shape, + sigma=random.uniform(self.std[0], self.std[1]), + mode="REFLECT", + ), + axis=0, )
    + + +
    +[docs] +class ChannelShuffle(NestedObject): + """Randomly shuffle channel order of a given image""" + + def __init__(self): + pass + + def __call__(self, img: tf.Tensor) -> tf.Tensor: + return tf.transpose(tf.random.shuffle(tf.transpose(img, perm=[2, 0, 1])), perm=[1, 2, 0])
    + + + +
    +[docs] +class GaussianNoise(NestedObject): + """Adds Gaussian Noise to the input tensor + + >>> import tensorflow as tf + >>> from doctr.transforms import GaussianNoise + >>> transfo = GaussianNoise(0., 1.) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + mean : mean of the gaussian distribution + std : std of the gaussian distribution + """ + + def __init__(self, mean: float = 0.0, std: float = 1.0) -> None: + super().__init__() + self.std = std + self.mean = mean + + def __call__(self, x: tf.Tensor) -> tf.Tensor: + # Reshape the distribution + noise = self.mean + 2 * self.std * tf.random.uniform(x.shape) - self.std + if x.dtype == tf.uint8: + return tf.cast( + tf.clip_by_value(tf.math.round(tf.cast(x, dtype=tf.float32) + 255 * noise), 0, 255), dtype=tf.uint8 + ) + else: + return tf.cast(tf.clip_by_value(x + noise, 0, 1), dtype=x.dtype) + + def extra_repr(self) -> str: + return f"mean={self.mean}, std={self.std}"
    + + + +
    +[docs] +class RandomHorizontalFlip(NestedObject): + """Adds random horizontal flip to the input tensor/np.ndarray + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomHorizontalFlip + >>> transfo = RandomHorizontalFlip(p=0.5) + >>> image = tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1) + >>> target = np.array([[0.1, 0.1, 0.4, 0.5] ], dtype= np.float32) + >>> out = transfo(image, target) + + Args: + ---- + p : probability of Horizontal Flip + """ + + def __init__(self, p: float) -> None: + super().__init__() + self.p = p + + def __call__(self, img: Union[tf.Tensor, np.ndarray], target: np.ndarray) -> Tuple[tf.Tensor, np.ndarray]: + if np.random.rand(1) <= self.p: + _img = tf.image.flip_left_right(img) + _target = target.copy() + # Changing the relative bbox coordinates + if target.shape[1:] == (4,): + _target[:, ::2] = 1 - target[:, [2, 0]] + else: + _target[..., 0] = 1 - target[..., 0] + return _img, _target + return img, target
    + + + +
    +[docs] +class RandomShadow(NestedObject): + """Adds random shade to the input image + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomShadow + >>> transfo = RandomShadow(0., 1.) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + opacity_range : minimum and maximum opacity of the shade + """ + + def __init__(self, opacity_range: Optional[Tuple[float, float]] = None) -> None: + super().__init__() + self.opacity_range = opacity_range if isinstance(opacity_range, tuple) else (0.2, 0.8) + + def __call__(self, x: tf.Tensor) -> tf.Tensor: + # Reshape the distribution + if x.dtype == tf.uint8: + return tf.cast( + tf.clip_by_value( + tf.math.round(255 * random_shadow(tf.cast(x, dtype=tf.float32) / 255, self.opacity_range)), + 0, + 255, + ), + dtype=tf.uint8, + ) + else: + return tf.clip_by_value(random_shadow(x, self.opacity_range), 0, 1) + + def extra_repr(self) -> str: + return f"opacity_range={self.opacity_range}"
    + + + +
    +[docs] +class RandomResize(NestedObject): + """Randomly resize the input image and align corresponding targets + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomResize + >>> transfo = RandomResize((0.3, 0.9), preserve_aspect_ratio=True, symmetric_pad=True, p=0.5) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + scale_range: range of the resizing factor for width and height (independently) + preserve_aspect_ratio: whether to preserve the aspect ratio of the image, + given a float value, the aspect ratio will be preserved with this probability + symmetric_pad: whether to symmetrically pad the image, + given a float value, the symmetric padding will be applied with this probability + p: probability to apply the transformation + """ + + def __init__( + self, + scale_range: Tuple[float, float] = (0.3, 0.9), + preserve_aspect_ratio: Union[bool, float] = False, + symmetric_pad: Union[bool, float] = False, + p: float = 0.5, + ): + super().__init__() + self.scale_range = scale_range + self.preserve_aspect_ratio = preserve_aspect_ratio + self.symmetric_pad = symmetric_pad + self.p = p + self._resize = Resize + + def __call__(self, img: tf.Tensor, target: np.ndarray) -> Tuple[tf.Tensor, np.ndarray]: + if np.random.rand(1) <= self.p: + scale_h = random.uniform(*self.scale_range) + scale_w = random.uniform(*self.scale_range) + new_size = (int(img.shape[-3] * scale_h), int(img.shape[-2] * scale_w)) + + _img, _target = self._resize( + new_size, + preserve_aspect_ratio=self.preserve_aspect_ratio + if isinstance(self.preserve_aspect_ratio, bool) + else bool(np.random.rand(1) <= self.symmetric_pad), + symmetric_pad=self.symmetric_pad + if isinstance(self.symmetric_pad, bool) + else bool(np.random.rand(1) <= self.symmetric_pad), + )(img, target) + + return _img, _target + return img, target + + def extra_repr(self) -> str: + return f"scale_range={self.scale_range}, preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}, p={self.p}" # noqa: E501
    +
    @@ -638,8 +955,8 @@

    Source code for doctr.transforms.modules.tensorflow

    - +
    + diff --git a/v0.4.0/_modules/doctr/utils/metrics.html b/v0.4.0/_modules/doctr/utils/metrics.html index 460c64a385..8a37d5949a 100644 --- a/v0.4.0/_modules/doctr/utils/metrics.html +++ b/v0.4.0/_modules/doctr/utils/metrics.html @@ -13,7 +13,7 @@ - + doctr.utils.metrics - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.utils.metrics

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
    +
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +from typing import Dict, List, Optional, Tuple
     
     import numpy as np
    -import cv2
    -from typing import List, Tuple, Dict, Optional
    -from unidecode import unidecode
    +from anyascii import anyascii
     from scipy.optimize import linear_sum_assignment
    -from doctr.utils.geometry import rbbox_to_polygon
    +from shapely.geometry import Polygon
     
    -__all__ = ['TextMatch', 'box_iou', 'box_ioa', 'mask_iou', 'rbox_to_mask',
    -           'nms', 'LocalizationConfusion', 'OCRMetric']
    +__all__ = [
    +    "TextMatch",
    +    "box_iou",
    +    "polygon_iou",
    +    "nms",
    +    "LocalizationConfusion",
    +    "OCRMetric",
    +    "DetectionMetric",
    +]
     
     
     def string_match(word1: str, word2: str) -> Tuple[bool, bool, bool, bool]:
    -    """Perform string comparison with multiple levels of tolerance
    +    """Performs string comparison with multiple levels of tolerance
     
         Args:
    +    ----
             word1: a string
             word2: another string
     
         Returns:
    +    -------
             a tuple with booleans specifying respectively whether the raw strings, their lower-case counterparts, their
    -            unidecode counterparts and their lower-case unidecode counterparts match
    +            anyascii counterparts and their lower-case anyascii counterparts match
         """
    -    raw_match = (word1 == word2)
    -    caseless_match = (word1.lower() == word2.lower())
    -    unidecode_match = (unidecode(word1) == unidecode(word2))
    +    raw_match = word1 == word2
    +    caseless_match = word1.lower() == word2.lower()
    +    anyascii_match = anyascii(word1) == anyascii(word2)
     
         # Warning: the order is important here otherwise the pair ("EUR", "€") cannot be matched
    -    unicase_match = (unidecode(word1).lower() == unidecode(word2).lower())
    +    unicase_match = anyascii(word1).lower() == anyascii(word2).lower()
     
    -    return raw_match, caseless_match, unidecode_match, unicase_match
    +    return raw_match, caseless_match, anyascii_match, unicase_match
     
     
     
    -[docs] +[docs] class TextMatch: - """Implements text match metric (word-level accuracy) for recognition task. + r"""Implements text match metric (word-level accuracy) for recognition task. The raw aggregated metric is computed as follows: .. math:: - \\forall X, Y \\in \\mathcal{W}^N, - TextMatch(X, Y) = \\frac{1}{N} \\sum\\limits_{i=1}^N f_{Y_i}(X_i) + \forall X, Y \in \mathcal{W}^N, + TextMatch(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N f_{Y_i}(X_i) with the indicator function :math:`f_{a}` defined as: .. math:: - \\forall a, x \\in \\mathcal{W}, - f_a(x) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } x = a \\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{W}` is the set of all possible character sequences, + \forall a, x \in \mathcal{W}, + f_a(x) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } x = a \\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{W}` is the set of all possible character sequences, :math:`N` is a strictly positive integer. - Example:: - >>> from doctr.utils import TextMatch - >>> metric = TextMatch() - >>> metric.update(['Hello', 'world'], ['hello', 'world']) - >>> metric.summary() + >>> from doctr.utils import TextMatch + >>> metric = TextMatch() + >>> metric.update(['Hello', 'world'], ['hello', 'world']) + >>> metric.summary() """ def __init__(self) -> None: self.reset() +
    +[docs] def update( self, gt: List[str], @@ -354,29 +386,32 @@

    Source code for doctr.utils.metrics

             """Update the state of the metric with new predictions
     
             Args:
    +        ----
                 gt: list of groung-truth character sequences
    -            pred: list of predicted character sequences"""
    -
    +            pred: list of predicted character sequences
    +        """
             if len(gt) != len(pred):
                 raise AssertionError("prediction size does not match with ground-truth labels size")
     
             for gt_word, pred_word in zip(gt, pred):
    -            _raw, _caseless, _unidecode, _unicase = string_match(gt_word, pred_word)
    +            _raw, _caseless, _anyascii, _unicase = string_match(gt_word, pred_word)
                 self.raw += int(_raw)
                 self.caseless += int(_caseless)
    -            self.unidecode += int(_unidecode)
    +            self.anyascii += int(_anyascii)
                 self.unicase += int(_unicase)
     
    -        self.total += len(gt)
    +        self.total += len(gt)
    +
    -[docs] +[docs] def summary(self) -> Dict[str, float]: """Computes the aggregated metrics - Returns: - a dictionary with the exact match score for the raw data, its lower-case counterpart, its unidecode - counterpart and its lower-case unidecode counterpart + Returns + ------- + a dictionary with the exact match score for the raw data, its lower-case counterpart, its anyascii + counterpart and its lower-case anyascii counterpart """ if self.total == 0: raise AssertionError("you need to update the metric before getting the summary") @@ -384,7 +419,7 @@

    Source code for doctr.utils.metrics

             return dict(
                 raw=self.raw / self.total,
                 caseless=self.caseless / self.total,
    -            unidecode=self.unidecode / self.total,
    +            anyascii=self.anyascii / self.total,
                 unicase=self.unicase / self.total,
             )
    @@ -392,23 +427,25 @@

    Source code for doctr.utils.metrics

         def reset(self) -> None:
             self.raw = 0
             self.caseless = 0
    -        self.unidecode = 0
    +        self.anyascii = 0
             self.unicase = 0
             self.total = 0
    def box_iou(boxes_1: np.ndarray, boxes_2: np.ndarray) -> np.ndarray: - """Compute the IoU between two sets of bounding boxes + """Computes the IoU between two sets of bounding boxes Args: + ---- boxes_1: bounding boxes of shape (N, 4) in format (xmin, ymin, xmax, ymax) boxes_2: bounding boxes of shape (M, 4) in format (xmin, ymin, xmax, ymax) + Returns: + ------- the IoU matrix of shape (N, M) """ - - iou_mat = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32) + iou_mat: np.ndarray = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32) if boxes_1.shape[0] > 0 and boxes_2.shape[0] > 0: l1, t1, r1, b1 = np.split(boxes_1, 4, axis=1) @@ -419,107 +456,54 @@

    Source code for doctr.utils.metrics

             right = np.minimum(r1, r2.T)
             bot = np.minimum(b1, b2.T)
     
    -        intersection = np.clip(right - left, 0, np.Inf) * np.clip(bot - top, 0, np.Inf)
    +        intersection = np.clip(right - left, 0, np.inf) * np.clip(bot - top, 0, np.inf)
             union = (r1 - l1) * (b1 - t1) + ((r2 - l2) * (b2 - t2)).T - intersection
             iou_mat = intersection / union
     
         return iou_mat
     
     
    -def box_ioa(boxes_1: np.ndarray, boxes_2: np.ndarray) -> np.ndarray:
    -    """Compute the IoA (intersection over area) between two sets of bounding boxes:
    -    ioa(i, j) = inter(i, j) / area(i)
    -
    -    Args:
    -        boxes_1: bounding boxes of shape (N, 4) in format (xmin, ymin, xmax, ymax)
    -        boxes_2: bounding boxes of shape (M, 4) in format (xmin, ymin, xmax, ymax)
    -    Returns:
    -        the IoA matrix of shape (N, M)
    -    """
    -
    -    ioa_mat = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32)
    -
    -    if boxes_1.shape[0] > 0 and boxes_2.shape[0] > 0:
    -        l1, t1, r1, b1 = np.split(boxes_1, 4, axis=1)
    -        l2, t2, r2, b2 = np.split(boxes_2, 4, axis=1)
    -
    -        left = np.maximum(l1, l2.T)
    -        top = np.maximum(t1, t2.T)
    -        right = np.minimum(r1, r2.T)
    -        bot = np.minimum(b1, b2.T)
    -
    -        intersection = np.clip(right - left, 0, np.Inf) * np.clip(bot - top, 0, np.Inf)
    -        area = (r1 - l1) * (b1 - t1)
    -        ioa_mat = intersection / area
    -
    -    return ioa_mat
    -
    -
    -def mask_iou(masks_1: np.ndarray, masks_2: np.ndarray) -> np.ndarray:
    -    """Compute the IoU between two sets of boolean masks
    +def polygon_iou(polys_1: np.ndarray, polys_2: np.ndarray) -> np.ndarray:
    +    """Computes the IoU between two sets of rotated bounding boxes
     
         Args:
    -        masks_1: boolean masks of shape (N, H, W)
    -        masks_2: boolean masks of shape (M, H, W)
    +    ----
    +        polys_1: rotated bounding boxes of shape (N, 4, 2)
    +        polys_2: rotated bounding boxes of shape (M, 4, 2)
    +        mask_shape: spatial shape of the intermediate masks
    +        use_broadcasting: if set to True, leverage broadcasting speedup by consuming more memory
     
         Returns:
    +    -------
             the IoU matrix of shape (N, M)
         """
    +    if polys_1.ndim != 3 or polys_2.ndim != 3:
    +        raise AssertionError("expects boxes to be in format (N, 4, 2)")
     
    -    if masks_1.shape[1:] != masks_2.shape[1:]:
    -        raise AssertionError("both boolean masks should have the same spatial shape")
    +    iou_mat = np.zeros((polys_1.shape[0], polys_2.shape[0]), dtype=np.float32)
     
    -    iou_mat = np.zeros((masks_1.shape[0], masks_2.shape[0]), dtype=np.float32)
    +    shapely_polys_1 = [Polygon(poly) for poly in polys_1]
    +    shapely_polys_2 = [Polygon(poly) for poly in polys_2]
     
    -    if masks_1.shape[0] > 0 and masks_2.shape[0] > 0:
    -        intersection = np.logical_and(masks_1[:, None, ...], masks_2[None, ...])
    -        union = np.logical_or(masks_1[:, None, ...], masks_2[None, ...])
    -        axes = tuple(range(2, masks_1.ndim + 1))
    -        iou_mat = intersection.sum(axis=axes) / union.sum(axis=axes)
    +    for i, poly1 in enumerate(shapely_polys_1):
    +        for j, poly2 in enumerate(shapely_polys_2):
    +            intersection_area = poly1.intersection(poly2).area
    +            union_area = poly1.area + poly2.area - intersection_area
    +            iou_mat[i, j] = intersection_area / union_area
     
         return iou_mat
     
     
    -def rbox_to_mask(boxes: np.ndarray, shape: Tuple[int, int]) -> np.ndarray:
    -    """Convert boxes to masks
    -
    -    Args:
    -        boxes: rotated bounding boxes of shape (N, 5) in format (x, y, w, h, alpha)
    -        shape: spatial shapes of the output masks
    -
    -    Returns:
    -        the boolean masks of shape (N, H, W)
    -    """
    -
    -    masks = np.zeros((boxes.shape[0], *shape), dtype=np.uint8)
    -
    -    if boxes.shape[0] > 0:
    -        # Get absolute coordinates
    -        if boxes.dtype != np.int:
    -            abs_boxes = boxes.copy()
    -            abs_boxes[:, [0, 2]] = abs_boxes[:, [0, 2]] * shape[1]
    -            abs_boxes[:, [1, 3]] = abs_boxes[:, [1, 3]] * shape[0]
    -            abs_boxes = abs_boxes.round().astype(np.int)
    -        else:
    -            abs_boxes = boxes
    -            abs_boxes[:, 2:] = abs_boxes[:, 2:] + 1
    -
    -        # TODO: optimize slicing to improve vectorization
    -        for idx, _box in enumerate(abs_boxes):
    -            box = rbbox_to_polygon(_box)
    -            cv2.fillPoly(masks[idx], [np.array(box, np.int32)], 1)
    -
    -    return masks.astype(bool)
    -
    -
    -def nms(boxes: np.ndarray, thresh: float = .5) -> List[int]:
    +def nms(boxes: np.ndarray, thresh: float = 0.5) -> List[int]:
         """Perform non-max suppression, borrowed from <https://github.com/rbgirshick/fast-rcnn>`_.
     
         Args:
    +    ----
             boxes: np array of straight boxes: (*, 5), (xmin, ymin, xmax, ymax, score)
             thresh: iou threshold to perform box suppression.
     
         Returns:
    +    -------
             A list of box indexes to keep
         """
         x1 = boxes[:, 0]
    @@ -551,66 +535,71 @@ 

    Source code for doctr.utils.metrics

     
     
     
    -[docs] +[docs] class LocalizationConfusion: - """Implements common confusion metrics and mean IoU for localization evaluation. + r"""Implements common confusion metrics and mean IoU for localization evaluation. The aggregated metrics are computed as follows: .. math:: - \\forall Y \\in \\mathcal{B}^N, \\forall X \\in \\mathcal{B}^M, \\\\ - Recall(X, Y) = \\frac{1}{N} \\sum\\limits_{i=1}^N g_{X}(Y_i) \\\\ - Precision(X, Y) = \\frac{1}{M} \\sum\\limits_{i=1}^N g_{X}(Y_i) \\\\ - meanIoU(X, Y) = \\frac{1}{M} \\sum\\limits_{i=1}^M \\max\\limits_{j \\in [1, N]} IoU(X_i, Y_j) + \forall Y \in \mathcal{B}^N, \forall X \in \mathcal{B}^M, \\ + Recall(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N g_{X}(Y_i) \\ + Precision(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M g_{X}(Y_i) \\ + meanIoU(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(X_i, Y_j) with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and :math:`y`, and the function :math:`g_{X}` defined as: .. math:: - \\forall y \\in \\mathcal{B}, - g_X(y) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } y\\mbox{ has been assigned to any }(X_i)_i\\mbox{ with an }IoU \\geq 0.5 \\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{B}` is the set of possible bounding boxes, + \forall y \in \mathcal{B}, + g_X(y) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } y\mbox{ has been assigned to any }(X_i)_i\mbox{ with an }IoU \geq 0.5 \\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. - Example:: - >>> import numpy as np - >>> from doctr.utils import LocalizationConfusion - >>> metric = LocalizationConfusion(iou_thresh=0.5) - >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]])) - >>> metric.summary() + >>> import numpy as np + >>> from doctr.utils import LocalizationConfusion + >>> metric = LocalizationConfusion(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]])) + >>> metric.summary() Args: + ---- iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format """ def __init__( self, iou_thresh: float = 0.5, - rotated_bbox: bool = False, - mask_shape: Tuple[int, int] = (1024, 1024), + use_polygons: bool = False, ) -> None: self.iou_thresh = iou_thresh - self.rotated_bbox = rotated_bbox - self.mask_shape = mask_shape + self.use_polygons = use_polygons self.reset() +
    +[docs] def update(self, gts: np.ndarray, preds: np.ndarray) -> None: + """Updates the metric + Args: + ---- + gts: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones + preds: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones + """ if preds.shape[0] > 0: # Compute IoU - if self.rotated_bbox: - mask_gts = rbox_to_mask(gts, shape=self.mask_shape) - mask_preds = rbox_to_mask(preds, shape=self.mask_shape) - iou_mat = mask_iou(mask_gts, mask_preds) + if self.use_polygons: + iou_mat = polygon_iou(gts, preds) else: iou_mat = box_iou(gts, preds) - self.tot_iou += float(iou_mat.max(axis=1).sum()) + self.tot_iou += float(iou_mat.max(axis=0).sum()) # Assign pairs gt_indices, pred_indices = linear_sum_assignment(-iou_mat) @@ -618,17 +607,18 @@

    Source code for doctr.utils.metrics

     
             # Update counts
             self.num_gts += gts.shape[0]
    -        self.num_preds += preds.shape[0]
    +        self.num_preds += preds.shape[0]
    +
    -[docs] +[docs] def summary(self) -> Tuple[Optional[float], Optional[float], Optional[float]]: """Computes the aggregated metrics - Returns: + Returns + ------- a tuple with the recall, precision and meanIoU scores """ - # Recall recall = self.matches / self.num_gts if self.num_gts > 0 else None @@ -636,7 +626,7 @@

    Source code for doctr.utils.metrics

             precision = self.matches / self.num_preds if self.num_preds > 0 else None
     
             # mean IoU
    -        mean_iou = self.tot_iou / self.num_preds if self.num_preds > 0 else None
    +        mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None
     
             return recall, precision, mean_iou
    @@ -645,64 +635,65 @@

    Source code for doctr.utils.metrics

             self.num_gts = 0
             self.num_preds = 0
             self.matches = 0
    -        self.tot_iou = 0.
    + self.tot_iou = 0.0
    -[docs] +[docs] class OCRMetric: - """Implements end-to-end OCR metric. + r"""Implements an end-to-end OCR metric. The aggregated metrics are computed as follows: .. math:: - \\forall (B, L) \\in \\mathcal{B}^N \\times \\mathcal{L}^N, - \\forall (\\hat{B}, \\hat{L}) \\in \\mathcal{B}^M \\times \\mathcal{L}^M, \\\\ - Recall(B, \\hat{B}, L, \\hat{L}) = \\frac{1}{N} \\sum\\limits_{i=1}^N h_{B,L}(\\hat{B}_i, \\hat{L}_i) \\\\ - Precision(B, \\hat{B}, L, \\hat{L}) = \\frac{1}{M} \\sum\\limits_{i=1}^N h_{B,L}(\\hat{B}_i, \\hat{L}_i) \\\\ - meanIoU(B, \\hat{B}) = \\frac{1}{M} \\sum\\limits_{i=1}^M \\max\\limits_{j \\in [1, N]} IoU(\\hat{B}_i, B_j) + \forall (B, L) \in \mathcal{B}^N \times \mathcal{L}^N, + \forall (\hat{B}, \hat{L}) \in \mathcal{B}^M \times \mathcal{L}^M, \\ + Recall(B, \hat{B}, L, \hat{L}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ + Precision(B, \hat{B}, L, \hat{L}) = \frac{1}{M} \sum\limits_{i=1}^M h_{B,L}(\hat{B}_i, \hat{L}_i) \\ + meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j) with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and :math:`y`, and the function :math:`h_{B, L}` defined as: .. math:: - \\forall (b, l) \\in \\mathcal{B} \\times \\mathcal{L}, - h_{B,L}(b, l) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } b\\mbox{ has been assigned to a given }B_j\\mbox{ with an } \\\\ - & IoU \\geq 0.5 \\mbox{ and that for this assignment, } l = L_j\\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{B}` is the set of possible bounding boxes, - :math:`\\mathcal{L}` is the set of possible character sequences, + \forall (b, l) \in \mathcal{B} \times \mathcal{L}, + h_{B,L}(b, l) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ + & IoU \geq 0.5 \mbox{ and that for this assignment, } l = L_j\\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, + :math:`\mathcal{L}` is the set of possible character sequences, :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. - Example:: - >>> import numpy as np - >>> from doctr.utils import OCRMetric - >>> metric = OCRMetric(iou_thresh=0.5) - >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), - ['hello'], ['hello', 'world']) - >>> metric.summary() + >>> import numpy as np + >>> from doctr.utils import OCRMetric + >>> metric = OCRMetric(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), + >>> ['hello'], ['hello', 'world']) + >>> metric.summary() Args: + ---- iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format """ def __init__( self, iou_thresh: float = 0.5, - rotated_bbox: bool = False, - mask_shape: Tuple[int, int] = (1024, 1024), + use_polygons: bool = False, ) -> None: self.iou_thresh = iou_thresh - self.rotated_bbox = rotated_bbox - self.mask_shape = mask_shape + self.use_polygons = use_polygons self.reset() +
    +[docs] def update( self, gt_boxes: np.ndarray, @@ -710,50 +701,58 @@

    Source code for doctr.utils.metrics

             gt_labels: List[str],
             pred_labels: List[str],
         ) -> None:
    +        """Updates the metric
     
    +        Args:
    +        ----
    +            gt_boxes: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones
    +            pred_boxes: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones
    +            gt_labels: a list of N string labels
    +            pred_labels: a list of M string labels
    +        """
             if gt_boxes.shape[0] != len(gt_labels) or pred_boxes.shape[0] != len(pred_labels):
    -            raise AssertionError("there should be the same number of boxes and string both for the ground truth "
    -                                 "and the predictions")
    +            raise AssertionError(
    +                "there should be the same number of boxes and string both for the ground truth and the predictions"
    +            )
     
             # Compute IoU
             if pred_boxes.shape[0] > 0:
    -            if self.rotated_bbox:
    -                mask_gts = rbox_to_mask(gt_boxes, shape=self.mask_shape)
    -                mask_preds = rbox_to_mask(pred_boxes, shape=self.mask_shape)
    -                iou_mat = mask_iou(mask_gts, mask_preds)
    +            if self.use_polygons:
    +                iou_mat = polygon_iou(gt_boxes, pred_boxes)
                 else:
                     iou_mat = box_iou(gt_boxes, pred_boxes)
     
    -            self.tot_iou += float(iou_mat.max(axis=1).sum())
    +            self.tot_iou += float(iou_mat.max(axis=0).sum())
     
                 # Assign pairs
                 gt_indices, pred_indices = linear_sum_assignment(-iou_mat)
                 is_kept = iou_mat[gt_indices, pred_indices] >= self.iou_thresh
                 # String comparison
                 for gt_idx, pred_idx in zip(gt_indices[is_kept], pred_indices[is_kept]):
    -                _raw, _caseless, _unidecode, _unicase = string_match(gt_labels[gt_idx], pred_labels[pred_idx])
    +                _raw, _caseless, _anyascii, _unicase = string_match(gt_labels[gt_idx], pred_labels[pred_idx])
                     self.raw_matches += int(_raw)
                     self.caseless_matches += int(_caseless)
    -                self.unidecode_matches += int(_unidecode)
    +                self.anyascii_matches += int(_anyascii)
                     self.unicase_matches += int(_unicase)
     
             self.num_gts += gt_boxes.shape[0]
    -        self.num_preds += pred_boxes.shape[0]
    +        self.num_preds += pred_boxes.shape[0]
    +
    -[docs] +[docs] def summary(self) -> Tuple[Dict[str, Optional[float]], Dict[str, Optional[float]], Optional[float]]: """Computes the aggregated metrics - Returns: - a tuple with the recall & precision for each string comparison flexibility and the mean IoU + Returns + ------- + a tuple with the recall & precision for each string comparison and the mean IoU """ - # Recall recall = dict( raw=self.raw_matches / self.num_gts if self.num_gts > 0 else None, caseless=self.caseless_matches / self.num_gts if self.num_gts > 0 else None, - unidecode=self.unidecode_matches / self.num_gts if self.num_gts > 0 else None, + anyascii=self.anyascii_matches / self.num_gts if self.num_gts > 0 else None, unicase=self.unicase_matches / self.num_gts if self.num_gts > 0 else None, ) @@ -761,12 +760,12 @@

    Source code for doctr.utils.metrics

             precision = dict(
                 raw=self.raw_matches / self.num_preds if self.num_preds > 0 else None,
                 caseless=self.caseless_matches / self.num_preds if self.num_preds > 0 else None,
    -            unidecode=self.unidecode_matches / self.num_preds if self.num_preds > 0 else None,
    +            anyascii=self.anyascii_matches / self.num_preds if self.num_preds > 0 else None,
                 unicase=self.unicase_matches / self.num_preds if self.num_preds > 0 else None,
             )
     
             # mean IoU (overall detected boxes)
    -        mean_iou = self.tot_iou / self.num_preds if self.num_preds > 0 else None
    +        mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None
     
             return recall, precision, mean_iou
    @@ -774,12 +773,136 @@

    Source code for doctr.utils.metrics

         def reset(self) -> None:
             self.num_gts = 0
             self.num_preds = 0
    -        self.tot_iou = 0.
    +        self.tot_iou = 0.0
             self.raw_matches = 0
             self.caseless_matches = 0
    -        self.unidecode_matches = 0
    +        self.anyascii_matches = 0
             self.unicase_matches = 0
    + + +
    +[docs] +class DetectionMetric: + r"""Implements an object detection metric. + + The aggregated metrics are computed as follows: + + .. math:: + \forall (B, C) \in \mathcal{B}^N \times \mathcal{C}^N, + \forall (\hat{B}, \hat{C}) \in \mathcal{B}^M \times \mathcal{C}^M, \\ + Recall(B, \hat{B}, C, \hat{C}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,C}(\hat{B}_i, \hat{C}_i) \\ + Precision(B, \hat{B}, C, \hat{C}) = \frac{1}{M} \sum\limits_{i=1}^M h_{B,C}(\hat{B}_i, \hat{C}_i) \\ + meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j) + + with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and + :math:`y`, and the function :math:`h_{B, C}` defined as: + + .. math:: + \forall (b, c) \in \mathcal{B} \times \mathcal{C}, + h_{B,C}(b, c) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ + & IoU \geq 0.5 \mbox{ and that for this assignment, } c = C_j\\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, + :math:`\mathcal{C}` is the set of possible class indices, + :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. + + >>> import numpy as np + >>> from doctr.utils import DetectionMetric + >>> metric = DetectionMetric(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), + >>> np.zeros(1, dtype=np.int64), np.array([0, 1], dtype=np.int64)) + >>> metric.summary() + + Args: + ---- + iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format + """ + + def __init__( + self, + iou_thresh: float = 0.5, + use_polygons: bool = False, + ) -> None: + self.iou_thresh = iou_thresh + self.use_polygons = use_polygons + self.reset() + +
    +[docs] + def update( + self, + gt_boxes: np.ndarray, + pred_boxes: np.ndarray, + gt_labels: np.ndarray, + pred_labels: np.ndarray, + ) -> None: + """Updates the metric + + Args: + ---- + gt_boxes: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones + pred_boxes: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones + gt_labels: an array of class indices of shape (N,) + pred_labels: an array of class indices of shape (M,) + """ + if gt_boxes.shape[0] != gt_labels.shape[0] or pred_boxes.shape[0] != pred_labels.shape[0]: + raise AssertionError( + "there should be the same number of boxes and string both for the ground truth and the predictions" + ) + + # Compute IoU + if pred_boxes.shape[0] > 0: + if self.use_polygons: + iou_mat = polygon_iou(gt_boxes, pred_boxes) + else: + iou_mat = box_iou(gt_boxes, pred_boxes) + + self.tot_iou += float(iou_mat.max(axis=0).sum()) + + # Assign pairs + gt_indices, pred_indices = linear_sum_assignment(-iou_mat) + is_kept = iou_mat[gt_indices, pred_indices] >= self.iou_thresh + # Category comparison + self.num_matches += int((gt_labels[gt_indices[is_kept]] == pred_labels[pred_indices[is_kept]]).sum()) + + self.num_gts += gt_boxes.shape[0] + self.num_preds += pred_boxes.shape[0]
    + + +
    +[docs] + def summary(self) -> Tuple[Optional[float], Optional[float], Optional[float]]: + """Computes the aggregated metrics + + Returns + ------- + a tuple with the recall & precision for each class prediction and the mean IoU + """ + # Recall + recall = self.num_matches / self.num_gts if self.num_gts > 0 else None + + # Precision + precision = self.num_matches / self.num_preds if self.num_preds > 0 else None + + # mean IoU (overall detected boxes) + mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None + + return recall, precision, mean_iou
    + + + def reset(self) -> None: + self.num_gts = 0 + self.num_preds = 0 + self.tot_iou = 0.0 + self.num_matches = 0
    +
    @@ -812,8 +935,8 @@

    Source code for doctr.utils.metrics

           
         
       
    - - + + diff --git a/v0.4.0/_modules/doctr/utils/visualization.html b/v0.4.0/_modules/doctr/utils/visualization.html index 8e7dcca811..c818be6d7b 100644 --- a/v0.4.0/_modules/doctr/utils/visualization.html +++ b/v0.4.0/_modules/doctr/utils/visualization.html @@ -13,7 +13,7 @@ - + doctr.utils.visualization - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.utils.visualization

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
    +import colorsys
    +from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple, Union
     
    -import matplotlib.pyplot as plt
    -from matplotlib.figure import Figure
    +import cv2
     import matplotlib.patches as patches
    -import mplcursors
    -from PIL import ImageFont, ImageDraw, Image
    +import matplotlib.pyplot as plt
     import numpy as np
    -import cv2
    -from typing import Tuple, List, Dict, Any, Union
    +from matplotlib.figure import Figure
     
    -from .common_types import BoundingBox, RotatedBbox
    +from .common_types import BoundingBox, Polygon4P
     
    -__all__ = ['visualize_page', 'synthetize_page']
    +__all__ = ["visualize_page", "visualize_kie_page", "draw_boxes"]
     
     
    -def create_rect_patch(
    -    geometry: Union[BoundingBox, RotatedBbox],
    -    label: str,
    +def rect_patch(
    +    geometry: BoundingBox,
         page_dimensions: Tuple[int, int],
    -    color: Tuple[int, int, int],
    +    label: Optional[str] = None,
    +    color: Tuple[float, float, float] = (0, 0, 0),
         alpha: float = 0.3,
         linewidth: int = 2,
         fill: bool = True,
    -) -> patches.Patch:
    -    """Create a matplotlib patch (rectangle) bounding the element
    +    preserve_aspect_ratio: bool = False,
    +) -> patches.Rectangle:
    +    """Create a matplotlib rectangular patch for the element
     
         Args:
    +    ----
             geometry: bounding box of the element
    +        page_dimensions: dimensions of the Page in format (height, width)
             label: label to display when hovered
    -        page_dimensions: dimensions of the Page
             color: color to draw box
             alpha: opacity parameter to fill the boxes, 0 = transparent
             linewidth: line width
    +        fill: whether the patch should be filled
    +        preserve_aspect_ratio: pass True if you passed True to the predictor
     
         Returns:
    +    -------
             a rectangular Patch
         """
    +    if len(geometry) != 2 or any(not isinstance(elt, tuple) or len(elt) != 2 for elt in geometry):
    +        raise ValueError("invalid geometry format")
    +
    +    # Unpack
         height, width = page_dimensions
    -    if len(geometry) == 5:
    -        x, y, w, h, a = geometry  # type: ignore[misc]
    -        x, w = x * width, w * width
    -        y, h = y * height, h * height
    -        points = cv2.boxPoints(((x, y), (w, h), a))
    -        return patches.Polygon(
    -            points,
    -            fill=fill,
    -            linewidth=linewidth,
    -            edgecolor=(*color, alpha),
    -            facecolor=(*color, alpha),
    -            label=label
    -        )
    -    else:
    -        (xmin, ymin), (xmax, ymax) = geometry  # type: ignore[misc]
    -        xmin, xmax = xmin * width, xmax * width
    -        ymin, ymax = ymin * height, ymax * height
    -        return patches.Rectangle(
    -            (xmin, ymin),
    -            xmax - xmin,
    -            ymax - ymin,
    -            fill=fill,
    -            linewidth=linewidth,
    -            edgecolor=(*color, alpha),
    -            facecolor=(*color, alpha),
    -            label=label
    -        )
    +    (xmin, ymin), (xmax, ymax) = geometry
    +    # Switch to absolute coords
    +    if preserve_aspect_ratio:
    +        width = height = max(height, width)
    +    xmin, w = xmin * width, (xmax - xmin) * width
    +    ymin, h = ymin * height, (ymax - ymin) * height
    +
    +    return patches.Rectangle(
    +        (xmin, ymin),
    +        w,
    +        h,
    +        fill=fill,
    +        linewidth=linewidth,
    +        edgecolor=(*color, alpha),
    +        facecolor=(*color, alpha),
    +        label=label,
    +    )
    +
    +
    +def polygon_patch(
    +    geometry: np.ndarray,
    +    page_dimensions: Tuple[int, int],
    +    label: Optional[str] = None,
    +    color: Tuple[float, float, float] = (0, 0, 0),
    +    alpha: float = 0.3,
    +    linewidth: int = 2,
    +    fill: bool = True,
    +    preserve_aspect_ratio: bool = False,
    +) -> patches.Polygon:
    +    """Create a matplotlib polygon patch for the element
    +
    +    Args:
    +    ----
    +        geometry: bounding box of the element
    +        page_dimensions: dimensions of the Page in format (height, width)
    +        label: label to display when hovered
    +        color: color to draw box
    +        alpha: opacity parameter to fill the boxes, 0 = transparent
    +        linewidth: line width
    +        fill: whether the patch should be filled
    +        preserve_aspect_ratio: pass True if you passed True to the predictor
    +
    +    Returns:
    +    -------
    +        a polygon Patch
    +    """
    +    if not geometry.shape == (4, 2):
    +        raise ValueError("invalid geometry format")
    +
    +    # Unpack
    +    height, width = page_dimensions
    +    geometry[:, 0] = geometry[:, 0] * (max(width, height) if preserve_aspect_ratio else width)
    +    geometry[:, 1] = geometry[:, 1] * (max(width, height) if preserve_aspect_ratio else height)
    +
    +    return patches.Polygon(
    +        geometry,
    +        fill=fill,
    +        linewidth=linewidth,
    +        edgecolor=(*color, alpha),
    +        facecolor=(*color, alpha),
    +        label=label,
    +    )
    +
    +
    +def create_obj_patch(
    +    geometry: Union[BoundingBox, Polygon4P, np.ndarray],
    +    page_dimensions: Tuple[int, int],
    +    **kwargs: Any,
    +) -> patches.Patch:
    +    """Create a matplotlib patch for the element
    +
    +    Args:
    +    ----
    +        geometry: bounding box (straight or rotated) of the element
    +        page_dimensions: dimensions of the page in format (height, width)
    +        **kwargs: keyword arguments for the patch
    +
    +    Returns:
    +    -------
    +        a matplotlib Patch
    +    """
    +    if isinstance(geometry, tuple):
    +        if len(geometry) == 2:  # straight word BB (2 pts)
    +            return rect_patch(geometry, page_dimensions, **kwargs)
    +        elif len(geometry) == 4:  # rotated word BB (4 pts)
    +            return polygon_patch(np.asarray(geometry), page_dimensions, **kwargs)
    +    elif isinstance(geometry, np.ndarray) and geometry.shape == (4, 2):  # rotated line
    +        return polygon_patch(geometry, page_dimensions, **kwargs)
    +    raise ValueError("invalid geometry format")
    +
    +
    +def get_colors(num_colors: int) -> List[Tuple[float, float, float]]:
    +    """Generate num_colors color for matplotlib
    +
    +    Args:
    +    ----
    +        num_colors: number of colors to generate
    +
    +    Returns:
    +    -------
    +        colors: list of generated colors
    +    """
    +    colors = []
    +    for i in np.arange(0.0, 360.0, 360.0 / num_colors):
    +        hue = i / 360.0
    +        lightness = (50 + np.random.rand() * 10) / 100.0
    +        saturation = (90 + np.random.rand() * 10) / 100.0
    +        colors.append(colorsys.hls_to_rgb(hue, lightness, saturation))
    +    return colors
     
     
     
    -[docs] +[docs] def visualize_page( page: Dict[str, Any], image: np.ndarray, @@ -359,18 +472,18 @@

    Source code for doctr.utils.visualization

     ) -> Figure:
         """Visualize a full page with predicted blocks, lines and words
     
    -    Example::
    -        >>> import numpy as np
    -        >>> import matplotlib.pyplot as plt
    -        >>> from doctr.utils.visualization import visualize_page
    -        >>> from doctr.models import ocr_db_crnn
    -        >>> model = ocr_db_crnn(pretrained=True)
    -        >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    -        >>> out = model([[input_page]])
    -        >>> visualize_page(out[0].pages[0].export(), input_page)
    -        >>> plt.show()
    +    >>> import numpy as np
    +    >>> import matplotlib.pyplot as plt
    +    >>> from doctr.utils.visualization import visualize_page
    +    >>> from doctr.models import ocr_db_crnn
    +    >>> model = ocr_db_crnn(pretrained=True)
    +    >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    +    >>> out = model([[input_page]])
    +    >>> visualize_page(out[0].pages[0].export(), input_page)
    +    >>> plt.show()
     
         Args:
    +    ----
             page: the exported Page of a Document
             image: np array of the page, needs to have the same shape than page['dimensions']
             words_only: whether only words should be displayed
    @@ -378,6 +491,11 @@ 

    Source code for doctr.utils.visualization

             scale: figsize of the largest windows side
             interactive: whether the plot should be interactive
             add_labels: for static plot, adds text labels on top of bounding box
    +        **kwargs: keyword arguments for the polygon patch
    +
    +    Returns:
    +    -------
    +        the matplotlib figure
         """
         # Get proper scale and aspect ratio
         h, w = image.shape[:2]
    @@ -386,128 +504,189 @@ 

    Source code for doctr.utils.visualization

         # Display the image
         ax.imshow(image)
         # hide both axis
    -    ax.axis('off')
    +    ax.axis("off")
     
         if interactive:
             artists: List[patches.Patch] = []  # instantiate an empty list of patches (to be drawn on the page)
     
    -    for block in page['blocks']:
    +    for block in page["blocks"]:
             if not words_only:
    -            rect = create_rect_patch(block['geometry'], 'block', page['dimensions'], (0, 1, 0), linewidth=1, **kwargs)
    +            rect = create_obj_patch(
    +                block["geometry"], page["dimensions"], label="block", color=(0, 1, 0), linewidth=1, **kwargs
    +            )
                 # add patch on figure
                 ax.add_patch(rect)
                 if interactive:
                     # add patch to cursor's artists
                     artists.append(rect)
     
    -        for line in block['lines']:
    +        for line in block["lines"]:
                 if not words_only:
    -                rect = create_rect_patch(line['geometry'], 'line', page['dimensions'], (1, 0, 0), linewidth=1, **kwargs)
    +                rect = create_obj_patch(
    +                    line["geometry"], page["dimensions"], label="line", color=(1, 0, 0), linewidth=1, **kwargs
    +                )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
     
    -            for word in line['words']:
    -                rect = create_rect_patch(word['geometry'], f"{word['value']} (confidence: {word['confidence']:.2%})",
    -                                         page['dimensions'], (0, 0, 1), **kwargs)
    +            for word in line["words"]:
    +                rect = create_obj_patch(
    +                    word["geometry"],
    +                    page["dimensions"],
    +                    label=f"{word['value']} (confidence: {word['confidence']:.2%})",
    +                    color=(0, 0, 1),
    +                    **kwargs,
    +                )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
                     elif add_labels:
    -                    if len(word['geometry']) == 5:
    +                    if len(word["geometry"]) == 5:
                             text_loc = (
    -                            int(page['dimensions'][1] * (word['geometry'][0] - word['geometry'][2] / 2)),
    -                            int(page['dimensions'][0] * (word['geometry'][1] - word['geometry'][3] / 2))
    +                            int(page["dimensions"][1] * (word["geometry"][0] - word["geometry"][2] / 2)),
    +                            int(page["dimensions"][0] * (word["geometry"][1] - word["geometry"][3] / 2)),
                             )
                         else:
                             text_loc = (
    -                            int(page['dimensions'][1] * word['geometry'][0][0]),
    -                            int(page['dimensions'][0] * word['geometry'][0][1])
    +                            int(page["dimensions"][1] * word["geometry"][0][0]),
    +                            int(page["dimensions"][0] * word["geometry"][0][1]),
    +                        )
    +
    +                    if len(word["geometry"]) == 2:
    +                        # We draw only if boxes are in straight format
    +                        ax.text(
    +                            *text_loc,
    +                            word["value"],
    +                            size=10,
    +                            alpha=0.5,
    +                            color=(0, 0, 1),
                             )
    -                    ax.text(
    -                        *text_loc,
    -                        word['value'],
    -                        size=10,
    -                        alpha=0.5,
    -                        color=(0, 0, 1),
    -                    )
     
             if display_artefacts:
    -            for artefact in block['artefacts']:
    -                rect = create_rect_patch(
    -                    artefact['geometry'],
    -                    'artefact',
    -                    page['dimensions'],
    -                    (0.5, 0.5, 0.5),  # type: ignore[arg-type]
    +            for artefact in block["artefacts"]:
    +                rect = create_obj_patch(
    +                    artefact["geometry"],
    +                    page["dimensions"],
    +                    label="artefact",
    +                    color=(0.5, 0.5, 0.5),
                         linewidth=1,
    -                    **kwargs
    +                    **kwargs,
                     )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
     
         if interactive:
    +        import mplcursors
    +
             # Create mlp Cursor to hover patches in artists
             mplcursors.Cursor(artists, hover=2).connect("add", lambda sel: sel.annotation.set_text(sel.artist.get_label()))
    -    fig.tight_layout(pad=0.)
    +    fig.tight_layout(pad=0.0)
     
         return fig
    -def synthetize_page( +def visualize_kie_page( page: Dict[str, Any], - draw_proba: bool = False, - font_size: int = 13, -) -> np.ndarray: - """Draw a the content of the element page (OCR response) on a blank page. + image: np.ndarray, + words_only: bool = False, + display_artefacts: bool = True, + scale: float = 10, + interactive: bool = True, + add_labels: bool = True, + **kwargs: Any, +) -> Figure: + """Visualize a full page with predicted blocks, lines and words + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from doctr.utils.visualization import visualize_page + >>> from doctr.models import ocr_db_crnn + >>> model = ocr_db_crnn(pretrained=True) + >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) + >>> out = model([[input_page]]) + >>> visualize_kie_page(out[0].pages[0].export(), input_page) + >>> plt.show() Args: - page: exported Page object to represent - draw_proba: if True, draw words in colors to represent confidence. Blue: p=1, red: p=0 - font_size: size of the font, default font = 13 + ---- + page: the exported Page of a Document + image: np array of the page, needs to have the same shape than page['dimensions'] + words_only: whether only words should be displayed + display_artefacts: whether artefacts should be displayed + scale: figsize of the largest windows side + interactive: whether the plot should be interactive + add_labels: for static plot, adds text labels on top of bounding box + **kwargs: keyword arguments for the polygon patch - Return: - A np array (drawn page) + Returns: + ------- + the matplotlib figure """ - # Draw template - h, w = page["dimensions"] - response = 255 * np.ones((h, w, 3), dtype=np.int32) + # Get proper scale and aspect ratio + h, w = image.shape[:2] + size = (scale * w / h, scale) if h > w else (scale, h / w * scale) + fig, ax = plt.subplots(figsize=size) + # Display the image + ax.imshow(image) + # hide both axis + ax.axis("off") - # Draw each word - for block in page["blocks"]: - for line in block["lines"]: - for word in line["words"]: - # Get aboslute word geometry - (xmin, ymin), (xmax, ymax) = word["geometry"] - xmin, xmax = int(w * xmin), int(w * xmax) - ymin, ymax = int(h * ymin), int(h * ymax) - - # White drawing context adapted to font size, 0.75 factor to convert pts --> pix - h_box, w_box = ymax - ymin, xmax - xmin - h_font, w_font = font_size, int(font_size * w_box / (h_box * 0.75)) - img = Image.new('RGB', (w_font, h_font), color=(255, 255, 255)) - d = ImageDraw.Draw(img) - - # Draw in black the value of the word - d.text((0, 0), word["value"], font=ImageFont.load_default(), fill=(0, 0, 0)) - - # Resize back to box size - img = img.resize((w_box, h_box), Image.NEAREST) - - # Colorize if draw_proba - if draw_proba: - p = int(255 * word["confidence"]) - mask = np.where(np.array(img) == 0, 1, 0) - proba = np.array([255 - p, 0, p]) - color = mask * proba[np.newaxis, np.newaxis, :] - white_mask = 255 * (1 - mask) - img = color + white_mask - - # Write to response page - response[ymin:ymax, xmin:xmax, :] = np.array(img) - - return response + if interactive: + artists: List[patches.Patch] = [] # instantiate an empty list of patches (to be drawn on the page) + + colors = {k: color for color, k in zip(get_colors(len(page["predictions"])), page["predictions"])} + for key, value in page["predictions"].items(): + for prediction in value: + if not words_only: + rect = create_obj_patch( + prediction["geometry"], + page["dimensions"], + label=f"{key} \n {prediction['value']} (confidence: {prediction['confidence']:.2%}", + color=colors[key], + linewidth=1, + **kwargs, + ) + # add patch on figure + ax.add_patch(rect) + if interactive: + # add patch to cursor's artists + artists.append(rect) + + if interactive: + import mplcursors + + # Create mlp Cursor to hover patches in artists + mplcursors.Cursor(artists, hover=2).connect("add", lambda sel: sel.annotation.set_text(sel.artist.get_label())) + fig.tight_layout(pad=0.0) + + return fig + + +def draw_boxes(boxes: np.ndarray, image: np.ndarray, color: Optional[Tuple[int, int, int]] = None, **kwargs) -> None: + """Draw an array of relative straight boxes on an image + + Args: + ---- + boxes: array of relative boxes, of shape (*, 4) + image: np array, float32 or uint8 + color: color to use for bounding box edges + **kwargs: keyword arguments from `matplotlib.pyplot.plot` + """ + h, w = image.shape[:2] + # Convert boxes to absolute coords + _boxes = deepcopy(boxes) + _boxes[:, [0, 2]] *= w + _boxes[:, [1, 3]] *= h + _boxes = _boxes.astype(np.int32) + for box in _boxes.tolist(): + xmin, ymin, xmax, ymax = box + image = cv2.rectangle( + image, (xmin, ymin), (xmax, ymax), color=color if isinstance(color, tuple) else (0, 0, 255), thickness=2 + ) + plt.imshow(image) + plt.plot(**kwargs)
    @@ -540,8 +719,8 @@

    Source code for doctr.utils.visualization

           
         
       
    - - + + diff --git a/v0.4.0/_modules/index.html b/v0.4.0/_modules/index.html index e86abcd4d4..5793c44f20 100644 --- a/v0.4.0/_modules/index.html +++ b/v0.4.0/_modules/index.html @@ -13,7 +13,7 @@ - + Overview: module code - docTR documentation @@ -225,20 +225,42 @@ - - + + diff --git a/v0.4.0/_sources/changelog.rst.txt b/v0.4.0/_sources/changelog.rst.txt index 430097d6c8..35befe7b96 100644 --- a/v0.4.0/_sources/changelog.rst.txt +++ b/v0.4.0/_sources/changelog.rst.txt @@ -1,6 +1,54 @@ Changelog ========= +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 `_ diff --git a/v0.4.0/_sources/datasets.rst.txt b/v0.4.0/_sources/datasets.rst.txt deleted file mode 100644 index 354122f1e5..0000000000 --- a/v0.4.0/_sources/datasets.rst.txt +++ /dev/null @@ -1,68 +0,0 @@ -doctr.datasets -============== - -.. currentmodule:: doctr.datasets - -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. - - -.. _datasets: - -Available Datasets ------------------- -The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL. - -.. autoclass:: doctr.datasets.datasets.VisionDataset - - -Here are all datasets that are available through DocTR: - -.. autoclass:: FUNSD -.. autoclass:: SROIE -.. autoclass:: CORD -.. autoclass:: OCRDataset - - -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. - -.. autoclass:: doctr.datasets.loader.DataLoader - - -.. _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 - * - digits - - 10 - - 0123456789 - * - ascii_letters - - 52 - - abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ - * - punctuation - - 32 - - !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ - * - currency - - 5 - - £€¥¢฿ - * - latin - - 96 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~° - * - french - - 154 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿ - -.. autofunction:: encode_sequences diff --git a/v0.4.0/_sources/documents.rst.txt b/v0.4.0/_sources/documents.rst.txt deleted file mode 100644 index 655730073e..0000000000 --- a/v0.4.0/_sources/documents.rst.txt +++ /dev/null @@ -1,87 +0,0 @@ -doctr.documents -=============== - - -.. currentmodule:: doctr.documents - -The documents module enables users to easily access content from documents and export analysis -results to structured formats. - - -Document structure ------------------- - -Structural organization of the documents. - -Word -^^^^ -A Word is an uninterrupted sequence of characters. - -.. autoclass:: Word - -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 - -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 - - -Document -^^^^^^^^ - -A Document is a collection of Pages. - -.. autoclass:: Document - - .. automethod:: show - - -File reading ------------- - -High-performance file reading and conversion to processable structured data. - -.. autofunction:: read_pdf - -.. autofunction:: read_img - -.. autofunction:: read_html - - -.. autoclass:: DocumentFile - - .. automethod:: from_pdf - - .. automethod:: from_url - - .. automethod:: from_images - -.. autoclass:: PDF - - .. automethod:: as_images - - .. automethod:: get_words - - .. automethod:: get_artefacts diff --git a/v0.4.0/_sources/getting_started/installing.rst.txt b/v0.4.0/_sources/getting_started/installing.rst.txt index e764e734a7..39e79aa3dd 100644 --- a/v0.4.0/_sources/getting_started/installing.rst.txt +++ b/v0.4.0/_sources/getting_started/installing.rst.txt @@ -3,7 +3,7 @@ Installation ************ -This library requires `Python `_ 3.9 or higher. +This library requires `Python `_ 3.10 or higher. Prerequisites diff --git a/v0.4.0/_sources/index.rst.txt b/v0.4.0/_sources/index.rst.txt index fc3ff89fdf..53251db142 100644 --- a/v0.4.0/_sources/index.rst.txt +++ b/v0.4.0/_sources/index.rst.txt @@ -1,7 +1,8 @@ -DocTR: Document Text Recognition -================================ +******************************** +docTR: Document Text Recognition +******************************** -State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 (PyTorch now in beta) +State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 & PyTorch .. image:: https://github.com/mindee/doctr/releases/download/v0.2.0/ocr.png :align: center @@ -9,38 +10,29 @@ State-of-the-art Optical Character Recognition made seamless & accessible to any DocTR provides an easy and powerful way to extract valuable information from your documents: -* |:receipt:| **for automation**: seemlessly process documents for Natural Language Understanding tasks: we provide OCR predictors to parse textual information (localize and identify each word) 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. -Welcome to the documentation of `DocTR `_! - - Main Features ------------- * |:robot:| Robust 2-stage (detection + recognition) OCR predictors with pretrained parameters * |:zap:| User-friendly, 3 lines of code to load a document and extract text with a predictor -* |:rocket:| State-of-the-art performances on public document datasets, comparable with GoogleVision/AWS Textract +* |: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, small dependencies -* |:tools:| Daily maintained -* |:factory:| Easy integration - +* |:bird:| Light package, minimal dependencies +* |:tools:| Actively maintained by Mindee +* |:factory:| Easy integration (available templates for browser demo & API deployment) -Getting Started ---------------- .. toctree:: :maxdepth: 2 + :caption: Getting started + :hidden: - installing - - -Build & train your predictor -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* Compose your own end-to-end OCR predictor: mix and match detection & recognition predictors (all-pretrained) -* Fine-tune or train from scratch any detection or recognition model to specialize on your data + getting_started/installing + notebooks Model zoo @@ -48,36 +40,83 @@ Model zoo Text detection models """"""""""""""""""""" - * `DBNet `_ (Differentiable Binarization) - * `LinkNet `_ +* 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 `_ (Show, Attend and Read) - * `CRNN `_ (Convolutional Recurrent Neural Network) - * `MASTER `_ (Multi-Aspect Non-local Network for Scene Text Recognition) +* 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" `_ 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 `_. +* 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" `_. .. toctree:: :maxdepth: 2 - :caption: Notes + :caption: Using docTR + :hidden: - changelog + 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 + + +.. toctree:: + :maxdepth: 2 + :caption: Community + :hidden: + + community/resources .. toctree:: :maxdepth: 2 :caption: Package Reference + :hidden: - datasets - documents - models - transforms - utils + 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/v0.4.0/_sources/installing.rst.txt b/v0.4.0/_sources/installing.rst.txt deleted file mode 100644 index 5c8779dc1c..0000000000 --- a/v0.4.0/_sources/installing.rst.txt +++ /dev/null @@ -1,46 +0,0 @@ - -************ -Installation -************ - -This library requires Python 3.6 or higher. - - -Prerequisites -============= - -Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so: - -* TensorFlow: `installation page `_. -* PyTorch: `installation page `_. - -If you are running another OS than Linux, you will need a few extra dependencies. - -For MacOS users, you can install them as follows: - -.. code:: shell - - brew install cairo pango gdk-pixbuf libffi - -For Windows users, those dependencies are included in GTK. You can find the latest installer over `here `_. - - -Via Python Package -================== - -Install the last stable release of the package using pip: - -.. code:: bash - - pip install python-doctr - - -Via Git -======= - -Install the library in developper mode: - -.. code:: bash - - git clone https://github.com/mindee/doctr.git - pip install -e doctr/. diff --git a/v0.4.0/_sources/models.rst.txt b/v0.4.0/_sources/models.rst.txt deleted file mode 100644 index 9830c6c153..0000000000 --- a/v0.4.0/_sources/models.rst.txt +++ /dev/null @@ -1,215 +0,0 @@ -doctr.models -============ - -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. - -.. currentmodule:: doctr.models - -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 TensorFlow model. -* Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable. - - -Text Detection --------------- -Localizing text elements in images - -+---------------------------------------------------+----------------------------+----------------------------+---------+ -| | FUNSD | CORD | | -+==================+=================+==============+============+===============+============+===============+=========+ -| **Architecture** | **Input shape** | **# params** | **Recall** | **Precision** | **Recall** | **Precision** | **FPS** | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ -| db_resnet50 | (1024, 1024, 3) | 25.2 M | 82.14 | 87.64 | 92.49 | 89.66 | 2.1 | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ - -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 combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for detection -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for detection is the following: - -1. resize each input image to the target size (bilinear interpolation by default) with potential deformation. -2. batch images together -3. normalize the batch using the training data statistics - - -Detection models -^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - -.. autofunction:: doctr.models.detection.db_resnet50 -.. autofunction:: doctr.models.detection.linknet16 - -Detection predictors -^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information. - -.. autofunction:: doctr.models.detection.detection_predictor - - -Text Recognition ----------------- -Identifying strings in images - -.. list-table:: Text recognition model zoo - :widths: 20 20 15 10 10 10 - :header-rows: 1 - - * - Architecture - - Input shape - - # params - - FUNSD - - CORD - - FPS - * - crnn_vgg16_bn - - (32, 128, 3) - - 15.8M - - 86.02 - - 91.3 - - 12.8 - * - sar_vgg16_bn - - (32, 128, 3) - - 21.5M - - 86.2 - - 91.7 - - 3.3 - * - sar_resnet31 - - (32, 128, 3) - - 53.1M - - **86.3** - - **92.1** - - 2.7 - -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 metrics being used are available in :ref:`metrics`. - -All these recognition models are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for recognition -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for recognition is the following: - -1. resize each input image to the target size (bilinear interpolation by default) without deformation. -2. pad the image to the target size (with zeros by default) -3. batch images together -4. normalize the batch using the training data statistics - -Recognition models -^^^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - - -.. autofunction:: doctr.models.recognition.crnn_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_resnet31 -.. autofunction:: doctr.models.recognition.master - - -Recognition predictors -^^^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage. - -.. autofunction:: doctr.models.recognition.recognition_predictor - - -End-to-End OCR --------------- -Predictors that localize and identify text elements in images - -+-----------------------------+--------------------------------------+--------------------------------------+ -| | FUNSD | CORD | -+=============================+============+===============+=========+============+===============+=========+ -| **Architecture** | **Recall** | **Precision** | **FPS** | **Recall** | **Precision** | **FPS** | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + crnn_vgg16_bn | 70.08 | 74.77 | 0.85 | 82.19 | **79.67** | 1.6 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_vgg16_bn | N/A | N/A | 0.49 | N/A | N/A | 1.0 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_resnet31 | N/A | N/A | 0.27 | N/A | N/A | 0.83 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision text detection | 59.50 | 62.50 | | 75.30 | 70.00 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision doc. text detection | 64.00 | 53.30 | | 68.90 | 61.10 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| AWS textract | **78.10** | **83.00** | | **87.50** | 66.00 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ - -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`. - -All recognition models of predictors are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Results on private ocr datasets - -+------------------------------------+----------------------------+----------------------------+----------------------------+ -| | Receipts | Invoices | IDs | -+====================================+============+===============+============+===============+============+===============+ -| **Architecture** | **Recall** | **Precision** | **Recall** | **Precision** | **Recall** | **Precision** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| db_resnet50 + crnn_vgg16_bn (ours) | **78.90** | **81.01** | 65.68 | **69.86** | **49.48** | **50.46** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| Gvision doc. text detection | 68.91 | 59.89 | 63.20 | 52.85 | 43.70 | 29.21 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| AWS textract | 75.77 | 77.70 | **70.47** | 69.13 | 46.39 | 43.32 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ - - -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. - -.. autofunction:: doctr.models.zoo.ocr_predictor - - -Model export ------------- -Utility functions to make the most of document analysis models. - -.. currentmodule:: doctr.models.export - -Model compression -^^^^^^^^^^^^^^^^^ - -.. autofunction:: convert_to_tflite - -.. autofunction:: convert_to_fp16 - -.. autofunction:: quantize_model - -Using SavedModel -^^^^^^^^^^^^^^^^ - -Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -`SavedModel `_ format as follows: - - - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> _ = model(input_t, training=False) - >>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/') - -And loaded just as easily: - - - >>> import tensorflow as tf - >>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/') diff --git a/v0.4.0/_sources/transforms.rst.txt b/v0.4.0/_sources/transforms.rst.txt deleted file mode 100644 index 0230fe75f5..0000000000 --- a/v0.4.0/_sources/transforms.rst.txt +++ /dev/null @@ -1,32 +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: - -.. autoclass:: Resize -.. autoclass:: Normalize -.. autoclass:: LambdaTransformation -.. autoclass:: ToGray -.. autoclass:: ColorInversion -.. autoclass:: RandomBrightness -.. autoclass:: RandomContrast -.. autoclass:: RandomSaturation -.. autoclass:: RandomHue -.. autoclass:: RandomGamma -.. autoclass:: RandomJpegQuality - - -Composing transformations ---------------------------------------------- -It is common to require several transformations to be performed consecutively. - -.. autoclass:: Compose -.. autoclass:: OneOf -.. autoclass:: RandomApply diff --git a/v0.4.0/_sources/utils.rst.txt b/v0.4.0/_sources/utils.rst.txt deleted file mode 100644 index 69c1abe0eb..0000000000 --- a/v0.4.0/_sources/utils.rst.txt +++ /dev/null @@ -1,36 +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 - - -.. _metrics: - -Task evaluation ---------------- -Implementations of task-specific metrics to easily assess your model performances. - -.. currentmodule:: doctr.utils.metrics - -.. autoclass:: TextMatch - - .. automethod:: summary - -.. autoclass:: LocalizationConfusion - - .. automethod:: summary - -.. autoclass:: OCRMetric - - .. automethod:: summary diff --git a/v0.4.0/_static/basic.css b/v0.4.0/_static/basic.css index f316efcb47..7ebbd6d07b 100644 --- a/v0.4.0/_static/basic.css +++ b/v0.4.0/_static/basic.css @@ -1,12 +1,5 @@ /* - * basic.css - * ~~~~~~~~~ - * * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ /* -- main layout ----------------------------------------------------------- */ @@ -115,15 +108,11 @@ img { /* -- search page ----------------------------------------------------------- */ ul.search { - margin: 10px 0 0 20px; - padding: 0; + margin-top: 10px; } ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; + padding: 5px 0; } ul.search li a { diff --git a/v0.4.0/_static/doctools.js b/v0.4.0/_static/doctools.js index 4d67807d17..0398ebb9f0 100644 --- a/v0.4.0/_static/doctools.js +++ b/v0.4.0/_static/doctools.js @@ -1,12 +1,5 @@ /* - * doctools.js - * ~~~~~~~~~~~ - * * Base JavaScript utilities for all Sphinx HTML documentation. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; diff --git a/v0.4.0/_static/documentation_options.js b/v0.4.0/_static/documentation_options.js index a7b5cbe04a..4f656fdbea 100644 --- a/v0.4.0/_static/documentation_options.js +++ b/v0.4.0/_static/documentation_options.js @@ -1,5 +1,5 @@ const DOCUMENTATION_OPTIONS = { - VERSION: '0.3.0a0-git', + VERSION: '0.10.1a0-git', LANGUAGE: 'en', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/v0.4.0/_static/language_data.js b/v0.4.0/_static/language_data.js index 367b8ed81b..c7fe6c6faf 100644 --- a/v0.4.0/_static/language_data.js +++ b/v0.4.0/_static/language_data.js @@ -1,13 +1,6 @@ /* - * language_data.js - * ~~~~~~~~~~~~~~~~ - * * This script contains the language-specific data used by searchtools.js, * namely the list of stopwords, stemmer, scorer and splitter. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; diff --git a/v0.4.0/_static/searchtools.js b/v0.4.0/_static/searchtools.js index b08d58c9b9..2c774d17af 100644 --- a/v0.4.0/_static/searchtools.js +++ b/v0.4.0/_static/searchtools.js @@ -1,12 +1,5 @@ /* - * searchtools.js - * ~~~~~~~~~~~~~~~~ - * * Sphinx JavaScript utilities for the full-text search. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; @@ -20,7 +13,7 @@ if (typeof Scorer === "undefined") { // and returns the new score. /* score: result => { - const [docname, title, anchor, descr, score, filename] = result + const [docname, title, anchor, descr, score, filename, kind] = result return score }, */ @@ -47,6 +40,14 @@ if (typeof Scorer === "undefined") { }; } +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + const _removeChildren = (element) => { while (element && element.lastChild) element.removeChild(element.lastChild); }; @@ -64,9 +65,13 @@ const _displayItem = (item, searchTerms, highlightTerms) => { const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; const contentRoot = document.documentElement.dataset.content_root; - const [docName, title, anchor, descr, score, _filename] = item; + const [docName, title, anchor, descr, score, _filename, kind] = item; let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); let requestUrl; let linkUrl; if (docBuilder === "dirhtml") { @@ -115,8 +120,10 @@ const _finishSearch = (resultCount) => { "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." ); else - Search.status.innerText = _( - "Search finished, found ${resultCount} page(s) matching the search query." + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, ).replace('${resultCount}', resultCount); }; const _displayNextItem = ( @@ -138,7 +145,7 @@ const _displayNextItem = ( else _finishSearch(resultCount); }; // Helper function used by query() to order search results. -// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. // Order the results by score (in opposite order of appearance, since the // `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. const _orderResultsByScoreThenName = (a, b) => { @@ -248,6 +255,7 @@ const Search = { searchSummary.classList.add("search-summary"); searchSummary.innerText = ""; const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); searchList.classList.add("search"); const out = document.getElementById("search-results"); @@ -318,7 +326,7 @@ const Search = { const indexEntries = Search._index.indexentries; // Collect multiple result groups to be sorted separately and then ordered. - // Each is an array of [docname, title, anchor, descr, score, filename]. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. const normalResults = []; const nonMainIndexResults = []; @@ -337,6 +345,7 @@ const Search = { null, score + boost, filenames[file], + SearchResultKind.title, ]); } } @@ -354,6 +363,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.index, ]; if (isMain) { normalResults.push(result); @@ -475,6 +485,7 @@ const Search = { descr, score, filenames[match[0]], + SearchResultKind.object, ]); }; Object.keys(objects).forEach((prefix) => @@ -585,6 +596,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.text, ]); } return results; diff --git a/v0.4.0/changelog.html b/v0.4.0/changelog.html index eafac3a877..fc45a50384 100644 --- a/v0.4.0/changelog.html +++ b/v0.4.0/changelog.html @@ -12,9 +12,9 @@ gtag('js', new Date()); gtag('config', 'G-40DVRMX8T4'); - + - + Changelog - docTR documentation @@ -226,20 +226,42 @@ + diff --git a/v0.4.0/community/resources.html b/v0.4.0/community/resources.html index 2564037893..9a1988258c 100644 --- a/v0.4.0/community/resources.html +++ b/v0.4.0/community/resources.html @@ -14,7 +14,7 @@ - + Community resources - docTR documentation @@ -389,7 +389,7 @@

    Community resources - + diff --git a/v0.4.0/contributing/code_of_conduct.html b/v0.4.0/contributing/code_of_conduct.html index 5ea4a1f99d..03422dbb4d 100644 --- a/v0.4.0/contributing/code_of_conduct.html +++ b/v0.4.0/contributing/code_of_conduct.html @@ -14,7 +14,7 @@ - + Contributor Covenant Code of Conduct - docTR documentation @@ -504,7 +504,7 @@

    Attribution - + diff --git a/v0.4.0/contributing/contributing.html b/v0.4.0/contributing/contributing.html index e5a85682c6..05e2b3641b 100644 --- a/v0.4.0/contributing/contributing.html +++ b/v0.4.0/contributing/contributing.html @@ -14,7 +14,7 @@ - + Contributing to docTR - docTR documentation @@ -481,7 +481,7 @@

    Let’s connect - + diff --git a/v0.4.0/datasets.html b/v0.4.0/datasets.html deleted file mode 100644 index 193e576c57..0000000000 --- a/v0.4.0/datasets.html +++ /dev/null @@ -1,578 +0,0 @@ - - - - - - - - - - - - - doctr.datasets - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.datasets

    -

    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.

    -
    -

    Available Datasets

    -

    The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL.

    -
    -
    -class doctr.datasets.datasets.VisionDataset(url: str, file_name: str | None = None, file_hash: str | None = None, extract_archive: bool = False, download: bool = False, overwrite: bool = False)[source]
    -
    - -

    Here are all datasets that are available through DocTR:

    -
    -
    -class doctr.datasets.FUNSD(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    FUNSD dataset from “FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents”.

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD
    ->>> train_set = FUNSD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.SROIE(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    SROIE dataset from “ICDAR2019 Competition on Scanned Receipt OCR and Information Extraction”.

    -
    -
    Example::
    >>> from doctr.datasets import SROIE
    ->>> train_set = SROIE(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.CORD(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    CORD dataset from “CORD: A Consolidated Receipt Dataset forPost-OCR Parsing”.

    -
    -
    Example::
    >>> from doctr.datasets import CORD
    ->>> train_set = CORD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.OCRDataset(img_folder: str, label_file: str, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    Implements an OCR dataset

    -
    -
    Parameters:
    -
      -
    • img_folder – local path to image folder (all jpg at the root)

    • -
    • label_file – local path to the label file

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -

    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.

    -
    -
    -class doctr.datasets.loader.DataLoader(dataset, shuffle: bool = True, batch_size: int = 1, drop_last: bool = False, workers: int | None = None)[source]
    -

    Implements a dataset wrapper for fast data loading

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD, 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)
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • dataset – the dataset

    • -
    • shuffle – whether the samples should be shuffled before passing it to the iterator

    • -
    • batch_size – number of elements in each batch

    • -
    • drop_last – if True, drops the last batch if it isn’t full

    • -
    • workers – number of workers to use for data loading

    • -
    -
    -
    -
    - -
    -
    -

    Supported Vocabs

    -

    Since textual content has to be encoded properly for models to interpret them efficiently, DocTR supports multiple sets -of vocabs.

    -
    - - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    DocTR Vocabs

    Name

    size

    characters

    digits

    10

    0123456789

    ascii_letters

    52

    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

    punctuation

    32

    !”#$%&'()*+,-./:;<=>?@[\]^_`{|}~

    currency

    5

    £€¥¢฿

    latin

    96

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°

    french

    154

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿

    -
    -
    -
    -doctr.datasets.encode_sequences(sequences: List[str], vocab: str, target_size: int | None = None, eos: int = -1, sos: int | None = None, pad: int | None = None, **kwargs: Any) ndarray[source]
    -

    Encode character sequences using a given vocab as mapping

    -
    -
    Parameters:
    -
      -
    • sequences – the list of character sequences of size N

    • -
    • vocab – the ordered vocab to use for encoding

    • -
    • target_size – maximum length of the encoded data

    • -
    • eos – encoding of End Of String

    • -
    • sos – optional encoding of Start Of String

    • -
    • pad – optional encoding for padding. In case of padding, all sequences are followed by 1 EOS then PAD

    • -
    -
    -
    Returns:
    -

    the padded encoded data as a tensor

    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.0/documents.html b/v0.4.0/documents.html deleted file mode 100644 index 98cbb2c5ef..0000000000 --- a/v0.4.0/documents.html +++ /dev/null @@ -1,772 +0,0 @@ - - - - - - - - - - - - - doctr.documents - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.documents

    -

    The documents module enables users to easily access content from documents and export analysis -results to structured formats.

    -
    -

    Document structure

    -

    Structural organization of the documents.

    -
    -

    Word

    -

    A Word is an uninterrupted sequence of characters.

    -
    -
    -class doctr.documents.Word(value: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float])[source]
    -

    Implements a word element

    -
    -
    Parameters:
    -
      -
    • value – the text string of the word

    • -
    • confidence – the confidence associated with the text prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to

    • -
    • size (the page's)

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Line(words: List[Word], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float] | None = None)[source]
    -

    Implements a line element as a collection of words

    -
    -
    Parameters:
    -
      -
    • words – list of word elements

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all words in it.

    • -
    -
    -
    -
    - -
    -
    -

    Artefact

    -

    An Artefact is a non-textual element (e.g. QR code, picture, chart, signature, logo, etc.).

    -
    -
    -class doctr.documents.Artefact(artefact_type: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]])[source]
    -

    Implements a non-textual element

    -
    -
    Parameters:
    -
      -
    • artefact_type – the type of artefact

    • -
    • confidence – the confidence of the type prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size.

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Block(lines: List[Line] = [], artefacts: List[Artefact] = [], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float] | None = None)[source]
    -

    Implements a block element as a collection of lines and artefacts

    -
    -
    Parameters:
    -
      -
    • lines – list of line elements

    • -
    • artefacts – list of artefacts

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all lines and artefacts in it.

    • -
    -
    -
    -
    - -
    -
    -

    Page

    -

    A Page is a collection of Blocks that were on the same physical page.

    -
    -
    -class doctr.documents.Page(blocks: List[Block], page_idx: int, dimensions: Tuple[int, int], orientation: Dict[str, Any] | None = None, language: Dict[str, Any] | None = None)[source]
    -

    Implements a page element as a collection of blocks

    -
    -
    Parameters:
    -
      -
    • blocks – list of block elements

    • -
    • page_idx – the index of the page in the input raw document

    • -
    • dimensions – the page size in pixels in format (width, height)

    • -
    • orientation – a dictionary with the value of the rotation angle in degress and confidence of the prediction

    • -
    • language – a dictionary with the language value and confidence of the prediction

    • -
    -
    -
    -
    -
    -show(page: ndarray, interactive: bool = True, **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -
      -
    • page – image encoded as a numpy array in uint8

    • -
    • interactive – whether the display should be interactive

    • -
    -
    -
    -
    - -
    - -
    -
    -

    Document

    -

    A Document is a collection of Pages.

    -
    -
    -class doctr.documents.Document(pages: List[Page])[source]
    -

    Implements a document element as a collection of pages

    -
    -
    Parameters:
    -

    pages – list of page elements

    -
    -
    -
    -
    -show(pages: List[ndarray], **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -

    pages – list of images encoded as numpy arrays in uint8

    -
    -
    -
    - -
    - -
    -
    -
    -

    File reading

    -

    High-performance file reading and conversion to processable structured data.

    -
    -
    -doctr.documents.read_pdf(file: str | Path | bytes, **kwargs: Any) Document[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_pdf
    ->>> doc = read_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_img(file: str | Path | bytes, output_size: Tuple[int, int] | None = None, rgb_output: bool = True) ndarray[source]
    -

    Read an image file into numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_img
    ->>> page = read_img("path/to/your/doc.jpg")
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • file – the path to the image file

    • -
    • output_size – the expected output size of each page in format H x W

    • -
    • rgb_output – whether the output ndarray channel order should be RGB instead of BGR.

    • -
    -
    -
    Returns:
    -

    the page decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_html(url: str, **kwargs: Any) bytes[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_html
    ->>> doc = read_html("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – URL of the target web page

    -
    -
    Returns:
    -

    decoded PDF file as a bytes stream

    -
    -
    -
    - -
    -
    -class doctr.documents.DocumentFile[source]
    -

    Read a document from multiple extensions

    -
    -
    -classmethod from_pdf(file: str | Path | bytes, **kwargs) PDF[source]
    -

    Read a PDF file

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file or a binary stream

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_url(url: str, **kwargs) PDF[source]
    -

    Interpret a web page as a PDF document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_url("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – the URL of the target web page

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_images(files: Sequence[str | Path | bytes] | str | Path | bytes, **kwargs) List[ndarray][source]
    -

    Read an image file (or a collection of image files) and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"])
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    files – the path to the image file or a binary stream, or a collection of those

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    - -
    -
    -class doctr.documents.PDF(doc: Document)[source]
    -

    PDF document template

    -
    -
    Parameters:
    -

    doc – input PDF document

    -
    -
    -
    -
    -as_images(**kwargs) List[ndarray][source]
    -

    Convert all document pages to images

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of convert_page_to_numpy

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -get_words(**kwargs) List[List[Tuple[Tuple[float, float, float, float], str]]][source]
    -

    Get the annotations for all words in the document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of fitz.Page.getTextWords

    -
    -
    Returns:
    -

    the list of pages annotations, represented as a list of tuple (bounding box, value)

    -
    -
    -
    - -
    -
    -get_artefacts() List[List[Tuple[float, float, float, float]]][source]
    -

    Get the artefacts for the entire document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts()
    -
    -
    -
    -
    -
    -
    Returns:
    -

    the list of pages artefacts, represented as a list of bounding boxes

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.0/genindex.html b/v0.4.0/genindex.html index a19b433943..21520455b4 100644 --- a/v0.4.0/genindex.html +++ b/v0.4.0/genindex.html @@ -13,7 +13,7 @@ - Index - docTR documentation + Index - docTR documentation @@ -224,20 +224,42 @@

    +
    +

    U

    + + +
    +
    +

    V

    @@ -561,7 +711,13 @@

    V

    W

    +
    @@ -599,8 +755,8 @@

    W

    - - + + diff --git a/v0.4.0/getting_started/installing.html b/v0.4.0/getting_started/installing.html index a488e9a030..af3b58193e 100644 --- a/v0.4.0/getting_started/installing.html +++ b/v0.4.0/getting_started/installing.html @@ -14,7 +14,7 @@ - + Installation - docTR documentation @@ -305,7 +305,7 @@

    Installation

    -

    This library requires Python 3.9 or higher.

    +

    This library requires Python 3.10 or higher.

    Prerequisites

    Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so:

    @@ -435,7 +435,7 @@

    Via Git - + diff --git a/v0.4.0/index.html b/v0.4.0/index.html index 4c6a28c66a..3a06afc6d9 100644 --- a/v0.4.0/index.html +++ b/v0.4.0/index.html @@ -12,9 +12,9 @@ gtag('js', new Date()); gtag('config', 'G-40DVRMX8T4'); - + - + docTR documentation @@ -226,20 +226,42 @@
    -

    DocTR: Document Text Recognition

    -

    State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 (PyTorch now in beta)

    +

    docTR: Document Text Recognition

    +

    State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 & PyTorch

    https://github.com/mindee/doctr/releases/download/v0.2.0/ocr.png

    DocTR provides an easy and powerful way to extract valuable information from your documents:

      -
    • 🧾 for automation: seemlessly process documents for Natural Language Understanding tasks: we provide OCR predictors to parse textual information (localize and identify each word) from your documents.

    • +
    • 🧾 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.

    • 👩‍🔬 for research: quickly compare your own architectures speed & performances with state-of-art models on public datasets.

    -

    Welcome to the documentation of DocTR!

    Main Features

    • 🤖 Robust 2-stage (detection + recognition) OCR predictors with pretrained parameters

    • ⚡ User-friendly, 3 lines of code to load a document and extract text with a predictor

    • -
    • 🚀 State-of-the-art performances on public document datasets, comparable with GoogleVision/AWS Textract

    • +
    • 🚀 State-of-the-art performance on public document datasets, comparable with GoogleVision/AWS Textract

    • ⚡ Optimized for inference speed on both CPU & GPU

    • -
    • 🐦 Light package, small dependencies

    • -
    • 🛠️ Daily maintained

    • -
    • 🏭 Easy integration

    • +
    • 🐦 Light package, minimal dependencies

    • +
    • 🛠️ Actively maintained by Mindee

    • +
    • 🏭 Easy integration (available templates for browser demo & API deployment)

    -
    -
    -

    Getting Started

    -
    -

    Build & train your predictor

    -
      -
    • Compose your own end-to-end OCR predictor: mix and match detection & recognition predictors (all-pretrained)

    • -
    • Fine-tune or train from scratch any detection or recognition model to specialize on your data

    • -
    -

    Model zoo

    Text detection models

    -
    -

    Text recognition models

    -
    -

    Supported datasets

    -
    -
    +
    +
    +
    +
    +
    @@ -406,7 +381,7 @@

    Supported datasets - +
    Next @@ -446,10 +421,8 @@

    Supported datasets + diff --git a/v0.4.0/installing.html b/v0.4.0/installing.html deleted file mode 100644 index b61c60134b..0000000000 --- a/v0.4.0/installing.html +++ /dev/null @@ -1,395 +0,0 @@ - - - - - - - - - - - - - Installation - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    Installation

    -

    This library requires Python 3.6 or higher.

    -
    -

    Prerequisites

    -

    Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so:

    - -

    If you are running another OS than Linux, you will need a few extra dependencies.

    -

    For MacOS users, you can install them as follows:

    -
    brew install cairo pango gdk-pixbuf libffi
    -
    -
    -

    For Windows users, those dependencies are included in GTK. You can find the latest installer over here.

    -
    -
    -

    Via Python Package

    -

    Install the last stable release of the package using pip:

    -
    pip install python-doctr
    -
    -
    -
    -
    -

    Via Git

    -

    Install the library in developper mode:

    -
    git clone https://github.com/mindee/doctr.git
    -pip install -e doctr/.
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.0/models.html b/v0.4.0/models.html deleted file mode 100644 index b5cd44c9fa..0000000000 --- a/v0.4.0/models.html +++ /dev/null @@ -1,1002 +0,0 @@ - - - - - - - - - - - - - doctr.models - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.models

    -

    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 TensorFlow model.

    • -
    • Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable.

    • -
    -
    -

    Text Detection

    -

    Localizing text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Input shape

    # params

    Recall

    Precision

    Recall

    Precision

    FPS

    db_resnet50

    (1024, 1024, 3)

    25.2 M

    82.14

    87.64

    92.49

    89.66

    2.1

    -
    -

    All text detection models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for detection

    -

    In DocTR, the pre-processing scheme for detection is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) with potential deformation.

    2. -
    3. batch images together

    4. -
    5. normalize the batch using the training data statistics

    6. -
    -
    -
    -

    Detection models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.detection.db_resnet50(pretrained: bool = False, **kwargs: Any) DBNet[source]
    -

    DBNet as described in “Real-time Scene Text Detection with Differentiable Binarization”, using a ResNet-50 backbone.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -doctr.models.detection.linknet16(pretrained: bool = False, **kwargs: Any) LinkNet[source]
    -

    LinkNet as described in “LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import linknet16
    ->>> model = linknet16(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -

    Detection predictors

    -

    Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information.

    -
    -
    -doctr.models.detection.detection_predictor(arch: str = 'db_resnet50', pretrained: bool = False, **kwargs: Any) DetectionPredictor[source]
    -

    Text detection architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import detection_predictor
    ->>> model = detection_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_resnet50’)

    • -
    • pretrained – If True, returns a model pre-trained on our text detection dataset

    • -
    -
    -
    Returns:
    -

    Detection predictor

    -
    -
    -
    - -
    -
    -
    -

    Text Recognition

    -

    Identifying strings in images

    -
    - - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Text recognition model zoo

    Architecture

    Input shape

    # params

    FUNSD

    CORD

    FPS

    crnn_vgg16_bn

    (32, 128, 3)

    15.8M

    86.02

    91.3

    12.8

    sar_vgg16_bn

    (32, 128, 3)

    21.5M

    86.2

    91.7

    3.3

    sar_resnet31

    (32, 128, 3)

    53.1M

    86.3

    92.1

    2.7

    -
    -

    All text recognition models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All these recognition models are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for recognition

    -

    In DocTR, the pre-processing scheme for recognition is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) without deformation.

    2. -
    3. pad the image to the target size (with zeros by default)

    4. -
    5. batch images together

    6. -
    7. normalize the batch using the training data statistics

    8. -
    -
    -
    -

    Recognition models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.recognition.crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) CRNN[source]
    -

    CRNN with a VGG-16 backbone as described in “An End-to-End Trainable Neural Network for Image-based -Sequence Recognition and Its Application to Scene Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import crnn_vgg16_bn
    ->>> model = crnn_vgg16_bn(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a VGG16 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_vgg16_bn
    ->>> model = sar_vgg16_bn(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_resnet31(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a resnet-31 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -

    Example

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_resnet31
    ->>> model = sar_resnet31(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.master(pretrained: bool = False, **kwargs: Any) MASTER[source]
    -

    MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_. -Example:

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import master
    ->>> model = master(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 48, 160, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -

    Recognition predictors

    -

    Combining the right components around a given architecture for easier usage.

    -
    -
    -doctr.models.recognition.recognition_predictor(arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) RecognitionPredictor[source]
    -

    Text recognition architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import recognition_predictor
    ->>> model = recognition_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(32, 128, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘crnn_vgg16_bn’, ‘crnn_resnet31’, ‘sar_vgg16_bn’, ‘sar_resnet31’)

    • -
    • pretrained – If True, returns a model pre-trained on our text recognition dataset

    • -
    -
    -
    Returns:
    -

    Recognition predictor

    -
    -
    -
    - -
    -
    -
    -

    End-to-End OCR

    -

    Predictors that localize and identify text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Recall

    Precision

    FPS

    Recall

    Precision

    FPS

    db_resnet50 + crnn_vgg16_bn

    70.08

    74.77

    0.85

    82.19

    79.67

    1.6

    db_resnet50 + sar_vgg16_bn

    N/A

    N/A

    0.49

    N/A

    N/A

    1.0

    db_resnet50 + sar_resnet31

    N/A

    N/A

    0.27

    N/A

    N/A

    0.83

    Gvision text detection

    59.50

    62.50

    75.30

    70.00

    Gvision doc. text detection

    64.00

    53.30

    68.90

    61.10

    AWS textract

    78.10

    83.00

    87.50

    66.00

    -
    -

    All OCR models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All recognition models of predictors are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -

    Results on private ocr datasets

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Receipts

    Invoices

    IDs

    Architecture

    Recall

    Precision

    Recall

    Precision

    Recall

    Precision

    db_resnet50 + crnn_vgg16_bn (ours)

    78.90

    81.01

    65.68

    69.86

    49.48

    50.46

    Gvision doc. text detection

    68.91

    59.89

    63.20

    52.85

    43.70

    29.21

    AWS textract

    75.77

    77.70

    70.47

    69.13

    46.39

    43.32

    -
    -
    -

    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.

    -
    -
    -doctr.models.zoo.ocr_predictor(det_arch: str = 'db_resnet50', reco_arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) OCRPredictor[source]
    -

    End-to-end OCR architecture using one model for localization, and another for text recognition.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import ocr_predictor
    ->>> model = ocr_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_sar_vgg’, ‘db_sar_resnet’, ‘db_crnn_vgg’, ‘db_crnn_resnet’)

    • -
    • pretrained – If True, returns a model pre-trained on our OCR dataset

    • -
    -
    -
    Returns:
    -

    OCR predictor

    -
    -
    -
    - -
    -
    -
    -

    Model export

    -

    Utility functions to make the most of document analysis models.

    -
    -

    Model compression

    -
    -
    -doctr.models.export.convert_to_tflite(tf_model: Model) bytes[source]
    -

    Converts a model to TFLite format

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_tflite, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_tflite(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.convert_to_fp16(tf_model: Model) bytes[source]
    -

    Converts a model to half precision

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_fp16, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_fp16(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the serialized FP16 model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) bytes[source]
    -

    Quantize a Tensorflow model

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import quantize_model, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = quantize_model(model, (224, 224, 3))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • tf_model – a keras model

    • -
    • input_shape – shape of the expected input tensor (excluding batch dimension) with channel last order

    • -
    -
    -
    Returns:
    -

    the serialized quantized model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -

    Using SavedModel

    -

    Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -SavedModel format as follows:

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> _ = model(input_t, training=False)
    ->>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/')
    -
    -
    -

    And loaded just as easily:

    -
    >>> import tensorflow as tf
    ->>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/')
    -
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.0/modules/contrib.html b/v0.4.0/modules/contrib.html index e99f6b3f74..7fb86b8b38 100644 --- a/v0.4.0/modules/contrib.html +++ b/v0.4.0/modules/contrib.html @@ -14,7 +14,7 @@ - + doctr.contrib - docTR documentation @@ -380,7 +380,7 @@

    Supported contribution modules - + diff --git a/v0.4.0/modules/datasets.html b/v0.4.0/modules/datasets.html index 456e10b172..380a986793 100644 --- a/v0.4.0/modules/datasets.html +++ b/v0.4.0/modules/datasets.html @@ -14,7 +14,7 @@ - + doctr.datasets - docTR documentation @@ -1081,7 +1081,7 @@

    Returns:

    - + diff --git a/v0.4.0/modules/io.html b/v0.4.0/modules/io.html index 01eadaa4b8..24c41954be 100644 --- a/v0.4.0/modules/io.html +++ b/v0.4.0/modules/io.html @@ -14,7 +14,7 @@ - + doctr.io - docTR documentation @@ -760,7 +760,7 @@

    Returns: - + diff --git a/v0.4.0/modules/models.html b/v0.4.0/modules/models.html index c465cc0586..91b8810a6a 100644 --- a/v0.4.0/modules/models.html +++ b/v0.4.0/modules/models.html @@ -14,7 +14,7 @@ - + doctr.models - docTR documentation @@ -1612,7 +1612,7 @@

    Args: - + diff --git a/v0.4.0/modules/transforms.html b/v0.4.0/modules/transforms.html index 30f7a2631a..c5ead3f3ce 100644 --- a/v0.4.0/modules/transforms.html +++ b/v0.4.0/modules/transforms.html @@ -14,7 +14,7 @@ - + doctr.transforms - docTR documentation @@ -835,7 +835,7 @@

    Args:< - + diff --git a/v0.4.0/modules/utils.html b/v0.4.0/modules/utils.html index 888a32c321..b7f6fc570b 100644 --- a/v0.4.0/modules/utils.html +++ b/v0.4.0/modules/utils.html @@ -14,7 +14,7 @@ - + doctr.utils - docTR documentation @@ -715,7 +715,7 @@

    Args: - + diff --git a/v0.4.0/notebooks.html b/v0.4.0/notebooks.html index f97771aebb..d36539f59e 100644 --- a/v0.4.0/notebooks.html +++ b/v0.4.0/notebooks.html @@ -14,7 +14,7 @@ - + docTR Notebooks - docTR documentation @@ -391,7 +391,7 @@

    docTR Notebooks - + diff --git a/v0.4.0/objects.inv b/v0.4.0/objects.inv index a22d2ce821026792e7b2f75cbac9aecf06cc0d89..c1700f291b7b6bc7252a625eb1acde3a63b67eca 100644 GIT binary patch delta 4090 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 delta 1868 zcmV-S2ebIcAgT|LI|DN=Fp)nzf2CSYZ`(K!z4KQH*kG}Hpb@8NQRLRfO*V_9$=YuA zXwVYv2$4jUq@1R|zWk6VlKNIpp``cTkeuOgNGBp8r;7-2#4u-ztB3U}$lor*1ThvY z1M}gTr^z2@0R)jtxc>HaGmY_ZwO;A=B-&>EaQkHvBP2BP1_XUt)H3{re~@WB#VjV- zoZg!T#~CB^kdW6dwV^(C^rm4FXCaC3j^XcxXksQU9EvRDf;6Vf0?Q)bzeAnV@P<}G zP=x71_VrFRCrus+X=~IBb;jZ}G#Mo^_Je9jP{WND35yhG;{7Me@d1TyNSLqwu*`?g z2?<{&a#m$)CT+o<$*bh1f0#d$Xd3xCPVh{-lDarhlJ4RZ9d$6y?Sj_Hqr>lu6f~JC zau;V)C*g5*J)N;YZ01}^@)7eLDx-3?z^h20)5)UCQ%5T(vjX#f(ZPwfaDuzmENlpL zuJayUZ&446YC?};d}I4?+)uJe|%GqVifHp!QTLf zgGEu}^f;4Qrl{#mxmC92-0_ZAyiRo|BaQHsfLB_nC@Ki`fZNzT;$f$bVOXACj2k85-XU zl=1i{-l^A91Sk&4e=&>IJcKmMcBkWU`C+%u=8B|x+F}gRGQ{33e@A1^ zl>8(_)}Ipx!70kvfzVVODM&)-tqcuWt=qem1?r=xIbnD*?+&H=2yLquh|e+pRWcJ1 zkCT}X8GblSX01^ck@QoZCvP*kpJ{x2<{4&eroa`+#5=}kf6{MInxdK9e+oR4`EQPo z{}sYheD1t$5HIMwAX#HJCqlO5hN9`+73-}?Fk9@!iL7&N!KT>Ix*Rg)1@ssTnldEZ z8uMf1ZDNlR>%yUtOUBKUCXF-EpLWLBYgHI$yd_ikkw(3wJj#^jj5FY;?=c2S zPGz9rv8bHHd7s9iSob89i%<c7_oe3s#6wq;JmU!U(a4tM$EVPV9u? z%{9EmKlnZ3;qyephBwvlDQ1P4H}iydd3m6>%3V*Tf4X7{7>_O!v=?U<*mBCP?o9wX zp;*Ag(X_f#^_b4>t3gA9{$vEon_UL>>i(p^1L}?i?29;wf$tPIhqf5m4zZj>hZeY3^uXK( zlqWOSqJ3I3NLV6Q9@Ww^ZXKm(p;s*u-cJ#|Gb)jF^f&Hvdc(dX8?1Y>?HP#Tm8j-! z&>vr#Y@wZ0<8rNJqG=jaGA_+{0xmLJV4dbWe;2DeWGH#i=B-G$U0(*~c2uU!U|j=% zG1;F_Dghf$i%@LRc*rHXXEl7)PynubtEhLB0xu8%&P0+TQ2Yro~2f=5J$*@C6wV@5Y_sON4zSSI3 zDpB_qrR4u4q^D-rh<7Fe0B^oM0Ff3UNkgtmlf{syZLnscuz-!foTJ=E_%`I`n1 zvmo{W63V#{;UV&1P;Vujli{+UV8NR8*dy6yuQOQ=ShRVMy|U6O#ovpV#k?7%{?d80 z!N8j4f%$)Y6Xk2>y20+_&|YR?s~v6KpgW3Vwt8FD!mnFp0K*8d_ue*|J2o z?K;3K0?Qoh#ZKTCjDWuqxp|A^e~EIDP*C`cdlr{LLkqoakpP9tOAMWGr1y(XO@)LO z@|&V=r!b_=gb9mIRfOCz}Q~EsFI53>6&YH - - - - - - - - - - Python Module Index - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    - -
    -

    Python Module Index

    - -
    - - - - - - - - - - - -
     
    d
    - doctr -
    - -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.4.0/search.html b/v0.4.0/search.html index 73772822d2..d050f5eac7 100644 --- a/v0.4.0/search.html +++ b/v0.4.0/search.html @@ -14,7 +14,7 @@ - + Search - docTR documentation @@ -226,20 +226,42 @@ - - + + diff --git a/v0.4.0/searchindex.js b/v0.4.0/searchindex.js index 803f4f4bcf..6f154115ab 100644 --- a/v0.4.0/searchindex.js +++ b/v0.4.0/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles": {"Artefact": [[2, "artefact"]], "Available Datasets": [[1, "available-datasets"]], "Block": [[2, "block"]], "Build & train your predictor": [[3, "build-train-your-predictor"]], "Changelog": [[0, null]], "Composing transformations": [[6, "composing-transformations"]], "Data Loading": [[1, "data-loading"]], "Detection models": [[5, "detection-models"]], "Detection predictors": [[5, "detection-predictors"]], "DocTR Vocabs": [[1, "id1"]], "DocTR: Document Text Recognition": [[3, null]], "Document": [[2, "document"]], "Document structure": [[2, "document-structure"]], "End-to-End OCR": [[5, "end-to-end-ocr"]], "File reading": [[2, "file-reading"]], "Getting Started": [[3, "getting-started"]], "Installation": [[4, null]], "Line": [[2, "line"]], "Main Features": [[3, "main-features"]], "Model compression": [[5, "model-compression"]], "Model export": [[5, "model-export"]], "Model zoo": [[3, "model-zoo"]], "Notes": [[3, null]], "Package Reference": [[3, null]], "Page": [[2, "page"]], "Pre-processing for detection": [[5, "pre-processing-for-detection"]], "Pre-processing for recognition": [[5, "pre-processing-for-recognition"]], "Prerequisites": [[4, "prerequisites"]], "Recognition models": [[5, "recognition-models"]], "Recognition predictors": [[5, "recognition-predictors"]], "Supported Vocabs": [[1, "supported-vocabs"]], "Supported datasets": [[3, "supported-datasets"]], "Supported transformations": [[6, "supported-transformations"]], "Task evaluation": [[7, "task-evaluation"]], "Text Detection": [[5, "text-detection"]], "Text Recognition": [[5, "text-recognition"]], "Text detection models": [[3, "text-detection-models"]], "Text recognition model zoo": [[5, "id2"]], "Text recognition models": [[3, "text-recognition-models"]], "Two-stage approaches": [[5, "two-stage-approaches"]], "Using SavedModel": [[5, "using-savedmodel"]], "Via Git": [[4, "via-git"]], "Via Python Package": [[4, "via-python-package"]], "Visualization": [[7, "visualization"]], "Word": [[2, "word"]], "doctr.datasets": [[1, null]], "doctr.documents": [[2, null]], "doctr.models": [[5, null]], "doctr.transforms": [[6, null]], "doctr.utils": [[7, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]]}, "docnames": ["changelog", "datasets", "documents", "index", "installing", "models", "transforms", "utils"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "datasets.rst", "documents.rst", "index.rst", "installing.rst", "models.rst", "transforms.rst", "utils.rst"], "indexentries": {"artefact (class in doctr.documents)": [[2, "doctr.documents.Artefact", false]], "as_images() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.as_images", false]], "block (class in doctr.documents)": [[2, "doctr.documents.Block", false]], "colorinversion (class in doctr.transforms)": [[6, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[6, "doctr.transforms.Compose", false]], "convert_to_fp16() (in module doctr.models.export)": [[5, "doctr.models.export.convert_to_fp16", false]], "convert_to_tflite() (in module doctr.models.export)": [[5, "doctr.models.export.convert_to_tflite", false]], "cord (class in doctr.datasets)": [[1, "doctr.datasets.CORD", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.crnn_vgg16_bn", false]], "dataloader (class in doctr.datasets.loader)": [[1, "doctr.datasets.loader.DataLoader", false]], "db_resnet50() (in module doctr.models.detection)": [[5, "doctr.models.detection.db_resnet50", false]], "detection_predictor() (in module doctr.models.detection)": [[5, "doctr.models.detection.detection_predictor", false]], "document (class in doctr.documents)": [[2, "doctr.documents.Document", false]], "documentfile (class in doctr.documents)": [[2, "doctr.documents.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[1, "doctr.datasets.encode_sequences", false]], "from_images() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_images", false]], "from_pdf() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_pdf", false]], "from_url() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[1, "doctr.datasets.FUNSD", false]], "get_artefacts() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.get_artefacts", false]], "get_words() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.get_words", false]], "lambdatransformation (class in doctr.transforms)": [[6, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.documents)": [[2, "doctr.documents.Line", false]], "linknet16() (in module doctr.models.detection)": [[5, "doctr.models.detection.linknet16", false]], "localizationconfusion (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.LocalizationConfusion", false]], "master() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.master", false]], "normalize (class in doctr.transforms)": [[6, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models.zoo)": [[5, "doctr.models.zoo.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[1, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[6, "doctr.transforms.OneOf", false]], "page (class in doctr.documents)": [[2, "doctr.documents.Page", false]], "pdf (class in doctr.documents)": [[2, "doctr.documents.PDF", false]], "quantize_model() (in module doctr.models.export)": [[5, "doctr.models.export.quantize_model", false]], "randomapply (class in doctr.transforms)": [[6, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[6, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[6, "doctr.transforms.RandomContrast", false]], "randomgamma (class in doctr.transforms)": [[6, "doctr.transforms.RandomGamma", false]], "randomhue (class in doctr.transforms)": [[6, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[6, "doctr.transforms.RandomJpegQuality", false]], "randomsaturation (class in doctr.transforms)": [[6, "doctr.transforms.RandomSaturation", false]], "read_html() (in module doctr.documents)": [[2, "doctr.documents.read_html", false]], "read_img() (in module doctr.documents)": [[2, "doctr.documents.read_img", false]], "read_pdf() (in module doctr.documents)": [[2, "doctr.documents.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.recognition_predictor", false]], "resize (class in doctr.transforms)": [[6, "doctr.transforms.Resize", false]], "sar_resnet31() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.sar_resnet31", false]], "sar_vgg16_bn() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.sar_vgg16_bn", false]], "show() (doctr.documents.document method)": [[2, "doctr.documents.Document.show", false]], "show() (doctr.documents.page method)": [[2, "doctr.documents.Page.show", false]], "sroie (class in doctr.datasets)": [[1, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[7, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[7, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[7, "doctr.utils.metrics.TextMatch.summary", false]], "textmatch (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.TextMatch", false]], "togray (class in doctr.transforms)": [[6, "doctr.transforms.ToGray", false]], "visiondataset (class in doctr.datasets.datasets)": [[1, "doctr.datasets.datasets.VisionDataset", false]], "visualize_page() (in module doctr.utils.visualization)": [[7, "doctr.utils.visualization.visualize_page", false]], "word (class in doctr.documents)": [[2, "doctr.documents.Word", false]]}, "objects": {"doctr.datasets": [[1, 0, 1, "", "CORD"], [1, 0, 1, "", "FUNSD"], [1, 0, 1, "", "OCRDataset"], [1, 0, 1, "", "SROIE"], [1, 1, 1, "", "encode_sequences"]], "doctr.datasets.datasets": [[1, 0, 1, "", "VisionDataset"]], "doctr.datasets.loader": [[1, 0, 1, "", "DataLoader"]], "doctr.documents": [[2, 0, 1, "", "Artefact"], [2, 0, 1, "", "Block"], [2, 0, 1, "", "Document"], [2, 0, 1, "", "DocumentFile"], [2, 0, 1, "", "Line"], [2, 0, 1, "", "PDF"], [2, 0, 1, "", "Page"], [2, 0, 1, "", "Word"], [2, 1, 1, "", "read_html"], [2, 1, 1, "", "read_img"], [2, 1, 1, "", "read_pdf"]], "doctr.documents.Document": [[2, 2, 1, "", "show"]], "doctr.documents.DocumentFile": [[2, 2, 1, "", "from_images"], [2, 2, 1, "", "from_pdf"], [2, 2, 1, "", "from_url"]], "doctr.documents.PDF": [[2, 2, 1, "", "as_images"], [2, 2, 1, "", "get_artefacts"], [2, 2, 1, "", "get_words"]], "doctr.documents.Page": [[2, 2, 1, "", "show"]], "doctr.models.detection": [[5, 1, 1, "", "db_resnet50"], [5, 1, 1, "", "detection_predictor"], [5, 1, 1, "", "linknet16"]], "doctr.models.export": [[5, 1, 1, "", "convert_to_fp16"], [5, 1, 1, "", "convert_to_tflite"], [5, 1, 1, "", "quantize_model"]], "doctr.models.recognition": [[5, 1, 1, "", "crnn_vgg16_bn"], [5, 1, 1, "", "master"], [5, 1, 1, "", "recognition_predictor"], [5, 1, 1, "", "sar_resnet31"], [5, 1, 1, "", "sar_vgg16_bn"]], "doctr.models.zoo": [[5, 1, 1, "", "ocr_predictor"]], "doctr.transforms": [[6, 0, 1, "", "ColorInversion"], [6, 0, 1, "", "Compose"], [6, 0, 1, "", "LambdaTransformation"], [6, 0, 1, "", "Normalize"], [6, 0, 1, "", "OneOf"], [6, 0, 1, "", "RandomApply"], [6, 0, 1, "", "RandomBrightness"], [6, 0, 1, "", "RandomContrast"], [6, 0, 1, "", "RandomGamma"], [6, 0, 1, "", "RandomHue"], [6, 0, 1, "", "RandomJpegQuality"], [6, 0, 1, "", "RandomSaturation"], [6, 0, 1, "", "Resize"], [6, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[7, 0, 1, "", "LocalizationConfusion"], [7, 0, 1, "", "OCRMetric"], [7, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.LocalizationConfusion": [[7, 2, 1, "", "summary"]], "doctr.utils.metrics.OCRMetric": [[7, 2, 1, "", "summary"]], "doctr.utils.metrics.TextMatch": [[7, 2, 1, "", "summary"]], "doctr.utils.visualization": [[7, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [2, 7], "0": [1, 3, 5, 6, 7], "00": 5, "01": 5, "0123456789": 1, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 1, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": [], "02": 5, "02562": 5, "03": 3, "035": [], "0361328125": [], "04": [], "05": 3, "06": [], "06640625": [], "07": [], "08": 5, "09": [], "0966796875": [], "1": [1, 3, 5, 6, 7], "10": [1, 5, 7], "100": [5, 6, 7], "1000": 5, "101": [], "1024": [5, 7], "104": [], "106": [], "108": [], "1095": [], "11": 3, "110": 7, "1107": [], "114": [], "115": [], "1156": [], "116": [], "118": [], "11800h": [], "11th": [], "12": 5, "120": [], "123": [], "126": [], "1268": [], "128": 5, "13": 5, "130": [], "13068": [], "131": [], "1337891": [], "1357421875": [], "1396484375": [], "14": 5, "1420": [], "14470v1": [], "149": [], "15": 5, "150": 7, "154": 1, "1552": [], "16": 5, "160": 5, "1630859375": [], "1684": [], "16x16": [], "17": [], "1778": [], "1782": [], "18": 3, "185546875": [], "19": 5, "1900": [], "1910": 5, "19342": [], "19370": [], "195": [], "19598": [], "199": 5, "1999": [], "1m": 5, "2": [3, 5, 6], "20": 5, "200": 7, "2000": [], "2003": [], "2012": [], "2013": [], "2015": [], "2019": 3, "2021": 3, "2023": [], "207901": [], "21": 5, "2103": [], "2186": [], "21888": [], "22": [], "224": [5, 6], "225": 6, "22672": [], "229": 6, "23": [], "233": [], "236": [], "24": [], "246": [], "249": [], "25": 5, "2504": [], "255": [5, 6, 7], "256": 5, "257": [], "26": [], "26032": [], "264": [], "27": 5, "2700": [], "2710": [], "2749": [], "28": 3, "287": [], "29": 5, "296": [], "299": [], "2d": [], "3": [2, 3, 4, 5, 6, 7], "30": 5, "300": [], "3000": [], "301": [], "30595": 5, "30ghz": [], "31": 5, "32": [1, 5, 6], "3232421875": [], "33": [], "33402": [], "33608": [], "34": [], "340": [], "3456": [], "3515625": [], "36": [], "360": [], "37": [], "38": [], "39": 5, "4": [], "40": [], "406": 6, "41": [], "42": [], "43": 5, "44": [], "45": [], "456": 6, "46": 5, "47": 5, "472": [], "48": 5, "485": 6, "49": 5, "49377": [], "5": [1, 6, 7], "50": 5, "51": [], "51171875": [], "512": [], "52": [1, 5], "529": [], "53": 5, "533": [], "54": [], "540": [], "5478515625": [], "55": [], "56": [], "57": [], "58": [], "580": [], "5810546875": [], "583": [], "59": 5, "595": [], "597": [], "5k": [], "5m": 5, "6": [4, 5, 6], "60": 6, "600": [5, 7], "61": 5, "611": [], "62": 5, "625": [], "626": [], "629": [], "63": 5, "630": [], "64": [5, 6], "640": [], "641": [], "647": [], "65": 5, "66": 5, "660": [], "664": [], "666": [], "67": 5, "672": [], "68": 5, "689": [], "69": 5, "693": [], "694": [], "695": [], "6m": [], "7": 5, "70": [5, 7], "700": [], "701": [], "702": [], "707470": [], "71": [], "7100000": [], "713": [], "7141797": [], "7149": [], "72": [], "72dpi": [], "73": [], "73257": [], "733": [], "74": 5, "745": [], "75": 5, "753": [], "7581382": [], "76": [], "77": 5, "772": [], "772875": [], "78": 5, "780": [], "781": [], "783": [], "785": [], "789": [], "79": 5, "793533": [], "796": [], "798": [], "7m": [], "8": [5, 6], "80": [], "800": [5, 7], "81": 5, "817": [], "82": 5, "8275l": 5, "83": 5, "830": [], "84": [], "849": [], "85": 5, "8564453125": [], "857": [], "85875": [], "86": 5, "860": [], "8603515625": [], "862": [], "863": [], "87": 5, "8707": [], "875": [], "88": [], "89": 5, "8m": 5, "9": [], "90": 5, "90k": [], "90kdict32px": [], "91": 5, "913": [], "914085328578949": [], "917": [], "92": 5, "921": [], "93": [], "94": [], "95": 7, "9578408598899841": [], "96": 1, "97": [], "98": [], "99": [], "9949972033500671": [], "A": [1, 2, 3, 5], "And": 5, "As": [], "Be": [], "Being": [], "By": [], "For": [4, 5], "If": [2, 4, 5], "In": [1, 5], "It": 6, "Its": 5, "No": [], "Of": 1, "Or": [], "The": [1, 2, 5, 7], "Then": 5, "To": [], "_": [1, 5], "__call__": [], "_build": [], "_i": 7, "ab": [], "abc": [], "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 1, "abdef": [], "abl": [], "about": 5, "abov": 5, "abstract": 1, "abstractdataset": [], "abus": [], "accent": [], "accept": [], "access": [1, 2, 3], "account": [], "accur": [], "accuraci": 7, "achiev": [], "act": [], "action": [], "activ": [], "ad": 6, "adapt": [], "add": [6, 7], "add_hook": [], "add_label": 7, "addit": [], "addition": 5, "address": 2, "adjust": 6, "advanc": [], "advantag": [], "advis": [], "aesthet": [], "affect": [], "after": [], "ag": [], "again": [], "aggreg": [1, 7], "aggress": [], "align": 2, "all": [1, 2, 3, 5, 6, 7], "allow": [], "along": 5, "alreadi": [], "also": [], "alwai": [], "an": [1, 2, 3, 5, 7], "analysi": [2, 5], "ancient_greek": [], "andrej": [], "angl": 2, "ani": [1, 2, 3, 5, 6, 7], "annot": 2, "anot": [], "anoth": [1, 4, 5], "answer": [], "anyascii": [], "anyon": 3, "anyth": [], "api": [], "apolog": [], "apologi": [], "app": [], "appear": [], "appli": [1, 6], "applic": 5, "appoint": [], "appreci": [], "appropri": [], "ar": [1, 2, 4, 5, 6, 7], "arab": [], "arabic_diacrit": [], "arabic_lett": [], "arabic_punctu": [], "arbitrarili": [], "arch": 5, "architectur": [3, 5], "archiv": [], "area": [], "argument": [1, 2], "around": 5, "arrai": [2, 7], "art": 3, "artefact": 7, "artefact_typ": 2, "articl": [], "artifici": [], "arxiv": 5, "as_imag": 2, "asarrai": 7, "ascii_lett": 1, "aspect": [3, 6], "assess": 7, "assign": 7, "associ": 2, "assum": [], "assume_straight_pag": [], "astyp": [5, 7], "attack": [], "attend": [3, 5], "attent": [], "autoclass": [], "autom": 3, "automat": [], "autoregress": [], "avail": [3, 5, 6], "averag": [5, 6], "avoid": [], "aw": [3, 5], "awar": [], "azur": [], "b": 7, "b_j": 7, "back": [], "backbon": 5, "backend": 5, "background": [], "bangla": [], "bar": [], "bar_cod": [], "baranovskij": [], "base": 5, "baselin": 5, "batch": [1, 5, 6], "batch_siz": 1, "bblanchon": [], "bbox": [], "becaus": [], "been": [5, 7], "befor": 1, "begin": 7, "behavior": [], "being": [5, 7], "belong": [], "benchmark": [], "best": [], "beta": 3, "better": [], "between": [6, 7], "bgr": 2, "bilinear": [5, 6], "bin_thresh": [], "binar": [3, 5], "binari": 2, "bit": [], "block": [5, 7], "block_1_1": [], "blur": [], "bmvc": [], "bn": [], "bodi": [], "bool": [1, 2, 5, 6, 7], "boolean": [], "both": [3, 5, 6], "bottom": [], "bound": [1, 2, 6, 7], "box": [1, 2, 7], "box_thresh": [], "brew": 4, "bright": 6, "browser": [], "build": [], "built": [], "byte": [2, 5], "c": [], "c5": 5, "c_j": [], "cach": [], "cache_sampl": [], "cairo": 4, "call": [], "callabl": [1, 6], "can": [1, 4, 5], "capabl": 5, "case": [1, 7], "cf": 5, "cfg": [], "challeng": [], "challenge2_test_task12_imag": [], "challenge2_test_task1_gt": [], "challenge2_training_task12_imag": [], "challenge2_training_task1_gt": [], "chang": [], "changelog": 3, "channel": [2, 5, 6], "channel_prior": [], "channelshuffl": [], "charact": [1, 2, 3, 5, 7], "charactergener": [], "characterist": [], "charg": 5, "charset": [], "chart": 2, "check": [], "checkpoint": [], "chip": [], "christian": [], "ci": [], "clarifi": [], "clariti": [], "class": [1, 2, 6, 7], "class_nam": [], "classif": [], "classmethod": 2, "clear": [], "clone": 4, "close": [], "co": [], "code": [2, 3], "codecov": [], "colab": [], "collate_fn": [], "collect": 2, "color": 6, "colorinvers": 6, "column": 2, "com": [2, 4], "combin": 5, "command": [], "comment": [], "commit": [], "common": [6, 7], "commun": [], "compar": 3, "comparison": 7, "competit": 1, "compil": [], "complaint": [], "complementari": 7, "complet": [], "compon": 5, "compos": [1, 3, 5], "comprehens": [], "comput": [5, 7], "conf_threshold": [], "confid": 2, "config": [], "configur": [], "confus": 7, "consecut": [5, 6], "consequ": [], "consid": [1, 2, 7], "consist": [], "consolid": [1, 3], "constant": 6, "construct": [], "contact": [], "contain": [], "content": [1, 2], "context": [], "contib": [], "continu": [], "contrast": 6, "contrast_factor": 6, "contrib": [], "contribut": [], "contributor": [], "conv_sequ": 5, "convers": 2, "convert": [2, 5, 6], "convert_page_to_numpi": 2, "convert_to_fp16": 5, "convert_to_tflit": 5, "convolut": 3, "cool": [], "coordin": 2, "cord": [1, 3, 5], "core": 7, "corner": [], "correct": 6, "correspond": [4, 5], "could": [], "counterpart": 7, "cover": [], "coverag": [], "cpu": [3, 5], "creat": [], "crnn": [3, 5], "crnn_mobilenet_v3_larg": [], "crnn_mobilenet_v3_smal": [], "crnn_resnet31": 5, "crnn_vgg16_bn": 5, "crop": 5, "crop_orient": [], "crop_orientation_predictor": [], "crop_param": [], "cuda": [], "currenc": 1, "current": [], "custom": [], "custom_crop_orientation_model": [], "custom_page_orientation_model": [], "customhook": [], "cvit": [], "czczup": [], "czech": [], "d": [], "daili": 3, "danish": [], "data": [2, 3, 5, 6, 7], "dataload": 1, "dataset": 5, "dataset_info": [], "date": [], "db": [], "db_crnn_resnet": 5, "db_crnn_vgg": 5, "db_mobilenet_v3_larg": [], "db_resnet34": [], "db_resnet50": 5, "db_sar_resnet": 5, "db_sar_vgg": 5, "dbnet": [3, 5], "deal": [], "decis": [], "decod": 2, "decode_img_as_tensor": [], "dedic": [], "deem": [], "deep": 5, "def": [], "default": [2, 5], "defer": 1, "defin": 7, "deform": 5, "degre": [], "degress": 2, "delet": [], "delimit": [], "delta": 6, "demo": [], "demonstr": [], "depend": [3, 4], "deploi": [], "deploy": [], "derogatori": [], "describ": 5, "descript": [], "design": 6, "desir": [], "det_arch": 5, "det_b": [], "det_model": [], "det_param": [], "det_predictor": [], "detail": [], "detect": [], "detect_languag": [], "detect_orient": [], "detection_predictor": 5, "detection_task": [], "detectiondataset": [], "detectionmetr": [], "detectionpredictor": 5, "detector": [], "deterior": [], "determin": [], "dev": [], "develop": [], "developp": 4, "deviat": 6, "devic": [], "dict": [2, 7], "dictionari": [2, 7], "differ": [], "differenti": [3, 5], "digit": 1, "dimens": [2, 5, 7], "dimension": 6, "direct": [], "directli": 5, "directori": [], "disabl": [], "disable_crop_orient": [], "disable_page_orient": [], "disclaim": 5, "discuss": [], "disk": [], "disparag": [], "displai": [2, 7], "display_artefact": 7, "distanc": [], "distribut": 6, "div": [], "divers": [], "divid": [], "do": 4, "doc": [2, 5], "docartefact": [], "docstr": [], "doctr": 4, "doctr_cache_dir": [], "doctr_multiprocessing_dis": [], "document": [1, 5, 7], "documentbuild": [], "documentfil": 2, "doesn": [], "don": [], "done": 6, "download": 1, "downsiz": [], "draw": 6, "drop": 1, "drop_last": 1, "dtype": 5, "dual": [], "dummi": [], "dummy_img": [], "dummy_input": [], "dure": [], "dutch": [], "dynam": [], "dynamic_seq_length": [], "e": [2, 4], "each": [1, 2, 3, 5, 6, 7], "eas": [], "easi": [3, 7], "easier": 5, "easili": [2, 5, 7], "econom": [], "edit": [], "educ": [], "effect": [], "effici": [1, 5], "either": 5, "element": [1, 2, 5], "els": [], "email": [], "empathi": [], "en": [], "enabl": 2, "enclos": 2, "encod": [1, 2, 5], "encode_sequ": 1, "encount": [], "encrypt": [], "end": [1, 3, 7], "english": [], "enough": 5, "ensur": [], "entir": 2, "entri": [], "environ": [], "eo": 1, "equiv": [], "error": [], "estim": [], "etc": 2, "ethnic": [], "evalu": [1, 3, 5], "event": [], "everyon": [], "everyth": [], "exact": 7, "exactmatch": [], "exampl": [1, 2, 5, 6, 7], "exchang": [], "exclud": 5, "execut": [], "exist": [], "expand": [], "expect": [2, 5, 6], "experi": 5, "explan": 5, "explicit": [], "exploit": 5, "export": [2, 3, 7], "export_as_straight_box": [], "export_as_xml": [], "export_model_to_onnx": [], "express": 6, "extens": 2, "extern": [], "extra": 4, "extract": [1, 3], "extract_arch": 1, "extractor": 5, "f_": 7, "f_a": 7, "factor": 6, "fair": [], "fairli": [], "fals": [1, 5, 6, 7], "faq": [], "fascan": [], "fast": 1, "fast_bas": [], "fast_smal": [], "fast_tini": [], "faster": [], "fasterrcnn_mobilenet_v3_large_fpn": [], "favorit": [], "featur": [5, 7], "feed": 5, "feedback": [], "feel": [], "felix92": [], "few": 4, "figsiz": 7, "figur": 7, "file": [1, 3], "file_hash": 1, "file_nam": 1, "final": [], "find": 4, "fine": 3, "finnish": [], "first": [], "firsthand": [], "fit": [], "fitz": 2, "flag": [], "flexibl": 7, "flip": [], "float": [2, 6, 7], "float32": 5, "fn": 6, "focu": [], "focus": [], "folder": [1, 5], "follow": [1, 4, 5, 6, 7], "font": [], "font_famili": [], "foral": 7, "forc": [], "forg": [], "form": [1, 3], "format": [2, 5], "forpost": [1, 3], "forum": [], "found": [], "fp": 5, "fp16": 5, "frac": 7, "frame": 5, "framework": 1, "free": [], "french": [1, 5], "friendli": 3, "from": [1, 2, 3, 5, 6, 7], "from_hub": [], "from_imag": 2, "from_pdf": 2, "from_url": 2, "full": [1, 5, 7], "fulli": [], "function": [5, 6, 7], "funsd": [1, 3, 5], "further": [], "futur": [], "g": 2, "g_": 7, "g_x": 7, "gallagh": [], "gamma": 6, "gaussian": 6, "gaussianblur": [], "gaussiannois": [], "gdk": 4, "gen": [], "gender": [], "gener": [], "generic_cyrillic_lett": [], "geometri": 2, "geq": 7, "german": [], "get": 2, "get_artefact": 2, "get_word": 2, "gettextword": 2, "git": 3, "github": 4, "give": [], "given": [1, 2, 5, 7], "global": [], "go": [], "good": [], "googl": [], "googlevis": 3, "gpu": 3, "gracefulli": [], "graph": 2, "grayscal": 6, "ground": 7, "groung": [], "group": [], "gt": [], "gt_box": [], "gt_label": [], "gtk": 4, "guid": [], "guidanc": [], "gvision": 5, "h": 2, "h_": 7, "ha": [1, 7], "half": 5, "handl": 1, "handwrit": [], "handwritten": [], "harass": [], "hardwar": [], "harm": [], "hat": 7, "have": [1, 5, 7], "head": [], "healthi": [], "hebrew": [], "height": 2, "hello": 7, "help": [], "here": [1, 4, 6], "hf": [], "hf_hub_download": [], "high": 2, "higher": 4, "hindi": [], "hindi_digit": [], "hocr": [], "hook": [], "horizont": 2, "hous": [], "how": [], "howev": [], "hsv": 6, "html": [], "http": [2, 4, 5], "hub": [], "hue": 6, "huggingfac": [], "hw": [], "i": [1, 2, 5, 6, 7], "i7": [], "ibrahimov": [], "ic03": [], "ic13": [], "icdar": 3, "icdar2019": 1, "id": 5, "ident": [], "identifi": [3, 5], "ignor": [], "ignore_acc": [], "ignore_cas": [], "iiit": [], "iiit5k": [], "iiithw": [], "imag": [1, 2, 5, 6, 7], "imagenet": [], "imageri": [], "images_90k_norm": [], "img": [1, 6], "img_cont": [], "img_fold": 1, "img_path": [], "img_transform": [], "imgur5k": [], "imgur5k_annot": [], "imlist": [], "impact": [], "implement": [1, 2, 5, 6, 7], "import": [1, 2, 5, 6, 7], "improv": [], "inappropri": [], "incid": [], "includ": [4, 5], "inclus": [], "increas": 6, "independ": [], "index": 2, "indic": 7, "individu": [], "infer": [3, 6], "inform": [1, 3, 5], "inherit": [1, 5], "input": [2, 5, 6], "input_crop": [], "input_pag": [5, 7], "input_shap": 5, "input_t": 5, "input_tensor": 5, "inspir": 6, "instal": 3, "instanc": 5, "instanti": 5, "instead": [1, 2], "insult": [], "int": [1, 2, 5, 6, 7], "int64": [], "integ": 7, "integr": 3, "intel": [], "interact": [2, 7], "interfac": [], "interoper": [], "interpol": [5, 6], "interpret": [1, 2], "intersect": 7, "invert": 6, "investig": [], "invis": [], "invoic": 5, "involv": 5, "io": [], "iou": 7, "iou_thresh": 7, "iou_threshold": [], "irregular": 5, "isn": 1, "issu": [], "italian": [], "iter": 1, "its": [1, 2, 5, 7], "itself": [], "j": 7, "jame": [], "job": [], "join": [], "jpeg": 6, "jpegqual": 6, "jpg": [1, 2], "json": [], "json_output": [], "jump": [], "just": 5, "kei": [], "kera": 5, "kernel": [], "kernel_s": 5, "kernel_shap": [], "keywoard": [], "keyword": [1, 2], "kie": [], "kie_predictor": [], "kiepredictor": [], "kind": [], "know": [], "kwarg": [1, 2, 5, 7], "l": 7, "l_j": 7, "label": [1, 7], "label_fil": 1, "label_fold": [], "label_path": [], "labels_path": [], "ladder": [], "lambda": 6, "lambdatransform": 6, "lang": [], "languag": [2, 3], "larg": [], "largest": 7, "last": [1, 4, 5], "latenc": [], "later": [], "latest": 4, "latin": 1, "layer": [], "layout": [], "lead": [], "leader": [], "learn": 5, "least": 4, "left": 7, "legacy_french": [], "length": 1, "less": [], "let": 5, "letter": [], "level": [5, 7], "levenshtein": [], "leverag": [], "lf": [], "libffi": 4, "librari": 4, "light": 3, "lightweight": [], "like": [], "limits_": 7, "line": [3, 7], "line_1_1": [], "link": [], "linknet": [3, 5], "linknet16": 5, "linknet_resnet18": [], "linknet_resnet34": [], "linknet_resnet50": [], "linux": 4, "list": [1, 2, 6], "ll": 7, "load": [3, 5], "load_state_dict": [], "load_weight": [], "loader": 1, "loc_pr": [], "local": [1, 3, 5, 7], "localis": [], "localizationconfus": 7, "locat": [], "login": [], "login_to_hub": [], "logo": 2, "love": [], "lower": [6, 7], "m": [5, 7], "m1": [], "macbook": [], "machin": [], "maco": 4, "made": 3, "magc_resnet31": [], "mai": [], "mail": [], "main": [], "maintain": 3, "mainten": [], "make": [5, 7], "mani": [], "manipul": [], "map": 1, "map_loc": [], "mask_shap": 7, "master": [3, 5], "match": [3, 7], "mathcal": 7, "matplotlib": 7, "max": 7, "max_angl": [], "max_area": [], "max_char": [], "max_delta": 6, "max_dist": [], "max_gain": 6, "max_gamma": 6, "max_qual": 6, "max_ratio": [], "maximum": 1, "maxval": [5, 6], "mbox": 7, "mean": [6, 7], "meaniou": 7, "meant": 2, "measur": 5, "media": [], "median": [], "meet": [], "member": [], "memori": [], "mention": [], "merg": [], "messag": [], "meta": [], "metadata": [], "metal": [], "method": 6, "metric": [5, 7], "middl": [], "might": 5, "min": [], "min_area": [], "min_char": [], "min_gain": 6, "min_gamma": 6, "min_qual": 6, "min_ratio": [], "min_val": 6, "minde": 4, "minim": [], "minimalist": [], "minimum": 7, "minval": 6, "miss": [], "mistak": [], "mix": 3, "mixed_float16": [], "mixed_precis": [], "mjsynth": [], "mnt": [], "mobilenet": [], "mobilenet_v3_larg": [], "mobilenet_v3_large_r": [], "mobilenet_v3_smal": [], "mobilenet_v3_small_crop_orient": [], "mobilenet_v3_small_page_orient": [], "mobilenet_v3_small_r": [], "mobilenetv3": [], "modal": [], "mode": 4, "model": [1, 7], "model_nam": [], "model_path": [], "moder": [], "modif": [], "modifi": [], "modul": [2, 5, 6, 7], "more": [], "moscardi": [], "most": 5, "mozilla": [], "multi": 3, "multilingu": [], "multipl": [1, 2, 6], "multipli": 6, "multiprocess": [], "my": [], "my_awesome_model": [], "my_hook": [], "n": [1, 5, 7], "na": [], "name": [1, 5], "nation": [], "natur": 3, "ndarrai": [1, 2, 7], "necessari": [], "need": [4, 7], "neg": 6, "nest": [], "nestedobject": [], "netraj": [], "network": [3, 5], "neural": [3, 5], "new": [], "newer": [], "next": 1, "nois": [], "noisi": [1, 3], "non": [2, 3, 6, 7], "none": [1, 2, 7], "normal": [5, 6], "norwegian": [], "note": 0, "now": 3, "np": [5, 7], "num_output_channel": [], "num_sampl": [], "number": [1, 6, 7], "numpi": [2, 5, 7], "o": 4, "obb": [], "obj_detect": [], "object": 1, "objectness_scor": [], "oblig": [], "obtain": [], "occupi": [], "ocr": [1, 3, 7], "ocr_carea": [], "ocr_db_crnn": 7, "ocr_lin": [], "ocr_pag": [], "ocr_par": [], "ocr_predictor": 5, "ocrdataset": 1, "ocrmetr": 7, "ocrpredictor": 5, "ocrx_word": [], "offens": [], "offici": [], "offlin": [], "offset": 6, "onc": 5, "one": [1, 5, 6], "oneof": 6, "ones": 1, "onli": [6, 7], "onlin": [], "onnx": [], "onnxruntim": [], "onnxtr": [], "opac": [], "opacity_rang": [], "open": [], "opinion": [], "optic": [3, 5], "optim": 3, "option": 1, "order": [1, 2, 5], "org": 5, "organ": 2, "orient": 2, "orientationpredictor": [], "other": [], "otherwis": 7, "our": 5, "out": [5, 6, 7], "outpout": [], "output": [2, 5, 6], "output_s": [2, 6], "outsid": [], "over": [4, 7], "overal": [], "overlai": 2, "overview": [], "overwrit": 1, "overwritten": [], "own": 3, "p": 6, "packag": 7, "pad": [1, 5, 6], "page": [4, 5, 7], "page1": 2, "page2": 2, "page_1": [], "page_idx": 2, "page_orientation_predictor": [], "page_param": [], "pair": 7, "pango": 4, "paper": 5, "par_1_1": [], "paragraph": [], "paragraph_break": [], "parallel": [], "param": [5, 6], "paramet": [1, 2, 3, 5, 6, 7], "pars": [1, 3], "parseq": [], "part": 6, "parti": [], "partial": [], "particip": [], "pass": [1, 5], "password": [], "patch": [], "path": [1, 2, 5], "path_to_checkpoint": [], "path_to_custom_model": [], "path_to_pt": [], "patil": [], "pattern": [], "pdf": [2, 5], "pdfpage": [], "peopl": [], "per": [5, 6], "perform": [2, 3, 5, 6, 7], "period": [], "permiss": [], "permut": [], "persian_lett": [], "person": [], "phase": [], "photo": [], "physic": 2, "pick": 6, "pictur": 2, "pip": 4, "pipelin": [], "pixbuf": 4, "pixel": [2, 6], "platinum": 5, "pleas": [], "plot": 7, "plt": 7, "plug": [], "plugin": [], "png": 2, "point": [], "polici": [], "polish": [], "polit": [], "polygon": 1, "pool": [], "portugues": [], "posit": 7, "possibl": 7, "post": 5, "postprocessor": [], "potenti": 5, "power": 3, "ppageno": [], "pre": [], "precis": [5, 7], "pred": [], "pred_box": [], "pred_label": [], "predefin": 1, "predict": [2, 7], "predictor": [], "prefer": 1, "preinstal": [], "preprocessor": 5, "prerequisit": 3, "present": [], "preserv": 6, "preserve_aspect_ratio": 6, "pretrain": [3, 5, 7], "pretrained_backbon": [], "print": [], "prior": [], "privaci": [], "privat": 5, "probabl": 6, "problem": [], "procedur": 6, "process": [2, 3], "processor": 5, "produc": 5, "product": [], "profession": [], "project": [], "promptli": [], "proper": [], "properli": 1, "properti": 5, "provid": [3, 5], "public": 3, "publicli": [], "publish": [], "pull": [], "punctuat": 1, "pure": [], "purpos": [], "push_to_hf_hub": [], "py": [], "pypdfium2": [], "pyplot": 7, "python": 3, "python3": [], "pytorch": [3, 4], "q": [], "qr": 2, "qr_code": [], "qualiti": 6, "quantiz": 5, "quantize_model": 5, "question": [], "quickli": 3, "quicktour": [], "r": [], "race": [], "ramdisk": [], "rand": [5, 7], "random": [5, 6, 7], "randomappli": 6, "randombright": 6, "randomcontrast": 6, "randomcrop": [], "randomgamma": 6, "randomhorizontalflip": [], "randomhu": 6, "randomjpegqu": 6, "randomli": 6, "randomres": [], "randomrot": [], "randomsatur": 6, "randomshadow": [], "rang": 6, "rassi": [], "ratio": 6, "raw": [2, 7], "re": [], "read": [3, 5], "read_html": 2, "read_img": 2, "read_img_as_numpi": [], "read_img_as_tensor": [], "read_pdf": 2, "readi": [], "real": [5, 6], "realli": [], "reason": [], "rebuild": [], "rebuilt": [], "recal": [5, 7], "receipt": [1, 3, 5], "reco_arch": 5, "reco_b": [], "reco_model": [], "reco_param": [], "reco_predictor": [], "recogn": [], "recognit": 7, "recognition_predictor": 5, "recognition_task": [], "recognitiondataset": [], "recognitionpredictor": 5, "rectangular": [], "recurr": 3, "reduc": 6, "refer": 4, "regardless": [], "region": [], "regroup": 7, "regular": [], "reject": [], "rel": 2, "relat": [], "releas": [0, 4], "relev": [], "religion": [], "relu": 5, "remov": [], "render": [], "repo": [], "repo_id": [], "report": [], "repositori": [], "repres": [2, 5], "represent": 5, "request": [], "requir": [4, 6], "research": 3, "residu": [], "resiz": [5, 6], "resnet": 5, "resnet18": [], "resnet31": [], "resnet34": [], "resnet50": [], "resolv": 2, "resolve_block": [], "resolve_lin": [], "resourc": [], "respect": [], "rest": [6, 7], "restrict": [], "result": [2, 5], "return": [1, 2, 5, 7], "reusabl": 5, "review": [], "rgb": [2, 6], "rgb_mode": [], "rgb_output": 2, "right": [5, 7], "roboflow": [], "robust": 3, "root": 1, "rotat": [1, 2], "rotated_bbox": [1, 7], "run": 4, "same": [2, 7], "sampl": 1, "sample_transform": 1, "sanjin": [], "sar": [3, 5], "sar_resnet31": 5, "sar_vgg16_bn": 5, "satur": 6, "save": [1, 5], "saved_model": 5, "scale": 7, "scale_rang": [], "scan": [1, 3], "scene": [3, 5], "scheme": 5, "score": 7, "scratch": 3, "script": [], "seamless": 3, "seamlessli": [], "search": [], "searchabl": [], "sec": [], "second": 5, "section": [], "secur": [], "see": [], "seemlessli": 3, "seen": 5, "segment": 5, "self": [], "semant": 5, "send": [], "sens": 7, "sensit": [], "separ": 5, "sequenc": [1, 2, 5, 7], "sequenti": [5, 6], "seri": [], "serial": 5, "serialized_model": 5, "seriou": [], "set": [1, 5, 7], "set_global_polici": [], "sever": [2, 6], "sex": [], "sexual": [], "sha256": [], "shade": [], "shape": [2, 5, 6, 7], "share": [], "shift": 6, "shm": [], "should": [1, 2, 7], "show": [2, 3, 5, 7], "showcas": [], "shuffl": 1, "side": 7, "signatur": 2, "signific": 1, "simpl": 5, "simpler": [], "sinc": 1, "singl": [], "single_img_doc": [], "size": [1, 2, 5, 6], "skew": [], "slack": [], "slightli": [], "small": 3, "smallest": 2, "snapshot_download": [], "snippet": [], "so": [1, 4], "social": [], "socio": [], "some": [], "someth": [], "somewher": [], "sort": [], "sourc": [1, 2, 5, 6, 7], "space": [], "span": [], "spanish": [], "spatial": 2, "special": 3, "specif": [1, 5, 7], "specifi": 2, "speed": [3, 5], "sphinx": [], "sroie": [1, 3], "stabl": 4, "stackoverflow": [], "stage": 3, "standalon": [], "standard": 6, "start": 1, "state": 3, "static": 7, "statist": 5, "statu": [], "std": 6, "step": [], "still": [], "str": [1, 2, 5, 6, 7], "straight": 1, "straighten": [], "straighten_pag": [], "straigten_pag": [], "stream": 2, "street": [], "strict": [], "strictli": 7, "string": [1, 2, 5, 7], "strive": [], "strong": 5, "structur": [3, 5], "subset": [1, 5], "suggest": [], "sum": 7, "summari": 7, "support": 5, "sustain": [], "svhn": [], "svt": [], "swedish": [], "symbol": [], "symmetr": 6, "symmetric_pad": 6, "synthet": [], "synthtext": [], "system": [], "t": 1, "tabl": [], "take": [], "target": [1, 2, 5, 6], "target_s": 1, "task": [1, 3, 5], "task2": [], "team": [], "techminde": [], "templat": 2, "tensor": [1, 5, 6], "tensorflow": [3, 4, 5, 6], "tensorspec": [], "term": [], "test": [], "test_set": [], "text": [2, 7], "text_output": [], "textmatch": 7, "textnet": [], "textnet_bas": [], "textnet_smal": [], "textnet_tini": [], "textract": [3, 5], "textstylebrush": [], "textual": [1, 2, 3], "tf": [5, 6], "tf_model": 5, "tflite": 5, "than": [4, 7], "thank": [], "thei": [], "them": [1, 4], "thi": [4, 5, 7], "thing": [], "third": [], "those": [2, 4, 5], "threaten": [], "threshold": [], "through": [1, 6], "tilman": [], "time": [1, 5, 7], "tini": [], "titl": 2, "tm": [], "tmp": [], "togeth": [2, 5], "tograi": 6, "tool": [], "top": 7, "topic": [], "torch": [], "torchvis": 6, "total": [], "toward": [], "train": [1, 5, 6], "train_it": 1, "train_load": 1, "train_pytorch": [], "train_set": 1, "train_tensorflow": [], "trainabl": 5, "tranform": 6, "transcrib": [], "transfer": [], "transfo": 6, "transform": [1, 3], "translat": [], "troll": [], "true": [1, 2, 5, 6, 7], "truth": 7, "tune": 3, "tupl": [2, 5, 6, 7], "turn": [], "two": 2, "txt": [], "type": [2, 5], "typic": [], "u": [], "ucsd": [], "udac": [], "uint8": [2, 5, 7], "ukrainian": [], "unaccept": [], "underli": 1, "underneath": 2, "understand": [1, 3], "unidecod": 7, "uniform": [5, 6], "uniformli": [], "uninterrupt": 2, "union": 7, "unit": [], "unittest": [], "unlock": [], "unoffici": [], "unprofession": [], "unsolicit": [], "unsupervis": [], "unwelcom": [], "up": 5, "updat": 7, "upgrad": [], "upper": 6, "uppercas": [], "url": [1, 2], "us": [1, 4, 7], "usabl": 5, "usag": 5, "use_polygon": [], "useabl": [], "user": [2, 3, 4], "utf": [], "util": [3, 5], "v0": 3, "v1": [], "v3": [], "valid": [], "valu": [2, 6], "valuabl": 3, "variabl": [], "varieti": [], "veri": [], "verifi": 1, "verma": [], "version": 5, "vgg": 5, "vgg16": 5, "vgg16_bn_r": [], "via": 3, "video": [], "vietnames": [], "view": [], "viewpoint": [], "violat": [], "visibl": [], "vision": [], "visiondataset": 1, "visiontransform": [], "visual": 3, "visualize_pag": 7, "vit_": [], "vit_b": [], "vitstr": [], "vitstr_bas": [], "vitstr_smal": [], "viz": [], "vocab": [3, 5], "vocabulari": [], "w": [2, 7], "w3": [], "wa": [], "wai": [1, 3, 5], "want": [], "warm": 5, "warmup": [], "wasn": [], "we": [2, 3, 5, 6], "weasyprint": [], "web": 2, "websit": [], "welcom": 3, "well": [], "were": 2, "what": [], "when": [], "whenev": [], "where": [2, 7], "whether": [1, 2, 7], "which": 5, "whichev": 4, "while": 6, "why": [], "width": 2, "wiki": [], "wildreceipt": [], "window": [4, 7], "wish": [], "within": [], "without": 5, "wonder": [], "word": [3, 5, 7], "word_1_1": [], "word_1_2": [], "word_1_3": [], "wordgener": [], "words_onli": 7, "work": [], "worker": 1, "workflow": [], "worklow": [], "world": 7, "worth": [], "wrap": [], "wrapper": [1, 6], "write": [], "written": 2, "www": 2, "x": [2, 6, 7], "x12larg": 5, "x_ascend": [], "x_descend": [], "x_i": 7, "x_size": [], "x_wconf": [], "xeon": 5, "xhtml": [], "xmax": 2, "xmin": 2, "xml": [], "xml_bytes_str": [], "xml_element": [], "xml_output": [], "xmln": [], "y": 7, "y_i": 7, "y_j": 7, "yet": [], "ymax": 2, "ymin": 2, "yolov8": [], "you": [4, 5], "your": [1, 2, 5, 7], "yoursit": 2, "yugesh": [], "zero": [5, 6], "zoo": [], "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 1, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": [], "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": [], "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": [], "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": [], "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": [], "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": [], "\u00e4\u00f6\u00e4\u00f6": [], "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": [], "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": [], "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": [], "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": [], "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": [], "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": [], "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": [], "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": [], "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": [], "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": [], "\u067e\u0686\u06a2\u06a4\u06af": [], "\u0905": [], "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": [], "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": [], "\u0950": [], "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": [], "\u09bd": [], "\u09ce": [], "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": []}, "titles": ["Changelog", "doctr.datasets", "doctr.documents", "DocTR: Document Text Recognition", "Installation", "doctr.models", "doctr.transforms", "doctr.utils"], "titleterms": {"": [], "0": 0, "01": [], "02": [], "03": 0, "04": [], "05": 0, "07": [], "08": [], "09": [], "1": 0, "10": [], "11": 0, "12": [], "18": 0, "2": 0, "2021": 0, "2022": [], "2023": [], "2024": [], "21": [], "22": [], "27": [], "28": 0, "29": [], "3": [], "31": [], "4": [], "5": [], "6": [], "7": [], "8": [], "9": [], "advanc": [], "approach": 5, "architectur": [], "arg": [], "artefact": 2, "artefactdetect": [], "attribut": [], "avail": 1, "aw": [], "ban": [], "block": 2, "bug": [], "build": 3, "changelog": 0, "choos": [], "classif": [], "code": [], "codebas": [], "commit": [], "commun": [], "compos": 6, "compress": 5, "conda": [], "conduct": [], "connect": [], "content": [], "continu": [], "contrib": [], "contribut": [], "contributor": [], "convent": [], "correct": [], "coven": [], "custom": [], "data": 1, "dataload": [], "dataset": [1, 3], "detect": [3, 5], "develop": [], "do": [], "doctr": [1, 2, 3, 5, 6, 7], "document": [2, 3], "end": 5, "enforc": [], "evalu": 7, "export": 5, "factori": [], "featur": 3, "feedback": [], "file": 2, "from": [], "gener": [], "get": 3, "git": 4, "guidelin": [], "half": [], "hub": [], "huggingfac": [], "i": [], "implement": [], "infer": [], "instal": 4, "integr": [], "io": [], "lambda": [], "let": [], "line": 2, "linux": [], "load": 1, "loader": [], "main": 3, "mode": [], "model": [3, 5], "modifi": [], "modul": [], "name": [], "note": 3, "notebook": [], "object": [], "ocr": 5, "onli": [], "onnx": [], "optim": [], "option": [], "orient": [], "our": [], "output": [], "own": [], "packag": [3, 4], "page": 2, "perman": [], "pipelin": [], "pledg": [], "post": [], "pre": 5, "precis": [], "predictor": [3, 5], "prepar": [], "prerequisit": 4, "pretrain": [], "process": 5, "push": [], "python": 4, "qualiti": [], "question": [], "read": 2, "readi": [], "recognit": [3, 5], "refer": 3, "report": [], "request": [], "resourc": [], "respons": [], "return": [], "right": [], "savedmodel": 5, "scope": [], "share": [], "should": [], "stage": 5, "standard": [], "start": 3, "structur": 2, "style": [], "support": [1, 3, 6], "synthet": [], "task": 7, "temporari": [], "test": [], "text": [3, 5], "train": 3, "transform": 6, "two": 5, "unit": [], "us": 5, "util": 7, "v0": 0, "verif": [], "via": 4, "visual": 7, "vocab": 1, "warn": [], "what": [], "word": 2, "your": 3, "zoo": [3, 5]}}) \ No newline at end of file +Search.setIndex({"alltitles": {"1. Correction": [[2, "correction"]], "2. Warning": [[2, "warning"]], "3. Temporary Ban": [[2, "temporary-ban"]], "4. Permanent Ban": [[2, "permanent-ban"]], "AWS Lambda": [[14, null]], "Advanced options": [[19, "advanced-options"]], "Args:": [[7, "args"], [7, "id4"], [7, "id7"], [7, "id10"], [7, "id13"], [7, "id16"], [7, "id19"], [7, "id22"], [7, "id25"], [7, "id29"], [7, "id32"], [7, "id37"], [7, "id40"], [7, "id46"], [7, "id49"], [7, "id50"], [7, "id51"], [7, "id54"], [7, "id57"], [7, "id60"], [7, "id61"], [8, "args"], [8, "id2"], [8, "id3"], [8, "id4"], [8, "id5"], [8, "id6"], [8, "id7"], [8, "id10"], [8, "id12"], [8, "id14"], [8, "id16"], [8, "id20"], [8, "id24"], [8, "id28"], [9, "args"], [9, "id3"], [9, "id8"], [9, "id13"], [9, "id17"], [9, "id21"], [9, "id26"], [9, "id31"], [9, "id36"], [9, "id41"], [9, "id46"], [9, "id50"], [9, "id54"], [9, "id59"], [9, "id63"], [9, "id68"], [9, "id73"], [9, "id77"], [9, "id81"], [9, "id85"], [9, "id90"], [9, "id95"], [9, "id99"], [9, "id104"], [9, "id109"], [9, "id114"], [9, "id119"], [9, "id123"], [9, "id127"], [9, "id132"], [9, "id137"], [9, "id142"], [9, "id146"], [9, "id150"], [9, "id155"], [9, "id159"], [9, "id163"], [9, "id167"], [9, "id169"], [9, "id171"], [9, "id173"], [10, "args"], [10, "id1"], [10, "id2"], [10, "id3"], [10, "id4"], [10, "id5"], [10, "id6"], [10, "id7"], [10, "id8"], [10, "id9"], [10, "id10"], [10, "id11"], [10, "id12"], [10, "id13"], [10, "id14"], [10, "id15"], [10, "id16"], [10, "id17"], [10, "id18"], [10, "id19"], [11, "args"], [11, "id3"], [11, "id4"], [11, "id5"], [11, "id6"], [11, "id7"], [11, "id8"], [11, "id9"]], "Artefact": [[8, "artefact"]], "ArtefactDetection": [[16, "artefactdetection"]], "Attribution": [[2, "attribution"]], "Available Datasets": [[17, "available-datasets"]], "Available architectures": [[19, "available-architectures"], [19, "id1"], [19, "id2"]], "Available contribution modules": [[16, "available-contribution-modules"]], "Block": [[8, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[17, null]], "Choosing the right model": [[19, null]], "Classification": [[15, "classification"]], "Code quality": [[3, "code-quality"]], "Code style verification": [[3, "code-style-verification"]], "Codebase structure": [[3, "codebase-structure"]], "Commits": [[3, "commits"]], "Community resources": [[1, null]], "Composing transformations": [[10, "composing-transformations"]], "Continuous Integration": [[3, "continuous-integration"]], "Contributing to docTR": [[3, null]], "Contributor Covenant Code of Conduct": [[2, null]], "Custom dataset loader": [[7, "custom-dataset-loader"]], "Custom orientation classification models": [[13, "custom-orientation-classification-models"]], "Data Loading": [[17, "data-loading"]], "Dataloader": [[7, "dataloader"]], "Detection": [[15, "detection"], [17, "detection"]], "Detection predictors": [[19, "detection-predictors"]], "Developer mode installation": [[3, "developer-mode-installation"]], "Developing docTR": [[3, "developing-doctr"]], "Document": [[8, "document"]], "Document structure": [[8, "document-structure"]], "End-to-End OCR": [[19, "end-to-end-ocr"]], "Enforcement": [[2, "enforcement"]], "Enforcement Guidelines": [[2, "enforcement-guidelines"]], "Enforcement Responsibilities": [[2, "enforcement-responsibilities"]], "Export to ONNX": [[18, "export-to-onnx"]], "Feature requests & bug report": [[3, "feature-requests-bug-report"]], "Feedback": [[3, "feedback"]], "File reading": [[8, "file-reading"]], "Half-precision": [[18, "half-precision"]], "Installation": [[4, null]], "Integrate contributions into your pipeline": [[16, null]], "Let\u2019s connect": [[3, "let-s-connect"]], "Line": [[8, "line"]], "Loading from Huggingface Hub": [[15, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[13, "loading-your-custom-trained-model"]], "Loading your custom trained orientation classification model": [[13, "loading-your-custom-trained-orientation-classification-model"]], "Main Features": [[5, "main-features"]], "Model optimization": [[18, "model-optimization"]], "Model zoo": [[5, "model-zoo"]], "Modifying the documentation": [[3, "modifying-the-documentation"]], "Naming conventions": [[15, "naming-conventions"]], "OCR": [[17, "ocr"]], "Object Detection": [[17, "object-detection"]], "Our Pledge": [[2, "our-pledge"]], "Our Standards": [[2, "our-standards"]], "Page": [[8, "page"]], "Preparing your model for inference": [[18, null]], "Prerequisites": [[4, "prerequisites"]], "Pretrained community models": [[15, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[15, "pushing-to-the-huggingface-hub"]], "Questions": [[3, "questions"]], "Recognition": [[15, "recognition"], [17, "recognition"]], "Recognition predictors": [[19, "recognition-predictors"]], "Returns:": [[7, "returns"], [8, "returns"], [8, "id11"], [8, "id13"], [8, "id15"], [8, "id19"], [8, "id23"], [8, "id27"], [8, "id31"], [9, "returns"], [9, "id6"], [9, "id11"], [9, "id16"], [9, "id20"], [9, "id24"], [9, "id29"], [9, "id34"], [9, "id39"], [9, "id44"], [9, "id49"], [9, "id53"], [9, "id57"], [9, "id62"], [9, "id66"], [9, "id71"], [9, "id76"], [9, "id80"], [9, "id84"], [9, "id88"], [9, "id93"], [9, "id98"], [9, "id102"], [9, "id107"], [9, "id112"], [9, "id117"], [9, "id122"], [9, "id126"], [9, "id130"], [9, "id135"], [9, "id140"], [9, "id145"], [9, "id149"], [9, "id153"], [9, "id158"], [9, "id162"], [9, "id166"], [9, "id168"], [9, "id170"], [9, "id172"], [11, "returns"]], "Scope": [[2, "scope"]], "Share your model with the community": [[15, null]], "Supported Vocabs": [[7, "supported-vocabs"]], "Supported contribution modules": [[6, "supported-contribution-modules"]], "Supported datasets": [[5, "supported-datasets"]], "Supported transformations": [[10, "supported-transformations"]], "Synthetic dataset generator": [[7, "synthetic-dataset-generator"], [17, "synthetic-dataset-generator"]], "Task evaluation": [[11, "task-evaluation"]], "Text Detection": [[19, "text-detection"]], "Text Recognition": [[19, "text-recognition"]], "Text detection models": [[5, "text-detection-models"]], "Text recognition models": [[5, "text-recognition-models"]], "Train your own model": [[13, null]], "Two-stage approaches": [[19, "two-stage-approaches"]], "Unit tests": [[3, "unit-tests"]], "Use your own datasets": [[17, "use-your-own-datasets"]], "Using your ONNX exported model": [[18, "using-your-onnx-exported-model"]], "Via Conda (Only for Linux)": [[4, "via-conda-only-for-linux"]], "Via Git": [[4, "via-git"]], "Via Python Package": [[4, "via-python-package"]], "Visualization": [[11, "visualization"]], "What should I do with the output?": [[19, "what-should-i-do-with-the-output"]], "Word": [[8, "word"]], "docTR Notebooks": [[12, null]], "docTR Vocabs": [[7, "id62"]], "docTR: Document Text Recognition": [[5, null]], "doctr.contrib": [[6, null]], "doctr.datasets": [[7, null], [7, "datasets"]], "doctr.io": [[8, null]], "doctr.models": [[9, null]], "doctr.models.classification": [[9, "doctr-models-classification"]], "doctr.models.detection": [[9, "doctr-models-detection"]], "doctr.models.factory": [[9, "doctr-models-factory"]], "doctr.models.recognition": [[9, "doctr-models-recognition"]], "doctr.models.zoo": [[9, "doctr-models-zoo"]], "doctr.transforms": [[10, null]], "doctr.utils": [[11, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.10.0 (2024-10-21)": [[0, "v0-10-0-2024-10-21"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]], "v0.7.0 (2023-09-09)": [[0, "v0-7-0-2023-09-09"]], "v0.8.0 (2024-02-28)": [[0, "v0-8-0-2024-02-28"]], "v0.8.1 (2024-03-04)": [[0, "v0-8-1-2024-03-04"]], "v0.9.0 (2024-08-08)": [[0, "v0-9-0-2024-08-08"]]}, "docnames": ["changelog", "community/resources", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/contrib", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_contrib_modules", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "community/resources.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/contrib.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_contrib_modules.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[8, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[8, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[10, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[7, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[10, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[10, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[7, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[7, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[9, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[7, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[7, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[8, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[8, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[7, "doctr.datasets.encode_sequences", false]], "fast_base() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_base", false]], "fast_small() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_small", false]], "fast_tiny() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_tiny", false]], "from_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[7, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[10, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[10, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[7, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[7, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[7, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[7, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[7, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[9, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[10, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[8, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet34() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[7, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_crop_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_crop_orientation", false]], "mobilenet_v3_small_page_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_page_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[10, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[9, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[7, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[10, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[8, "doctr.io.Page", false]], "page_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.page_orientation_predictor", false]], "parseq() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[10, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[10, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[10, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[10, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[10, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[10, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[10, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[10, "doctr.transforms.RandomJpegQuality", false]], "randomresize (class in doctr.transforms)": [[10, "doctr.transforms.RandomResize", false]], "randomrotate (class in doctr.transforms)": [[10, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[10, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[10, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[8, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[8, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[8, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[7, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[10, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[8, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[8, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[7, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[7, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[7, "doctr.datasets.SVT", false]], "synthtext (class in doctr.datasets)": [[7, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.TextMatch", false]], "textnet_base() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_base", false]], "textnet_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_small", false]], "textnet_tiny() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_tiny", false]], "togray (class in doctr.transforms)": [[10, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[11, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_small", false]], "wildreceipt (class in doctr.datasets)": [[7, "doctr.datasets.WILDRECEIPT", false]], "word (class in doctr.io)": [[8, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[7, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[7, 0, 1, "", "CORD"], [7, 0, 1, "", "CharacterGenerator"], [7, 0, 1, "", "DetectionDataset"], [7, 0, 1, "", "DocArtefacts"], [7, 0, 1, "", "FUNSD"], [7, 0, 1, "", "IC03"], [7, 0, 1, "", "IC13"], [7, 0, 1, "", "IIIT5K"], [7, 0, 1, "", "IIITHWS"], [7, 0, 1, "", "IMGUR5K"], [7, 0, 1, "", "MJSynth"], [7, 0, 1, "", "OCRDataset"], [7, 0, 1, "", "RecognitionDataset"], [7, 0, 1, "", "SROIE"], [7, 0, 1, "", "SVHN"], [7, 0, 1, "", "SVT"], [7, 0, 1, "", "SynthText"], [7, 0, 1, "", "WILDRECEIPT"], [7, 0, 1, "", "WordGenerator"], [7, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[7, 0, 1, "", "DataLoader"]], "doctr.io": [[8, 0, 1, "", "Artefact"], [8, 0, 1, "", "Block"], [8, 0, 1, "", "Document"], [8, 0, 1, "", "DocumentFile"], [8, 0, 1, "", "Line"], [8, 0, 1, "", "Page"], [8, 0, 1, "", "Word"], [8, 1, 1, "", "decode_img_as_tensor"], [8, 1, 1, "", "read_html"], [8, 1, 1, "", "read_img_as_numpy"], [8, 1, 1, "", "read_img_as_tensor"], [8, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[8, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[8, 2, 1, "", "from_images"], [8, 2, 1, "", "from_pdf"], [8, 2, 1, "", "from_url"]], "doctr.io.Page": [[8, 2, 1, "", "show"]], "doctr.models": [[9, 1, 1, "", "kie_predictor"], [9, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[9, 1, 1, "", "crop_orientation_predictor"], [9, 1, 1, "", "magc_resnet31"], [9, 1, 1, "", "mobilenet_v3_large"], [9, 1, 1, "", "mobilenet_v3_large_r"], [9, 1, 1, "", "mobilenet_v3_small"], [9, 1, 1, "", "mobilenet_v3_small_crop_orientation"], [9, 1, 1, "", "mobilenet_v3_small_page_orientation"], [9, 1, 1, "", "mobilenet_v3_small_r"], [9, 1, 1, "", "page_orientation_predictor"], [9, 1, 1, "", "resnet18"], [9, 1, 1, "", "resnet31"], [9, 1, 1, "", "resnet34"], [9, 1, 1, "", "resnet50"], [9, 1, 1, "", "textnet_base"], [9, 1, 1, "", "textnet_small"], [9, 1, 1, "", "textnet_tiny"], [9, 1, 1, "", "vgg16_bn_r"], [9, 1, 1, "", "vit_b"], [9, 1, 1, "", "vit_s"]], "doctr.models.detection": [[9, 1, 1, "", "db_mobilenet_v3_large"], [9, 1, 1, "", "db_resnet50"], [9, 1, 1, "", "detection_predictor"], [9, 1, 1, "", "fast_base"], [9, 1, 1, "", "fast_small"], [9, 1, 1, "", "fast_tiny"], [9, 1, 1, "", "linknet_resnet18"], [9, 1, 1, "", "linknet_resnet34"], [9, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[9, 1, 1, "", "from_hub"], [9, 1, 1, "", "login_to_hub"], [9, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[9, 1, 1, "", "crnn_mobilenet_v3_large"], [9, 1, 1, "", "crnn_mobilenet_v3_small"], [9, 1, 1, "", "crnn_vgg16_bn"], [9, 1, 1, "", "master"], [9, 1, 1, "", "parseq"], [9, 1, 1, "", "recognition_predictor"], [9, 1, 1, "", "sar_resnet31"], [9, 1, 1, "", "vitstr_base"], [9, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[10, 0, 1, "", "ChannelShuffle"], [10, 0, 1, "", "ColorInversion"], [10, 0, 1, "", "Compose"], [10, 0, 1, "", "GaussianBlur"], [10, 0, 1, "", "GaussianNoise"], [10, 0, 1, "", "LambdaTransformation"], [10, 0, 1, "", "Normalize"], [10, 0, 1, "", "OneOf"], [10, 0, 1, "", "RandomApply"], [10, 0, 1, "", "RandomBrightness"], [10, 0, 1, "", "RandomContrast"], [10, 0, 1, "", "RandomCrop"], [10, 0, 1, "", "RandomGamma"], [10, 0, 1, "", "RandomHorizontalFlip"], [10, 0, 1, "", "RandomHue"], [10, 0, 1, "", "RandomJpegQuality"], [10, 0, 1, "", "RandomResize"], [10, 0, 1, "", "RandomRotate"], [10, 0, 1, "", "RandomSaturation"], [10, 0, 1, "", "RandomShadow"], [10, 0, 1, "", "Resize"], [10, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[11, 0, 1, "", "DetectionMetric"], [11, 0, 1, "", "LocalizationConfusion"], [11, 0, 1, "", "OCRMetric"], [11, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.visualization": [[11, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [2, 8, 9, 11, 15, 18], "0": [2, 4, 7, 10, 11, 13, 16, 17, 19], "00": 19, "01": 19, "0123456789": 7, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "02562": 9, "03": 19, "035": 19, "0361328125": 19, "04": 19, "05": 19, "06": 19, "06640625": 19, "07": 19, "08": [10, 19], "09": 19, "0966796875": 19, "1": [7, 8, 9, 10, 11, 13, 17, 19], "10": [4, 7, 11, 19], "100": [7, 10, 11, 17, 19], "1000": 19, "101": 7, "1024": [9, 13, 19], "104": 7, "106": 7, "108": 7, "1095": 17, "11": 19, "110": 11, "1107": 17, "114": 7, "115": 7, "1156": 17, "116": 7, "118": 7, "11800h": 19, "11th": 19, "12": 19, "120": 7, "123": 7, "126": 7, "1268": 17, "128": [9, 13, 18, 19], "13": 19, "130": 7, "13068": 17, "131": 7, "1337891": 17, "1357421875": 19, "1396484375": 19, "14": 19, "1420": 19, "14470v1": 7, "149": 17, "15": 19, "150": [11, 19], "1552": 19, "16": [9, 18, 19], "1630859375": 19, "1684": 19, "16x16": 9, "17": 19, "1778": 19, "1782": 19, "18": [9, 19], "185546875": 19, "1900": 19, "1910": 9, "19342": 17, "19370": 17, "195": 7, "19598": 17, "199": 19, "1999": 19, "2": [4, 5, 7, 8, 9, 10, 16, 19], "20": 19, "200": 11, "2000": 17, "2003": [5, 7], "2012": 7, "2013": [5, 7], "2015": 7, "2019": 5, "2023": 1, "207901": 17, "21": 19, "2103": 7, "2186": 17, "21888": 17, "22": 19, "224": [9, 10], "225": 10, "22672": 17, "229": [10, 17], "23": 19, "233": 17, "236": 7, "24": 19, "246": 17, "249": 17, "25": 19, "2504": 19, "255": [8, 9, 10, 11, 19], "256": 9, "257": 17, "26": 19, "26032": 17, "264": 13, "27": 19, "2700": 17, "2710": 19, "2749": 13, "28": 19, "287": 13, "29": 19, "296": 13, "299": 13, "2d": 19, "3": [4, 5, 8, 9, 10, 11, 18, 19], "30": 19, "300": 17, "3000": 17, "301": 13, "30595": 19, "30ghz": 19, "31": 9, "32": [7, 9, 10, 13, 17, 18, 19], "3232421875": 19, "33": [10, 19], "33402": 17, "33608": 17, "34": [9, 19], "340": 19, "3456": 19, "3515625": 19, "36": 19, "360": 17, "37": [7, 19], "38": 19, "39": 19, "4": [9, 10, 11, 19], "40": 19, "406": 10, "41": 19, "42": 19, "43": 19, "44": 19, "45": 19, "456": 10, "46": 19, "47": 19, "472": 17, "48": [7, 19], "485": 10, "49": 19, "49377": 17, "5": [7, 10, 11, 16, 19], "50": [9, 17, 19], "51": 19, "51171875": 19, "512": 9, "52": [7, 19], "529": 19, "53": 19, "54": 19, "540": 19, "5478515625": 19, "55": 19, "56": 19, "57": 19, "58": [7, 19], "580": 19, "5810546875": 19, "583": 19, "59": 19, "597": 19, "5k": [5, 7], "5m": 19, "6": [10, 19], "60": 10, "600": [9, 11, 19], "61": 19, "62": 19, "626": 17, "63": 19, "64": [9, 10, 19], "641": 19, "647": 17, "65": 19, "66": 19, "67": 19, "68": 19, "69": 19, "693": 13, "694": 13, "695": 13, "6m": 19, "7": 19, "70": [7, 11, 19], "707470": 17, "71": [7, 19], "7100000": 17, "7141797": 17, "7149": 17, "72": 19, "72dpi": 8, "73": 19, "73257": 17, "74": 19, "75": [10, 19], "7581382": 17, "76": 19, "77": 19, "772": 13, "772875": 17, "78": 19, "785": 13, "79": 19, "793533": 17, "796": 17, "798": 13, "7m": 19, "8": [9, 10, 19], "80": 19, "800": [9, 11, 17, 19], "81": 19, "82": 19, "83": 19, "84": 19, "849": 17, "85": 19, "8564453125": 19, "857": 19, "85875": 17, "86": 19, "8603515625": 19, "87": 19, "8707": 17, "88": 19, "89": 19, "9": [10, 19], "90": 19, "90k": 7, "90kdict32px": 7, "91": 19, "914085328578949": 19, "92": 19, "93": 19, "94": [7, 19], "95": [11, 19], "9578408598899841": 19, "96": 19, "97": 19, "98": 19, "99": 19, "9949972033500671": 19, "A": [2, 3, 5, 7, 8, 9, 12, 18], "As": 3, "Be": 19, "Being": 2, "By": 14, "For": [2, 3, 4, 13, 19], "If": [3, 8, 9, 13, 19], "In": [3, 7, 17], "It": [10, 15, 16, 18], "Its": [5, 9], "No": [2, 19], "Of": 7, "Or": [16, 18], "The": [2, 3, 7, 8, 11, 14, 16, 17, 18, 19], "Then": 9, "To": [3, 4, 14, 15, 16, 18, 19], "_": [2, 7, 9], "__call__": 19, "_build": 3, "_i": 11, "ab": 7, "abc": 18, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "abdef": [7, 17], "abl": [17, 19], "about": [2, 17, 19], "abov": 19, "abstract": 1, "abstractdataset": 7, "abus": 2, "accept": 2, "access": [5, 8, 17, 19], "account": [2, 15], "accur": 19, "accuraci": 11, "achiev": 18, "act": 2, "action": 2, "activ": 5, "ad": [3, 9, 10], "adapt": 2, "add": [10, 11, 15, 19], "add_hook": 19, "add_label": 11, "addit": [3, 4, 8, 16, 19], "addition": [3, 19], "address": [2, 8], "adjust": 10, "advanc": 2, "advantag": 18, "advis": 3, "aesthet": [5, 7], "affect": 2, "after": [15, 19], "ag": 2, "again": 9, "aggreg": [11, 17], "aggress": 2, "align": [2, 8, 10], "all": [2, 3, 6, 7, 8, 10, 11, 16, 17, 19], "allow": [2, 18], "along": 19, "alreadi": [3, 18], "also": [2, 9, 15, 16, 17, 19], "alwai": 17, "an": [2, 3, 5, 7, 8, 9, 11, 16, 18, 19], "analysi": [8, 16], "ancient_greek": 7, "andrej": 1, "angl": [8, 10], "ani": [2, 7, 8, 9, 10, 11, 18, 19], "annot": 7, "anot": 17, "anoth": [9, 13, 17], "answer": 2, "anyascii": 11, "anyon": 5, "anyth": 16, "api": [3, 5], "apolog": 2, "apologi": 2, "app": 3, "appear": 2, "appli": [2, 7, 10], "applic": [5, 9], "appoint": 2, "appreci": 15, "appropri": [2, 3, 19], "ar": [2, 3, 4, 6, 7, 8, 10, 11, 12, 16, 17, 19], "arab": 7, "arabic_diacrit": 7, "arabic_lett": 7, "arabic_punctu": 7, "arbitrarili": [5, 9], "arch": [9, 15], "architectur": [5, 9, 15, 16], "area": 19, "argument": [7, 8, 9, 11, 13, 19], "around": 2, "arrai": [8, 10, 11], "art": [5, 16], "artefact": [11, 16, 19], "artefact_typ": 8, "articl": 1, "artifici": [5, 7], "arxiv": [7, 9], "asarrai": 11, "ascii_lett": 7, "aspect": [5, 9, 10, 19], "assess": 11, "assign": 11, "associ": 8, "assum": 9, "assume_straight_pag": [9, 13, 19], "astyp": [9, 11, 19], "attack": 2, "attend": [5, 9], "attent": [2, 9], "autom": 5, "automat": 19, "autoregress": [5, 9], "avail": [2, 5, 6, 10], "averag": [10, 19], "avoid": [2, 4], "aw": [5, 19], "awar": 19, "azur": 19, "b": [9, 11, 19], "b_j": 11, "back": 3, "backbon": 9, "backend": 19, "background": 17, "bangla": 7, "bar": 16, "bar_cod": 17, "baranovskij": 1, "base": [5, 9, 16], "baselin": [5, 9, 19], "batch": [7, 9, 10, 16, 17, 19], "batch_siz": [7, 9, 13, 16, 17, 18], "bblanchon": 4, "bbox": 19, "becaus": 14, "been": [3, 11, 17, 19], "befor": [7, 9, 10, 19], "begin": 11, "behavior": [2, 19], "being": [11, 19], "belong": 19, "benchmark": 19, "best": [1, 2], "better": [12, 19], "between": [10, 11, 19], "bgr": 8, "bilinear": 10, "bin_thresh": 19, "binar": [5, 9, 19], "binari": [8, 18, 19], "bit": 18, "block": [11, 19], "block_1_1": 19, "blur": 10, "bmvc": 7, "bn": 15, "bodi": [2, 19], "bool": [7, 8, 9, 10, 11], "boolean": [9, 19], "both": [5, 7, 10, 17, 19], "bottom": [9, 19], "bound": [7, 8, 9, 10, 11, 16, 17, 19], "box": [7, 8, 9, 10, 11, 16, 17, 19], "box_thresh": 19, "bright": 10, "browser": [3, 5], "build": [3, 4, 18], "built": 3, "byte": [8, 19], "c": [4, 8, 11], "c_j": 11, "cach": [3, 7, 14], "cache_sampl": 7, "call": 18, "callabl": [7, 10], "can": [3, 4, 13, 14, 15, 16, 17, 19], "capabl": [3, 12, 19], "case": [7, 11], "cf": 19, "cfg": 19, "challeng": 7, "challenge2_test_task12_imag": 7, "challenge2_test_task1_gt": 7, "challenge2_training_task12_imag": 7, "challenge2_training_task1_gt": 7, "chang": [14, 19], "channel": [2, 3, 8, 10], "channel_prior": 4, "channelshuffl": 10, "charact": [5, 7, 8, 11, 17, 19], "charactergener": [7, 17], "characterist": 2, "charg": 19, "charset": 19, "chart": 8, "check": [3, 15, 19], "checkpoint": 9, "chip": 4, "christian": 1, "ci": 3, "clarifi": 2, "clariti": 2, "class": [2, 7, 8, 10, 11, 19], "class_nam": 13, "classif": [17, 19], "classmethod": 8, "clear": 3, "clone": 4, "close": 3, "co": 15, "code": [5, 8, 16], "codecov": 3, "colab": 12, "collate_fn": 7, "collect": [8, 16], "color": 10, "colorinvers": 10, "column": 8, "com": [2, 4, 8, 9, 15], "combin": 19, "command": [3, 16], "comment": 2, "commit": 2, "common": [2, 10, 11, 18], "commun": 2, "compar": 5, "comparison": [11, 19], "competit": 7, "compil": [12, 19], "complaint": 2, "complementari": 11, "complet": 3, "compon": 19, "compos": [7, 19], "comprehens": 19, "comput": [7, 11, 18, 19], "conf_threshold": 16, "confid": [8, 19], "config": [4, 9], "configur": 9, "confus": 11, "consecut": [10, 19], "consequ": 2, "consid": [2, 3, 7, 8, 11, 19], "consist": 19, "consolid": [5, 7], "constant": 10, "construct": 2, "contact": 2, "contain": [1, 6, 7, 12, 17, 19], "content": [7, 8, 19], "context": 9, "contib": 4, "continu": 2, "contrast": 10, "contrast_factor": 10, "contrib": [4, 16], "contribut": 2, "contributor": 3, "convers": 8, "convert": [8, 10], "convolut": 9, "cool": 1, "coordin": [8, 19], "cord": [5, 7, 17, 19], "core": [11, 19], "corner": 19, "correct": 10, "correspond": [4, 8, 10, 19], "could": [2, 16], "counterpart": 11, "cover": 3, "coverag": 3, "cpu": [5, 13, 18], "creat": [1, 15], "crnn": [5, 9, 15], "crnn_mobilenet_v3_larg": [9, 15, 19], "crnn_mobilenet_v3_smal": [9, 18, 19], "crnn_vgg16_bn": [9, 13, 15, 19], "crop": [8, 9, 10, 13, 17, 19], "crop_orient": [8, 19], "crop_orientation_predictor": [9, 13], "crop_param": 13, "cuda": 18, "currenc": 7, "current": [3, 13, 19], "custom": [15, 16, 18, 19], "custom_crop_orientation_model": 13, "custom_page_orientation_model": 13, "customhook": 19, "cvit": 5, "czczup": 9, "czech": 7, "d": [7, 17], "danish": 7, "data": [5, 7, 8, 10, 11, 13, 15], "dataload": 17, "dataset": [9, 13, 19], "dataset_info": 7, "date": [13, 19], "db": 15, "db_mobilenet_v3_larg": [9, 15, 19], "db_resnet34": 19, "db_resnet50": [9, 13, 15, 19], "dbnet": [5, 9], "deal": [12, 19], "decis": 2, "decod": 8, "decode_img_as_tensor": 8, "dedic": 18, "deem": 2, "deep": [9, 19], "def": 19, "default": [4, 8, 13, 14, 19], "defer": 17, "defin": [11, 18], "degre": [8, 10, 19], "degress": 8, "delet": 3, "delimit": 19, "delta": 10, "demo": [3, 5], "demonstr": 2, "depend": [3, 4, 5, 19], "deploi": 3, "deploy": 5, "derogatori": 2, "describ": 9, "descript": 12, "design": 10, "desir": 8, "det_arch": [9, 13, 15, 18], "det_b": 19, "det_model": [13, 15, 18], "det_param": 13, "det_predictor": [13, 19], "detail": [13, 19], "detect": [1, 7, 8, 11, 12, 13, 16], "detect_languag": 9, "detect_orient": [9, 13, 19], "detection_predictor": [9, 19], "detection_task": [7, 17], "detectiondataset": [7, 17], "detectionmetr": 11, "detectionpredictor": [9, 13], "detector": [5, 9, 16], "deterior": 9, "determin": 2, "dev": [3, 14], "develop": 4, "deviat": 10, "devic": 18, "dict": [8, 11, 19], "dictionari": [8, 11], "differ": 2, "differenti": [5, 9], "digit": [5, 7, 17], "dimens": [8, 11, 19], "dimension": 10, "direct": 7, "directli": [15, 19], "directori": [3, 14], "disabl": [2, 14, 19], "disable_crop_orient": 19, "disable_page_orient": 19, "disclaim": 19, "discuss": 3, "disparag": 2, "displai": [8, 11], "display_artefact": 11, "distribut": 10, "div": 19, "divers": 2, "divid": 8, "do": [3, 4, 9], "doc": [3, 8, 16, 18, 19], "docartefact": [7, 17], "docstr": 3, "doctr": [1, 4, 13, 14, 15, 16, 17, 18, 19], "doctr_cache_dir": 14, "doctr_multiprocessing_dis": 14, "document": [1, 7, 9, 11, 12, 13, 16, 17, 18, 19], "documentbuild": 19, "documentfil": [8, 13, 15, 16, 18], "doesn": 18, "don": [13, 19], "done": 10, "download": [7, 17], "downsiz": 9, "draw": 10, "drop": 7, "drop_last": 7, "dtype": [8, 9, 10, 11, 18], "dual": [5, 7], "dummi": 15, "dummy_img": 19, "dummy_input": 18, "dure": 2, "dutch": 7, "dynam": [7, 16], "dynamic_seq_length": 7, "e": [2, 3, 4, 8, 9], "each": [5, 7, 8, 9, 10, 11, 17, 19], "eas": 3, "easi": [5, 11, 15, 18], "easili": [8, 11, 13, 15, 17, 19], "econom": 2, "edit": 2, "educ": 2, "effect": 19, "effici": [3, 5, 7, 9], "either": [11, 19], "element": [7, 8, 9, 19], "els": [3, 16], "email": 2, "empathi": 2, "en": 19, "enabl": [7, 8], "enclos": 8, "encod": [5, 7, 8, 9, 19], "encode_sequ": 7, "encount": 3, "encrypt": 8, "end": [5, 7, 9, 11], "english": [7, 17], "enough": [3, 19], "ensur": 3, "entri": 7, "environ": [2, 14], "eo": 7, "equiv": 19, "estim": 9, "etc": [8, 16], "ethnic": 2, "evalu": [17, 19], "event": 2, "everyon": 2, "everyth": [3, 19], "exact": [11, 19], "exampl": [2, 3, 5, 7, 9, 15, 19], "exchang": 18, "execut": 19, "exist": 15, "expand": 10, "expect": [8, 10, 11], "experi": 2, "explan": [2, 19], "explicit": 2, "exploit": [5, 9], "export": [8, 9, 11, 12, 16, 19], "export_as_straight_box": [9, 19], "export_as_xml": 19, "export_model_to_onnx": 18, "express": [2, 10], "extens": 8, "extern": [2, 17], "extract": [1, 5, 7], "extractor": 9, "f_": 11, "f_a": 11, "factor": 10, "fair": 2, "fairli": 2, "fals": [7, 8, 9, 10, 11, 13, 19], "faq": 2, "fascan": 15, "fast": [5, 7, 9], "fast_bas": [9, 19], "fast_smal": [9, 19], "fast_tini": [9, 19], "faster": [5, 9, 18], "fasterrcnn_mobilenet_v3_large_fpn": 9, "favorit": 19, "featur": [4, 9, 11, 12, 13, 16], "feedback": 2, "feel": [3, 15], "felix92": 15, "few": [18, 19], "figsiz": 11, "figur": [11, 16], "file": [3, 7], "final": 9, "find": [3, 17], "fine": 1, "finnish": 7, "first": [3, 7], "firsthand": 7, "fit": [9, 19], "flag": 19, "flip": 10, "float": [8, 10, 11, 18], "float32": [8, 9, 10, 18], "fn": 10, "focu": 15, "focus": [2, 7], "folder": 7, "follow": [2, 3, 4, 7, 10, 11, 13, 14, 15, 16, 19], "font": 7, "font_famili": 7, "foral": 11, "forc": 3, "forg": 4, "form": [5, 7, 19], "format": [8, 11, 13, 17, 18, 19], "forpost": [5, 7], "forum": 3, "found": 1, "fp16": 18, "frac": 11, "framework": [4, 15, 17, 19], "free": [2, 3, 15], "french": [7, 13, 15, 19], "friendli": 5, "from": [1, 2, 5, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19], "from_hub": [9, 15], "from_imag": [8, 15, 16, 18], "from_pdf": 8, "from_url": 8, "full": [7, 11, 19], "function": [7, 10, 11, 16], "funsd": [5, 7, 17, 19], "further": 17, "futur": 7, "g": [8, 9], "g_": 11, "g_x": 11, "gallagh": 1, "gamma": 10, "gaussian": 10, "gaussianblur": 10, "gaussiannois": 10, "gen": 19, "gender": 2, "gener": [3, 5, 8, 9], "generic_cyrillic_lett": 7, "geometri": [5, 8, 19], "geq": 11, "german": [7, 13, 15], "get": [18, 19], "git": 15, "github": [3, 4, 9, 15], "give": [2, 16], "given": [7, 8, 10, 11, 19], "global": 9, "go": 19, "good": 18, "googl": 3, "googlevis": 5, "gpu": [5, 16, 18], "gracefulli": 2, "graph": [5, 7, 8], "grayscal": 10, "ground": 11, "groung": 11, "group": [5, 19], "gt": 11, "gt_box": 11, "gt_label": 11, "guid": 3, "guidanc": 17, "gvision": 19, "h": [8, 9, 10], "h_": 11, "ha": [3, 7, 11, 17], "handl": [12, 17, 19], "handwrit": 7, "handwritten": 17, "harass": 2, "hardwar": 19, "harm": 2, "hat": 11, "have": [2, 3, 11, 13, 15, 17, 18, 19], "head": [9, 19], "healthi": 2, "hebrew": 7, "height": [8, 10], "hello": [11, 19], "help": 18, "here": [6, 10, 12, 16, 17, 19], "hf": 9, "hf_hub_download": 9, "high": 8, "higher": [4, 7, 19], "hindi": 7, "hindi_digit": 7, "hocr": 19, "hook": 19, "horizont": [8, 10, 19], "hous": 7, "how": [1, 3, 12, 13, 15, 17], "howev": 17, "hsv": 10, "html": [2, 3, 4, 8, 19], "http": [2, 4, 7, 8, 9, 15, 19], "hub": 9, "hue": 10, "huggingfac": 9, "hw": 7, "i": [2, 3, 7, 8, 9, 10, 11, 14, 15, 16, 17, 18], "i7": 19, "ibrahimov": 1, "ic03": [5, 7, 17], "ic13": [5, 7, 17], "icdar": [5, 7], "icdar2019": 7, "id": 19, "ident": 2, "identifi": 5, "iiit": [5, 7], "iiit5k": [7, 17], "iiithw": [5, 7, 17], "imag": [1, 5, 7, 8, 9, 10, 11, 15, 16, 17, 19], "imagenet": 9, "imageri": 2, "images_90k_norm": 7, "img": [7, 10, 17, 18], "img_cont": 8, "img_fold": [7, 17], "img_path": 8, "img_transform": 7, "imgur5k": [5, 7, 17], "imgur5k_annot": 7, "imlist": 7, "impact": 2, "implement": [7, 8, 9, 10, 11, 19], "import": [7, 8, 9, 10, 11, 13, 15, 16, 17, 18, 19], "improv": 9, "inappropri": 2, "incid": 2, "includ": [2, 7, 17, 18], "inclus": 2, "increas": 10, "independ": 10, "index": [3, 8], "indic": 11, "individu": 2, "infer": [5, 9, 10, 16, 19], "inform": [1, 2, 3, 5, 7, 17], "input": [3, 8, 9, 10, 18, 19], "input_crop": 9, "input_pag": [9, 11, 19], "input_shap": 18, "input_tensor": 9, "inspir": [2, 10], "instal": [15, 16, 18], "instanc": [2, 19], "instanti": [9, 19], "instead": [7, 8, 9], "insult": 2, "int": [7, 8, 9, 10], "int64": 11, "integ": 11, "integr": [1, 5, 15, 17], "intel": 19, "interact": [2, 8, 11], "interfac": [15, 18], "interoper": 18, "interpol": 10, "interpret": [7, 8], "intersect": 11, "invert": 10, "investig": 2, "invis": 2, "involv": [2, 19], "io": [13, 15, 16, 18], "iou": 11, "iou_thresh": 11, "iou_threshold": 16, "irregular": [5, 9, 17], "isn": 7, "issu": [2, 3, 15], "italian": 7, "iter": [7, 10, 17, 19], "its": [8, 9, 10, 11, 17, 19], "itself": [9, 15], "j": 11, "jame": 1, "job": 3, "join": 3, "jpeg": 10, "jpegqual": 10, "jpg": [7, 8, 15, 18], "json": [7, 17, 19], "json_output": 19, "jump": 3, "just": 2, "kei": [5, 7], "kera": [9, 18], "kernel": [5, 9, 10], "kernel_shap": 10, "keywoard": 9, "keyword": [7, 8, 9, 11], "kie": [9, 13], "kie_predictor": [9, 13], "kiepredictor": 9, "kind": 2, "know": [3, 18], "kwarg": [7, 8, 9, 11], "l": 11, "l_j": 11, "label": [7, 11, 16, 17], "label_fil": [7, 17], "label_fold": 7, "label_path": [7, 17], "labels_path": [7, 17], "ladder": 2, "lambda": 10, "lambdatransform": 10, "lang": 19, "languag": [2, 5, 7, 8, 9, 15, 19], "larg": [9, 15], "largest": 11, "last": [4, 7], "latenc": 9, "later": 3, "latest": 19, "latin": 7, "layer": 18, "layout": 19, "lead": 2, "leader": 2, "learn": [2, 5, 9, 18, 19], "least": 4, "left": [11, 19], "legacy_french": 7, "length": [7, 19], "less": [18, 19], "level": [2, 7, 11, 19], "leverag": 12, "lf": 15, "librari": [3, 4, 12, 13], "light": 5, "lightweight": 18, "like": 2, "limits_": 11, "line": [5, 9, 11, 19], "line_1_1": 19, "link": 13, "linknet": [5, 9], "linknet_resnet18": [9, 13, 18, 19], "linknet_resnet34": [9, 18, 19], "linknet_resnet50": [9, 19], "list": [7, 8, 10, 11, 15], "ll": 11, "load": [5, 7, 9, 16, 18], "load_state_dict": 13, "load_weight": 13, "loc_pr": 19, "local": [3, 5, 7, 9, 11, 17, 19], "localis": 7, "localizationconfus": 11, "locat": [3, 8, 19], "login": 9, "login_to_hub": [9, 15], "logo": [8, 16, 17], "love": 15, "lower": [10, 11, 19], "m": [3, 11, 19], "m1": 4, "macbook": 4, "machin": 18, "made": 5, "magc_resnet31": 9, "mai": [2, 3], "mail": 2, "main": 12, "maintain": 5, "mainten": 3, "make": [2, 3, 11, 13, 14, 15, 18, 19], "mani": [17, 19], "manipul": 19, "map": [7, 9], "map_loc": 13, "master": [5, 9, 19], "match": [11, 19], "mathcal": 11, "matplotlib": [8, 11], "max": [7, 10, 11], "max_angl": 10, "max_area": 10, "max_char": [7, 17], "max_delta": 10, "max_gain": 10, "max_gamma": 10, "max_qual": 10, "max_ratio": 10, "maximum": [7, 10], "maxval": [9, 10], "mbox": 11, "mean": [10, 11, 13], "meaniou": 11, "meant": [8, 18], "measur": 19, "media": 2, "median": 9, "meet": 13, "member": 2, "memori": [14, 18], "mention": 19, "merg": 7, "messag": 3, "meta": 19, "metadata": 18, "metal": 4, "method": [8, 10, 19], "metric": [11, 19], "middl": 19, "might": [18, 19], "min": 10, "min_area": 10, "min_char": [7, 17], "min_gain": 10, "min_gamma": 10, "min_qual": 10, "min_ratio": 10, "min_val": 10, "minde": [1, 2, 4, 5, 9], "minim": [3, 5], "minimalist": [5, 9], "minimum": [4, 7, 10, 11, 19], "minval": 10, "miss": 4, "mistak": 2, "mixed_float16": 18, "mixed_precis": 18, "mjsynth": [5, 7, 17], "mnt": 7, "mobilenet": [9, 15], "mobilenet_v3_larg": 9, "mobilenet_v3_large_r": 9, "mobilenet_v3_smal": [9, 13], "mobilenet_v3_small_crop_orient": [9, 13], "mobilenet_v3_small_page_orient": [9, 13], "mobilenet_v3_small_r": 9, "mobilenetv3": 9, "modal": [5, 7], "mode": 4, "model": [7, 11, 14, 16, 17], "model_nam": [9, 15, 18], "model_path": [16, 18], "moder": 2, "modif": 3, "modifi": [9, 14, 19], "modul": [4, 8, 9, 10, 11, 19], "more": [3, 17, 19], "moscardi": 1, "most": 19, "mozilla": 2, "multi": [5, 9], "multilingu": [7, 15], "multipl": [7, 8, 10, 19], "multipli": 10, "multiprocess": 14, "my": 9, "my_awesome_model": 15, "my_hook": 19, "n": [7, 11], "name": [7, 9, 18, 19], "nation": 2, "natur": [2, 5, 7], "ndarrai": [7, 8, 10, 11], "necessari": [4, 13, 14], "need": [3, 4, 7, 11, 13, 14, 15, 16, 19], "neg": 10, "nest": 19, "netraj": 1, "network": [5, 7, 9, 18], "neural": [5, 7, 9, 18], "new": [3, 11], "next": [7, 17], "nois": 10, "noisi": [5, 7], "non": [5, 7, 8, 9, 10, 11], "none": [7, 8, 9, 10, 11, 19], "normal": [9, 10], "norwegian": 7, "note": [0, 3, 7, 9, 13, 15, 16, 18], "now": 3, "np": [9, 10, 11, 19], "num_output_channel": 10, "num_sampl": [7, 17], "number": [7, 9, 10, 11, 19], "numpi": [8, 9, 11, 19], "o": 4, "obb": 16, "obj_detect": 15, "object": [7, 8, 11, 16, 19], "objectness_scor": [8, 19], "oblig": 2, "obtain": 19, "occupi": 18, "ocr": [1, 5, 7, 9, 11, 15], "ocr_carea": 19, "ocr_db_crnn": 11, "ocr_lin": 19, "ocr_pag": 19, "ocr_par": 19, "ocr_predictor": [9, 13, 15, 18, 19], "ocrdataset": [7, 17], "ocrmetr": 11, "ocrpredictor": [9, 13], "ocrx_word": 19, "offens": 2, "offici": [2, 9], "offlin": 2, "offset": 10, "onc": 19, "one": [3, 7, 9, 10, 13, 15, 19], "oneof": 10, "ones": [7, 11], "onli": [3, 9, 10, 11, 13, 15, 17, 18, 19], "onlin": 2, "onnx": 16, "onnxruntim": [16, 18], "onnxtr": 18, "opac": 10, "opacity_rang": 10, "open": [1, 2, 3, 15, 18], "opinion": 2, "optic": [5, 19], "optim": [5, 19], "option": [7, 9, 13], "order": [3, 7, 8, 10], "org": [2, 7, 9, 19], "organ": 8, "orient": [2, 8, 9, 12, 16, 19], "orientationpredictor": 9, "other": [2, 3], "otherwis": [2, 8, 11], "our": [1, 3, 9, 19], "out": [3, 9, 10, 11, 19], "outpout": 19, "output": [8, 10, 18], "output_s": [8, 10], "outsid": 14, "over": [7, 11, 19], "overal": [2, 9], "overlai": 8, "overview": 16, "overwrit": 13, "overwritten": 15, "own": 5, "p": [10, 19], "packag": [3, 5, 11, 14, 16, 17, 18], "pad": [7, 9, 10, 19], "page": [4, 7, 9, 11, 13, 19], "page1": 8, "page2": 8, "page_1": 19, "page_idx": [8, 19], "page_orientation_predictor": [9, 13], "page_param": 13, "pair": 11, "paper": 9, "par_1_1": 19, "paragraph": 19, "paragraph_break": 19, "parallel": 9, "param": [10, 19], "paramet": [5, 8, 9, 18], "pars": [5, 7], "parseq": [5, 9, 15, 18, 19], "part": [7, 10, 19], "parti": 4, "partial": 19, "particip": 2, "pass": [7, 8, 9, 13, 19], "password": 8, "patch": [9, 11], "path": [7, 8, 16, 17, 18], "path_to_checkpoint": 13, "path_to_custom_model": 18, "path_to_pt": 13, "patil": 1, "pattern": 2, "pdf": [8, 9, 12], "pdfpage": 8, "peopl": 2, "per": [10, 19], "perform": [5, 8, 9, 10, 11, 14, 18, 19], "period": 2, "permiss": 2, "permut": [5, 9], "persian_lett": 7, "person": [2, 17], "phase": 19, "photo": 17, "physic": [2, 8], "pick": 10, "pictur": 8, "pip": [3, 4, 16, 18], "pipelin": 19, "pixel": [8, 10, 19], "pleas": 3, "plot": 11, "plt": 11, "plug": 15, "plugin": 4, "png": 8, "point": 18, "polici": 14, "polish": 7, "polit": 2, "polygon": [7, 11, 19], "pool": 9, "portugues": 7, "posit": [2, 11], "possibl": [3, 11, 15, 19], "post": [2, 19], "postprocessor": 19, "potenti": 9, "power": 5, "ppageno": 19, "pre": [3, 9, 18], "precis": [11, 19], "pred": 11, "pred_box": 11, "pred_label": 11, "predefin": 17, "predict": [8, 9, 11, 19], "predictor": [5, 8, 9, 12, 13, 15, 18], "prefer": 17, "preinstal": 4, "preprocessor": [13, 19], "prerequisit": 15, "present": 12, "preserv": [9, 10, 19], "preserve_aspect_ratio": [8, 9, 10, 13, 19], "pretrain": [5, 9, 11, 13, 18, 19], "pretrained_backbon": [9, 13], "print": 19, "prior": 7, "privaci": 2, "privat": 2, "probabl": [1, 10], "problem": 3, "procedur": 10, "process": [3, 5, 8, 9, 13, 19], "processor": 19, "produc": [12, 19], "product": 18, "profession": 2, "project": [3, 17], "promptli": 2, "proper": 3, "properli": 7, "provid": [2, 3, 5, 15, 16, 17, 19], "public": [2, 5], "publicli": 19, "publish": 2, "pull": 15, "punctuat": 7, "pure": 7, "purpos": 3, "push_to_hf_hub": [9, 15], "py": 15, "pypdfium2": [4, 8], "pyplot": [8, 11], "python": [1, 3, 16], "python3": 15, "pytorch": [4, 5, 9, 10, 13, 15, 18, 19], "q": 3, "qr": [8, 16], "qr_code": 17, "qualiti": 10, "question": 2, "quickli": 5, "quicktour": 12, "r": 19, "race": 2, "ramdisk": 7, "rand": [9, 10, 11, 18, 19], "random": [9, 10, 11, 19], "randomappli": 10, "randombright": 10, "randomcontrast": 10, "randomcrop": 10, "randomgamma": 10, "randomhorizontalflip": 10, "randomhu": 10, "randomjpegqu": 10, "randomli": 10, "randomres": 10, "randomrot": 10, "randomsatur": 10, "randomshadow": 10, "rang": 10, "rassi": 15, "ratio": [9, 10, 19], "raw": [8, 11], "re": 18, "read": [5, 7, 9], "read_html": 8, "read_img_as_numpi": 8, "read_img_as_tensor": 8, "read_pdf": 8, "readi": 18, "real": [1, 5, 9, 10], "realli": 1, "reason": [2, 5, 7], "rebuild": 3, "rebuilt": 3, "recal": [11, 19], "receipt": [5, 7, 19], "reco_arch": [9, 13, 15, 18], "reco_b": 19, "reco_model": [13, 15, 18], "reco_param": 13, "reco_predictor": 13, "recogn": 19, "recognit": [7, 11, 12, 13], "recognition_predictor": [9, 19], "recognition_task": [7, 17], "recognitiondataset": [7, 17], "recognitionpredictor": [9, 13], "rectangular": 9, "reduc": [4, 10], "refer": [3, 4, 13, 15, 16, 17, 19], "regardless": 2, "region": 19, "regroup": 11, "regular": 17, "reject": 2, "rel": [8, 10, 11, 19], "relat": 8, "releas": [0, 4], "relev": 16, "religion": 2, "remov": 2, "render": [8, 19], "repo": 9, "repo_id": [9, 15], "report": 2, "repositori": [7, 9, 15], "repres": [2, 18, 19], "represent": [5, 9], "request": [2, 15], "requir": [4, 10, 18], "research": 5, "residu": 9, "resiz": [10, 19], "resnet": 9, "resnet18": [9, 15], "resnet31": 9, "resnet34": 9, "resnet50": [9, 15], "resolv": 8, "resolve_block": 19, "resolve_lin": 19, "resourc": 17, "respect": 2, "rest": [3, 10, 11], "restrict": 14, "result": [3, 7, 8, 12, 15, 18, 19], "return": 19, "reusabl": 19, "review": 2, "rgb": [8, 10], "rgb_mode": 8, "rgb_output": 8, "right": [2, 9, 11], "roboflow": 1, "robust": [5, 7], "root": 7, "rotat": [7, 8, 9, 10, 11, 12, 13, 17, 19], "run": [3, 4, 9], "same": [3, 8, 11, 17, 18, 19], "sampl": [7, 9, 17, 19], "sample_transform": 7, "sanjin": 1, "sar": [5, 9], "sar_resnet31": [9, 19], "satur": 10, "save": [9, 17], "scale": [8, 9, 10, 11], "scale_rang": 10, "scan": [5, 7], "scene": [5, 7, 9], "score": [8, 11], "script": [3, 17], "seamless": 5, "seamlessli": [5, 19], "search": [1, 9], "searchabl": 12, "sec": 19, "second": 19, "section": [1, 13, 15, 16, 18, 19], "secur": [2, 14], "see": [2, 3], "seen": 19, "segment": [5, 9, 19], "self": 19, "semant": [5, 9], "send": 19, "sens": 11, "sensit": 17, "separ": 19, "sequenc": [5, 7, 8, 9, 11, 19], "sequenti": [10, 19], "seri": 2, "seriou": 2, "set": [2, 4, 7, 9, 11, 14, 16, 19], "set_global_polici": 18, "sever": [8, 10, 19], "sex": 2, "sexual": 2, "shade": 10, "shape": [5, 8, 9, 10, 11, 19], "share": [14, 17], "shift": 10, "shm": 14, "should": [3, 7, 8, 10, 11], "show": [5, 8, 9, 11, 13, 15, 16], "showcas": [3, 12], "shuffl": [7, 10], "side": 11, "signatur": 8, "signific": 17, "simpl": [5, 9, 18], "simpler": 9, "sinc": [7, 17], "singl": [2, 3, 5, 7], "single_img_doc": 18, "size": [2, 7, 8, 10, 16, 19], "skew": 19, "slack": 3, "slightli": 9, "small": [3, 9, 19], "smallest": 8, "snapshot_download": 9, "snippet": 19, "so": [3, 4, 7, 9, 15, 17], "social": 2, "socio": 2, "some": [1, 4, 12, 15, 17], "someth": 3, "somewher": 3, "sort": 2, "sourc": [1, 7, 8, 9, 10, 11, 15], "space": [2, 19], "span": 19, "spanish": 7, "spatial": [5, 7, 8], "specif": [3, 4, 11, 13, 17, 19], "specifi": [2, 7, 8], "speed": [5, 9, 19], "sphinx": 3, "sroie": [5, 7, 17], "stabl": 4, "stackoverflow": 3, "stage": 5, "standalon": 12, "standard": 10, "start": 7, "state": [1, 5, 11, 16], "static": 11, "statist": 1, "statu": 2, "std": [10, 13], "step": 14, "still": 19, "str": [7, 8, 9, 10, 11], "straight": [7, 9, 17, 19], "straighten": 19, "straighten_pag": [9, 13, 19], "straigten_pag": 13, "stream": 8, "street": [5, 7], "strict": 4, "strictli": 11, "string": [7, 8, 11, 19], "strive": 4, "strong": [5, 9], "structur": [18, 19], "subset": [7, 19], "suggest": [3, 15], "sum": 11, "summari": 11, "support": [4, 13, 16, 18, 19], "sustain": 2, "svhn": [5, 7, 17], "svt": [7, 17], "swedish": 7, "symmetr": [9, 10, 19], "symmetric_pad": [9, 10, 19], "synthet": 5, "synthtext": [5, 7, 17], "system": 19, "t": [3, 7, 13, 18, 19], "tabl": [15, 16, 17], "take": [2, 7, 19], "target": [7, 8, 10, 11, 17], "target_s": 7, "task": [5, 7, 9, 15, 17, 19], "task2": 7, "team": 4, "techminde": 4, "templat": [3, 5], "tensor": [7, 8, 10, 19], "tensorflow": [4, 5, 8, 9, 10, 13, 15, 18, 19], "tensorspec": 18, "term": 2, "test": [7, 17], "test_set": 7, "text": [1, 7, 8, 9, 11, 17], "text_output": 19, "textmatch": 11, "textnet": 9, "textnet_bas": 9, "textnet_smal": 9, "textnet_tini": 9, "textract": [5, 19], "textstylebrush": [5, 7], "textual": [5, 7, 8, 9, 19], "tf": [4, 8, 9, 10, 15, 18], "than": [3, 11, 15], "thank": 3, "thei": [2, 11], "them": [7, 19], "thi": [1, 2, 3, 4, 6, 7, 10, 11, 13, 14, 15, 17, 18, 19], "thing": [18, 19], "third": 4, "those": [2, 8, 19], "threaten": 2, "threshold": 19, "through": [2, 10, 16, 17], "tilman": 15, "time": [1, 2, 5, 9, 11, 17], "tini": 9, "titl": [8, 19], "tm": 19, "tmp": 14, "togeth": [3, 8], "tograi": 10, "tool": [1, 17], "top": [11, 18, 19], "topic": 3, "torch": [4, 10, 13, 15, 18], "torchvis": 10, "total": 13, "toward": [2, 4], "train": [3, 7, 9, 10, 15, 16, 17, 18, 19], "train_it": [7, 17], "train_load": [7, 17], "train_pytorch": 15, "train_set": [7, 17], "train_tensorflow": 15, "trainabl": [5, 9], "tranform": 10, "transcrib": 19, "transfer": [5, 7], "transfo": 10, "transform": [5, 7, 9], "translat": 2, "troll": 2, "true": [7, 8, 9, 10, 11, 13, 14, 15, 17, 18, 19], "truth": 11, "tune": [1, 18], "tupl": [7, 8, 10, 11], "two": [8, 14], "txt": 7, "type": [8, 11, 15, 18, 19], "typic": 19, "u": [2, 3], "ucsd": 7, "udac": 3, "uint8": [8, 9, 11, 19], "ukrainian": 7, "unaccept": 2, "underli": [17, 19], "underneath": 8, "understand": [5, 7, 19], "uniform": [9, 10], "uniformli": 10, "uninterrupt": [8, 19], "union": 11, "unit": 1, "unittest": 3, "unlock": 8, "unoffici": 9, "unprofession": 2, "unsolicit": 2, "unsupervis": 5, "unwelcom": 2, "up": [9, 19], "updat": 11, "upgrad": 3, "upper": [7, 10], "uppercas": 17, "url": 8, "us": [2, 3, 4, 7, 9, 11, 12, 13, 14, 15, 16, 19], "usabl": 19, "usag": [14, 18], "use_polygon": [7, 11, 17], "useabl": 19, "user": [5, 8, 12], "utf": 19, "util": 18, "v1": 15, "v3": [9, 15, 19], "valid": 17, "valu": [3, 8, 10, 19], "valuabl": 5, "variabl": 14, "varieti": 7, "veri": 9, "verma": 1, "version": [2, 3, 4, 18, 19], "vgg": 9, "vgg16": 15, "vgg16_bn_r": 9, "via": 2, "video": 1, "vietnames": 7, "view": [5, 7], "viewpoint": 2, "violat": 2, "visibl": 2, "vision": [5, 7, 9], "visiondataset": 7, "visiontransform": 9, "visual": [4, 5, 16], "visualize_pag": 11, "vit_": 9, "vit_b": 9, "vitstr": [5, 9, 18], "vitstr_bas": [9, 19], "vitstr_smal": [9, 13, 18, 19], "viz": 4, "vocab": [13, 15, 17, 18, 19], "vocabulari": [7, 13, 15], "w": [8, 9, 10, 11], "w3": 19, "wa": 2, "wai": [2, 5, 17], "want": [3, 18, 19], "warmup": 19, "wasn": 3, "we": [1, 2, 3, 4, 5, 8, 10, 13, 15, 17, 18, 19], "weasyprint": 8, "web": [3, 8], "websit": 7, "welcom": 2, "well": [1, 2, 18], "were": [2, 8, 19], "what": [1, 2], "when": [2, 3, 9], "whenev": 3, "where": [3, 8, 10, 11], "whether": [3, 7, 8, 10, 11, 17, 19], "which": [2, 9, 14, 16, 17, 19], "whichev": 4, "while": [10, 19], "why": 2, "width": [8, 10], "wiki": 2, "wildreceipt": [5, 7, 17], "window": [9, 11], "wish": 3, "within": 2, "without": [2, 7, 9], "wonder": 3, "word": [5, 7, 9, 11, 19], "word_1_1": 19, "word_1_2": 19, "word_1_3": 19, "wordgener": [7, 17], "words_onli": 11, "work": [1, 13, 14, 19], "workflow": 3, "worklow": 3, "world": [11, 19], "worth": 9, "wrap": 19, "wrapper": [7, 10], "write": 14, "written": [2, 8], "www": [2, 8, 19], "x": [8, 10, 11], "x_ascend": 19, "x_descend": 19, "x_i": 11, "x_size": 19, "x_wconf": 19, "xhtml": 19, "xmax": 8, "xmin": 8, "xml": 19, "xml_bytes_str": 19, "xml_element": 19, "xml_output": 19, "xmln": 19, "y": 11, "y_i": 11, "y_j": 11, "yet": 16, "ymax": 8, "ymin": 8, "yolov8": 16, "you": [3, 4, 7, 8, 9, 13, 14, 15, 16, 17, 18, 19], "your": [3, 5, 8, 11, 19], "yoursit": 8, "yugesh": 1, "zero": [10, 11], "zoo": 13, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 7, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 7, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": 7, "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 7, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 7, "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 7, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 7, "\u00e4\u00f6\u00e4\u00f6": 7, "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 7, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": 7, "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": 7, "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": 7, "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": 7, "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": 7, "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0905": 7, "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": 7, "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": 7, "\u0950": 7, "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": 7, "\u09bd": 7, "\u09ce": 7, "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": 7}, "titles": ["Changelog", "Community resources", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.contrib", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Integrate contributions into your pipeline", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 3, "0": 0, "01": 0, "02": 0, "03": 0, "04": 0, "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 2], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 2], "2021": 0, "2022": 0, "2023": 0, "2024": 0, "21": 0, "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 2], "31": 0, "4": [0, 2], "5": 0, "6": 0, "7": 0, "8": 0, "9": 0, "advanc": 19, "approach": 19, "architectur": 19, "arg": [7, 8, 9, 10, 11], "artefact": 8, "artefactdetect": 16, "attribut": 2, "avail": [16, 17, 19], "aw": 14, "ban": 2, "block": 8, "bug": 3, "changelog": 0, "choos": [17, 19], "classif": [9, 13, 15], "code": [2, 3], "codebas": 3, "commit": 3, "commun": [1, 15], "compos": 10, "conda": 4, "conduct": 2, "connect": 3, "continu": 3, "contrib": 6, "contribut": [3, 6, 16], "contributor": 2, "convent": 15, "correct": 2, "coven": 2, "custom": [7, 13], "data": 17, "dataload": 7, "dataset": [5, 7, 17], "detect": [5, 9, 15, 17, 19], "develop": 3, "do": 19, "doctr": [3, 5, 6, 7, 8, 9, 10, 11, 12], "document": [3, 5, 8], "end": 19, "enforc": 2, "evalu": 11, "export": 18, "factori": 9, "featur": [3, 5], "feedback": 3, "file": 8, "from": 15, "gener": [7, 17], "git": 4, "guidelin": 2, "half": 18, "hub": 15, "huggingfac": 15, "i": 19, "infer": 18, "instal": [3, 4], "integr": [3, 16], "io": 8, "lambda": 14, "let": 3, "line": 8, "linux": 4, "load": [13, 15, 17], "loader": 7, "main": 5, "mode": 3, "model": [5, 9, 13, 15, 18, 19], "modifi": 3, "modul": [6, 16], "name": 15, "notebook": 12, "object": 17, "ocr": [17, 19], "onli": 4, "onnx": 18, "optim": 18, "option": 19, "orient": 13, "our": 2, "output": 19, "own": [13, 17], "packag": 4, "page": 8, "perman": 2, "pipelin": 16, "pledg": 2, "precis": 18, "predictor": 19, "prepar": 18, "prerequisit": 4, "pretrain": 15, "push": 15, "python": 4, "qualiti": 3, "question": 3, "read": 8, "readi": 17, "recognit": [5, 9, 15, 17, 19], "report": 3, "request": 3, "resourc": 1, "respons": 2, "return": [7, 8, 9, 11], "right": 19, "scope": 2, "share": 15, "should": 19, "stage": 19, "standard": 2, "structur": [3, 8], "style": 3, "support": [5, 6, 7, 10], "synthet": [7, 17], "task": 11, "temporari": 2, "test": 3, "text": [5, 19], "train": 13, "transform": 10, "two": 19, "unit": 3, "us": [17, 18], "util": 11, "v0": 0, "verif": 3, "via": 4, "visual": 11, "vocab": 7, "warn": 2, "what": 19, "word": 8, "your": [13, 15, 16, 17, 18], "zoo": [5, 9]}}) \ No newline at end of file diff --git a/v0.4.0/transforms.html b/v0.4.0/transforms.html deleted file mode 100644 index 85e94d8a76..0000000000 --- a/v0.4.0/transforms.html +++ /dev/null @@ -1,684 +0,0 @@ - - - - - - - - - - - - - doctr.transforms - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    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:

    -
    -
    -class doctr.transforms.Resize(output_size: Tuple[int, int], method: str = 'bilinear', preserve_aspect_ratio: bool = False, symmetric_pad: bool = False)[source]
    -

    Resizes a tensor to a target size

    -
    -
    Example::
    >>> from doctr.transforms import Resize
    ->>> import tensorflow as tf
    ->>> transfo = Resize((32, 32))
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • output_size – expected output size

    • -
    • method – interpolation method

    • -
    • preserve_aspect_ratio – if True, preserve aspect ratio and pad the rest with zeros

    • -
    • symmetric_pad – if True while preserving aspect ratio, the padding will be done symmetrically

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.Normalize(mean: Tuple[float, float, float], std: Tuple[float, float, float])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • mean – average value per channel

    • -
    • std – standard deviation per channel

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.LambdaTransformation(fn: Callable[[Tensor], Tensor])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import LambdaTransformation
    ->>> import tensorflow as tf
    ->>> transfo = LambdaTransformation(lambda x: x/ 255.)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    fn – the function to be applied to the input tensor

    -
    -
    -
    - -
    -
    -class doctr.transforms.ToGray[source]
    -

    Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ToGray()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    - -
    -
    -class doctr.transforms.ColorInversion(min_val: float = 0.5)[source]
    -

    Applies the following tranformation to a tensor (image or batch of images): -convert to grayscale, colorize (shift 0-values randomly), and then invert colors

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ColorInversion(min_val=0.6)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    min_val – range [min_val, 1] to colorize RGB pixels

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomBrightness(max_delta: float = 0.3)[source]
    -

    Randomly adjust brightness of a tensor (batch of images or image) by adding a delta -to all pixels

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Brightness()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    • -
    • p – probability to apply transformation

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomContrast(delta: float = 0.3)[source]
    -

    Randomly adjust contrast of a tensor (batch of images or image) by adjusting -each pixel: (img - mean) * contrast_factor + mean.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Contrast()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomSaturation(delta: float = 0.5)[source]
    -

    Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and -increasing saturation by a factor.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Saturation()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomHue(max_delta: float = 0.3)[source]
    -

    Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Hue()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomGamma(min_gamma: float = 0.5, max_gamma: float = 1.5, min_gain: float = 0.8, max_gain: float = 1.2)[source]
    -

    randomly performs gamma correction for a tensor (batch of images or image)

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Gamma()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_gamma – non-negative real number, lower bound for gamma param

    • -
    • max_gamma – non-negative real number, upper bound for gamma

    • -
    • min_gain – lower bound for constant multiplier

    • -
    • max_gain – upper bound for constant multiplier

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomJpegQuality(min_quality: int = 60, max_quality: int = 100)[source]
    -

    Randomly adjust jpeg quality of a 3 dimensional RGB image

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = JpegQuality()
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_quality – int between [0, 100]

    • -
    • max_quality – int between [0, 100]

    • -
    -
    -
    -
    - -
    -
    -

    Composing transformations

    -

    It is common to require several transformations to be performed consecutively.

    -
    -
    -class doctr.transforms.Compose(transforms: List[Callable[[Any], Any]])[source]
    -

    Implements a wrapper that will apply transformations sequentially

    -
    -
    Example::
    >>> from doctr.transforms import Compose, Resize
    ->>> import tensorflow as tf
    ->>> transfos = Compose([Resize((32, 32))])
    ->>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformation modules

    -
    -
    -
    - -
    -
    -class doctr.transforms.OneOf(transforms: List[Callable[[Any], Any]])[source]
    -

    Randomly apply one of the input transformations

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = OneOf([JpegQuality(), Gamma()])
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformations, one only will be picked

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomApply(transform: Callable[[Any], Any], p: float = 0.5)[source]
    -

    Apply with a probability p the input transformation

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = RandomApply(Gamma(), p=.5)
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • transform – transformation to apply

    • -
    • p – probability to apply

    • -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.0/using_doctr/custom_models_training.html b/v0.4.0/using_doctr/custom_models_training.html index df39d8d568..b714c1f971 100644 --- a/v0.4.0/using_doctr/custom_models_training.html +++ b/v0.4.0/using_doctr/custom_models_training.html @@ -14,7 +14,7 @@ - + Train your own model - docTR documentation @@ -619,7 +619,7 @@

    Loading your custom trained orientation classification model - + diff --git a/v0.4.0/using_doctr/running_on_aws.html b/v0.4.0/using_doctr/running_on_aws.html index 16ceaca7a1..808ea541cd 100644 --- a/v0.4.0/using_doctr/running_on_aws.html +++ b/v0.4.0/using_doctr/running_on_aws.html @@ -14,7 +14,7 @@ - + AWS Lambda - docTR documentation @@ -362,7 +362,7 @@

    AWS Lambda - + diff --git a/v0.4.0/using_doctr/sharing_models.html b/v0.4.0/using_doctr/sharing_models.html index d76b4017f4..c9e978400a 100644 --- a/v0.4.0/using_doctr/sharing_models.html +++ b/v0.4.0/using_doctr/sharing_models.html @@ -14,7 +14,7 @@ - + Share your model with the community - docTR documentation @@ -544,7 +544,7 @@

    Recognition - + diff --git a/v0.4.0/using_doctr/using_contrib_modules.html b/v0.4.0/using_doctr/using_contrib_modules.html index 50598dae5d..0c5fffdf9f 100644 --- a/v0.4.0/using_doctr/using_contrib_modules.html +++ b/v0.4.0/using_doctr/using_contrib_modules.html @@ -14,7 +14,7 @@ - + Integrate contributions into your pipeline - docTR documentation @@ -415,7 +415,7 @@

    ArtefactDetection - + diff --git a/v0.4.0/using_doctr/using_datasets.html b/v0.4.0/using_doctr/using_datasets.html index 460476dbbf..8a7d4f0a64 100644 --- a/v0.4.0/using_doctr/using_datasets.html +++ b/v0.4.0/using_doctr/using_datasets.html @@ -14,7 +14,7 @@ - + Choose a ready to use dataset - docTR documentation @@ -642,7 +642,7 @@

    Data Loading - + diff --git a/v0.4.0/using_doctr/using_model_export.html b/v0.4.0/using_doctr/using_model_export.html index 6124c00ebe..6790dd0642 100644 --- a/v0.4.0/using_doctr/using_model_export.html +++ b/v0.4.0/using_doctr/using_model_export.html @@ -14,7 +14,7 @@ - + Preparing your model for inference - docTR documentation @@ -467,7 +467,7 @@

    Using your ONNX exported model - + diff --git a/v0.4.0/using_doctr/using_models.html b/v0.4.0/using_doctr/using_models.html index 61f1f5ab7a..9ead8498e1 100644 --- a/v0.4.0/using_doctr/using_models.html +++ b/v0.4.0/using_doctr/using_models.html @@ -14,7 +14,7 @@ - + Choosing the right model - docTR documentation @@ -1253,7 +1253,7 @@

    Advanced options - + diff --git a/v0.4.0/utils.html b/v0.4.0/utils.html deleted file mode 100644 index e2f223f06a..0000000000 --- a/v0.4.0/utils.html +++ /dev/null @@ -1,574 +0,0 @@ - - - - - - - - - - - - - doctr.utils - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.utils

    -

    This module regroups non-core features that are complementary to the rest of the package.

    -
    -

    Visualization

    -

    Easy-to-use functions to make sense of your model’s predictions.

    -
    -
    -doctr.utils.visualization.visualize_page(page: Dict[str, Any], image: ndarray, words_only: bool = True, display_artefacts: bool = True, scale: float = 10, interactive: bool = True, add_labels: bool = True, **kwargs: Any) Figure[source]
    -

    Visualize a full page with predicted blocks, lines and words

    -
    -
    Example::
    >>> import numpy as np
    ->>> import matplotlib.pyplot as plt
    ->>> from doctr.utils.visualization import visualize_page
    ->>> from doctr.models import ocr_db_crnn
    ->>> model = ocr_db_crnn(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([[input_page]])
    ->>> visualize_page(out[0].pages[0].export(), input_page)
    ->>> plt.show()
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • page – the exported Page of a Document

    • -
    • image – np array of the page, needs to have the same shape than page[‘dimensions’]

    • -
    • words_only – whether only words should be displayed

    • -
    • display_artefacts – whether artefacts should be displayed

    • -
    • scale – figsize of the largest windows side

    • -
    • interactive – whether the plot should be interactive

    • -
    • add_labels – for static plot, adds text labels on top of bounding box

    • -
    -
    -
    -
    - -
    -
    -

    Task evaluation

    -

    Implementations of task-specific metrics to easily assess your model performances.

    -
    -
    -class doctr.utils.metrics.TextMatch[source]
    -

    Implements text match metric (word-level accuracy) for recognition task.

    -

    The raw aggregated metric is computed as follows:

    -
    -
    -\[\forall X, Y \in \mathcal{W}^N, -TextMatch(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N f_{Y_i}(X_i)\]
    -
    -

    with the indicator function \(f_{a}\) defined as:

    -
    -
    -\[\begin{split}\forall a, x \in \mathcal{W}, -f_a(x) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } x = a \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{W}\) is the set of all possible character sequences, -\(N\) is a strictly positive integer.

    -
    -
    Example::
    >>> from doctr.utils import TextMatch
    ->>> metric = TextMatch()
    ->>> metric.update(['Hello', 'world'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    -summary() Dict[str, float][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a dictionary with the exact match score for the raw data, its lower-case counterpart, its unidecode -counterpart and its lower-case unidecode counterpart

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.LocalizationConfusion(iou_thresh: float = 0.5, rotated_bbox: bool = False, mask_shape: Tuple[int, int] = (1024, 1024))[source]
    -

    Implements common confusion metrics and mean IoU for localization evaluation.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall Y \in \mathcal{B}^N, \forall X \in \mathcal{B}^M, \\ -Recall(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -Precision(X, Y) = \frac{1}{M} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -meanIoU(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(X_i, Y_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(g_{X}\) defined as:

    -
    -
    -\[\begin{split}\forall y \in \mathcal{B}, -g_X(y) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } y\mbox{ has been assigned to any }(X_i)_i\mbox{ with an }IoU \geq 0.5 \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import LocalizationConfusion
    ->>> metric = LocalizationConfusion(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]))
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[float | None, float | None, float | None][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall, precision and meanIoU scores

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.OCRMetric(iou_thresh: float = 0.5, rotated_bbox: bool = False, mask_shape: Tuple[int, int] = (1024, 1024))[source]
    -

    Implements end-to-end OCR metric.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall (B, L) \in \mathcal{B}^N \times \mathcal{L}^N, -\forall (\hat{B}, \hat{L}) \in \mathcal{B}^M \times \mathcal{L}^M, \\ -Recall(B, \hat{B}, L, \hat{L}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -Precision(B, \hat{B}, L, \hat{L}) = \frac{1}{M} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(h_{B, L}\) defined as:

    -
    -
    -\[\begin{split}\forall (b, l) \in \mathcal{B} \times \mathcal{L}, -h_{B,L}(b, l) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ - & IoU \geq 0.5 \mbox{ and that for this assignment, } l = L_j\\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(\mathcal{L}\) is the set of possible character sequences, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import OCRMetric
    ->>> metric = OCRMetric(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]),
    -['hello'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[Dict[str, float | None], Dict[str, float | None], float | None][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall & precision for each string comparison flexibility and the mean IoU

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.4.1/_modules/doctr/datasets/cord.html b/v0.4.1/_modules/doctr/datasets/cord.html index f98ee6901c..55b0584830 100644 --- a/v0.4.1/_modules/doctr/datasets/cord.html +++ b/v0.4.1/_modules/doctr/datasets/cord.html @@ -13,7 +13,7 @@ - + doctr.datasets.cord - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.cord

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
     from .datasets import VisionDataset
    -from doctr.utils.geometry import fit_rbbox
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['CORD']
    +__all__ = ["CORD"]
     
     
     
    -[docs] +[docs] class CORD(VisionDataset): """CORD dataset from `"CORD: A Consolidated Receipt Dataset forPost-OCR Parsing" <https://openreview.net/pdf?id=SJl3z659UH>`_. - Example:: - >>> from doctr.datasets import CORD - >>> train_set = CORD(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/cord-grid.png&src=0 + :align: center + + >>> from doctr.datasets import CORD + >>> train_set = CORD(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - TRAIN = ('https://github.com/mindee/doctr/releases/download/v0.1.1/cord_train.zip', - '45f9dc77f126490f3e52d7cb4f70ef3c57e649ea86d19d862a2757c9c455d7f8') - TEST = ('https://github.com/mindee/doctr/releases/download/v0.1.1/cord_test.zip', - '8c895e3d6f7e1161c5b7245e3723ce15c04d84be89eaa6093949b75a66fb3c58') + TRAIN = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/cord_train.zip&src=0", + "45f9dc77f126490f3e52d7cb4f70ef3c57e649ea86d19d862a2757c9c455d7f8", + "cord_train.zip", + ) + + TEST = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/cord_test.zip&src=0", + "8c895e3d6f7e1161c5b7245e3723ce15c04d84be89eaa6093949b75a66fb3c58", + "cord_test.zip", + ) def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + url, sha256, name = self.TRAIN if train else self.TEST + super().__init__( + url, + name, + sha256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - url, sha256 = self.TRAIN if train else self.TEST - super().__init__(url, None, sha256, True, **kwargs) - - # # List images - self.root = os.path.join(self._root, 'image') - self.data: List[Tuple[str, Dict[str, Any]]] = [] + # List images + tmp_root = os.path.join(self.root, "image") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] self.train = train - self.sample_transforms = sample_transforms - for img_path in os.listdir(self.root): + np_dtype = np.float32 + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking CORD", total=len(os.listdir(tmp_root))): # File existence check - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem _targets = [] - with open(os.path.join(self._root, 'json', f"{stem}.json"), 'rb') as f: + with open(os.path.join(self.root, "json", f"{stem}.json"), "rb") as f: label = json.load(f) for line in label["valid_line"]: for word in line["words"]: if len(word["text"]) > 0: x = word["quad"]["x1"], word["quad"]["x2"], word["quad"]["x3"], word["quad"]["x4"] y = word["quad"]["y1"], word["quad"]["y2"], word["quad"]["y3"], word["quad"]["y4"] - if rotated_bbox: - box = list(fit_rbbox(np.array([ - [x[0], y[0]], - [x[1], y[1]], - [x[2], y[2]], - [x[3], y[3]], - ], dtype=np.float32))) + box: Union[List[float], np.ndarray] + if use_polygons: + # (x, y) coordinates of top left, top right, bottom right, bottom left corners + box = np.array( + [ + [x[0], y[0]], + [x[1], y[1]], + [x[2], y[2]], + [x[3], y[3]], + ], + dtype=np_dtype, + ) else: - # Reduce 8 coords to 4 + # Reduce 8 coords to 4 -> xmin, ymin, xmax, ymax box = [min(x), min(y), max(x), max(y)] - _targets.append((word['text'], box)) + _targets.append((word["text"], box)) text_targets, box_targets = zip(*_targets) - self.data.append(( - img_path, - dict(boxes=np.asarray(box_targets, dtype=int).clip(min=0), labels=text_targets) - )) + if recognition_task: + crops = crop_bboxes_from_image( + img_path=os.path.join(tmp_root, img_path), geoms=np.asarray(box_targets, dtype=int).clip(min=0) + ) + for crop, label in zip(crops, list(text_targets)): + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, np.asarray(box_targets, dtype=int).clip(min=0))) + else: + self.data.append(( + img_path, + dict(boxes=np.asarray(box_targets, dtype=int).clip(min=0), labels=list(text_targets)), + )) + + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -397,8 +461,8 @@

    Source code for doctr.datasets.cord

           
         
       
    -
    - + + diff --git a/v0.4.1/_modules/doctr/datasets/core.html b/v0.4.1/_modules/doctr/datasets/core.html deleted file mode 100644 index b3dcc29ff9..0000000000 --- a/v0.4.1/_modules/doctr/datasets/core.html +++ /dev/null @@ -1,417 +0,0 @@ - - - - - - - - - - - - doctr.datasets.core - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.core

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from pathlib import Path
    -from zipfile import ZipFile
    -from typing import List, Any, Optional, Tuple
    -import tensorflow as tf
    -
    -from doctr.models.utils import download_from_url
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset:
    -
    -    data: List[Any] = []
    -
    -    def __len__(self):
    -        return len(self.data)
    -
    -    def __getitem__(
    -        self,
    -        index: int
    -    ) -> Tuple[tf.Tensor, Any]:
    -
    -        img_name, target = self.data[index]
    -        # Read image
    -        img = tf.io.read_file(os.path.join(self.root, img_name))
    -        img = tf.image.decode_jpeg(img, channels=3)
    -        if self.sample_transforms is not None:
    -            img = self.sample_transforms(img)
    -
    -        return img, target
    -
    -    def extra_repr(self) -> str:
    -        return ""
    -
    -    def __repr__(self) -> str:
    -        return f"{self.__class__.__name__}({self.extra_repr()})"
    -
    -    @staticmethod
    -    def collate_fn(samples: List[Tuple[tf.Tensor, Any]]) -> Tuple[tf.Tensor, List[Any]]:
    -
    -        images, targets = zip(*samples)
    -        images = tf.stack(images, axis=0)
    -
    -        return images, list(targets)
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset): - """Implements an abstract dataset - - Args: - url: URL of the dataset - file_name: name of the file once downloaded - file_hash: expected SHA256 of the file - extract_archive: whether the downloaded file is an archive to be extracted - download: whether the dataset should be downloaded if not present on disk - overwrite: whether the archive should be re-extracted - """ - - def __init__( - self, - url: str, - file_name: Optional[str] = None, - file_hash: Optional[str] = None, - extract_archive: bool = False, - download: bool = False, - overwrite: bool = False, - ) -> None: - - dataset_cache = os.path.join(os.path.expanduser('~'), '.cache', 'doctr', 'datasets') - - file_name = file_name if isinstance(file_name, str) else os.path.basename(url) - # Download the file if not present - archive_path = os.path.join(dataset_cache, file_name) - - if not os.path.exists(archive_path) and not download: - raise ValueError("the dataset needs to be downloaded first with download=True") - - archive_path = download_from_url(url, file_name, file_hash, cache_subdir='datasets') - - # Extract the archive - if extract_archive: - archive_path = Path(archive_path) - dataset_path = archive_path.parent.joinpath(archive_path.stem) - if not dataset_path.is_dir() or overwrite: - with ZipFile(archive_path, 'r') as f: - f.extractall(path=dataset_path) - - # List images - self._root = dataset_path if extract_archive else archive_path - self.data: List[Any] = []
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.1/_modules/doctr/datasets/datasets/tensorflow.html b/v0.4.1/_modules/doctr/datasets/datasets/tensorflow.html deleted file mode 100644 index a236abd9fe..0000000000 --- a/v0.4.1/_modules/doctr/datasets/datasets/tensorflow.html +++ /dev/null @@ -1,356 +0,0 @@ - - - - - - - - - - - - doctr.datasets.datasets.tensorflow - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.datasets.tensorflow

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from typing import List, Any, Tuple
    -import tensorflow as tf
    -
    -from .base import _AbstractDataset, _VisionDataset
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset(_AbstractDataset):
    -
    -    def _read_sample(self, index: int) -> Tuple[tf.Tensor, Any]:
    -        img_name, target = self.data[index]
    -        # Read image
    -        img = tf.io.read_file(os.path.join(self.root, img_name))
    -        img = tf.image.decode_jpeg(img, channels=3)
    -
    -        return img, target
    -
    -    @staticmethod
    -    def collate_fn(samples: List[Tuple[tf.Tensor, Any]]) -> Tuple[tf.Tensor, List[Any]]:
    -
    -        images, targets = zip(*samples)
    -        images = tf.stack(images, axis=0)
    -
    -        return images, list(targets)
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset, _VisionDataset): - pass
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.1/_modules/doctr/datasets/detection.html b/v0.4.1/_modules/doctr/datasets/detection.html index 739563e466..718001e4cf 100644 --- a/v0.4.1/_modules/doctr/datasets/detection.html +++ b/v0.4.1/_modules/doctr/datasets/detection.html @@ -13,7 +13,7 @@ - + doctr.datasets.detection - docTR documentation @@ -430,7 +430,7 @@

    Source code for doctr.datasets.detection

         
       
    - + diff --git a/v0.4.1/_modules/doctr/datasets/doc_artefacts.html b/v0.4.1/_modules/doctr/datasets/doc_artefacts.html index 3313ae4660..94c32aaa0f 100644 --- a/v0.4.1/_modules/doctr/datasets/doc_artefacts.html +++ b/v0.4.1/_modules/doctr/datasets/doc_artefacts.html @@ -13,7 +13,7 @@ - + doctr.datasets.doc_artefacts - docTR documentation @@ -414,7 +414,7 @@

    Source code for doctr.datasets.doc_artefacts

       
    - + diff --git a/v0.4.1/_modules/doctr/datasets/funsd.html b/v0.4.1/_modules/doctr/datasets/funsd.html index 35d7ad4cf5..f08612f9fa 100644 --- a/v0.4.1/_modules/doctr/datasets/funsd.html +++ b/v0.4.1/_modules/doctr/datasets/funsd.html @@ -13,7 +13,7 @@ - + doctr.datasets.funsd - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.funsd

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
     from .datasets import VisionDataset
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['FUNSD']
    +__all__ = ["FUNSD"]
     
     
     
    -[docs] +[docs] class FUNSD(VisionDataset): """FUNSD dataset from `"FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents" <https://arxiv.org/pdf/1905.13538.pdf>`_. - Example:: - >>> from doctr.datasets import FUNSD - >>> train_set = FUNSD(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/funsd-grid.png&src=0 + :align: center + + >>> from doctr.datasets import FUNSD + >>> train_set = FUNSD(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - URL = 'https://guillaumejaume.github.io/FUNSD/dataset.zip' - SHA256 = 'c31735649e4f441bcbb4fd0f379574f7520b42286e80b01d80b445649d54761f' - FILE_NAME = 'funsd.zip' + URL = "https://guillaumejaume.github.io/FUNSD/dataset.zip" + SHA256 = "c31735649e4f441bcbb4fd0f379574f7520b42286e80b01d80b445649d54761f" + FILE_NAME = "funsd.zip" def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + super().__init__( + self.URL, + self.FILE_NAME, + self.SHA256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - super().__init__(self.URL, self.FILE_NAME, self.SHA256, True, **kwargs) self.train = train - self.sample_transforms = sample_transforms + np_dtype = np.float32 # Use the subset - subfolder = os.path.join('dataset', 'training_data' if train else 'testing_data') + subfolder = os.path.join("dataset", "training_data" if train else "testing_data") # # List images - self.root = os.path.join(self._root, subfolder, 'images') - self.data: List[Tuple[str, Dict[str, Any]]] = [] - for img_path in os.listdir(self.root): + tmp_root = os.path.join(self.root, subfolder, "images") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking FUNSD", total=len(os.listdir(tmp_root))): # File existence check - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem - with open(os.path.join(self._root, subfolder, 'annotations', f"{stem}.json"), 'rb') as f: + with open(os.path.join(self.root, subfolder, "annotations", f"{stem}.json"), "rb") as f: data = json.load(f) - _targets = [(word['text'], word['box']) for block in data['form'] - for word in block['words'] if len(word['text']) > 0] + _targets = [ + (word["text"], word["box"]) + for block in data["form"] + for word in block["words"] + if len(word["text"]) > 0 + ] text_targets, box_targets = zip(*_targets) - if rotated_bbox: - # box_targets: xmin, ymin, xmax, ymax -> x, y, w, h, alpha = 0 - box_targets = [ + if use_polygons: + # xmin, ymin, xmax, ymax -> (x, y) coordinates of top left, top right, bottom right, bottom left corners + box_targets = [ # type: ignore[assignment] [ - (box[0] + box[2]) / 2, (box[1] + box[3]) / 2, box[2] - box[0], box[3] - box[1], 0 - ] for box in box_targets + [box[0], box[1]], + [box[2], box[1]], + [box[2], box[3]], + [box[0], box[3]], + ] + for box in box_targets ] - self.data.append((img_path, dict(boxes=np.asarray(box_targets, dtype=int), labels=text_targets))) + if recognition_task: + crops = crop_bboxes_from_image( + img_path=os.path.join(tmp_root, img_path), geoms=np.asarray(box_targets, dtype=np_dtype) + ) + for crop, label in zip(crops, list(text_targets)): + # filter labels with unknown characters + if not any(char in label for char in ["☑", "☐", "\uf703", "\uf702"]): + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, np.asarray(box_targets, dtype=np_dtype))) + else: + self.data.append(( + img_path, + dict(boxes=np.asarray(box_targets, dtype=np_dtype), labels=list(text_targets)), + )) + + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -386,8 +453,8 @@

    Source code for doctr.datasets.funsd

           
         
       
    -
    - + + diff --git a/v0.4.1/_modules/doctr/datasets/generator/tensorflow.html b/v0.4.1/_modules/doctr/datasets/generator/tensorflow.html index 9f562582d9..a3e619f720 100644 --- a/v0.4.1/_modules/doctr/datasets/generator/tensorflow.html +++ b/v0.4.1/_modules/doctr/datasets/generator/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.datasets.generator.tensorflow - docTR documentation @@ -395,7 +395,7 @@

    Source code for doctr.datasets.generator.tensorflow

    - + diff --git a/v0.4.1/_modules/doctr/datasets/ic03.html b/v0.4.1/_modules/doctr/datasets/ic03.html index 3d221d07de..60e54a8a4b 100644 --- a/v0.4.1/_modules/doctr/datasets/ic03.html +++ b/v0.4.1/_modules/doctr/datasets/ic03.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic03 - docTR documentation @@ -468,7 +468,7 @@

    Source code for doctr.datasets.ic03

         
       
    - + diff --git a/v0.4.1/_modules/doctr/datasets/ic13.html b/v0.4.1/_modules/doctr/datasets/ic13.html index 8137e08e9f..219c98dcd1 100644 --- a/v0.4.1/_modules/doctr/datasets/ic13.html +++ b/v0.4.1/_modules/doctr/datasets/ic13.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic13 - docTR documentation @@ -440,7 +440,7 @@

    Source code for doctr.datasets.ic13

         
       
    - + diff --git a/v0.4.1/_modules/doctr/datasets/iiit5k.html b/v0.4.1/_modules/doctr/datasets/iiit5k.html index 1fc8ecfb27..b49c80fe18 100644 --- a/v0.4.1/_modules/doctr/datasets/iiit5k.html +++ b/v0.4.1/_modules/doctr/datasets/iiit5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiit5k - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.datasets.iiit5k

         
       
    - + diff --git a/v0.4.1/_modules/doctr/datasets/iiithws.html b/v0.4.1/_modules/doctr/datasets/iiithws.html index 07f5b13685..f7220afbc7 100644 --- a/v0.4.1/_modules/doctr/datasets/iiithws.html +++ b/v0.4.1/_modules/doctr/datasets/iiithws.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiithws - docTR documentation @@ -407,7 +407,7 @@

    Source code for doctr.datasets.iiithws

         
       
    - + diff --git a/v0.4.1/_modules/doctr/datasets/imgur5k.html b/v0.4.1/_modules/doctr/datasets/imgur5k.html index 68d433ca62..51c6545db4 100644 --- a/v0.4.1/_modules/doctr/datasets/imgur5k.html +++ b/v0.4.1/_modules/doctr/datasets/imgur5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.imgur5k - docTR documentation @@ -488,7 +488,7 @@

    Source code for doctr.datasets.imgur5k

         
       
    - + diff --git a/v0.4.1/_modules/doctr/datasets/loader.html b/v0.4.1/_modules/doctr/datasets/loader.html index d32e6da298..ed80350ef0 100644 --- a/v0.4.1/_modules/doctr/datasets/loader.html +++ b/v0.4.1/_modules/doctr/datasets/loader.html @@ -13,7 +13,7 @@ - + doctr.datasets.loader - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.loader

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import math
    -import tensorflow as tf
    -import numpy as np
    -from typing import Optional
    +from typing import Callable, Optional
     
    -from .multithreading import multithread_exec
    +import numpy as np
    +import tensorflow as tf
     
     __all__ = ["DataLoader"]
     
    @@ -293,12 +314,13 @@ 

    Source code for doctr.datasets.loader

         """Collate multiple elements into batches
     
         Args:
    +    ----
             samples: list of N tuples containing M elements
     
         Returns:
    +    -------
             Tuple of M sequences contianing N elements each
         """
    -
         batch_data = zip(*samples)
     
         tf_data = tuple(tf.stack(elt, axis=0) for elt in batch_data)
    @@ -307,23 +329,23 @@ 

    Source code for doctr.datasets.loader

     
     
     
    -[docs] +[docs] class DataLoader: """Implements a dataset wrapper for fast data loading - Example:: - >>> from doctr.datasets import FUNSD, 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) + >>> 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) Args: + ---- dataset: the dataset shuffle: whether the samples should be shuffled before passing it to the iterator batch_size: number of elements in each batch drop_last: if `True`, drops the last batch if it isn't full - workers: number of workers to use for data loading + collate_fn: function to merge samples into a batch """ def __init__( @@ -332,17 +354,22 @@

    Source code for doctr.datasets.loader

             shuffle: bool = True,
             batch_size: int = 1,
             drop_last: bool = False,
    -        workers: Optional[int] = None,
    +        collate_fn: Optional[Callable] = None,
         ) -> None:
             self.dataset = dataset
             self.shuffle = shuffle
             self.batch_size = batch_size
             nb = len(self.dataset) / batch_size
             self.num_batches = math.floor(nb) if drop_last else math.ceil(nb)
    -        self.collate_fn = self.dataset.collate_fn if hasattr(self.dataset, 'collate_fn') else default_collate
    -        self.workers = workers
    +        if collate_fn is None:
    +            self.collate_fn = self.dataset.collate_fn if hasattr(self.dataset, "collate_fn") else default_collate
    +        else:
    +            self.collate_fn = collate_fn
             self.reset()
     
    +    def __len__(self) -> int:
    +        return self.num_batches
    +
         def reset(self) -> None:
             # Updates indices after each epoch
             self._num_yielded = 0
    @@ -358,9 +385,9 @@ 

    Source code for doctr.datasets.loader

             if self._num_yielded < self.num_batches:
                 # Get next indices
                 idx = self._num_yielded * self.batch_size
    -            indices = self.indices[idx: min(len(self.dataset), idx + self.batch_size)]
    +            indices = self.indices[idx : min(len(self.dataset), idx + self.batch_size)]
     
    -            samples = multithread_exec(self.dataset.__getitem__, indices, threads=self.workers)
    +            samples = list(map(self.dataset.__getitem__, indices))
     
                 batch_data = self.collate_fn(samples)
     
    @@ -401,8 +428,8 @@ 

    Source code for doctr.datasets.loader

           
         
       
    -
    - +
    + diff --git a/v0.4.1/_modules/doctr/datasets/mjsynth.html b/v0.4.1/_modules/doctr/datasets/mjsynth.html index 77bb01d523..df34e49cf9 100644 --- a/v0.4.1/_modules/doctr/datasets/mjsynth.html +++ b/v0.4.1/_modules/doctr/datasets/mjsynth.html @@ -13,7 +13,7 @@ - + doctr.datasets.mjsynth - docTR documentation @@ -438,7 +438,7 @@

    Source code for doctr.datasets.mjsynth

         
       
    - + diff --git a/v0.4.1/_modules/doctr/datasets/ocr.html b/v0.4.1/_modules/doctr/datasets/ocr.html index 11297d5952..ce1ed8b0d4 100644 --- a/v0.4.1/_modules/doctr/datasets/ocr.html +++ b/v0.4.1/_modules/doctr/datasets/ocr.html @@ -13,7 +13,7 @@ - + doctr.datasets.ocr - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.ocr

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple
     
    -from .datasets import AbstractDataset
    -from doctr.utils.geometry import fit_rbbox
    +import numpy as np
     
    +from .datasets import AbstractDataset
     
    -__all__ = ['OCRDataset']
    +__all__ = ["OCRDataset"]
     
     
     
    -[docs] +[docs] class OCRDataset(AbstractDataset): """Implements an OCR dataset + >>> from doctr.datasets import OCRDataset + >>> train_set = OCRDataset(img_folder="/path/to/images", + >>> label_file="/path/to/labels.json") + >>> img, target = train_set[0] + Args: + ---- img_folder: local path to image folder (all jpg at the root) label_file: local path to the label file - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) - **kwargs: keyword arguments from `VisionDataset`. + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + **kwargs: keyword arguments from `AbstractDataset`. """ def __init__( self, img_folder: str, label_file: str, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, **kwargs: Any, ) -> None: - - self.sample_transforms = sample_transforms - self.root = img_folder + super().__init__(img_folder, **kwargs) # List images self.data: List[Tuple[str, Dict[str, Any]]] = [] - with open(label_file, 'rb') as f: + np_dtype = np.float32 + with open(label_file, "rb") as f: data = json.load(f) - for file_dic in data: + for img_name, annotations in data.items(): # Get image path - img_name = Path(os.path.basename(file_dic["raw-archive-filepath"])).stem + '.jpg' + img_name = Path(img_name) # File existence check if not os.path.exists(os.path.join(self.root, img_name)): raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_name)}") # handle empty images - if (len(file_dic["coordinates"]) == 0 or - (len(file_dic["coordinates"]) == 1 and file_dic["coordinates"][0] == "N/A")): - self.data.append((img_name, dict(boxes=np.zeros((0, 4), dtype=np.float32), labels=[]))) + if len(annotations["typed_words"]) == 0: + self.data.append((img_name, dict(boxes=np.zeros((0, 4), dtype=np_dtype), labels=[]))) continue - is_valid: List[bool] = [] - box_targets: List[List[float]] = [] - for box in file_dic["coordinates"]: - if rotated_bbox: - x, y, w, h, alpha = fit_rbbox(np.asarray(box, dtype=np.float32)) - box = [x, y, w, h, alpha] - is_valid.append(w > 0 and h > 0) - else: - xs, ys = zip(*box) - box = [min(xs), min(ys), max(xs), max(ys)] - is_valid.append(box[0] < box[2] and box[1] < box[3]) - if is_valid[-1]: - box_targets.append(box) + # Unpack the straight boxes (xmin, ymin, xmax, ymax) + geoms = [list(map(float, obj["geometry"][:4])) for obj in annotations["typed_words"]] + if use_polygons: + # (x, y) coordinates of top left, top right, bottom right, bottom left corners + geoms = [ + [geom[:2], [geom[2], geom[1]], geom[2:], [geom[0], geom[3]]] # type: ignore[list-item] + for geom in geoms + ] + + text_targets = [obj["value"] for obj in annotations["typed_words"]] - text_targets = [word for word, _valid in zip(file_dic["string"], is_valid) if _valid] - self.data.append((img_name, dict(boxes=np.asarray(box_targets, dtype=np.float32), labels=text_targets)))
    + self.data.append((img_name, dict(boxes=np.asarray(geoms, dtype=np_dtype), labels=text_targets)))
    @@ -383,8 +402,8 @@

    Source code for doctr.datasets.ocr

           
         
       
    - - + + diff --git a/v0.4.1/_modules/doctr/datasets/recognition.html b/v0.4.1/_modules/doctr/datasets/recognition.html index 512c70c308..1754789364 100644 --- a/v0.4.1/_modules/doctr/datasets/recognition.html +++ b/v0.4.1/_modules/doctr/datasets/recognition.html @@ -13,7 +13,7 @@ - + doctr.datasets.recognition - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.datasets.recognition

         
       
    - + diff --git a/v0.4.1/_modules/doctr/datasets/sroie.html b/v0.4.1/_modules/doctr/datasets/sroie.html index 66fd4ca3e0..04cf10bda2 100644 --- a/v0.4.1/_modules/doctr/datasets/sroie.html +++ b/v0.4.1/_modules/doctr/datasets/sroie.html @@ -13,7 +13,7 @@ - + doctr.datasets.sroie - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.sroie

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import csv
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
     from .datasets import VisionDataset
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['SROIE']
    +__all__ = ["SROIE"]
     
     
     
    -[docs] +[docs] class SROIE(VisionDataset): """SROIE dataset from `"ICDAR2019 Competition on Scanned Receipt OCR and Information Extraction" <https://arxiv.org/pdf/2103.10213.pdf>`_. - Example:: - >>> from doctr.datasets import SROIE - >>> train_set = SROIE(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/sroie-grid.png&src=0 + :align: center + + >>> from doctr.datasets import SROIE + >>> train_set = SROIE(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - TRAIN = ('https://github.com/mindee/doctr/releases/download/v0.1.1/sroie2019_train_task1.zip', - 'd4fa9e60abb03500d83299c845b9c87fd9c9430d1aeac96b83c5d0bb0ab27f6f') - TEST = ('https://github.com/mindee/doctr/releases/download/v0.1.1/sroie2019_test.zip', - '41b3c746a20226fddc80d86d4b2a903d43b5be4f521dd1bbe759dbf8844745e2') + TRAIN = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/sroie2019_train_task1.zip&src=0", + "d4fa9e60abb03500d83299c845b9c87fd9c9430d1aeac96b83c5d0bb0ab27f6f", + "sroie2019_train_task1.zip", + ) + TEST = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/sroie2019_test.zip&src=0", + "41b3c746a20226fddc80d86d4b2a903d43b5be4f521dd1bbe759dbf8844745e2", + "sroie2019_test.zip", + ) def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + url, sha256, name = self.TRAIN if train else self.TEST + super().__init__( + url, + name, + sha256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - url, sha256 = self.TRAIN if train else self.TEST - super().__init__(url, None, sha256, True, **kwargs) - self.sample_transforms = sample_transforms self.train = train - if rotated_bbox: - raise NotImplementedError + tmp_root = os.path.join(self.root, "images") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] + np_dtype = np.float32 - # # List images - self.root = os.path.join(self._root, 'images') - self.data: List[Tuple[str, Dict[str, Any]]] = [] - for img_path in os.listdir(self.root): + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking SROIE", total=len(os.listdir(tmp_root))): # File existence check - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem - _targets = [] - with open(os.path.join(self._root, 'annotations', f"{stem}.txt"), encoding='latin') as f: - for row in csv.reader(f, delimiter=','): - # Safeguard for blank lines - if len(row) > 0: - # Label may contain commas - label = ",".join(row[8:]) - # Reduce 8 coords to 4 - p1_x, p1_y, p2_x, p2_y, p3_x, p3_y, p4_x, p4_y = map(int, row[:8]) - left, right = min(p1_x, p2_x, p3_x, p4_x), max(p1_x, p2_x, p3_x, p4_x) - top, bot = min(p1_y, p2_y, p3_y, p4_y), max(p1_y, p2_y, p3_y, p4_y) - if len(label) > 0: - _targets.append((label, [left, top, right, bot])) - - text_targets, box_targets = zip(*_targets) - - self.data.append((img_path, dict(boxes=np.asarray(box_targets, dtype=np.float32), labels=text_targets))) + with open(os.path.join(self.root, "annotations", f"{stem}.txt"), encoding="latin") as f: + _rows = [row for row in list(csv.reader(f, delimiter=",")) if len(row) > 0] + + labels = [",".join(row[8:]) for row in _rows] + # reorder coordinates (8 -> (4,2) -> + # (x, y) coordinates of top left, top right, bottom right, bottom left corners) and filter empty lines + coords: np.ndarray = np.stack( + [np.array(list(map(int, row[:8])), dtype=np_dtype).reshape((4, 2)) for row in _rows], axis=0 + ) + + if not use_polygons: + # xmin, ymin, xmax, ymax + coords = np.concatenate((coords.min(axis=1), coords.max(axis=1)), axis=1) + + if recognition_task: + crops = crop_bboxes_from_image(img_path=os.path.join(tmp_root, img_path), geoms=coords) + for crop, label in zip(crops, labels): + if crop.shape[0] > 0 and crop.shape[1] > 0 and len(label) > 0: + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, coords)) + else: + self.data.append((img_path, dict(boxes=coords, labels=labels))) + + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -390,8 +444,8 @@

    Source code for doctr.datasets.sroie

           
         
       
    -
    - + + diff --git a/v0.4.1/_modules/doctr/datasets/svhn.html b/v0.4.1/_modules/doctr/datasets/svhn.html index 48e4e4d210..60e02b1b3b 100644 --- a/v0.4.1/_modules/doctr/datasets/svhn.html +++ b/v0.4.1/_modules/doctr/datasets/svhn.html @@ -13,7 +13,7 @@ - + doctr.datasets.svhn - docTR documentation @@ -473,7 +473,7 @@

    Source code for doctr.datasets.svhn

         
       
    - + diff --git a/v0.4.1/_modules/doctr/datasets/svt.html b/v0.4.1/_modules/doctr/datasets/svt.html index 4144dc6b9b..a997fcbb50 100644 --- a/v0.4.1/_modules/doctr/datasets/svt.html +++ b/v0.4.1/_modules/doctr/datasets/svt.html @@ -13,7 +13,7 @@ - + doctr.datasets.svt - docTR documentation @@ -459,7 +459,7 @@

    Source code for doctr.datasets.svt

         
       
    - + diff --git a/v0.4.1/_modules/doctr/datasets/synthtext.html b/v0.4.1/_modules/doctr/datasets/synthtext.html index 3b9de506a7..c776e1d673 100644 --- a/v0.4.1/_modules/doctr/datasets/synthtext.html +++ b/v0.4.1/_modules/doctr/datasets/synthtext.html @@ -13,7 +13,7 @@ - + doctr.datasets.synthtext - docTR documentation @@ -470,7 +470,7 @@

    Source code for doctr.datasets.synthtext

         
       
    - + diff --git a/v0.4.1/_modules/doctr/datasets/utils.html b/v0.4.1/_modules/doctr/datasets/utils.html index 2259698c0f..bde9304597 100644 --- a/v0.4.1/_modules/doctr/datasets/utils.html +++ b/v0.4.1/_modules/doctr/datasets/utils.html @@ -13,7 +13,7 @@ - + doctr.datasets.utils - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.utils

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import string
     import unicodedata
    +from collections.abc import Sequence
    +from functools import partial
    +from pathlib import Path
    +from typing import Any, Dict, List, Optional, Tuple, TypeVar, Union
    +from typing import Sequence as SequenceType
    +
     import numpy as np
    -from typing import List, Optional, Any
    +from PIL import Image
    +
    +from doctr.io.image import get_img_shape
    +from doctr.utils.geometry import convert_to_relative_coords, extract_crops, extract_rcrops
     
     from .vocabs import VOCABS
     
    -__all__ = ['translate', 'encode_sequence', 'decode_sequence', 'encode_sequences']
    +__all__ = ["translate", "encode_string", "decode_sequence", "encode_sequences", "pre_transform_multiclass"]
    +
    +ImageTensor = TypeVar("ImageTensor")
     
     
     def translate(
         input_string: str,
         vocab_name: str,
    -    unknown_char: str = '■',
    +    unknown_char: str = "■",
     ) -> str:
         """Translate a string input in a given vocabulary
     
         Args:
    +    ----
             input_string: input string to translate
             vocab_name: vocabulary to use (french, latin, ...)
             unknown_char: unknown character for non-translatable characters
     
         Returns:
    -        A string translated in a given vocab"""
    -
    +    -------
    +        A string translated in a given vocab
    +    """
         if VOCABS.get(vocab_name) is None:
             raise KeyError("output vocabulary must be in vocabs dictionnary")
     
    -    translated = ''
    +    translated = ""
         for char in input_string:
             if char not in VOCABS[vocab_name]:
                 # we need to translate char into a vocab char
    @@ -315,51 +350,63 @@ 

    Source code for doctr.datasets.utils

                     # remove whitespaces
                     continue
                 # normalize character if it is not in vocab
    -            char = unicodedata.normalize('NFD', char).encode('ascii', 'ignore').decode('ascii')
    -            if char == '' or char not in VOCABS[vocab_name]:
    +            char = unicodedata.normalize("NFD", char).encode("ascii", "ignore").decode("ascii")
    +            if char == "" or char not in VOCABS[vocab_name]:
                     # if normalization fails or char still not in vocab, return unknown character)
                     char = unknown_char
             translated += char
         return translated
     
     
    -def encode_sequence(
    +def encode_string(
         input_string: str,
         vocab: str,
     ) -> List[int]:
         """Given a predefined mapping, encode the string to a sequence of numbers
     
         Args:
    +    ----
             input_string: string to encode
             vocab: vocabulary (string), the encoding is given by the indexing of the character sequence
     
         Returns:
    -        A list encoding the input_string"""
    -
    -    return list(map(vocab.index, input_string))  # type: ignore[arg-type]
    +    -------
    +        A list encoding the input_string
    +    """
    +    try:
    +        return list(map(vocab.index, input_string))
    +    except ValueError:
    +        raise ValueError(
    +            f"some characters cannot be found in 'vocab'. \
    +                         Please check the input string {input_string} and the vocabulary {vocab}"
    +        )
     
     
     def decode_sequence(
    -    input_array: np.array,
    +    input_seq: Union[np.ndarray, SequenceType[int]],
         mapping: str,
     ) -> str:
         """Given a predefined mapping, decode the sequence of numbers to a string
     
         Args:
    -        input_array: array to decode
    +    ----
    +        input_seq: array to decode
             mapping: vocabulary (string), the encoding is given by the indexing of the character sequence
     
         Returns:
    -        A string, decoded from input_array"""
    -
    -    if not input_array.dtype == np.int_ or input_array.max() >= len(mapping):
    +    -------
    +        A string, decoded from input_seq
    +    """
    +    if not isinstance(input_seq, (Sequence, np.ndarray)):
    +        raise TypeError("Invalid sequence type")
    +    if isinstance(input_seq, np.ndarray) and (input_seq.dtype != np.int_ or input_seq.max() >= len(mapping)):
             raise AssertionError("Input must be an array of int, with max less than mapping size")
    -    decoded = ''.join(mapping[idx] for idx in input_array)
    -    return decoded
    +
    +    return "".join(map(mapping.__getitem__, input_seq))
     
     
     
    -[docs] +[docs] def encode_sequences( sequences: List[str], vocab: str, @@ -367,48 +414,53 @@

    Source code for doctr.datasets.utils

         eos: int = -1,
         sos: Optional[int] = None,
         pad: Optional[int] = None,
    -    **kwargs: Any,
    +    dynamic_seq_length: bool = False,
     ) -> np.ndarray:
         """Encode character sequences using a given vocab as mapping
     
         Args:
    +    ----
             sequences: the list of character sequences of size N
             vocab: the ordered vocab to use for encoding
             target_size: maximum length of the encoded data
             eos: encoding of End Of String
             sos: optional encoding of Start Of String
             pad: optional encoding for padding. In case of padding, all sequences are followed by 1 EOS then PAD
    +        dynamic_seq_length: if `target_size` is specified, uses it as upper bound and enables dynamic sequence size
     
         Returns:
    +    -------
             the padded encoded data as a tensor
         """
    -
         if 0 <= eos < len(vocab):
             raise ValueError("argument 'eos' needs to be outside of vocab possible indices")
     
    -    if not isinstance(target_size, int):
    -        target_size = max(len(w) for w in sequences)
    -        if sos:
    -            target_size += 1
    -        if pad:
    -            target_size += 1
    +    if not isinstance(target_size, int) or dynamic_seq_length:
    +        # Maximum string length + EOS
    +        max_length = max(len(w) for w in sequences) + 1
    +        if isinstance(sos, int):
    +            max_length += 1
    +        if isinstance(pad, int):
    +            max_length += 1
    +        target_size = max_length if not isinstance(target_size, int) else min(max_length, target_size)
     
         # Pad all sequences
    -    if pad:  # pad with padding symbol
    +    if isinstance(pad, int):  # pad with padding symbol
             if 0 <= pad < len(vocab):
                 raise ValueError("argument 'pad' needs to be outside of vocab possible indices")
             # In that case, add EOS at the end of the word before padding
    -        encoded_data = np.full([len(sequences), target_size], pad, dtype=np.int32)
    +        default_symbol = pad
         else:  # pad with eos symbol
    -        encoded_data = np.full([len(sequences), target_size], eos, dtype=np.int32)
    +        default_symbol = eos
    +    encoded_data: np.ndarray = np.full([len(sequences), target_size], default_symbol, dtype=np.int32)
     
    -    for idx, seq in enumerate(sequences):
    -        encoded_seq = encode_sequence(seq, vocab)
    -        if pad:  # add eos at the end of the sequence
    -            encoded_seq.append(eos)
    -        encoded_data[idx, :min(len(encoded_seq), target_size)] = encoded_seq[:min(len(encoded_seq), target_size)]
    +    # Encode the strings
    +    for idx, seq in enumerate(map(partial(encode_string, vocab=vocab), sequences)):
    +        if isinstance(pad, int):  # add eos at the end of the sequence
    +            seq.append(eos)
    +        encoded_data[idx, : min(len(seq), target_size)] = seq[: min(len(seq), target_size)]
     
    -    if sos:  # place eos symbol at the beginning of each sequence
    +    if isinstance(sos, int):  # place sos symbol at the beginning of each sequence
             if 0 <= sos < len(vocab):
                 raise ValueError("argument 'sos' needs to be outside of vocab possible indices")
             encoded_data = np.roll(encoded_data, 1)
    @@ -416,6 +468,59 @@ 

    Source code for doctr.datasets.utils

     
         return encoded_data
    + + +def convert_target_to_relative( + img: ImageTensor, target: Union[np.ndarray, Dict[str, Any]] +) -> Tuple[ImageTensor, Union[Dict[str, Any], np.ndarray]]: + if isinstance(target, np.ndarray): + target = convert_to_relative_coords(target, get_img_shape(img)) + else: + target["boxes"] = convert_to_relative_coords(target["boxes"], get_img_shape(img)) + return img, target + + +def crop_bboxes_from_image(img_path: Union[str, Path], geoms: np.ndarray) -> List[np.ndarray]: + """Crop a set of bounding boxes from an image + + Args: + ---- + img_path: path to the image + geoms: a array of polygons of shape (N, 4, 2) or of straight boxes of shape (N, 4) + + Returns: + ------- + a list of cropped images + """ + with Image.open(img_path) as pil_img: + img: np.ndarray = np.array(pil_img.convert("RGB")) + # Polygon + if geoms.ndim == 3 and geoms.shape[1:] == (4, 2): + return extract_rcrops(img, geoms.astype(dtype=int)) + if geoms.ndim == 2 and geoms.shape[1] == 4: + return extract_crops(img, geoms.astype(dtype=int)) + raise ValueError("Invalid geometry format") + + +def pre_transform_multiclass(img, target: Tuple[np.ndarray, List]) -> Tuple[np.ndarray, Dict[str, List]]: + """Converts multiclass target to relative coordinates. + + Args: + ---- + img: Image + target: tuple of target polygons and their classes names + + Returns: + ------- + Image and dictionary of boxes, with class names as keys + """ + boxes = convert_to_relative_coords(target[0], get_img_shape(img)) + boxes_classes = target[1] + boxes_dict: Dict = {k: [] for k in sorted(set(boxes_classes))} + for k, poly in zip(boxes_classes, boxes): + boxes_dict[k].append(poly) + boxes_dict = {k: np.stack(v, axis=0) for k, v in boxes_dict.items()} + return img, boxes_dict
    @@ -448,8 +553,8 @@

    Source code for doctr.datasets.utils

           
         
       
    - - + + diff --git a/v0.4.1/_modules/doctr/datasets/wildreceipt.html b/v0.4.1/_modules/doctr/datasets/wildreceipt.html index c543ee7cac..12c6aebd14 100644 --- a/v0.4.1/_modules/doctr/datasets/wildreceipt.html +++ b/v0.4.1/_modules/doctr/datasets/wildreceipt.html @@ -13,7 +13,7 @@ - + doctr.datasets.wildreceipt - docTR documentation @@ -454,7 +454,7 @@

    Source code for doctr.datasets.wildreceipt

         
       
    - + diff --git a/v0.4.1/_modules/doctr/documents/elements.html b/v0.4.1/_modules/doctr/documents/elements.html deleted file mode 100644 index 10c1e142d2..0000000000 --- a/v0.4.1/_modules/doctr/documents/elements.html +++ /dev/null @@ -1,577 +0,0 @@ - - - - - - - - - - - - doctr.documents.elements - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.elements

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import matplotlib.pyplot as plt
    -from typing import Tuple, Dict, List, Any, Optional, Union
    -
    -from doctr.utils.geometry import resolve_enclosing_bbox, resolve_enclosing_rbbox
    -from doctr.utils.visualization import visualize_page
    -from doctr.utils.common_types import BoundingBox, RotatedBbox
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['Element', 'Word', 'Artefact', 'Line', 'Block', 'Page', 'Document']
    -
    -
    -class Element(NestedObject):
    -    """Implements an abstract document element with exporting and text rendering capabilities"""
    -
    -    _exported_keys: List[str] = []
    -
    -    def __init__(self, **kwargs: Any) -> None:
    -        self._children_names: List[str] = []
    -        for k, v in kwargs.items():
    -            setattr(self, k, v)
    -            self._children_names.append(k)
    -
    -    def export(self) -> Dict[str, Any]:
    -        """Exports the object into a nested dict format"""
    -
    -        export_dict = {k: getattr(self, k) for k in self._exported_keys}
    -        for children_name in self._children_names:
    -            export_dict[children_name] = [c.export() for c in getattr(self, children_name)]
    -
    -        return export_dict
    -
    -    def render(self) -> str:
    -        raise NotImplementedError
    -
    -
    -
    -[docs] -class Word(Element): - """Implements a word element - - Args: - value: the text string of the word - confidence: the confidence associated with the text prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size - """ - - _exported_keys: List[str] = ["value", "confidence", "geometry"] - - def __init__(self, value: str, confidence: float, geometry: Union[BoundingBox, RotatedBbox]) -> None: - super().__init__() - self.value = value - self.confidence = confidence - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return self.value - - def extra_repr(self) -> str: - return f"value='{self.value}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Artefact(Element): - """Implements a non-textual element - - Args: - artefact_type: the type of artefact - confidence: the confidence of the type prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. - """ - - _exported_keys: List[str] = ["geometry", "type", "confidence"] - - def __init__(self, artefact_type: str, confidence: float, geometry: BoundingBox) -> None: - super().__init__() - self.geometry = geometry - self.type = artefact_type - self.confidence = confidence - - def render(self) -> str: - """Renders the full text of the element""" - return f"[{self.type.upper()}]" - - def extra_repr(self) -> str: - return f"type='{self.type}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Line(Element): - """Implements a line element as a collection of words - - Args: - words: list of word elements - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all words in it. - """ - - _exported_keys: List[str] = ["geometry"] - words: List[Word] = [] - - def __init__( - self, - words: List[Word], - geometry: Optional[Union[BoundingBox, RotatedBbox]] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - # Check whether this is a rotated or straight box - box_resolution_fn = resolve_enclosing_rbbox if len(words[0].geometry) == 5 else resolve_enclosing_bbox - geometry = box_resolution_fn([w.geometry for w in words]) # type: ignore[operator, misc] - - super().__init__(words=words) - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return " ".join(w.render() for w in self.words)
    - - - -
    -[docs] -class Block(Element): - """Implements a block element as a collection of lines and artefacts - - Args: - lines: list of line elements - artefacts: list of artefacts - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all lines and artefacts in it. - """ - - _exported_keys: List[str] = ["geometry"] - lines: List[Line] = [] - artefacts: List[Artefact] = [] - - def __init__( - self, - lines: List[Line] = [], - artefacts: List[Artefact] = [], - geometry: Optional[Union[BoundingBox, RotatedBbox]] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - line_boxes = [word.geometry for line in lines for word in line.words] - artefact_boxes = [artefact.geometry for artefact in artefacts] - box_resolution_fn = resolve_enclosing_rbbox if len(lines[0].geometry) == 5 else resolve_enclosing_bbox - geometry = box_resolution_fn(line_boxes + artefact_boxes) # type: ignore[operator, arg-type] - - super().__init__(lines=lines, artefacts=artefacts) - self.geometry = geometry - - def render(self, line_break: str = '\n') -> str: - """Renders the full text of the element""" - return line_break.join(line.render() for line in self.lines)
    - - - -
    -[docs] -class Page(Element): - """Implements a page element as a collection of blocks - - Args: - blocks: list of block elements - page_idx: the index of the page in the input raw document - dimensions: the page size in pixels in format (width, height) - orientation: a dictionary with the value of the rotation angle in degress and confidence of the prediction - language: a dictionary with the language value and confidence of the prediction - """ - - _exported_keys: List[str] = ["page_idx", "dimensions", "orientation", "language"] - blocks: List[Block] = [] - - def __init__( - self, - blocks: List[Block], - page_idx: int, - dimensions: Tuple[int, int], - orientation: Optional[Dict[str, Any]] = None, - language: Optional[Dict[str, Any]] = None, - ) -> None: - super().__init__(blocks=blocks) - self.page_idx = page_idx - self.dimensions = dimensions - self.orientation = orientation if isinstance(orientation, dict) else dict(value=None, confidence=None) - self.language = language if isinstance(language, dict) else dict(value=None, confidence=None) - - def render(self, block_break: str = '\n\n') -> str: - """Renders the full text of the element""" - return block_break.join(b.render() for b in self.blocks) - - def extra_repr(self) -> str: - return f"dimensions={self.dimensions}" - -
    -[docs] - def show( - self, page: np.ndarray, interactive: bool = True, **kwargs - ) -> None: - """Overlay the result on a given image - - Args: - page: image encoded as a numpy array in uint8 - interactive: whether the display should be interactive - """ - visualize_page(self.export(), page, interactive=interactive) - plt.show(**kwargs)
    -
    - - - -
    -[docs] -class Document(Element): - """Implements a document element as a collection of pages - - Args: - pages: list of page elements - """ - - pages: List[Page] = [] - - def __init__( - self, - pages: List[Page], - ) -> None: - super().__init__(pages=pages) - - def render(self, page_break: str = '\n\n\n\n') -> str: - """Renders the full text of the element""" - return page_break.join(p.render() for p in self.pages) - -
    -[docs] - def show(self, pages: List[np.ndarray], **kwargs) -> None: - """Overlay the result on a given image - - Args: - pages: list of images encoded as numpy arrays in uint8 - """ - for img, result in zip(pages, self.pages): - result.show(img, **kwargs)
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.1/_modules/doctr/documents/reader.html b/v0.4.1/_modules/doctr/documents/reader.html deleted file mode 100644 index cdcd814b6c..0000000000 --- a/v0.4.1/_modules/doctr/documents/reader.html +++ /dev/null @@ -1,612 +0,0 @@ - - - - - - - - - - - - doctr.documents.reader - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.reader

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import cv2
    -from pathlib import Path
    -import fitz
    -from weasyprint import HTML
    -from typing import List, Tuple, Optional, Any, Union, Sequence, Dict
    -
    -__all__ = ['read_pdf', 'read_img', 'read_html', 'DocumentFile', 'PDF']
    -
    -
    -AbstractPath = Union[str, Path]
    -AbstractFile = Union[AbstractPath, bytes]
    -Bbox = Tuple[float, float, float, float]
    -
    -
    -
    -[docs] -def read_img( - file: AbstractFile, - output_size: Optional[Tuple[int, int]] = None, - rgb_output: bool = True, -) -> np.ndarray: - """Read an image file into numpy format - - Example:: - >>> from doctr.documents import read_img - >>> page = read_img("path/to/your/doc.jpg") - - Args: - file: the path to the image file - output_size: the expected output size of each page in format H x W - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - Returns: - the page decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)): - if not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - img = cv2.imread(str(file), cv2.IMREAD_COLOR) - elif isinstance(file, bytes): - file = np.frombuffer(file, np.uint8) - img = cv2.imdecode(file, cv2.IMREAD_COLOR) - else: - raise TypeError("unsupported object type for argument 'file'") - - # Validity check - if img is None: - raise ValueError("unable to read file.") - # Resizing - if isinstance(output_size, tuple): - img = cv2.resize(img, output_size[::-1], interpolation=cv2.INTER_LINEAR) - # Switch the channel order - if rgb_output: - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - return img
    - - - -
    -[docs] -def read_pdf(file: AbstractFile, **kwargs: Any) -> fitz.Document: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_pdf - >>> doc = read_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)) and not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - - fitz_args: Dict[str, AbstractFile] = {} - - if isinstance(file, (str, Path)): - fitz_args['filename'] = file - elif isinstance(file, bytes): - fitz_args['stream'] = file - else: - raise TypeError("unsupported object type for argument 'file'") - - # Read pages with fitz and convert them to numpy ndarrays - return fitz.open(**fitz_args, filetype="pdf", **kwargs)
    - - - -def convert_page_to_numpy( - page: fitz.fitz.Page, - output_size: Optional[Tuple[int, int]] = None, - bgr_output: bool = False, - default_scales: Tuple[float, float] = (2, 2), -) -> np.ndarray: - """Convert a fitz page to a numpy-formatted image - - Args: - page: the page of a file read with PyMuPDF - output_size: the expected output size of each page in format H x W. Default goes to 840 x 595 for A4 pdf, - if you want to increase the resolution while preserving the original A4 aspect ratio can pass (1024, 726) - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - default_scales: spatial scaling to be applied when output_size is not specified where (1, 1) - corresponds to 72 dpi rendering. - - Returns: - the rendered image in numpy format - """ - - # If no output size is specified, keep the origin one - if output_size is not None: - scales = (output_size[1] / page.MediaBox[2], output_size[0] / page.MediaBox[3]) - else: - # Default 72 DPI (scales of (1, 1)) is unnecessarily low - scales = default_scales - - transform_matrix = fitz.Matrix(*scales) - - # Generate the pixel map using the transformation matrix - pixmap = page.getPixmap(matrix=transform_matrix) - # Decode it into a numpy - img = np.frombuffer(pixmap.samples, dtype=np.uint8).reshape(pixmap.height, pixmap.width, 3) - - # Switch the channel order - if bgr_output: - img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) - - return img - - -
    -[docs] -def read_html(url: str, **kwargs: Any) -> bytes: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_html - >>> doc = read_html("https://www.yoursite.com") - - Args: - url: URL of the target web page - Returns: - decoded PDF file as a bytes stream - """ - - return HTML(url, **kwargs).write_pdf()
    - - - -
    -[docs] -class PDF: - """PDF document template - - Args: - doc: input PDF document - """ - def __init__(self, doc: fitz.Document) -> None: - self.doc = doc - -
    -[docs] - def as_images(self, **kwargs) -> List[np.ndarray]: - """Convert all document pages to images - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images() - - Args: - kwargs: keyword arguments of `convert_page_to_numpy` - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - return [convert_page_to_numpy(page, **kwargs) for page in self.doc]
    - - - def get_page_words(self, idx, **kwargs) -> List[Tuple[Bbox, str]]: - """Get the annotations for all words of a given page""" - - # xmin, ymin, xmax, ymax, value, block_idx, line_idx, word_idx - return [(info[:4], info[4]) for info in self.doc[idx].getTextWords(**kwargs)] - -
    -[docs] - def get_words(self, **kwargs) -> List[List[Tuple[Bbox, str]]]: - """Get the annotations for all words in the document - - Example:: - >>> from doctr.documents import DocumentFile - >>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words() - - Args: - kwargs: keyword arguments of `fitz.Page.getTextWords` - Returns: - the list of pages annotations, represented as a list of tuple (bounding box, value) - """ - return [self.get_page_words(idx, **kwargs) for idx in range(len(self.doc))]
    - - - def get_page_artefacts(self, idx) -> List[Tuple[float, float, float, float]]: - return [tuple(self.doc[idx].getImageBbox(artefact)) # type: ignore[misc] - for artefact in self.doc[idx].get_images(full=True)] - -
    -[docs] - def get_artefacts(self) -> List[List[Tuple[float, float, float, float]]]: - """Get the artefacts for the entire document - - Example:: - >>> from doctr.documents import DocumentFile - >>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts() - - Returns: - the list of pages artefacts, represented as a list of bounding boxes - """ - - return [self.get_page_artefacts(idx) for idx in range(len(self.doc))]
    -
    - - - -
    -[docs] -class DocumentFile: - """Read a document from multiple extensions""" - -
    -[docs] - @classmethod - def from_pdf(cls, file: AbstractFile, **kwargs) -> PDF: - """Read a PDF file - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file or a binary stream - Returns: - a PDF document - """ - - doc = read_pdf(file, **kwargs) - - return PDF(doc)
    - - -
    -[docs] - @classmethod - def from_url(cls, url: str, **kwargs) -> PDF: - """Interpret a web page as a PDF document - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_url("https://www.yoursite.com") - - Args: - url: the URL of the target web page - Returns: - a PDF document - """ - pdf_stream = read_html(url) - return cls.from_pdf(pdf_stream, **kwargs)
    - - -
    -[docs] - @classmethod - def from_images(cls, files: Union[Sequence[AbstractFile], AbstractFile], **kwargs) -> List[np.ndarray]: - """Read an image file (or a collection of image files) and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"]) - - Args: - files: the path to the image file or a binary stream, or a collection of those - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - if isinstance(files, (str, Path, bytes)): - files = [files] - - return [read_img(file, **kwargs) for file in files]
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.1/_modules/doctr/io/elements.html b/v0.4.1/_modules/doctr/io/elements.html index 753a47455c..e049d6ce30 100644 --- a/v0.4.1/_modules/doctr/io/elements.html +++ b/v0.4.1/_modules/doctr/io/elements.html @@ -13,7 +13,7 @@ - + doctr.io.elements - docTR documentation @@ -1008,7 +1008,7 @@

    Source code for doctr.io.elements

         
       
    - + diff --git a/v0.4.1/_modules/doctr/io/html.html b/v0.4.1/_modules/doctr/io/html.html index 7ad5b97031..be73631500 100644 --- a/v0.4.1/_modules/doctr/io/html.html +++ b/v0.4.1/_modules/doctr/io/html.html @@ -13,7 +13,7 @@ - + doctr.io.html - docTR documentation @@ -360,7 +360,7 @@

    Source code for doctr.io.html

         
       
    - + diff --git a/v0.4.1/_modules/doctr/io/image/base.html b/v0.4.1/_modules/doctr/io/image/base.html index 336b4bff0e..a50c95d595 100644 --- a/v0.4.1/_modules/doctr/io/image/base.html +++ b/v0.4.1/_modules/doctr/io/image/base.html @@ -13,7 +13,7 @@ - + doctr.io.image.base - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.io.image.base

         
       
    - + diff --git a/v0.4.1/_modules/doctr/io/image/tensorflow.html b/v0.4.1/_modules/doctr/io/image/tensorflow.html index f1846820a3..3b9e731756 100644 --- a/v0.4.1/_modules/doctr/io/image/tensorflow.html +++ b/v0.4.1/_modules/doctr/io/image/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.io.image.tensorflow - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.io.image.tensorflow

         
       
    - + diff --git a/v0.4.1/_modules/doctr/io/pdf.html b/v0.4.1/_modules/doctr/io/pdf.html index e3abf6960b..e5b94811c3 100644 --- a/v0.4.1/_modules/doctr/io/pdf.html +++ b/v0.4.1/_modules/doctr/io/pdf.html @@ -13,7 +13,7 @@ - + doctr.io.pdf - docTR documentation @@ -377,7 +377,7 @@

    Source code for doctr.io.pdf

         
       
    - + diff --git a/v0.4.1/_modules/doctr/io/reader.html b/v0.4.1/_modules/doctr/io/reader.html index c1ddc26edd..d36e5bb553 100644 --- a/v0.4.1/_modules/doctr/io/reader.html +++ b/v0.4.1/_modules/doctr/io/reader.html @@ -13,7 +13,7 @@ - + doctr.io.reader - docTR documentation @@ -426,7 +426,7 @@

    Source code for doctr.io.reader

         
       
    - + diff --git a/v0.4.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html b/v0.4.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html index 9f074805c1..61a010d548 100644 --- a/v0.4.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html +++ b/v0.4.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.magc_resnet.tensorflow - docTR documentation @@ -531,7 +531,7 @@

    Source code for doctr.models.classification.magc_resnet.tensorflow

    - + diff --git a/v0.4.1/_modules/doctr/models/classification/mobilenet/tensorflow.html b/v0.4.1/_modules/doctr/models/classification/mobilenet/tensorflow.html index 6a63851276..7c448394ad 100644 --- a/v0.4.1/_modules/doctr/models/classification/mobilenet/tensorflow.html +++ b/v0.4.1/_modules/doctr/models/classification/mobilenet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.mobilenet.tensorflow - docTR documentation @@ -793,7 +793,7 @@

    Source code for doctr.models.classification.mobilenet.tensorflow

    - + diff --git a/v0.4.1/_modules/doctr/models/classification/resnet/tensorflow.html b/v0.4.1/_modules/doctr/models/classification/resnet/tensorflow.html index 095d377f31..aed4343741 100644 --- a/v0.4.1/_modules/doctr/models/classification/resnet/tensorflow.html +++ b/v0.4.1/_modules/doctr/models/classification/resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.resnet.tensorflow - docTR documentation @@ -749,7 +749,7 @@

    Source code for doctr.models.classification.resnet.tensorflow

    - + diff --git a/v0.4.1/_modules/doctr/models/classification/textnet/tensorflow.html b/v0.4.1/_modules/doctr/models/classification/textnet/tensorflow.html index ad254ebbfb..c5567d7d67 100644 --- a/v0.4.1/_modules/doctr/models/classification/textnet/tensorflow.html +++ b/v0.4.1/_modules/doctr/models/classification/textnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.textnet.tensorflow - docTR documentation @@ -611,7 +611,7 @@

    Source code for doctr.models.classification.textnet.tensorflow

    - + diff --git a/v0.4.1/_modules/doctr/models/classification/vgg/tensorflow.html b/v0.4.1/_modules/doctr/models/classification/vgg/tensorflow.html index 01ae452624..788111ae87 100644 --- a/v0.4.1/_modules/doctr/models/classification/vgg/tensorflow.html +++ b/v0.4.1/_modules/doctr/models/classification/vgg/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vgg.tensorflow - docTR documentation @@ -451,7 +451,7 @@

    Source code for doctr.models.classification.vgg.tensorflow

    - + diff --git a/v0.4.1/_modules/doctr/models/classification/vit/tensorflow.html b/v0.4.1/_modules/doctr/models/classification/vit/tensorflow.html index 1333cf6045..971ba5abe9 100644 --- a/v0.4.1/_modules/doctr/models/classification/vit/tensorflow.html +++ b/v0.4.1/_modules/doctr/models/classification/vit/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vit.tensorflow - docTR documentation @@ -533,7 +533,7 @@

    Source code for doctr.models.classification.vit.tensorflow

    - + diff --git a/v0.4.1/_modules/doctr/models/classification/zoo.html b/v0.4.1/_modules/doctr/models/classification/zoo.html index f7796a7522..3eb2a3ec4e 100644 --- a/v0.4.1/_modules/doctr/models/classification/zoo.html +++ b/v0.4.1/_modules/doctr/models/classification/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.classification.zoo - docTR documentation @@ -447,7 +447,7 @@

    Source code for doctr.models.classification.zoo

    <
    - + diff --git a/v0.4.1/_modules/doctr/models/detection/differentiable_binarization.html b/v0.4.1/_modules/doctr/models/detection/differentiable_binarization.html deleted file mode 100644 index 38e9b36ec2..0000000000 --- a/v0.4.1/_modules/doctr/models/detection/differentiable_binarization.html +++ /dev/null @@ -1,879 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.differentiable_binarization - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.differentiable_binarization

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -import cv2
    -from copy import deepcopy
    -import numpy as np
    -from shapely.geometry import Polygon
    -import pyclipper
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers
    -from typing import Union, List, Tuple, Optional, Any, Dict
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..utils import IntermediateLayerGetter, load_pretrained_params, conv_sequence
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['DBPostProcessor', 'DBNet', 'db_resnet50']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'db_resnet50': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'backbone': 'ResNet50',
    -        'fpn_layers': ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"],
    -        'fpn_channels': 128,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'DBPostProcessor',
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.2.0/db_resnet50-adcafc63.zip',
    -    },
    -}
    -
    -
    -class DBPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for DBNet adapted from the implementation of `xuannianz
    -    <https://github.com/xuannianz/DifferentiableBinarization>`_.
    -
    -    Args:
    -        unclip ratio: ratio used to unshrink polygons
    -        min_size_box: minimal length (pix) to keep a box
    -        max_candidates: maximum boxes to consider in a single page
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        unclip_ratio: Union[float, int] = 1.5,
    -        max_candidates: int = 1000,
    -        box_thresh: float = 0.1,
    -        bin_thresh: float = 0.3,
    -    ) -> None:
    -
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -        self.unclip_ratio = unclip_ratio
    -        self.max_candidates = max_candidates
    -
    -    def polygon_to_box(
    -        self,
    -        points: np.ndarray,
    -    ) -> Optional[Tuple[int, int, int, int]]:
    -        """Expand a polygon (points) by a factor unclip_ratio, and returns a 4-points box
    -
    -        Args:
    -            points: The first parameter.
    -
    -        Returns:
    -            a box in absolute coordinates (x, y, w, h)
    -        """
    -        poly = Polygon(points)
    -        distance = poly.area * self.unclip_ratio / poly.length  # compute distance to expand polygon
    -        offset = pyclipper.PyclipperOffset()
    -        offset.AddPath(points, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        _points = offset.Execute(distance)
    -        # Take biggest stack of points
    -        idx = 0
    -        if len(_points) > 1:
    -            max_size = 0
    -            for _idx, p in enumerate(_points):
    -                if len(p) > max_size:
    -                    idx = _idx
    -                    max_size = len(p)
    -            # We ensure that _points can be correctly casted to a ndarray
    -            _points = [_points[idx]]
    -        expanded_points = np.asarray(_points)  # expand polygon
    -        if len(expanded_points) < 1:
    -            return None
    -        x, y, w, h = cv2.boundingRect(expanded_points)  # compute a 4-points box from expanded polygon
    -        return x, y, w, h
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map
    -
    -        Args:
    -            pred: Pred map from differentiable binarization output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        # get contours from connected components on the bitmap
    -        contours, _ = cv2.findContours(bitmap.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    -        for contour in contours[:self.max_candidates]:
    -            # Check whether smallest enclosing bounding box is not too small
    -            if np.any(contour[:, 0].max(axis=0) - contour[:, 0].min(axis=0) < min_size_box):
    -                continue
    -            x, y, w, h = cv2.boundingRect(contour)
    -            points = np.array([[x, y], [x, y + h], [x + w, y + h], [x + w, y]])
    -            # Compute objectness
    -            score = self.box_score(pred, points)
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            _box = self.polygon_to_box(points)
    -
    -            if _box is None or _box[2] < min_size_box or _box[3] < min_size_box:  # remove to small boxes
    -                continue
    -            x, y, w, h = _box
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -class FeaturePyramidNetwork(layers.Layer, NestedObject):
    -    """Feature Pyramid Network as described in `"Feature Pyramid Networks for Object Detection"
    -    <https://arxiv.org/pdf/1612.03144.pdf>`_.
    -
    -    Args:
    -        channels: number of channel to output
    -    """
    -
    -    def __init__(
    -        self,
    -        channels: int,
    -    ) -> None:
    -        super().__init__()
    -        self.channels = channels
    -        self.upsample = layers.UpSampling2D(size=(2, 2), interpolation='nearest')
    -        self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer='he_normal') for _ in range(4)]
    -        self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2 ** idx) for idx in range(4)]
    -
    -    @staticmethod
    -    def build_upsampling(
    -        channels: int,
    -        dilation_factor: int = 1,
    -    ) -> layers.Layer:
    -        """Module which performs a 3x3 convolution followed by up-sampling
    -
    -        Args:
    -            channels: number of output channels
    -            dilation_factor (int): dilation factor to scale the convolution output before concatenation
    -
    -        Returns:
    -            a keras.layers.Layer object, wrapping these operations in a sequential module
    -
    -        """
    -
    -        _layers = conv_sequence(channels, 'relu', True, kernel_size=3)
    -
    -        if dilation_factor > 1:
    -            _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation='nearest'))
    -
    -        module = keras.Sequential(_layers)
    -
    -        return module
    -
    -    def extra_repr(self) -> str:
    -        return f"channels={self.channels}"
    -
    -    def call(
    -        self,
    -        x: List[tf.Tensor],
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # Channel mapping
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.inner_blocks, x)]
    -        # Upsample & sum
    -        for idx in range(len(results) - 1, -1):
    -            results[idx] += self.upsample(results[idx + 1])
    -        # Conv & upsample
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.layer_blocks, results)]
    -
    -        return layers.concatenate(results)
    -
    -
    -class DBNet(DetectionModel, NestedObject):
    -    """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization"
    -    <https://arxiv.org/pdf/1911.08947.pdf>`_.
    -
    -    Args:
    -        feature extractor: the backbone serving as feature extractor
    -        fpn_channels: number of channels each extracted feature maps is mapped to
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'fpn', 'probability_head', 'threshold_head', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: IntermediateLayerGetter,
    -        fpn_channels: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(cfg=cfg)
    -
    -        self.shrink_ratio = 0.4
    -        self.thresh_min = 0.3
    -        self.thresh_max = 0.7
    -        self.min_size_box = 3
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.fpn = FeaturePyramidNetwork(channels=fpn_channels)
    -        # Initialize kernels
    -        _inputs = [layers.Input(shape=in_shape[1:]) for in_shape in self.feat_extractor.output_shape]
    -        output_shape = tuple(self.fpn(_inputs).shape)
    -
    -        self.probability_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -        self.threshold_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -
    -        self.postprocessor = DBPostProcessor()
    -
    -    @staticmethod
    -    def compute_distance(
    -        xs: np.array,
    -        ys: np.array,
    -        a: np.array,
    -        b: np.array,
    -        eps: float = 1e-7,
    -    ) -> float:
    -        """Compute the distance for each point of the map (xs, ys) to the (a, b) segment
    -
    -        Args:
    -            xs : map of x coordinates (height, width)
    -            ys : map of y coordinates (height, width)
    -            a: first point defining the [ab] segment
    -            b: second point defining the [ab] segment
    -
    -        Returns:
    -            The computed distance
    -
    -        """
    -        square_dist_1 = np.square(xs - a[0]) + np.square(ys - a[1])
    -        square_dist_2 = np.square(xs - b[0]) + np.square(ys - b[1])
    -        square_dist = np.square(a[0] - b[0]) + np.square(a[1] - b[1])
    -        cosin = (square_dist - square_dist_1 - square_dist_2) / (2 * np.sqrt(square_dist_1 * square_dist_2) + eps)
    -        square_sin = 1 - np.square(cosin)
    -        square_sin = np.nan_to_num(square_sin)
    -        result = np.sqrt(square_dist_1 * square_dist_2 * square_sin / square_dist)
    -        result[cosin < 0] = np.sqrt(np.fmin(square_dist_1, square_dist_2))[cosin < 0]
    -        return result
    -
    -    def draw_thresh_map(
    -        self,
    -        polygon: np.array,
    -        canvas: np.array,
    -        mask: np.array,
    -    ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    -        """Draw a polygon treshold map on a canvas, as described in the DB paper
    -
    -        Args:
    -            polygon : array of coord., to draw the boundary of the polygon
    -            canvas : threshold map to fill with polygons
    -            mask : mask for training on threshold polygons
    -        """
    -        if polygon.ndim != 2 or polygon.shape[1] != 2:
    -            raise AttributeError("polygon should be a 2 dimensional array of coords")
    -
    -        # Augment polygon by shrink_ratio
    -        polygon_shape = Polygon(polygon)
    -        distance = polygon_shape.area * (1 - np.power(self.shrink_ratio, 2)) / polygon_shape.length
    -        subject = [tuple(coor) for coor in polygon]  # Get coord as list of tuples
    -        padding = pyclipper.PyclipperOffset()
    -        padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        padded_polygon = np.array(padding.Execute(distance)[0])
    -
    -        # Fill the mask with 1 on the new padded polygon
    -        cv2.fillPoly(mask, [padded_polygon.astype(np.int32)], 1.0)
    -
    -        # Get min/max to recover polygon after distance computation
    -        xmin = padded_polygon[:, 0].min()
    -        xmax = padded_polygon[:, 0].max()
    -        ymin = padded_polygon[:, 1].min()
    -        ymax = padded_polygon[:, 1].max()
    -        width = xmax - xmin + 1
    -        height = ymax - ymin + 1
    -        # Get absolute polygon for distance computation
    -        polygon[:, 0] = polygon[:, 0] - xmin
    -        polygon[:, 1] = polygon[:, 1] - ymin
    -        # Get absolute padded polygon
    -        xs = np.broadcast_to(np.linspace(0, width - 1, num=width).reshape(1, width), (height, width))
    -        ys = np.broadcast_to(np.linspace(0, height - 1, num=height).reshape(height, 1), (height, width))
    -
    -        # Compute distance map to fill the padded polygon
    -        distance_map = np.zeros((polygon.shape[0], height, width), dtype=np.float32)
    -        for i in range(polygon.shape[0]):
    -            j = (i + 1) % polygon.shape[0]
    -            absolute_distance = self.compute_distance(xs, ys, polygon[i], polygon[j])
    -            distance_map[i] = np.clip(absolute_distance / distance, 0, 1)
    -        distance_map = np.min(distance_map, axis=0)
    -
    -        # Clip the padded polygon inside the canvas
    -        xmin_valid = min(max(0, xmin), canvas.shape[1] - 1)
    -        xmax_valid = min(max(0, xmax), canvas.shape[1] - 1)
    -        ymin_valid = min(max(0, ymin), canvas.shape[0] - 1)
    -        ymax_valid = min(max(0, ymax), canvas.shape[0] - 1)
    -
    -        # Fill the canvas with the distances computed inside the valid padded polygon
    -        canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1] = np.fmax(
    -            1 - distance_map[
    -                ymin_valid - ymin:ymax_valid - ymin + 1,
    -                xmin_valid - xmin:xmax_valid - xmin + 1
    -            ],
    -            canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1]
    -        )
    -
    -        return polygon, canvas, mask
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor, tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.uint8)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -        thresh_target = np.zeros(output_shape, dtype=np.uint8)
    -        thresh_mask = np.ones(output_shape, dtype=np.uint8)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            polys = np.stack([
    -                abs_boxes[:, [0, 1]],
    -                abs_boxes[:, [0, 3]],
    -                abs_boxes[:, [2, 3]],
    -                abs_boxes[:, [2, 1]],
    -            ], axis=1)
    -
    -            for box, box_size, poly, is_ambiguous in zip(abs_boxes, boxes_size, polys, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -
    -                # Negative shrink for gt, as described in paper
    -                polygon = Polygon(poly)
    -                distance = polygon.area * (1 - np.power(self.shrink_ratio, 2)) / polygon.length
    -                subject = [tuple(coor) for coor in poly]
    -                padding = pyclipper.PyclipperOffset()
    -                padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -                shrinked = padding.Execute(-distance)
    -
    -                # Draw polygon on gt if it is valid
    -                if len(shrinked) == 0:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                shrinked = np.array(shrinked[0]).reshape(-1, 2)
    -                if shrinked.shape[0] <= 2 or not Polygon(shrinked).is_valid:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                cv2.fillPoly(seg_target[idx], [shrinked.astype(np.int32)], 1)
    -
    -                # Draw on both thresh map and thresh mask
    -                poly, thresh_target[idx], thresh_mask[idx] = self.draw_thresh_map(poly, thresh_target[idx],
    -                                                                                  thresh_mask[idx])
    -
    -        thresh_target = thresh_target.astype(np.float32) * (self.thresh_max - self.thresh_min) + self.thresh_min
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -        thresh_target = tf.convert_to_tensor(thresh_target, dtype=tf.float32)
    -        thresh_mask = tf.convert_to_tensor(thresh_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask, thresh_target, thresh_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        thresh_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts, masks, thresh_gts, thresh_masks from a list of boxes
    -        and a list of masks for each image. From there it computes the loss with the model output
    -
    -        Args:
    -            out_map: output feature map of the model of shape (N, H, W, C)
    -            thresh_map: threshold map of shape (N, H, W, C)
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -
    -        prob_map = tf.math.sigmoid(tf.squeeze(out_map, axis=[-1]))
    -        thresh_map = tf.math.sigmoid(tf.squeeze(thresh_map, axis=[-1]))
    -
    -        seg_target, seg_mask, thresh_target, thresh_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute balanced BCE loss for proba_map
    -        bce_scale = 5.
    -        bce_loss = tf.keras.losses.binary_crossentropy(seg_target[..., None], out_map, from_logits=True)[seg_mask]
    -
    -        neg_target = 1 - seg_target[seg_mask]
    -        positive_count = tf.math.reduce_sum(seg_target[seg_mask])
    -        negative_count = tf.math.reduce_min([tf.math.reduce_sum(neg_target), 3. * positive_count])
    -        negative_loss = bce_loss * neg_target
    -        negative_loss, _ = tf.nn.top_k(negative_loss, tf.cast(negative_count, tf.int32))
    -        sum_losses = tf.math.reduce_sum(bce_loss * seg_target[seg_mask]) + tf.math.reduce_sum(negative_loss)
    -        balanced_bce_loss = sum_losses / (positive_count + negative_count + 1e-6)
    -
    -        # Compute dice loss for approxbin_map
    -        bin_map = 1 / (1 + tf.exp(-50. * (prob_map[seg_mask] - thresh_map[seg_mask])))
    -
    -        bce_min = tf.math.reduce_min(bce_loss)
    -        weights = (bce_loss - bce_min) / (tf.math.reduce_max(bce_loss) - bce_min) + 1.
    -        inter = tf.math.reduce_sum(bin_map * seg_target[seg_mask] * weights)
    -        union = tf.math.reduce_sum(bin_map) + tf.math.reduce_sum(seg_target[seg_mask]) + 1e-8
    -        dice_loss = 1 - 2.0 * inter / union
    -
    -        # Compute l1 loss for thresh_map
    -        l1_scale = 10.
    -        if tf.reduce_any(thresh_mask):
    -            l1_loss = tf.math.reduce_mean(tf.math.abs(thresh_map[thresh_mask] - thresh_target[thresh_mask]))
    -        else:
    -            l1_loss = tf.constant(0.)
    -
    -        return l1_scale * l1_loss + bce_scale * balanced_bce_loss + dice_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        feat_maps = self.feat_extractor(x, **kwargs)
    -        feat_concat = self.fpn(feat_maps, **kwargs)
    -        logits = self.probability_head(feat_concat, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            thresh_map = self.threshold_head(feat_concat, **kwargs)
    -            loss = self.compute_loss(logits, thresh_map, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _db_resnet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> DBNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['fpn_channels'] = kwargs.get('fpn_channels', _cfg['fpn_channels'])
    -
    -    # Feature extractor
    -    resnet = tf.keras.applications.__dict__[_cfg['backbone']](
    -        include_top=False,
    -        weights=None,
    -        input_shape=_cfg['input_shape'],
    -        pooling=None,
    -    )
    -
    -    feat_extractor = IntermediateLayerGetter(
    -        resnet,
    -        _cfg['fpn_layers'],
    -    )
    -
    -    kwargs['fpn_channels'] = _cfg['fpn_channels']
    -
    -    # Build the model
    -    model = DBNet(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def db_resnet50(pretrained: bool = False, **kwargs: Any) -> DBNet: - """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" - <https://arxiv.org/pdf/1911.08947.pdf>`_, using a ResNet-50 backbone. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _db_resnet('db_resnet50', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html b/v0.4.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html index 9145c7c3fd..66cef8663d 100644 --- a/v0.4.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html +++ b/v0.4.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.differentiable_binarization.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     # Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
     
     from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers
    -from typing import List, Tuple, Optional, Any, Dict
    +from typing import Any, Dict, List, Optional, Tuple
     
    +import numpy as np
    +import tensorflow as tf
    +from tensorflow.keras import Model, Sequential, layers, losses
    +from tensorflow.keras.applications import ResNet50
    +
    +from doctr.file_utils import CLASS_NAME
    +from doctr.models.utils import (
    +    IntermediateLayerGetter,
    +    _bf16_to_float32,
    +    _build_model,
    +    conv_sequence,
    +    load_pretrained_params,
    +)
     from doctr.utils.repr import NestedObject
    -from doctr.models.utils import IntermediateLayerGetter, load_pretrained_params, conv_sequence
    +
    +from ...classification import mobilenet_v3_large
     from .base import DBPostProcessor, _DBNet
     
    -__all__ = ['DBNet', 'db_resnet50']
    +__all__ = ["DBNet", "db_resnet50", "db_mobilenet_v3_large"]
     
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'db_resnet50': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'backbone': 'ResNet50',
    -        'fpn_layers': ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"],
    -        'fpn_channels': 128,
    -        'input_shape': (1024, 1024, 3),
    -        'rotated_bbox': False,
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.2.0/db_resnet50-adcafc63.zip',
    +    "db_resnet50": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/db_resnet50-649fa22b.weights.h5&src=0",
    +    },
    +    "db_mobilenet_v3_large": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/db_mobilenet_v3_large-ee2e1dbe.weights.h5&src=0",
         },
     }
     
    @@ -313,6 +348,7 @@ 

    Source code for doctr.models.detection.differentiable_binarization.tensorflo <https://arxiv.org/pdf/1612.03144.pdf>`_. Args: + ---- channels: number of channel to output """ @@ -322,9 +358,9 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo ) -> None: super().__init__() self.channels = channels - self.upsample = layers.UpSampling2D(size=(2, 2), interpolation='nearest') - self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer='he_normal') for _ in range(4)] - self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2 ** idx) for idx in range(4)] + self.upsample = layers.UpSampling2D(size=(2, 2), interpolation="nearest") + self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer="he_normal") for _ in range(4)] + self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2**idx) for idx in range(4)] @staticmethod def build_upsampling( @@ -334,20 +370,21 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo """Module which performs a 3x3 convolution followed by up-sampling Args: + ---- channels: number of output channels dilation_factor (int): dilation factor to scale the convolution output before concatenation Returns: + ------- a keras.layers.Layer object, wrapping these operations in a sequential module """ - - _layers = conv_sequence(channels, 'relu', True, kernel_size=3) + _layers = conv_sequence(channels, "relu", True, kernel_size=3) if dilation_factor > 1: - _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation='nearest')) + _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation="nearest")) - module = keras.Sequential(_layers) + module = Sequential(_layers) return module @@ -359,7 +396,6 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo x: List[tf.Tensor], **kwargs: Any, ) -> tf.Tensor: - # Channel mapping results = [block(fmap, **kwargs) for block, fmap in zip(self.inner_blocks, x)] # Upsample & sum @@ -371,200 +407,324 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo return layers.concatenate(results) -class DBNet(_DBNet, keras.Model, NestedObject): +class DBNet(_DBNet, Model, NestedObject): """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" <https://arxiv.org/pdf/1911.08947.pdf>`_. Args: + ---- feature extractor: the backbone serving as feature extractor fpn_channels: number of channels each extracted feature maps is mapped to + bin_thresh: threshold for binarization + box_thresh: minimal objectness score to consider a box + assume_straight_pages: if True, fit straight bounding boxes only + exportable: onnx exportable returns only logits + cfg: the configuration dict of the model + class_names: list of class names """ - _children_names: List[str] = ['feat_extractor', 'fpn', 'probability_head', 'threshold_head', 'postprocessor'] + _children_names: List[str] = ["feat_extractor", "fpn", "probability_head", "threshold_head", "postprocessor"] def __init__( self, feature_extractor: IntermediateLayerGetter, - fpn_channels: int = 128, - rotated_bbox: bool = False, + fpn_channels: int = 128, # to be set to 256 to represent the author's initial idea + bin_thresh: float = 0.3, + box_thresh: float = 0.1, + assume_straight_pages: bool = True, + exportable: bool = False, cfg: Optional[Dict[str, Any]] = None, + class_names: List[str] = [CLASS_NAME], ) -> None: - super().__init__() + self.class_names = class_names + num_classes: int = len(self.class_names) self.cfg = cfg self.feat_extractor = feature_extractor - self.rotated_bbox = rotated_bbox + self.exportable = exportable + self.assume_straight_pages = assume_straight_pages self.fpn = FeaturePyramidNetwork(channels=fpn_channels) # Initialize kernels _inputs = [layers.Input(shape=in_shape[1:]) for in_shape in self.feat_extractor.output_shape] output_shape = tuple(self.fpn(_inputs).shape) - self.probability_head = keras.Sequential( - [ - *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]), - layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'), - layers.BatchNormalization(), - layers.Activation('relu'), - layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'), - ] + self.probability_head = Sequential([ + *conv_sequence(64, "relu", True, kernel_size=3, input_shape=output_shape[1:]), + layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer="he_normal"), + layers.BatchNormalization(), + layers.Activation("relu"), + layers.Conv2DTranspose(num_classes, 2, strides=2, kernel_initializer="he_normal"), + ]) + self.threshold_head = Sequential([ + *conv_sequence(64, "relu", True, kernel_size=3, input_shape=output_shape[1:]), + layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer="he_normal"), + layers.BatchNormalization(), + layers.Activation("relu"), + layers.Conv2DTranspose(num_classes, 2, strides=2, kernel_initializer="he_normal"), + ]) + + self.postprocessor = DBPostProcessor( + assume_straight_pages=assume_straight_pages, bin_thresh=bin_thresh, box_thresh=box_thresh ) - self.threshold_head = keras.Sequential( - [ - *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]), - layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'), - layers.BatchNormalization(), - layers.Activation('relu'), - layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'), - ] - ) - - self.postprocessor = DBPostProcessor(rotated_bbox=rotated_bbox) def compute_loss( self, out_map: tf.Tensor, thresh_map: tf.Tensor, - target: List[Dict[str, Any]] + target: List[Dict[str, np.ndarray]], + gamma: float = 2.0, + alpha: float = 0.5, + eps: float = 1e-8, ) -> tf.Tensor: """Compute a batch of gts, masks, thresh_gts, thresh_masks from a list of boxes and a list of masks for each image. From there it computes the loss with the model output Args: + ---- out_map: output feature map of the model of shape (N, H, W, C) thresh_map: threshold map of shape (N, H, W, C) target: list of dictionary where each dict has a `boxes` and a `flags` entry + gamma: modulating factor in the focal loss formula + alpha: balancing factor in the focal loss formula + eps: epsilon factor in dice loss Returns: + ------- A loss tensor """ + if gamma < 0: + raise ValueError("Value of gamma should be greater than or equal to zero.") - prob_map = tf.math.sigmoid(tf.squeeze(out_map, axis=[-1])) - thresh_map = tf.math.sigmoid(tf.squeeze(thresh_map, axis=[-1])) + prob_map = tf.math.sigmoid(out_map) + thresh_map = tf.math.sigmoid(thresh_map) - seg_target, seg_mask, thresh_target, thresh_mask = self.compute_target(target, out_map.shape[:3]) - seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32) + seg_target, seg_mask, thresh_target, thresh_mask = self.build_target(target, out_map.shape[1:], True) + seg_target = tf.convert_to_tensor(seg_target, dtype=out_map.dtype) seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool) - thresh_target = tf.convert_to_tensor(thresh_target, dtype=tf.float32) + seg_mask = tf.cast(seg_mask, tf.float32) + thresh_target = tf.convert_to_tensor(thresh_target, dtype=out_map.dtype) thresh_mask = tf.convert_to_tensor(thresh_mask, dtype=tf.bool) - # Compute balanced BCE loss for proba_map - bce_scale = 5. - bce_loss = tf.keras.losses.binary_crossentropy(seg_target[..., None], out_map, from_logits=True)[seg_mask] - - neg_target = 1 - seg_target[seg_mask] - positive_count = tf.math.reduce_sum(seg_target[seg_mask]) - negative_count = tf.math.reduce_min([tf.math.reduce_sum(neg_target), 3. * positive_count]) - negative_loss = bce_loss * neg_target - negative_loss, _ = tf.nn.top_k(negative_loss, tf.cast(negative_count, tf.int32)) - sum_losses = tf.math.reduce_sum(bce_loss * seg_target[seg_mask]) + tf.math.reduce_sum(negative_loss) - balanced_bce_loss = sum_losses / (positive_count + negative_count + 1e-6) - - # Compute dice loss for approxbin_map - bin_map = 1 / (1 + tf.exp(-50. * (prob_map[seg_mask] - thresh_map[seg_mask]))) - - bce_min = tf.math.reduce_min(bce_loss) - weights = (bce_loss - bce_min) / (tf.math.reduce_max(bce_loss) - bce_min) + 1. - inter = tf.math.reduce_sum(bin_map * seg_target[seg_mask] * weights) - union = tf.math.reduce_sum(bin_map) + tf.math.reduce_sum(seg_target[seg_mask]) + 1e-8 - dice_loss = 1 - 2.0 * inter / union + # Focal loss + focal_scale = 10.0 + bce_loss = losses.binary_crossentropy(seg_target[..., None], out_map[..., None], from_logits=True) + + # Convert logits to prob, compute gamma factor + p_t = (seg_target * prob_map) + ((1 - seg_target) * (1 - prob_map)) + alpha_t = seg_target * alpha + (1 - seg_target) * (1 - alpha) + # Unreduced loss + focal_loss = alpha_t * (1 - p_t) ** gamma * bce_loss + # Class reduced + focal_loss = tf.reduce_sum(seg_mask * focal_loss, (0, 1, 2, 3)) / tf.reduce_sum(seg_mask, (0, 1, 2, 3)) + + # Compute dice loss for each class or for approx binary_map + if len(self.class_names) > 1: + dice_map = tf.nn.softmax(out_map, axis=-1) + else: + # compute binary map instead + dice_map = 1.0 / (1.0 + tf.exp(-50 * (prob_map - thresh_map))) + # Class-reduced dice loss + inter = tf.reduce_sum(seg_mask * dice_map * seg_target, axis=[0, 1, 2]) + cardinality = tf.reduce_sum(seg_mask * (dice_map + seg_target), axis=[0, 1, 2]) + dice_loss = tf.reduce_mean(1 - 2 * inter / (cardinality + eps)) # Compute l1 loss for thresh_map - l1_scale = 10. if tf.reduce_any(thresh_mask): - l1_loss = tf.math.reduce_mean(tf.math.abs(thresh_map[thresh_mask] - thresh_target[thresh_mask])) + thresh_mask = tf.cast(thresh_mask, tf.float32) + l1_loss = tf.reduce_sum(tf.abs(thresh_map - thresh_target) * thresh_mask) / ( + tf.reduce_sum(thresh_mask) + eps + ) else: - l1_loss = tf.constant(0.) + l1_loss = tf.constant(0.0) - return l1_scale * l1_loss + bce_scale * balanced_bce_loss + dice_loss + return l1_loss + focal_scale * focal_loss + dice_loss def call( self, x: tf.Tensor, - target: Optional[List[Dict[str, Any]]] = None, + target: Optional[List[Dict[str, np.ndarray]]] = None, return_model_output: bool = False, - return_boxes: bool = False, + return_preds: bool = False, **kwargs: Any, ) -> Dict[str, Any]: - feat_maps = self.feat_extractor(x, **kwargs) feat_concat = self.fpn(feat_maps, **kwargs) logits = self.probability_head(feat_concat, **kwargs) out: Dict[str, tf.Tensor] = {} - if return_model_output or target is None or return_boxes: - prob_map = tf.math.sigmoid(logits) + if self.exportable: + out["logits"] = logits + return out + + if return_model_output or target is None or return_preds: + prob_map = _bf16_to_float32(tf.math.sigmoid(logits)) if return_model_output: out["out_map"] = prob_map - if target is None or return_boxes: - # Post-process boxes - out["preds"] = self.postprocessor(tf.squeeze(prob_map, axis=-1).numpy()) + if target is None or return_preds: + # Post-process boxes (keep only text predictions) + out["preds"] = [dict(zip(self.class_names, preds)) for preds in self.postprocessor(prob_map.numpy())] if target is not None: thresh_map = self.threshold_head(feat_concat, **kwargs) loss = self.compute_loss(logits, thresh_map, target) - out['loss'] = loss + out["loss"] = loss return out -def _db_resnet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> DBNet: +def _db_resnet( + arch: str, + pretrained: bool, + backbone_fn, + fpn_layers: List[str], + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> DBNet: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['fpn_channels'] = kwargs.get('fpn_channels', _cfg['fpn_channels']) - _cfg['rotated_bbox'] = kwargs.get('rotated_bbox', _cfg['rotated_bbox']) + _cfg["input_shape"] = input_shape or _cfg["input_shape"] + if not kwargs.get("class_names", None): + kwargs["class_names"] = _cfg.get("class_names", [CLASS_NAME]) + else: + kwargs["class_names"] = sorted(kwargs["class_names"]) # Feature extractor - resnet = tf.keras.applications.__dict__[_cfg['backbone']]( - include_top=False, - weights=None, - input_shape=_cfg['input_shape'], - pooling=None, + feat_extractor = IntermediateLayerGetter( + backbone_fn( + weights="imagenet" if pretrained_backbone else None, + include_top=False, + pooling=None, + input_shape=_cfg["input_shape"], + ), + fpn_layers, ) + # Build the model + model = DBNet(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) + + # Load pretrained parameters + if pretrained: + # The given class_names differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, + _cfg["url"], + skip_mismatch=kwargs["class_names"] != default_cfgs[arch].get("class_names", [CLASS_NAME]), + ) + + return model + + +def _db_mobilenet( + arch: str, + pretrained: bool, + backbone_fn, + fpn_layers: List[str], + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> DBNet: + pretrained_backbone = pretrained_backbone and not pretrained + + # Patch the config + _cfg = deepcopy(default_cfgs[arch]) + _cfg["input_shape"] = input_shape or _cfg["input_shape"] + if not kwargs.get("class_names", None): + kwargs["class_names"] = default_cfgs[arch].get("class_names", [CLASS_NAME]) + else: + kwargs["class_names"] = sorted(kwargs["class_names"]) + + # Feature extractor feat_extractor = IntermediateLayerGetter( - resnet, - _cfg['fpn_layers'], + backbone_fn( + input_shape=_cfg["input_shape"], + include_top=False, + pretrained=pretrained_backbone, + ), + fpn_layers, ) - kwargs['fpn_channels'] = _cfg['fpn_channels'] - kwargs['rotated_bbox'] = _cfg['rotated_bbox'] - # Build the model model = DBNet(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) # Load pretrained parameters if pretrained: - load_pretrained_params(model, _cfg['url']) + # The given class_names differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, + _cfg["url"], + skip_mismatch=kwargs["class_names"] != default_cfgs[arch].get("class_names", [CLASS_NAME]), + ) return model
    -[docs] +[docs] def db_resnet50(pretrained: bool = False, **kwargs: Any) -> DBNet: """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" <https://arxiv.org/pdf/1911.08947.pdf>`_, using a ResNet-50 backbone. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import db_resnet50 + >>> model = db_resnet50(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the DBNet architecture Returns: + ------- text detection architecture """ + return _db_resnet( + "db_resnet50", + pretrained, + ResNet50, + ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"], + **kwargs, + )
    + + + +
    +[docs] +def db_mobilenet_v3_large(pretrained: bool = False, **kwargs: Any) -> DBNet: + """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" + <https://arxiv.org/pdf/1911.08947.pdf>`_, using a mobilenet v3 large backbone. + + >>> import tensorflow as tf + >>> from doctr.models import db_mobilenet_v3_large + >>> model = db_mobilenet_v3_large(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) - return _db_resnet('db_resnet50', pretrained, **kwargs)
    + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the DBNet architecture + + Returns: + ------- + text detection architecture + """ + return _db_mobilenet( + "db_mobilenet_v3_large", + pretrained, + mobilenet_v3_large, + ["inverted_2", "inverted_5", "inverted_11", "final_block"], + **kwargs, + )

    @@ -598,8 +758,8 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo - - + + diff --git a/v0.4.1/_modules/doctr/models/detection/fast/tensorflow.html b/v0.4.1/_modules/doctr/models/detection/fast/tensorflow.html index 5b84d2dea1..65e1a77af8 100644 --- a/v0.4.1/_modules/doctr/models/detection/fast/tensorflow.html +++ b/v0.4.1/_modules/doctr/models/detection/fast/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.fast.tensorflow - docTR documentation @@ -769,7 +769,7 @@

    Source code for doctr.models.detection.fast.tensorflow

    - + diff --git a/v0.4.1/_modules/doctr/models/detection/linknet.html b/v0.4.1/_modules/doctr/models/detection/linknet.html deleted file mode 100644 index 129cfdce8b..0000000000 --- a/v0.4.1/_modules/doctr/models/detection/linknet.html +++ /dev/null @@ -1,644 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.linknet - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.linknet

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -import numpy as np
    -import cv2
    -from tensorflow.keras import layers, Sequential
    -from typing import Dict, Any, Tuple, Optional, List
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..backbones import ResnetStage
    -from ..utils import conv_sequence, load_pretrained_params
    -from ...utils.repr import NestedObject
    -
    -__all__ = ['LinkNet', 'linknet', 'LinkNetPostProcessor']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'linknet': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'out_chan': 1,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'LinkNetPostProcessor',
    -        'url': None,
    -    },
    -}
    -
    -
    -class LinkNetPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for LinkNet model.
    -
    -    Args:
    -        min_size_box: minimal length (pix) to keep a box
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        min_size_box: int = 3,
    -        bin_thresh: float = 0.15,
    -        box_thresh: float = 0.1,
    -    ) -> None:
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map: find connected components then filter boxes
    -
    -        Args:
    -            pred: Pred map from differentiable linknet output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        label_num, labelimage = cv2.connectedComponents(bitmap.astype(np.uint8), connectivity=4)
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        for label in range(1, label_num + 1):
    -            points = np.array(np.where(labelimage == label)[::-1]).T
    -            if points.shape[0] < 4:  # remove polygons with 3 points or less
    -                continue
    -            score = self.box_score(pred, points.reshape(-1, 2))
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            x, y, w, h = cv2.boundingRect(points)
    -            if min(w, h) < min_size_box:  # filter too small boxes
    -                continue
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -def decoder_block(in_chan: int, out_chan: int) -> Sequential:
    -    """Creates a LinkNet decoder block"""
    -
    -    return Sequential([
    -        *conv_sequence(in_chan // 4, 'relu', True, kernel_size=1),
    -        layers.Conv2DTranspose(
    -            filters=in_chan // 4,
    -            kernel_size=3,
    -            strides=2,
    -            padding="same",
    -            use_bias=False,
    -            kernel_initializer='he_normal'
    -        ),
    -        layers.BatchNormalization(),
    -        layers.Activation('relu'),
    -        *conv_sequence(out_chan, 'relu', True, kernel_size=1),
    -    ])
    -
    -
    -class LinkNetFPN(layers.Layer, NestedObject):
    -    """LinkNet Encoder-Decoder module
    -
    -    """
    -
    -    def __init__(
    -        self,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.encoder_1 = ResnetStage(num_blocks=2, output_channels=64, downsample=True)
    -        self.encoder_2 = ResnetStage(num_blocks=2, output_channels=128, downsample=True)
    -        self.encoder_3 = ResnetStage(num_blocks=2, output_channels=256, downsample=True)
    -        self.encoder_4 = ResnetStage(num_blocks=2, output_channels=512, downsample=True)
    -        self.decoder_1 = decoder_block(in_chan=64, out_chan=64)
    -        self.decoder_2 = decoder_block(in_chan=128, out_chan=64)
    -        self.decoder_3 = decoder_block(in_chan=256, out_chan=128)
    -        self.decoder_4 = decoder_block(in_chan=512, out_chan=256)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor
    -    ) -> tf.Tensor:
    -        x_1 = self.encoder_1(x)
    -        x_2 = self.encoder_2(x_1)
    -        x_3 = self.encoder_3(x_2)
    -        x_4 = self.encoder_4(x_3)
    -        y_4 = self.decoder_4(x_4)
    -        y_3 = self.decoder_3(y_4 + x_3)
    -        y_2 = self.decoder_2(y_3 + x_2)
    -        y_1 = self.decoder_1(y_2 + x_1)
    -        return y_1
    -
    -
    -class LinkNet(DetectionModel, NestedObject):
    -    """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation"
    -    <https://arxiv.org/pdf/1707.03718.pdf>`_.
    -
    -    Args:
    -        out_chan: number of channels for the output
    -    """
    -
    -    _children_names: List[str] = ['stem', 'fpn', 'classifier', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        out_chan: int = 1,
    -        input_shape: Tuple[int, int, int] = (512, 512, 3),
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(cfg=cfg)
    -
    -        self.stem = Sequential([
    -            *conv_sequence(64, 'relu', True, strides=2, kernel_size=7, input_shape=input_shape),
    -            layers.MaxPool2D(pool_size=(3, 3), strides=2, padding='same'),
    -        ])
    -
    -        self.fpn = LinkNetFPN()
    -
    -        self.classifier = Sequential([
    -            layers.Conv2DTranspose(
    -                filters=32,
    -                kernel_size=3,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -            layers.BatchNormalization(),
    -            layers.Activation('relu'),
    -            *conv_sequence(32, 'relu', True, strides=1, kernel_size=3),
    -            layers.Conv2DTranspose(
    -                filters=out_chan,
    -                kernel_size=2,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -        ])
    -
    -        self.min_size_box = 3
    -
    -        self.postprocessor = LinkNetPostProcessor()
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.bool)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            for box, box_size, is_ambiguous in zip(abs_boxes, boxes_size, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Fill polygon with 1
    -                seg_target[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = True
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts and masks from a list of boxes and a list of masks for each image
    -        Then, it computes the loss function with proba_map, gts and masks
    -
    -        Args:
    -            out_map: output feature map of the model of shape N x H x W x 1
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -        seg_target, seg_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute BCE loss
    -        return tf.math.reduce_mean(tf.keras.losses.binary_crossentropy(
    -            seg_target[seg_mask],
    -            tf.squeeze(out_map, axis=[-1])[seg_mask],
    -            from_logits=True
    -        ))
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        logits = self.stem(x)
    -        logits = self.fpn(logits)
    -        logits = self.classifier(logits)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            loss = self.compute_loss(logits, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _linknet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> LinkNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['out_chan'] = kwargs.get('out_chan', _cfg['out_chan'])
    -
    -    kwargs['out_chan'] = _cfg['out_chan']
    -    kwargs['input_shape'] = _cfg['input_shape']
    -    # Build the model
    -    model = LinkNet(cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def linknet(pretrained: bool = False, **kwargs: Any) -> LinkNet: - """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" - <https://arxiv.org/pdf/1707.03718.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import linknet - >>> model = linknet(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _linknet('linknet', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.1/_modules/doctr/models/detection/linknet/tensorflow.html b/v0.4.1/_modules/doctr/models/detection/linknet/tensorflow.html index cd4f446673..ce995f99d4 100644 --- a/v0.4.1/_modules/doctr/models/detection/linknet/tensorflow.html +++ b/v0.4.1/_modules/doctr/models/detection/linknet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.linknet.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.detection.linknet.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     # Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
     
     from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers, Sequential
    -from typing import Dict, Any, Tuple, Optional, List
    +from typing import Any, Dict, List, Optional, Tuple
     
    +import numpy as np
    +import tensorflow as tf
    +from tensorflow.keras import Model, Sequential, layers, losses
    +
    +from doctr.file_utils import CLASS_NAME
    +from doctr.models.classification import resnet18, resnet34, resnet50
    +from doctr.models.utils import (
    +    IntermediateLayerGetter,
    +    _bf16_to_float32,
    +    _build_model,
    +    conv_sequence,
    +    load_pretrained_params,
    +)
     from doctr.utils.repr import NestedObject
    -from doctr.models.backbones import ResnetStage
    -from doctr.models.utils import conv_sequence, load_pretrained_params
    -from .base import LinkNetPostProcessor, _LinkNet
     
    -__all__ = ['LinkNet', 'linknet16']
    +from .base import LinkNetPostProcessor, _LinkNet
     
    +__all__ = ["LinkNet", "linknet_resnet18", "linknet_resnet34", "linknet_resnet50"]
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'linknet16': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'num_classes': 1,
    -        'input_shape': (1024, 1024, 3),
    -        'rotated_bbox': False,
    -        'url': None,
    +    "linknet_resnet18": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/linknet_resnet18-615a82c5.weights.h5&src=0",
    +    },
    +    "linknet_resnet34": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/linknet_resnet34-9d772be5.weights.h5&src=0",
    +    },
    +    "linknet_resnet50": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/linknet_resnet50-6bf6c8b5.weights.h5&src=0",
         },
     }
     
     
    -def decoder_block(in_chan: int, out_chan: int) -> Sequential:
    +def decoder_block(in_chan: int, out_chan: int, stride: int, **kwargs: Any) -> Sequential:
         """Creates a LinkNet decoder block"""
    -
         return Sequential([
    -        *conv_sequence(in_chan // 4, 'relu', True, kernel_size=1),
    +        *conv_sequence(in_chan // 4, "relu", True, kernel_size=1, **kwargs),
             layers.Conv2DTranspose(
                 filters=in_chan // 4,
                 kernel_size=3,
    -            strides=2,
    +            strides=stride,
                 padding="same",
                 use_bias=False,
    -            kernel_initializer='he_normal'
    +            kernel_initializer="he_normal",
             ),
             layers.BatchNormalization(),
    -        layers.Activation('relu'),
    -        *conv_sequence(out_chan, 'relu', True, kernel_size=1),
    +        layers.Activation("relu"),
    +        *conv_sequence(out_chan, "relu", True, kernel_size=1),
         ])
     
     
    -class LinkNetFPN(layers.Layer, NestedObject):
    -    """LinkNet Encoder-Decoder module"""
    +class LinkNetFPN(Model, NestedObject):
    +    """LinkNet Decoder module"""
     
         def __init__(
             self,
    +        out_chans: int,
    +        in_shapes: List[Tuple[int, ...]],
         ) -> None:
    -
             super().__init__()
    -        self.encoder_1 = ResnetStage(num_blocks=2, output_channels=64, downsample=True)
    -        self.encoder_2 = ResnetStage(num_blocks=2, output_channels=128, downsample=True)
    -        self.encoder_3 = ResnetStage(num_blocks=2, output_channels=256, downsample=True)
    -        self.encoder_4 = ResnetStage(num_blocks=2, output_channels=512, downsample=True)
    -        self.decoder_1 = decoder_block(in_chan=64, out_chan=64)
    -        self.decoder_2 = decoder_block(in_chan=128, out_chan=64)
    -        self.decoder_3 = decoder_block(in_chan=256, out_chan=128)
    -        self.decoder_4 = decoder_block(in_chan=512, out_chan=256)
    +        self.out_chans = out_chans
    +        strides = [2] * (len(in_shapes) - 1) + [1]
    +        i_chans = [s[-1] for s in in_shapes[::-1]]
    +        o_chans = i_chans[1:] + [out_chans]
    +        self.decoders = [
    +            decoder_block(in_chan, out_chan, s, input_shape=in_shape)
    +            for in_chan, out_chan, s, in_shape in zip(i_chans, o_chans, strides, in_shapes[::-1])
    +        ]
    +
    +    def call(self, x: List[tf.Tensor], **kwargs: Any) -> tf.Tensor:
    +        out = 0
    +        for decoder, fmap in zip(self.decoders, x[::-1]):
    +            out = decoder(out + fmap, **kwargs)
    +        return out
     
    -    def call(
    -        self,
    -        x: tf.Tensor
    -    ) -> tf.Tensor:
    -        x_1 = self.encoder_1(x)
    -        x_2 = self.encoder_2(x_1)
    -        x_3 = self.encoder_3(x_2)
    -        x_4 = self.encoder_4(x_3)
    -        y_4 = self.decoder_4(x_4)
    -        y_3 = self.decoder_3(y_4 + x_3)
    -        y_2 = self.decoder_2(y_3 + x_2)
    -        y_1 = self.decoder_1(y_2 + x_1)
    -        return y_1
    -
    -
    -class LinkNet(_LinkNet, keras.Model):
    +    def extra_repr(self) -> str:
    +        return f"out_chans={self.out_chans}"
    +
    +
    +class LinkNet(_LinkNet, Model):
         """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation"
         <https://arxiv.org/pdf/1707.03718.pdf>`_.
     
         Args:
    -        num_classes: number of channels for the output
    +    ----
    +        feature extractor: the backbone serving as feature extractor
    +        fpn_channels: number of channels each extracted feature maps is mapped to
    +        bin_thresh: threshold for binarization of the output feature map
    +        box_thresh: minimal objectness score to consider a box
    +        assume_straight_pages: if True, fit straight bounding boxes only
    +        exportable: onnx exportable returns only logits
    +        cfg: the configuration dict of the model
    +        class_names: list of class names
         """
     
    -    _children_names: List[str] = ['stem', 'fpn', 'classifier', 'postprocessor']
    +    _children_names: List[str] = ["feat_extractor", "fpn", "classifier", "postprocessor"]
     
         def __init__(
             self,
    -        num_classes: int = 1,
    -        input_shape: Tuple[int, int, int] = (512, 512, 3),
    -        rotated_bbox: bool = False,
    +        feat_extractor: IntermediateLayerGetter,
    +        fpn_channels: int = 64,
    +        bin_thresh: float = 0.1,
    +        box_thresh: float = 0.1,
    +        assume_straight_pages: bool = True,
    +        exportable: bool = False,
             cfg: Optional[Dict[str, Any]] = None,
    +        class_names: List[str] = [CLASS_NAME],
         ) -> None:
             super().__init__(cfg=cfg)
     
    -        self.rotated_bbox = rotated_bbox
    +        self.class_names = class_names
    +        num_classes: int = len(self.class_names)
     
    -        self.stem = Sequential([
    -            *conv_sequence(64, 'relu', True, strides=2, kernel_size=7, input_shape=input_shape),
    -            layers.MaxPool2D(pool_size=(3, 3), strides=2, padding='same'),
    -        ])
    +        self.exportable = exportable
    +        self.assume_straight_pages = assume_straight_pages
    +
    +        self.feat_extractor = feat_extractor
     
    -        self.fpn = LinkNetFPN()
    +        self.fpn = LinkNetFPN(fpn_channels, [_shape[1:] for _shape in self.feat_extractor.output_shape])
    +        self.fpn.build(self.feat_extractor.output_shape)
     
             self.classifier = Sequential([
                 layers.Conv2DTranspose(
    @@ -393,154 +442,246 @@ 

    Source code for doctr.models.detection.linknet.tensorflow

    strides=2, padding="same", use_bias=False, - kernel_initializer='he_normal' + kernel_initializer="he_normal", + input_shape=self.fpn.decoders[-1].output_shape[1:], ), layers.BatchNormalization(), - layers.Activation('relu'), - *conv_sequence(32, 'relu', True, strides=1, kernel_size=3), + layers.Activation("relu"), + *conv_sequence(32, "relu", True, kernel_size=3, strides=1), layers.Conv2DTranspose( filters=num_classes, kernel_size=2, strides=2, padding="same", - use_bias=False, - kernel_initializer='he_normal' + use_bias=True, + kernel_initializer="he_normal", ), ]) - self.postprocessor = LinkNetPostProcessor(rotated_bbox=rotated_bbox) + self.postprocessor = LinkNetPostProcessor( + assume_straight_pages=assume_straight_pages, bin_thresh=bin_thresh, box_thresh=box_thresh + ) def compute_loss( self, out_map: tf.Tensor, - target: List[Dict[str, Any]], - focal_loss: bool = False, - alpha: float = .5, - gamma: float = 2., - edge_factor: float = 2., + target: List[Dict[str, np.ndarray]], + gamma: float = 2.0, + alpha: float = 0.5, + eps: float = 1e-8, ) -> tf.Tensor: """Compute linknet loss, BCE with boosted box edges or focal loss. Focal loss implementation based on <https://github.com/tensorflow/addons/>`_. Args: + ---- out_map: output feature map of the model of shape N x H x W x 1 target: list of dictionary where each dict has a `boxes` and a `flags` entry - focal_loss: if True, use focal loss instead of BCE - edge_factor: boost factor for box edges (in case of BCE) + gamma: modulating factor in the focal loss formula alpha: balancing factor in the focal loss formula - gammma: modulating factor in the focal loss formula + eps: epsilon factor in dice loss Returns: + ------- A loss tensor """ - seg_target, seg_mask, edge_mask = self.compute_target(target, out_map.shape[:3]) - seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32) - edge_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool) + seg_target, seg_mask = self.build_target(target, out_map.shape[1:], True) + seg_target = tf.convert_to_tensor(seg_target, dtype=out_map.dtype) seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool) - - # Get the cross_entropy for each entry - bce = tf.keras.losses.binary_crossentropy( - seg_target[seg_mask], - tf.squeeze(out_map, axis=[-1])[seg_mask], - from_logits=True) - - if focal_loss: - if gamma and gamma < 0: - raise ValueError("Value of gamma should be greater than or equal to zero.") - - # Convert logits to prob, compute gamma factor - pred_prob = tf.sigmoid(tf.squeeze(out_map, axis=[-1])[seg_mask]) - p_t = (seg_target[seg_mask] * pred_prob) + ((1 - seg_target[seg_mask]) * (1 - pred_prob)) - modulating_factor = tf.pow((1.0 - p_t), gamma) - - # Compute alpha factor - alpha_factor = seg_target[seg_mask] * alpha + (1 - seg_target[seg_mask]) * (1 - alpha) - - # compute the final loss - loss = tf.reduce_mean(alpha_factor * modulating_factor * bce) - - else: - # Compute BCE loss with highlighted edges - loss = tf.math.multiply( - 1 + (edge_factor - 1) * tf.cast(edge_mask, tf.float32), - bce - ) - loss = tf.reduce_mean(loss) - - return loss + seg_mask = tf.cast(seg_mask, tf.float32) + + bce_loss = losses.binary_crossentropy(seg_target[..., None], out_map[..., None], from_logits=True) + proba_map = tf.sigmoid(out_map) + + # Focal loss + if gamma < 0: + raise ValueError("Value of gamma should be greater than or equal to zero.") + # Convert logits to prob, compute gamma factor + p_t = (seg_target * proba_map) + ((1 - seg_target) * (1 - proba_map)) + alpha_t = seg_target * alpha + (1 - seg_target) * (1 - alpha) + # Unreduced loss + focal_loss = alpha_t * (1 - p_t) ** gamma * bce_loss + # Class reduced + focal_loss = tf.reduce_sum(seg_mask * focal_loss, (0, 1, 2, 3)) / tf.reduce_sum(seg_mask, (0, 1, 2, 3)) + + # Compute dice loss for each class + dice_map = tf.nn.softmax(out_map, axis=-1) if len(self.class_names) > 1 else proba_map + # Class-reduced dice loss + inter = tf.reduce_sum(seg_mask * dice_map * seg_target, axis=[0, 1, 2]) + cardinality = tf.reduce_sum(seg_mask * (dice_map + seg_target), axis=[0, 1, 2]) + dice_loss = tf.reduce_mean(1 - 2 * inter / (cardinality + eps)) + + return focal_loss + dice_loss def call( self, x: tf.Tensor, - target: Optional[List[Dict[str, Any]]] = None, + target: Optional[List[Dict[str, np.ndarray]]] = None, return_model_output: bool = False, - return_boxes: bool = False, - focal_loss: bool = True, + return_preds: bool = False, **kwargs: Any, ) -> Dict[str, Any]: - - logits = self.stem(x) - logits = self.fpn(logits) - logits = self.classifier(logits) + feat_maps = self.feat_extractor(x, **kwargs) + logits = self.fpn(feat_maps, **kwargs) + logits = self.classifier(logits, **kwargs) out: Dict[str, tf.Tensor] = {} - if return_model_output or target is None or return_boxes: - prob_map = tf.math.sigmoid(logits) + if self.exportable: + out["logits"] = logits + return out + + if return_model_output or target is None or return_preds: + prob_map = _bf16_to_float32(tf.math.sigmoid(logits)) + if return_model_output: out["out_map"] = prob_map - if target is None or return_boxes: + if target is None or return_preds: # Post-process boxes - out["preds"] = self.postprocessor(tf.squeeze(prob_map, axis=-1).numpy()) + out["preds"] = [dict(zip(self.class_names, preds)) for preds in self.postprocessor(prob_map.numpy())] if target is not None: - loss = self.compute_loss(logits, target, focal_loss) - out['loss'] = loss + loss = self.compute_loss(logits, target) + out["loss"] = loss return out -def _linknet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> LinkNet: +def _linknet( + arch: str, + pretrained: bool, + backbone_fn, + fpn_layers: List[str], + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> LinkNet: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['num_classes'] = kwargs.get('num_classes', _cfg['num_classes']) - _cfg['rotated_bbox'] = kwargs.get('rotated_bbox', _cfg['rotated_bbox']) + _cfg["input_shape"] = input_shape or default_cfgs[arch]["input_shape"] + if not kwargs.get("class_names", None): + kwargs["class_names"] = _cfg.get("class_names", [CLASS_NAME]) + else: + kwargs["class_names"] = sorted(kwargs["class_names"]) + + # Feature extractor + feat_extractor = IntermediateLayerGetter( + backbone_fn( + pretrained=pretrained_backbone, + include_top=False, + input_shape=_cfg["input_shape"], + ), + fpn_layers, + ) - kwargs['num_classes'] = _cfg['num_classes'] - kwargs['input_shape'] = _cfg['input_shape'] - kwargs['rotated_bbox'] = _cfg['rotated_bbox'] # Build the model - model = LinkNet(cfg=_cfg, **kwargs) + model = LinkNet(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) + # Load pretrained parameters if pretrained: - load_pretrained_params(model, _cfg['url']) + # The given class_names differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, + _cfg["url"], + skip_mismatch=kwargs["class_names"] != default_cfgs[arch].get("class_names", [CLASS_NAME]), + ) return model -
    -[docs] -def linknet16(pretrained: bool = False, **kwargs: Any) -> LinkNet: +
    +[docs] +def linknet_resnet18(pretrained: bool = False, **kwargs: Any) -> LinkNet: + """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" + <https://arxiv.org/pdf/1707.03718.pdf>`_. + + >>> import tensorflow as tf + >>> from doctr.models import linknet_resnet18 + >>> model = linknet_resnet18(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the LinkNet architecture + + Returns: + ------- + text detection architecture + """ + return _linknet( + "linknet_resnet18", + pretrained, + resnet18, + ["resnet_block_1", "resnet_block_3", "resnet_block_5", "resnet_block_7"], + **kwargs, + )
    + + + +
    +[docs] +def linknet_resnet34(pretrained: bool = False, **kwargs: Any) -> LinkNet: """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" <https://arxiv.org/pdf/1707.03718.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import linknet16 - >>> model = linknet16(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import linknet_resnet34 + >>> model = linknet_resnet34(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the LinkNet architecture Returns: + ------- text detection architecture """ + return _linknet( + "linknet_resnet34", + pretrained, + resnet34, + ["resnet_block_2", "resnet_block_6", "resnet_block_12", "resnet_block_15"], + **kwargs, + )
    + + + +
    +[docs] +def linknet_resnet50(pretrained: bool = False, **kwargs: Any) -> LinkNet: + """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" + <https://arxiv.org/pdf/1707.03718.pdf>`_. + + >>> import tensorflow as tf + >>> from doctr.models import linknet_resnet50 + >>> model = linknet_resnet50(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the LinkNet architecture - return _linknet('linknet16', pretrained, **kwargs)
    + Returns: + ------- + text detection architecture + """ + return _linknet( + "linknet_resnet50", + pretrained, + resnet50, + ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"], + **kwargs, + )
    @@ -574,8 +715,8 @@

    Source code for doctr.models.detection.linknet.tensorflow

    - +
    + diff --git a/v0.4.1/_modules/doctr/models/detection/zoo.html b/v0.4.1/_modules/doctr/models/detection/zoo.html index d3128b8d14..3651c4e2d3 100644 --- a/v0.4.1/_modules/doctr/models/detection/zoo.html +++ b/v0.4.1/_modules/doctr/models/detection/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.detection.zoo - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.detection.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -from typing import Any
    +from typing import Any, List
     
     from doctr.file_utils import is_tf_available, is_torch_available
    -from .core import DetectionPredictor
    -from ..preprocessor import PreProcessor
    -from .. import detection
     
    +from .. import detection
    +from ..detection.fast import reparameterize
    +from ..preprocessor import PreProcessor
    +from .predictor import DetectionPredictor
     
     __all__ = ["detection_predictor"]
     
    +ARCHS: List[str]
    +
     
     if is_tf_available():
    -    ARCHS = ['db_resnet50', 'linknet16']
    +    ARCHS = [
    +        "db_resnet50",
    +        "db_mobilenet_v3_large",
    +        "linknet_resnet18",
    +        "linknet_resnet34",
    +        "linknet_resnet50",
    +        "fast_tiny",
    +        "fast_small",
    +        "fast_base",
    +    ]
     elif is_torch_available():
    -    ARCHS = ['db_resnet34', 'db_resnet50', 'db_mobilenet_v3', 'linknet16']
    +    ARCHS = [
    +        "db_resnet34",
    +        "db_resnet50",
    +        "db_mobilenet_v3_large",
    +        "linknet_resnet18",
    +        "linknet_resnet34",
    +        "linknet_resnet50",
    +        "fast_tiny",
    +        "fast_small",
    +        "fast_base",
    +    ]
    +
     
    +def _predictor(arch: Any, pretrained: bool, assume_straight_pages: bool = True, **kwargs: Any) -> DetectionPredictor:
    +    if isinstance(arch, str):
    +        if arch not in ARCHS:
    +            raise ValueError(f"unknown architecture '{arch}'")
     
    -def _predictor(arch: str, pretrained: bool, **kwargs: Any) -> DetectionPredictor:
    +        _model = detection.__dict__[arch](
    +            pretrained=pretrained,
    +            pretrained_backbone=kwargs.get("pretrained_backbone", True),
    +            assume_straight_pages=assume_straight_pages,
    +        )
    +        # Reparameterize FAST models by default to lower inference latency and memory usage
    +        if isinstance(_model, detection.FAST):
    +            _model = reparameterize(_model)
    +    else:
    +        if not isinstance(arch, (detection.DBNet, detection.LinkNet, detection.FAST)):
    +            raise ValueError(f"unknown architecture: {type(arch)}")
     
    -    if arch not in ARCHS:
    -        raise ValueError(f"unknown architecture '{arch}'")
    +        _model = arch
    +        _model.assume_straight_pages = assume_straight_pages
    +        _model.postprocessor.assume_straight_pages = assume_straight_pages
     
    -    # Detection
    -    _model = detection.__dict__[arch](pretrained=pretrained)
    -    kwargs['mean'] = kwargs.get('mean', _model.cfg['mean'])
    -    kwargs['std'] = kwargs.get('std', _model.cfg['std'])
    -    kwargs['batch_size'] = kwargs.get('batch_size', 1)
    +    kwargs.pop("pretrained_backbone", None)
    +
    +    kwargs["mean"] = kwargs.get("mean", _model.cfg["mean"])
    +    kwargs["std"] = kwargs.get("std", _model.cfg["std"])
    +    kwargs["batch_size"] = kwargs.get("batch_size", 2)
         predictor = DetectionPredictor(
    -        PreProcessor(_model.cfg['input_shape'][:2], **kwargs),
    -        _model
    +        PreProcessor(_model.cfg["input_shape"][:-1] if is_tf_available() else _model.cfg["input_shape"][1:], **kwargs),
    +        _model,
         )
         return predictor
     
     
     
    -[docs] -def detection_predictor(arch: str = 'db_resnet50', pretrained: bool = False, **kwargs: Any) -> DetectionPredictor: +[docs] +def detection_predictor( + arch: Any = "fast_base", + pretrained: bool = False, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + batch_size: int = 2, + **kwargs: Any, +) -> DetectionPredictor: """Text detection architecture. - Example:: - >>> import numpy as np - >>> from doctr.models import detection_predictor - >>> model = detection_predictor(pretrained=True) - >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) - >>> out = model([input_page]) + >>> import numpy as np + >>> from doctr.models import detection_predictor + >>> model = detection_predictor(arch='db_resnet50', pretrained=True) + >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) Args: - arch: name of the architecture to use ('db_resnet50') + ---- + arch: name of the architecture or model itself to use (e.g. 'db_resnet50') pretrained: If True, returns a model pre-trained on our text detection dataset + assume_straight_pages: If True, fit straight boxes to the page + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right + batch_size: number of samples the model processes in parallel + **kwargs: optional keyword arguments passed to the architecture Returns: + ------- Detection predictor """ - - return _predictor(arch, pretrained, **kwargs)
    + return _predictor( + arch=arch, + pretrained=pretrained, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + batch_size=batch_size, + **kwargs, + )
    @@ -367,8 +449,8 @@

    Source code for doctr.models.detection.zoo

           
         
       
    - - + + diff --git a/v0.4.1/_modules/doctr/models/export.html b/v0.4.1/_modules/doctr/models/export.html deleted file mode 100644 index f25a81aa21..0000000000 --- a/v0.4.1/_modules/doctr/models/export.html +++ /dev/null @@ -1,411 +0,0 @@ - - - - - - - - - - - - doctr.models.export - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.export

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import logging
    -import numpy as np
    -import tensorflow as tf
    -from tensorflow.keras import Model
    -from typing import Tuple
    -
    -logging.getLogger("tensorflow").setLevel(logging.DEBUG)
    -
    -
    -__all__ = ['convert_to_tflite', 'convert_to_fp16', 'quantize_model']
    -
    -
    -
    -[docs] -def convert_to_tflite(tf_model: Model) -> bytes: - """Converts a model to TFLite format - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_tflite, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_tflite(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - return converter.convert()
    - - - -
    -[docs] -def convert_to_fp16(tf_model: Model) -> bytes: - """Converts a model to half precision - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_fp16, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_fp16(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the serialized FP16 model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - converter.target_spec.supported_types = [tf.float16] - return converter.convert()
    - - - -
    -[docs] -def quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) -> bytes: - """Quantize a Tensorflow model - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import quantize_model, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = quantize_model(model, (224, 224, 3)) - - Args: - tf_model: a keras model - input_shape: shape of the expected input tensor (excluding batch dimension) with channel last order - - Returns: - bytes: the serialized quantized model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - - # Float fallback for operators that do not have an integer implementation - def representative_dataset(): - for _ in range(100): - data = np.random.rand(1, *input_shape) - yield [data.astype(np.float32)] - - converter.representative_dataset = representative_dataset - converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] - converter.inference_input_type = tf.int8 - converter.inference_output_type = tf.int8 - - return converter.convert()
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.1/_modules/doctr/models/factory/hub.html b/v0.4.1/_modules/doctr/models/factory/hub.html index 8274a809f5..756b2c7a17 100644 --- a/v0.4.1/_modules/doctr/models/factory/hub.html +++ b/v0.4.1/_modules/doctr/models/factory/hub.html @@ -13,7 +13,7 @@ - + doctr.models.factory.hub - docTR documentation @@ -568,7 +568,7 @@

    Source code for doctr.models.factory.hub

         
       
    - + diff --git a/v0.4.1/_modules/doctr/models/recognition/crnn.html b/v0.4.1/_modules/doctr/models/recognition/crnn.html deleted file mode 100644 index daa2393439..0000000000 --- a/v0.4.1/_modules/doctr/models/recognition/crnn.html +++ /dev/null @@ -1,565 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.crnn - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.crnn

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import layers
    -from tensorflow.keras.models import Sequential
    -from typing import Tuple, Dict, Any, Optional, List
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel, RecognitionPostProcessor
    -
    -__all__ = ['CRNN', 'crnn_vgg16_bn', 'crnn_resnet31', 'CTCPostProcessor']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'crnn_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/crnn_vgg16_bn-748c855f.zip',
    -    },
    -    'crnn_resnet31': {
    -        'mean': (0.694, 0.695, 0.693),
    -        'std': (0.299, 0.296, 0.301),
    -        'backbone': 'resnet31', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.1/crnn_resnet31-69ab71db.zip',
    -    },
    -}
    -
    -
    -class CTCPostProcessor(RecognitionPostProcessor):
    -    """
    -    Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor
    -    ) -> List[Tuple[str, float]]:
    -        """
    -        Performs decoding of raw output with CTC and decoding of CTC predictions
    -        with label_to_idx mapping dictionnary
    -
    -        Args:
    -            logits: raw output of the model, shape BATCH_SIZE X SEQ_LEN X NUM_CLASSES + 1
    -
    -        Returns:
    -            A list of decoded words of length BATCH_SIZE
    -
    -        """
    -        # Decode CTC
    -        _decoded, _log_prob = tf.nn.ctc_beam_search_decoder(
    -            tf.transpose(logits, perm=[1, 0, 2]),
    -            tf.fill(logits.shape[0], logits.shape[1]),
    -            beam_width=1, top_paths=1,
    -        )
    -        out_idxs = tf.sparse.to_dense(_decoded[0], default_value=len(self.vocab))
    -        probs = tf.math.exp(tf.squeeze(_log_prob, axis=1))
    -
    -        # Map it to characters
    -        _decoded_strings_pred = tf.strings.reduce_join(
    -            inputs=tf.nn.embedding_lookup(self._embedding, out_idxs),
    -            axis=-1
    -        )
    -        _decoded_strings_pred = tf.strings.split(_decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -class CRNN(RecognitionModel):
    -    """Implements a CRNN architecture as described in `"An End-to-End Trainable Neural Network for Image-based
    -    Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of units in the LSTM layers
    -        cfg: configuration dictionary
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: tf.keras.Model,
    -        vocab: str,
    -        rnn_units: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(vocab=vocab, cfg=cfg)
    -        self.feat_extractor = feature_extractor
    -
    -        # Initialize kernels
    -        h, w, c = self.feat_extractor.output_shape[1:]
    -        self.max_length = w
    -
    -        self.decoder = Sequential(
    -            [
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Dense(units=len(vocab) + 1)
    -            ]
    -        )
    -        self.decoder.build(input_shape=(None, w, h * c))
    -
    -        self.postprocessor = CTCPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        target: List[str],
    -    ) -> tf.Tensor:
    -        """Compute CTC loss for the model.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        gt, seq_len = self.compute_target(target)
    -        batch_len = model_output.shape[0]
    -        input_length = model_output.shape[1] * tf.ones(shape=(batch_len))
    -        ctc_loss = tf.nn.ctc_loss(
    -            gt, model_output, seq_len, input_length, logits_time_major=False, blank_index=len(self.vocab)
    -        )
    -        return ctc_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        # B x H x W x C --> B x W x H x C
    -        transposed_feat = tf.transpose(features, perm=[0, 2, 1, 3])
    -        w, h, c = transposed_feat.get_shape().as_list()[1:]
    -        # B x W x H x C --> B x W x H * C
    -        features_seq = tf.reshape(transposed_feat, shape=(-1, w, h * c))
    -        logits = self.decoder(features_seq, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = logits
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(logits)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(logits, target)
    -
    -        return out
    -
    -
    -def _crnn(arch: str, pretrained: bool, input_shape: Optional[Tuple[int, int, int]] = None, **kwargs: Any) -> CRNN:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[_cfg['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -
    -    # Build the model
    -    model = CRNN(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a VGG-16 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_vgg16_bn - >>> model = crnn_vgg16_bn(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_vgg16_bn', pretrained, **kwargs)
    - - - -def crnn_resnet31(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a resnet31 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_resnet31 - >>> model = crnn_resnet31(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_resnet31', pretrained, **kwargs) -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.1/_modules/doctr/models/recognition/crnn/tensorflow.html b/v0.4.1/_modules/doctr/models/recognition/crnn/tensorflow.html index 41cc93dd23..bc64da9a1b 100644 --- a/v0.4.1/_modules/doctr/models/recognition/crnn/tensorflow.html +++ b/v0.4.1/_modules/doctr/models/recognition/crnn/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.crnn.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple, Union
    +
     import tensorflow as tf
     from tensorflow.keras import layers
    -from tensorflow.keras.models import Sequential, Model
    -from typing import Tuple, Dict, Any, Optional, List
    +from tensorflow.keras.models import Model, Sequential
    +
    +from doctr.datasets import VOCABS
     
    -from ... import backbones
    -from ...utils import load_pretrained_params
    +from ...classification import mobilenet_v3_large_r, mobilenet_v3_small_r, vgg16_bn_r
    +from ...utils.tensorflow import _bf16_to_float32, _build_model, load_pretrained_params
     from ..core import RecognitionModel, RecognitionPostProcessor
     
    -__all__ = ['CRNN', 'crnn_vgg16_bn', 'crnn_resnet31', 'CTCPostProcessor']
    +__all__ = ["CRNN", "crnn_vgg16_bn", "crnn_mobilenet_v3_small", "crnn_mobilenet_v3_large"]
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'crnn_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/crnn_vgg16_bn-748c855f.zip',
    +    "crnn_vgg16_bn": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["legacy_french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/crnn_vgg16_bn-9c188f45.weights.h5&src=0",
         },
    -    'crnn_resnet31': {
    -        'mean': (0.694, 0.695, 0.693),
    -        'std': (0.299, 0.296, 0.301),
    -        'backbone': 'resnet31', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.1/crnn_resnet31-69ab71db.zip',
    +    "crnn_mobilenet_v3_small": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/crnn_mobilenet_v3_small-54850265.weights.h5&src=0",
    +    },
    +    "crnn_mobilenet_v3_large": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/crnn_mobilenet_v3_large-c64045e5.weights.h5&src=0",
         },
     }
     
     
     class CTCPostProcessor(RecognitionPostProcessor):
    -    """
    -    Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
    +    """Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
     
         Args:
    +    ----
             vocab: string containing the ordered sequence of supported characters
             ignore_case: if True, ignore case of letters
             ignore_accents: if True, ignore accents of letters
    @@ -325,37 +353,57 @@ 

    Source code for doctr.models.recognition.crnn.tensorflow

    def __call__( self, - logits: tf.Tensor - ) -> List[Tuple[str, float]]: - """ - Performs decoding of raw output with CTC and decoding of CTC predictions + logits: tf.Tensor, + beam_width: int = 1, + top_paths: int = 1, + ) -> Union[List[Tuple[str, float]], List[Tuple[List[str], List[float]]]]: + """Performs decoding of raw output with CTC and decoding of CTC predictions with label_to_idx mapping dictionnary Args: + ---- logits: raw output of the model, shape BATCH_SIZE X SEQ_LEN X NUM_CLASSES + 1 + beam_width: An int scalar >= 0 (beam search beam width). + top_paths: An int scalar >= 0, <= beam_width (controls output size). Returns: + ------- A list of decoded words of length BATCH_SIZE + """ # Decode CTC _decoded, _log_prob = tf.nn.ctc_beam_search_decoder( tf.transpose(logits, perm=[1, 0, 2]), - tf.fill(logits.shape[0], logits.shape[1]), - beam_width=1, top_paths=1, + tf.fill(tf.shape(logits)[:1], tf.shape(logits)[1]), + beam_width=beam_width, + top_paths=top_paths, ) - out_idxs = tf.sparse.to_dense(_decoded[0], default_value=len(self.vocab)) - probs = tf.math.exp(tf.squeeze(_log_prob, axis=1)) + + _decoded = tf.sparse.concat( + 1, + [tf.sparse.expand_dims(dec, axis=1) for dec in _decoded], + expand_nonconcat_dims=True, + ) # dim : batchsize x beamwidth x actual_max_len_predictions + out_idxs = tf.sparse.to_dense(_decoded, default_value=len(self.vocab)) # Map it to characters _decoded_strings_pred = tf.strings.reduce_join( inputs=tf.nn.embedding_lookup(tf.constant(self._embedding, dtype=tf.string), out_idxs), - axis=-1 + axis=-1, ) _decoded_strings_pred = tf.strings.split(_decoded_strings_pred, "<eos>") - decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0] - word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] - + decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value="not valid")[ + :, :, 0 + ] # dim : batch_size x beam_width + + if top_paths == 1: + probs = tf.math.exp(tf.squeeze(_log_prob, axis=1)) # dim : batchsize + decoded_strings_pred = tf.squeeze(decoded_strings_pred, axis=1) + word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] + else: + probs = tf.math.exp(_log_prob) # dim : batchsize x beamwidth + word_values = [[word.decode() for word in words] for words in decoded_strings_pred.numpy().tolist()] return list(zip(word_values, probs.numpy().tolist())) @@ -364,19 +412,26 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. Args: + ---- feature_extractor: the backbone serving as feature extractor vocab: vocabulary used for encoding rnn_units: number of units in the LSTM layers + exportable: onnx exportable returns only logits + beam_width: beam width for beam search decoding + top_paths: number of top paths for beam search decoding cfg: configuration dictionary """ - _children_names: List[str] = ['feat_extractor', 'decoder', 'postprocessor'] + _children_names: List[str] = ["feat_extractor", "decoder", "postprocessor"] def __init__( self, - feature_extractor: tf.keras.Model, + feature_extractor: Model, vocab: str, rnn_units: int = 128, + exportable: bool = False, + beam_width: int = 1, + top_paths: int = 1, cfg: Optional[Dict[str, Any]] = None, ) -> None: # Initialize kernels @@ -386,19 +441,21 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    self.vocab = vocab self.max_length = w self.cfg = cfg + self.exportable = exportable self.feat_extractor = feature_extractor - self.decoder = Sequential( - [ - layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), - layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), - layers.Dense(units=len(vocab) + 1) - ] - ) + self.decoder = Sequential([ + layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), + layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), + layers.Dense(units=len(vocab) + 1), + ]) self.decoder.build(input_shape=(None, w, h * c)) self.postprocessor = CTCPostProcessor(vocab=vocab) + self.beam_width = beam_width + self.top_paths = top_paths + def compute_loss( self, model_output: tf.Tensor, @@ -407,16 +464,17 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    """Compute CTC loss for the model. Args: - gt: the encoded tensor with gt labels + ---- model_output: predicted logits of the model - seq_len: lengths of each gt word inside the batch + target: lengths of each gt word inside the batch Returns: + ------- The loss of the model on the batch """ - gt, seq_len = self.compute_target(target) + gt, seq_len = self.build_target(target) batch_len = model_output.shape[0] - input_length = model_output.shape[1] * tf.ones(shape=(batch_len)) + input_length = tf.fill((batch_len,), model_output.shape[1]) ctc_loss = tf.nn.ctc_loss( gt, model_output, seq_len, input_length, logits_time_major=False, blank_index=len(self.vocab) ) @@ -428,8 +486,12 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    target: Optional[List[str]] = None, return_model_output: bool = False, return_preds: bool = False, + beam_width: int = 1, + top_paths: int = 1, **kwargs: Any, ) -> Dict[str, Any]: + if kwargs.get("training", False) and target is None: + raise ValueError("Need to provide labels during training") features = self.feat_extractor(x, **kwargs) # B x H x W x C --> B x W x H x C @@ -437,91 +499,132 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    w, h, c = transposed_feat.get_shape().as_list()[1:] # B x W x H x C --> B x W x H * C features_seq = tf.reshape(transposed_feat, shape=(-1, w, h * c)) - logits = self.decoder(features_seq, **kwargs) + logits = _bf16_to_float32(self.decoder(features_seq, **kwargs)) out: Dict[str, tf.Tensor] = {} + if self.exportable: + out["logits"] = logits + return out + if return_model_output: out["out_map"] = logits if target is None or return_preds: # Post-process boxes - out["preds"] = self.postprocessor(logits) + out["preds"] = self.postprocessor(logits, beam_width=beam_width, top_paths=top_paths) if target is not None: - out['loss'] = self.compute_loss(logits, target) + out["loss"] = self.compute_loss(logits, target) return out -def _crnn(arch: str, pretrained: bool, input_shape: Optional[Tuple[int, int, int]] = None, **kwargs: Any) -> CRNN: +def _crnn( + arch: str, + pretrained: bool, + backbone_fn, + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> CRNN: + pretrained_backbone = pretrained_backbone and not pretrained + + kwargs["vocab"] = kwargs.get("vocab", default_cfgs[arch]["vocab"]) - # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab']) - _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units']) + _cfg["vocab"] = kwargs["vocab"] + _cfg["input_shape"] = input_shape or default_cfgs[arch]["input_shape"] - # Feature extractor - feat_extractor = backbones.__dict__[_cfg['backbone']]( - input_shape=_cfg['input_shape'], + feat_extractor = backbone_fn( + input_shape=_cfg["input_shape"], include_top=False, + pretrained=pretrained_backbone, ) - kwargs['vocab'] = _cfg['vocab'] - kwargs['rnn_units'] = _cfg['rnn_units'] - # Build the model model = CRNN(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) # Load pretrained parameters if pretrained: - load_pretrained_params(model, _cfg['url']) + # The given vocab differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params(model, _cfg["url"], skip_mismatch=kwargs["vocab"] != default_cfgs[arch]["vocab"]) return model
    -[docs] +[docs] def crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> CRNN: """CRNN with a VGG-16 backbone as described in `"An End-to-End Trainable Neural Network for Image-based Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_vgg16_bn - >>> model = crnn_vgg16_bn(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import crnn_vgg16_bn + >>> model = crnn_vgg16_bn(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the CRNN architecture Returns: + ------- text recognition architecture """ + return _crnn("crnn_vgg16_bn", pretrained, vgg16_bn_r, **kwargs)
    + + + +
    +[docs] +def crnn_mobilenet_v3_small(pretrained: bool = False, **kwargs: Any) -> CRNN: + """CRNN with a MobileNet V3 Small backbone as described in `"An End-to-End Trainable Neural Network for Image-based + Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. + + >>> import tensorflow as tf + >>> from doctr.models import crnn_mobilenet_v3_small + >>> model = crnn_mobilenet_v3_small(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the CRNN architecture - return _crnn('crnn_vgg16_bn', pretrained, **kwargs)
    + Returns: + ------- + text recognition architecture + """ + return _crnn("crnn_mobilenet_v3_small", pretrained, mobilenet_v3_small_r, **kwargs)
    -def crnn_resnet31(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a resnet31 backbone as described in `"An End-to-End Trainable Neural Network for Image-based +
    +[docs] +def crnn_mobilenet_v3_large(pretrained: bool = False, **kwargs: Any) -> CRNN: + """CRNN with a MobileNet V3 Large backbone as described in `"An End-to-End Trainable Neural Network for Image-based Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_resnet31 - >>> model = crnn_resnet31(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import crnn_mobilenet_v3_large + >>> model = crnn_mobilenet_v3_large(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the CRNN architecture Returns: + ------- text recognition architecture """ + return _crnn("crnn_mobilenet_v3_large", pretrained, mobilenet_v3_large_r, **kwargs)
    - return _crnn('crnn_resnet31', pretrained, **kwargs)
    @@ -554,8 +657,8 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    - +
    + diff --git a/v0.4.1/_modules/doctr/models/recognition/master/tensorflow.html b/v0.4.1/_modules/doctr/models/recognition/master/tensorflow.html index 2dc5a27717..aa6aa69325 100644 --- a/v0.4.1/_modules/doctr/models/recognition/master/tensorflow.html +++ b/v0.4.1/_modules/doctr/models/recognition/master/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.master.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.master.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import tensorflow as tf
    -from tensorflow.keras import layers, Sequential, Model
    -from typing import Tuple, List, Dict, Any, Optional
     from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple
     
    -from ..core import RecognitionPostProcessor
    -from ...backbones.resnet import ResnetStage
    -from ...utils import conv_sequence, load_pretrained_params
    -from ..transformer import Decoder, positional_encoding, create_look_ahead_mask, create_padding_mask
    -from ....datasets import VOCABS
    -from .base import _MASTER, _MASTERPostProcessor
    +import tensorflow as tf
    +from tensorflow.keras import Model, layers
    +
    +from doctr.datasets import VOCABS
    +from doctr.models.classification import magc_resnet31
    +from doctr.models.modules.transformer import Decoder, PositionalEncoding
     
    +from ...utils.tensorflow import _bf16_to_float32, _build_model, load_pretrained_params
    +from .base import _MASTER, _MASTERPostProcessor
     
    -__all__ = ['MASTER', 'master', 'MASTERPostProcessor']
    +__all__ = ["MASTER", "master"]
     
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'master': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'input_shape': (48, 160, 3),
    -        'vocab': VOCABS['french'],
    -        'url': None,
    +    "master": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/master-d7fdaeff.weights.h5&src=0",
         },
     }
     
     
    -class MAGC(layers.Layer):
    -
    -    """Implements the Multi-Aspect Global Context Attention, as described in
    -    <https://arxiv.org/pdf/1910.02562.pdf>`_.
    -
    -    Args:
    -        inplanes: input channels
    -        headers: number of headers to split channels
    -        att_scale: if True, re-scale attention to counteract the variance distibutions
    -        **kwargs
    -    """
    -
    -    def __init__(
    -        self,
    -        inplanes: int,
    -        headers: int = 1,
    -        att_scale: bool = False,
    -        **kwargs
    -    ) -> None:
    -        super().__init__(**kwargs)
    -
    -        self.headers = headers  # h
    -        self.inplanes = inplanes  # C
    -        self.att_scale = att_scale
    -
    -        self.single_header_inplanes = int(inplanes / headers)  # C / h
    -
    -        self.conv_mask = tf.keras.layers.Conv2D(
    -            filters=1,
    -            kernel_size=1,
    -            kernel_initializer=tf.initializers.he_normal()
    -        )
    -
    -        self.transform = tf.keras.Sequential(
    -            [
    -                tf.keras.layers.Conv2D(
    -                    filters=self.inplanes,
    -                    kernel_size=1,
    -                    kernel_initializer=tf.initializers.he_normal()
    -                ),
    -                tf.keras.layers.LayerNormalization([1, 2, 3]),
    -                tf.keras.layers.ReLU(),
    -                tf.keras.layers.Conv2D(
    -                    filters=self.inplanes,
    -                    kernel_size=1,
    -                    kernel_initializer=tf.initializers.he_normal()
    -                ),
    -            ],
    -            name='transform'
    -        )
    -
    -    @tf.function
    -    def context_modeling(self, inputs: tf.Tensor) -> tf.Tensor:
    -        b, h, w, c = (tf.shape(inputs)[i] for i in range(4))
    -
    -        # B, H, W, C -->> B*h, H, W, C/h
    -        x = tf.reshape(inputs, shape=(b, h, w, self.headers, self.single_header_inplanes))
    -        x = tf.transpose(x, perm=(0, 3, 1, 2, 4))
    -        x = tf.reshape(x, shape=(b * self.headers, h, w, self.single_header_inplanes))
    -
    -        # Compute shorcut
    -        shortcut = x
    -        # B*h, 1, H*W, C/h
    -        shortcut = tf.reshape(shortcut, shape=(b * self.headers, 1, h * w, self.single_header_inplanes))
    -        # B*h, 1, C/h, H*W
    -        shortcut = tf.transpose(shortcut, perm=[0, 1, 3, 2])
    -
    -        # Compute context mask
    -        # B*h, H, W, 1,
    -        context_mask = self.conv_mask(x)
    -        # B*h, 1, H*W, 1
    -        context_mask = tf.reshape(context_mask, shape=(b * self.headers, 1, h * w, 1))
    -        # scale variance
    -        if self.att_scale and self.headers > 1:
    -            context_mask = context_mask / tf.sqrt(self.single_header_inplanes)
    -        # B*h, 1, H*W, 1
    -        context_mask = tf.keras.activations.softmax(context_mask, axis=2)
    -
    -        # Compute context
    -        # B*h, 1, C/h, 1
    -        context = tf.matmul(shortcut, context_mask)
    -        context = tf.reshape(context, shape=(b, 1, c, 1))
    -        # B, 1, 1, C
    -        context = tf.transpose(context, perm=(0, 1, 3, 2))
    -        # Set shape to resolve shape when calling this module in the Sequential MAGCResnet
    -        batch, chan = inputs.get_shape().as_list()[0], inputs.get_shape().as_list()[-1]
    -        context.set_shape([batch, 1, 1, chan])
    -        return context
    -
    -    def call(self, inputs: tf.Tensor, **kwargs) -> tf.Tensor:
    -        # Context modeling: B, H, W, C  ->  B, 1, 1, C
    -        context = self.context_modeling(inputs)
    -        # Transform: B, 1, 1, C  ->  B, 1, 1, C
    -        transformed = self.transform(context)
    -        return inputs + transformed
    -
    -
    -class MAGCResnet(Sequential):
    -
    -    """Implements the modified resnet with MAGC layers, as described in paper.
    -
    -    Args:
    -        headers: number of header to split channels in MAGC layers
    -        input_shape: shape of the model input (without batch dim)
    -    """
    -
    -    def __init__(
    -        self,
    -        headers: int = 1,
    -        input_shape: Tuple[int, int, int] = (48, 160, 3),
    -    ) -> None:
    -        _layers = [
    -            # conv_1x
    -            *conv_sequence(out_channels=64, activation='relu', bn=True, kernel_size=3, input_shape=input_shape),
    -            *conv_sequence(out_channels=128, activation='relu', bn=True, kernel_size=3),
    -            layers.MaxPooling2D((2, 2), (2, 2)),
    -            # conv_2x
    -            ResnetStage(num_blocks=1, output_channels=256),
    -            MAGC(inplanes=256, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=256, activation='relu', bn=True, kernel_size=3),
    -            layers.MaxPooling2D((2, 2), (2, 2)),
    -            # conv_3x
    -            ResnetStage(num_blocks=2, output_channels=512),
    -            MAGC(inplanes=512, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=512, activation='relu', bn=True, kernel_size=3),
    -            layers.MaxPooling2D((2, 1), (2, 1)),
    -            # conv_4x
    -            ResnetStage(num_blocks=5, output_channels=512),
    -            MAGC(inplanes=512, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=512, activation='relu', bn=True, kernel_size=3),
    -            # conv_5x
    -            ResnetStage(num_blocks=3, output_channels=512),
    -            MAGC(inplanes=512, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=512, activation='relu', bn=True, kernel_size=3),
    -        ]
    -        super().__init__(_layers)
    -
    -
     class MASTER(_MASTER, Model):
    -
         """Implements MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_.
         Implementation based on the official TF implementation: <https://github.com/jiangxiluning/MASTER-TF>`_.
     
         Args:
    +    ----
    +        feature_extractor: the backbone serving as feature extractor
             vocab: vocabulary, (without EOS, SOS, PAD)
             d_model: d parameter for the transformer decoder
    -        headers: headers for the MAGC module
             dff: depth of the pointwise feed-forward layer
             num_heads: number of heads for the mutli-head attention module
             num_layers: number of decoder layers to stack
             max_length: maximum length of character sequence handled by the model
    -        input_size: size of the image inputs
    +        dropout: dropout probability of the decoder
    +        input_shape: size of the image inputs
    +        exportable: onnx exportable returns only logits
    +        cfg: dictionary containing information about the model
         """
     
         def __init__(
             self,
    +        feature_extractor: Model,
             vocab: str,
             d_model: int = 512,
    -        headers: int = 1,
             dff: int = 2048,
    -        num_heads: int = 8,
    +        num_heads: int = 8,  # number of heads in the transformer decoder
             num_layers: int = 3,
             max_length: int = 50,
    -        input_shape: Tuple[int, int, int] = (48, 160, 3),
    +        dropout: float = 0.2,
    +        input_shape: Tuple[int, int, int] = (32, 128, 3),  # different from the paper
    +        exportable: bool = False,
             cfg: Optional[Dict[str, Any]] = None,
         ) -> None:
             super().__init__()
     
    -        self.vocab = vocab
    +        self.exportable = exportable
             self.max_length = max_length
    +        self.d_model = d_model
    +        self.vocab = vocab
             self.cfg = cfg
             self.vocab_size = len(vocab)
     
    -        self.feature_extractor = MAGCResnet(headers=headers, input_shape=input_shape)
    -        self.seq_embedding = layers.Embedding(self.vocab_size + 3, d_model)  # 3 more classes: EOS/PAD/SOS
    +        self.feat_extractor = feature_extractor
    +        self.positional_encoding = PositionalEncoding(self.d_model, dropout, max_len=input_shape[0] * input_shape[1])
     
             self.decoder = Decoder(
                 num_layers=num_layers,
    -            d_model=d_model,
    +            d_model=self.d_model,
                 num_heads=num_heads,
    +            vocab_size=self.vocab_size + 3,  # EOS, SOS, PAD
                 dff=dff,
    -            vocab_size=self.vocab_size,
    -            maximum_position_encoding=max_length,
    +            dropout=dropout,
    +            maximum_position_encoding=self.max_length,
             )
    -        self.feature_pe = positional_encoding(input_shape[0] * input_shape[1], d_model)
    -        self.linear = layers.Dense(self.vocab_size + 3, kernel_initializer=tf.initializers.he_uniform())
     
    +        self.linear = layers.Dense(self.vocab_size + 3, kernel_initializer=tf.initializers.he_uniform())
             self.postprocessor = MASTERPostProcessor(vocab=self.vocab)
     
         @tf.function
    -    def make_mask(self, target: tf.Tensor) -> tf.Tensor:
    -        look_ahead_mask = create_look_ahead_mask(tf.shape(target)[1])
    -        target_padding_mask = create_padding_mask(target, self.vocab_size + 2)  # Pad symbol
    -        combined_mask = tf.maximum(target_padding_mask, look_ahead_mask)
    -        return combined_mask
    +    def make_source_and_target_mask(self, source: tf.Tensor, target: tf.Tensor) -> Tuple[tf.Tensor, tf.Tensor]:
    +        # [1, 1, 1, ..., 0, 0, 0] -> 0 is masked
    +        # (N, 1, 1, max_length)
    +        target_pad_mask = tf.cast(tf.math.not_equal(target, self.vocab_size + 2), dtype=tf.uint8)
    +        target_pad_mask = target_pad_mask[:, tf.newaxis, tf.newaxis, :]
    +        target_length = target.shape[1]
    +        # sub mask filled diagonal with 1 = see 0 = masked (max_length, max_length)
    +        target_sub_mask = tf.linalg.band_part(tf.ones((target_length, target_length)), -1, 0)
    +        # source mask filled with ones (max_length, positional_encoded_seq_len)
    +        source_mask = tf.ones((target_length, source.shape[1]))
    +        # combine the two masks into one boolean mask where False is masked (N, 1, max_length, max_length)
    +        target_mask = tf.math.logical_and(
    +            tf.cast(target_sub_mask, dtype=tf.bool), tf.cast(target_pad_mask, dtype=tf.bool)
    +        )
    +        return source_mask, target_mask
     
    +    @staticmethod
         def compute_loss(
    -        self,
             model_output: tf.Tensor,
             gt: tf.Tensor,
             seq_len: List[int],
    @@ -512,11 +413,13 @@ 

    Source code for doctr.models.recognition.master.tensorflow

    Sequences are masked after the EOS character. Args: + ---- gt: the encoded tensor with gt labels model_output: predicted logits of the model seq_len: lengths of each gt word inside the batch Returns: + ------- The loss of the model on the batch """ # Input length : number of timesteps @@ -532,7 +435,7 @@

    Source code for doctr.models.recognition.master.tensorflow

    mask_values = tf.zeros_like(cce) mask_2d = tf.sequence_mask(seq_len, input_len - 1) # delete the last mask timestep as well masked_loss = tf.where(mask_2d, cce, mask_values) - ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32)) + ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, model_output.dtype)) return tf.expand_dims(ce_loss, axis=1) @@ -547,94 +450,103 @@

    Source code for doctr.models.recognition.master.tensorflow

    """Call function for training Args: + ---- x: images target: list of str labels return_model_output: if True, return logits return_preds: if True, decode logits + **kwargs: keyword arguments passed to the decoder - Return: + Returns: + ------- A dictionnary containing eventually loss, logits and predictions. """ - # Encode - feature = self.feature_extractor(x, **kwargs) - b, h, w, c = (tf.shape(feature)[i] for i in range(4)) + feature = self.feat_extractor(x, **kwargs) + b, h, w, c = feature.get_shape() + # (N, H, W, C) --> (N, H * W, C) feature = tf.reshape(feature, shape=(b, h * w, c)) - encoded = feature + self.feature_pe[:, :h * w, :] + # add positional encoding to features + encoded = self.positional_encoding(feature, **kwargs) out: Dict[str, tf.Tensor] = {} + if kwargs.get("training", False) and target is None: + raise ValueError("Need to provide labels during training") + if target is not None: # Compute target: tensor of gts and sequence lengths - gt, seq_len = self.compute_target(target) - - if kwargs.get('training', False): - if target is None: - raise AssertionError("In training mode, you need to pass a value to 'target'") - tgt_mask = self.make_mask(gt) + gt, seq_len = self.build_target(target) + # Compute decoder masks + source_mask, target_mask = self.make_source_and_target_mask(encoded, gt) # Compute logits - output = self.decoder(gt, encoded, tgt_mask, None, **kwargs) + output = self.decoder(gt, encoded, source_mask, target_mask, **kwargs) logits = self.linear(output, **kwargs) - else: - # When not training, we want to compute logits in with the decoder, although - # we have access to gts (we need gts to compute the loss, but not in the decoder) logits = self.decode(encoded, **kwargs) + logits = _bf16_to_float32(logits) + + if self.exportable: + out["logits"] = logits + return out + if target is not None: - out['loss'] = self.compute_loss(logits, gt, seq_len) + out["loss"] = self.compute_loss(logits, gt, seq_len) if return_model_output: - out['out_map'] = logits + out["out_map"] = logits if return_preds: - predictions = self.postprocessor(logits) - out['preds'] = predictions + out["preds"] = self.postprocessor(logits) return out + @tf.function def decode(self, encoded: tf.Tensor, **kwargs: Any) -> tf.Tensor: """Decode function for prediction Args: + ---- encoded: encoded features + **kwargs: keyword arguments passed to the decoder - Return: + Returns: + ------- A Tuple of tf.Tensor: predictions, logits """ - b = tf.shape(encoded)[0] - max_len = tf.constant(self.max_length, dtype=tf.int32) + b = encoded.shape[0] + start_symbol = tf.constant(self.vocab_size + 1, dtype=tf.int32) # SOS padding_symbol = tf.constant(self.vocab_size + 2, dtype=tf.int32) # PAD - ys = tf.fill(dims=(b, max_len - 1), value=padding_symbol) + ys = tf.fill(dims=(b, self.max_length - 1), value=padding_symbol) start_vector = tf.fill(dims=(b, 1), value=start_symbol) ys = tf.concat([start_vector, ys], axis=-1) - logits = tf.zeros(shape=(b, max_len - 1, self.vocab_size + 3), dtype=tf.float32) # 3 symbols - # max_len = len + 2 (sos + eos) + # Final dimension include EOS/SOS/PAD for i in range(self.max_length - 1): - ys_mask = self.make_mask(ys) - output = self.decoder(ys, encoded, ys_mask, None, **kwargs) + source_mask, target_mask = self.make_source_and_target_mask(encoded, ys) + output = self.decoder(ys, encoded, source_mask, target_mask, **kwargs) logits = self.linear(output, **kwargs) prob = tf.nn.softmax(logits, axis=-1) - next_word = tf.argmax(prob, axis=-1, output_type=ys.dtype) - # ys.shape = B, T - i_mesh, j_mesh = tf.meshgrid(tf.range(b), tf.range(max_len), indexing='ij') + next_token = tf.argmax(prob, axis=-1, output_type=ys.dtype) + # update ys with the next token and ignore the first token (SOS) + i_mesh, j_mesh = tf.meshgrid(tf.range(b), tf.range(self.max_length), indexing="ij") indices = tf.stack([i_mesh[:, i + 1], j_mesh[:, i + 1]], axis=1) - ys = tf.tensor_scatter_nd_update(ys, indices, next_word[:, i + 1]) + ys = tf.tensor_scatter_nd_update(ys, indices, next_token[:, i]) - # final_logits of shape (N, max_length - 1, vocab_size + 1) (whithout sos) + # Shape (N, max_length, vocab_size + 1) return logits class MASTERPostProcessor(_MASTERPostProcessor): """Post processor for MASTER architectures + Args: + ---- vocab: string containing the ordered sequence of supported characters - ignore_case: if True, ignore case of letters - ignore_accents: if True, ignore accents of letters """ def __call__( @@ -649,51 +561,66 @@

    Source code for doctr.models.recognition.master.tensorflow

    probs = tf.math.reduce_min(probs, axis=1) # decode raw output of the model with tf_label_to_idx - out_idxs = tf.cast(out_idxs, dtype='int32') + out_idxs = tf.cast(out_idxs, dtype="int32") embedding = tf.constant(self._embedding, dtype=tf.string) decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(embedding, out_idxs), axis=-1) decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>") - decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0] + decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value="not valid")[:, 0] word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] - return list(zip(word_values, probs.numpy().tolist())) + return list(zip(word_values, probs.numpy().clip(0, 1).tolist())) -def _master(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> MASTER: +def _master(arch: str, pretrained: bool, backbone_fn, pretrained_backbone: bool = True, **kwargs: Any) -> MASTER: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab']) + _cfg["input_shape"] = kwargs.get("input_shape", _cfg["input_shape"]) + _cfg["vocab"] = kwargs.get("vocab", _cfg["vocab"]) - kwargs['vocab'] = _cfg['vocab'] + kwargs["vocab"] = _cfg["vocab"] + kwargs["input_shape"] = _cfg["input_shape"] # Build the model - model = MASTER(cfg=_cfg, **kwargs) + model = MASTER( + backbone_fn(pretrained=pretrained_backbone, input_shape=_cfg["input_shape"], include_top=False), + cfg=_cfg, + **kwargs, + ) + _build_model(model) + # Load pretrained parameters if pretrained: - load_pretrained_params(model, default_cfgs[arch]['url']) + # The given vocab differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, default_cfgs[arch]["url"], skip_mismatch=kwargs["vocab"] != default_cfgs[arch]["vocab"] + ) return model
    -[docs] +[docs] def master(pretrained: bool = False, **kwargs: Any) -> MASTER: """MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import master - >>> model = master(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 48, 160, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + + >>> import tensorflow as tf + >>> from doctr.models import master + >>> model = master(pretrained=False) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keywoard arguments passed to the MASTER architecture + Returns: + ------- text recognition architecture """ - - return _master('master', pretrained, **kwargs)
    + return _master("master", pretrained, magc_resnet31, **kwargs)
    @@ -727,8 +654,8 @@

    Source code for doctr.models.recognition.master.tensorflow

    - +
    + diff --git a/v0.4.1/_modules/doctr/models/recognition/parseq/tensorflow.html b/v0.4.1/_modules/doctr/models/recognition/parseq/tensorflow.html index 0819737dfc..b181acef53 100644 --- a/v0.4.1/_modules/doctr/models/recognition/parseq/tensorflow.html +++ b/v0.4.1/_modules/doctr/models/recognition/parseq/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.parseq.tensorflow - docTR documentation @@ -845,7 +845,7 @@

    Source code for doctr.models.recognition.parseq.tensorflow

    - + diff --git a/v0.4.1/_modules/doctr/models/recognition/sar.html b/v0.4.1/_modules/doctr/models/recognition/sar.html deleted file mode 100644 index 2482e9f156..0000000000 --- a/v0.4.1/_modules/doctr/models/recognition/sar.html +++ /dev/null @@ -1,712 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.sar - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.sar

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import Sequential, layers
    -from typing import Tuple, Dict, List, Any, Optional
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel
    -from .core import RecognitionPostProcessor
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['SAR', 'SARPostProcessor', 'sar_vgg16_bn', 'sar_resnet31']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'sar_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1-models/sar_vgg16bn-0d7e2c26.zip',
    -    },
    -    'sar_resnet31': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'resnet31', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/sar_resnet31-ea202587.zip',
    -    },
    -}
    -
    -
    -class AttentionModule(layers.Layer, NestedObject):
    -    """Implements attention module of the SAR model
    -
    -    Args:
    -        attention_units: number of hidden attention units
    -
    -    """
    -    def __init__(
    -        self,
    -        attention_units: int
    -    ) -> None:
    -
    -        super().__init__()
    -        self.hidden_state_projector = layers.Conv2D(
    -            attention_units, 1, strides=1, use_bias=False, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.features_projector = layers.Conv2D(
    -            attention_units, 3, strides=1, use_bias=True, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.attention_projector = layers.Conv2D(
    -            1, 1, strides=1, use_bias=False, padding="same", kernel_initializer='he_normal',
    -        )
    -        self.flatten = layers.Flatten()
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        hidden_state: tf.Tensor,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        [H, W] = features.get_shape().as_list()[1:3]
    -        # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units)
    -        hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs)
    -        # shape (N, H, W, vgg_units) -> (N, H, W, attention_units)
    -        features_projection = self.features_projector(features, **kwargs)
    -        projection = tf.math.tanh(hidden_state_projection + features_projection)
    -        # shape (N, H, W, attention_units) -> (N, H, W, 1)
    -        attention = self.attention_projector(projection, **kwargs)
    -        # shape (N, H, W, 1) -> (N, H * W)
    -        attention = self.flatten(attention)
    -        attention = tf.nn.softmax(attention)
    -        # shape (N, H * W) -> (N, H, W, 1)
    -        attention_map = tf.reshape(attention, [-1, H, W, 1])
    -        glimpse = tf.math.multiply(features, attention_map)
    -        # shape (N, H * W) -> (N, 1)
    -        glimpse = tf.reduce_sum(glimpse, axis=[1, 2])
    -        return glimpse
    -
    -
    -class SARDecoder(layers.Layer, NestedObject):
    -    """Implements decoder module of the SAR model
    -
    -    Args:
    -        rnn_units: number of hidden units in recurrent cells
    -        max_length: maximum length of a sequence
    -        vocab_size: number of classes in the model alphabet
    -        embedding_units: number of hidden embedding units
    -        attention_units: number of hidden attention units
    -        num_decoder_layers: number of LSTM layers to stack
    -
    -    """
    -    def __init__(
    -        self,
    -        rnn_units: int,
    -        max_length: int,
    -        vocab_size: int,
    -        embedding_units: int,
    -        attention_units: int,
    -        num_decoder_layers: int = 2,
    -        input_shape: Optional[List[Tuple[Optional[int]]]] = None,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.vocab_size = vocab_size
    -        self.lstm_decoder = layers.StackedRNNCells(
    -            [layers.LSTMCell(rnn_units, dtype=tf.float32, implementation=1) for _ in range(num_decoder_layers)]
    -        )
    -        self.embed = layers.Dense(embedding_units, use_bias=False, input_shape=(None, self.vocab_size + 1))
    -        self.attention_module = AttentionModule(attention_units)
    -        self.output_dense = layers.Dense(vocab_size + 1, use_bias=True, input_shape=(None, 2 * rnn_units))
    -        self.max_length = max_length
    -
    -        # Initialize kernels
    -        if input_shape is not None:
    -            self.attention_module.call(layers.Input(input_shape[0][1:]), layers.Input((1, 1, rnn_units)))
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        holistic: tf.Tensor,
    -        gt: Optional[tf.Tensor] = None,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # initialize states (each of shape (N, rnn_units))
    -        states = self.lstm_decoder.get_initial_state(
    -            inputs=None, batch_size=features.shape[0], dtype=tf.float32
    -        )
    -        # run first step of lstm
    -        # holistic: shape (N, rnn_units)
    -        _, states = self.lstm_decoder(holistic, states, **kwargs)
    -        # Initialize with the index of virtual START symbol (placed after <eos>)
    -        symbol = tf.fill(features.shape[0], self.vocab_size + 1)
    -        logits_list = []
    -        if kwargs.get('training') and gt is None:
    -            raise ValueError('Need to provide labels during training for teacher forcing')
    -        for t in range(self.max_length + 1):  # keep 1 step for <eos>
    -            # one-hot symbol with depth vocab_size + 1
    -            # embeded_symbol: shape (N, embedding_units)
    -            embeded_symbol = self.embed(tf.one_hot(symbol, depth=self.vocab_size + 1), **kwargs)
    -            logits, states = self.lstm_decoder(embeded_symbol, states, **kwargs)
    -            glimpse = self.attention_module(
    -                features, tf.expand_dims(tf.expand_dims(logits, axis=1), axis=1), **kwargs,
    -            )
    -            # logits: shape (N, rnn_units), glimpse: shape (N, 1)
    -            logits = tf.concat([logits, glimpse], axis=-1)
    -            # shape (N, rnn_units + 1) -> (N, vocab_size + 1)
    -            logits = self.output_dense(logits, **kwargs)
    -            # update symbol with predicted logits for t+1 step
    -            if kwargs.get('training'):
    -                symbol = gt[:, t]
    -            else:
    -                symbol = tf.argmax(logits, axis=-1)
    -            logits_list.append(logits)
    -        outputs = tf.stack(logits_list, axis=1)  # shape (N, max_length + 1, vocab_size + 1)
    -
    -        return outputs
    -
    -
    -class SAR(RecognitionModel):
    -    """Implements a SAR architecture as described in `"Show, Attend and Read:A Simple and Strong Baseline for
    -    Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of hidden units in both encoder and decoder LSTM
    -        embedding_units: number of embedding units
    -        attention_units: number of hidden units in attention module
    -        max_length: maximum word length handled by the model
    -        num_decoders: number of LSTM to stack in decoder layer
    -
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'encoder', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor,
    -        vocab: str,
    -        rnn_units: int = 512,
    -        embedding_units: int = 512,
    -        attention_units: int = 512,
    -        max_length: int = 30,
    -        num_decoders: int = 2,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(vocab=vocab, cfg=cfg)
    -
    -        self.max_length = max_length + 1  # Add 1 timestep for EOS after the longest word
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.encoder = Sequential(
    -            [
    -                layers.LSTM(units=rnn_units, return_sequences=True),
    -                layers.LSTM(units=rnn_units, return_sequences=False)
    -            ]
    -        )
    -        # Initialize the kernels (watch out for reduce_max)
    -        self.encoder.build(input_shape=(None,) + self.feat_extractor.output_shape[2:])
    -
    -        self.decoder = SARDecoder(
    -            rnn_units, max_length, len(vocab), embedding_units, attention_units, num_decoders,
    -            input_shape=[self.feat_extractor.output_shape, self.encoder.output_shape]
    -        )
    -
    -        self.postprocessor = SARPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        gt: tf.Tensor,
    -        seq_len: tf.Tensor,
    -    ) -> tf.Tensor:
    -        """Compute categorical cross-entropy loss for the model.
    -        Sequences are masked after the EOS character.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        # Input length : number of timesteps
    -        input_len = tf.shape(model_output)[1]
    -        # Add one for additional <eos> token
    -        seq_len = seq_len + 1
    -        # One-hot gt labels
    -        oh_gt = tf.one_hot(gt, depth=model_output.shape[2])
    -        # Compute loss
    -        cce = tf.nn.softmax_cross_entropy_with_logits(oh_gt, model_output)
    -        # Compute mask
    -        mask_values = tf.zeros_like(cce)
    -        mask_2d = tf.sequence_mask(seq_len, input_len)
    -        masked_loss = tf.where(mask_2d, cce, mask_values)
    -        ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32))
    -        return tf.expand_dims(ce_loss, axis=1)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        pooled_features = tf.reduce_max(features, axis=1)  # vertical max pooling
    -        encoded = self.encoder(pooled_features, **kwargs)
    -        if target is not None:
    -            gt, seq_len = self.compute_target(target)
    -        decoded_features = self.decoder(features, encoded, gt=None if target is None else gt, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = decoded_features
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(decoded_features)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(decoded_features, gt, seq_len)
    -
    -        return out
    -
    -
    -class SARPostProcessor(RecognitionPostProcessor):
    -    """Post processor for SAR architectures
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor,
    -    ) -> List[Tuple[str, float]]:
    -        # compute pred with argmax for attention models
    -        out_idxs = tf.math.argmax(logits, axis=2)
    -        # N x L
    -        probs = tf.gather(tf.nn.softmax(logits, axis=-1), out_idxs, axis=-1, batch_dims=2)
    -        # Take the minimum confidence of the sequence
    -        probs = tf.math.reduce_min(probs, axis=1)
    -
    -        # decode raw output of the model with tf_label_to_idx
    -        out_idxs = tf.cast(out_idxs, dtype='int32')
    -        decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(self._embedding, out_idxs), axis=-1)
    -        decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -def _sar(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> SAR:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -    _cfg['embedding_units'] = kwargs.get('embedding_units', _cfg['rnn_units'])
    -    _cfg['attention_units'] = kwargs.get('attention_units', _cfg['rnn_units'])
    -    _cfg['max_length'] = kwargs.get('max_length', _cfg['max_length'])
    -    _cfg['num_decoders'] = kwargs.get('num_decoders', _cfg['num_decoders'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[default_cfgs[arch]['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -    kwargs['embedding_units'] = _cfg['embedding_units']
    -    kwargs['attention_units'] = _cfg['attention_units']
    -    kwargs['max_length'] = _cfg['max_length']
    -    kwargs['num_decoders'] = _cfg['num_decoders']
    -
    -    # Build the model
    -    model = SAR(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, default_cfgs[arch]['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a VGG16 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import sar_vgg16_bn - >>> model = sar_vgg16_bn(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_vgg16_bn', pretrained, **kwargs)
    - - - -
    -[docs] -def sar_resnet31(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a resnet-31 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example: - >>> import tensorflow as tf - >>> from doctr.models import sar_resnet31 - >>> model = sar_resnet31(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_resnet31', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.1/_modules/doctr/models/recognition/sar/tensorflow.html b/v0.4.1/_modules/doctr/models/recognition/sar/tensorflow.html index e514e4f0c4..4a591e6451 100644 --- a/v0.4.1/_modules/doctr/models/recognition/sar/tensorflow.html +++ b/v0.4.1/_modules/doctr/models/recognition/sar/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.sar.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.sar.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple
    +
     import tensorflow as tf
    -from tensorflow.keras import Sequential, layers, Model
    -from typing import Tuple, Dict, List, Any, Optional
    +from tensorflow.keras import Model, Sequential, layers
     
    -from ... import backbones
    -from ...utils import load_pretrained_params
    -from ..core import RecognitionModel, RecognitionPostProcessor
    +from doctr.datasets import VOCABS
     from doctr.utils.repr import NestedObject
     
    -__all__ = ['SAR', 'SARPostProcessor', 'sar_vgg16_bn', 'sar_resnet31']
    +from ...classification import resnet31
    +from ...utils.tensorflow import _bf16_to_float32, _build_model, load_pretrained_params
    +from ..core import RecognitionModel, RecognitionPostProcessor
    +
    +__all__ = ["SAR", "sar_resnet31"]
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'sar_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1-models/sar_vgg16bn-0d7e2c26.zip',
    -    },
    -    'sar_resnet31': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'resnet31', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/sar_resnet31-ea202587.zip',
    +    "sar_resnet31": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/sar_resnet31-5a58806c.weights.h5&src=0",
         },
     }
     
     
    +class SAREncoder(layers.Layer, NestedObject):
    +    """Implements encoder module of the SAR model
    +
    +    Args:
    +    ----
    +        rnn_units: number of hidden rnn units
    +        dropout_prob: dropout probability
    +    """
    +
    +    def __init__(self, rnn_units: int, dropout_prob: float = 0.0) -> None:
    +        super().__init__()
    +        self.rnn = Sequential([
    +            layers.LSTM(units=rnn_units, return_sequences=True, recurrent_dropout=dropout_prob),
    +            layers.LSTM(units=rnn_units, return_sequences=False, recurrent_dropout=dropout_prob),
    +        ])
    +
    +    def call(
    +        self,
    +        x: tf.Tensor,
    +        **kwargs: Any,
    +    ) -> tf.Tensor:
    +        # (N, C)
    +        return self.rnn(x, **kwargs)
    +
    +
     class AttentionModule(layers.Layer, NestedObject):
         """Implements attention module of the SAR model
     
         Args:
    +    ----
             attention_units: number of hidden attention units
     
         """
    -    def __init__(
    -        self,
    -        attention_units: int
    -    ) -> None:
     
    +    def __init__(self, attention_units: int) -> None:
             super().__init__()
             self.hidden_state_projector = layers.Conv2D(
    -            attention_units, 1, strides=1, use_bias=False, padding='same', kernel_initializer='he_normal',
    +            attention_units,
    +            1,
    +            strides=1,
    +            use_bias=False,
    +            padding="same",
    +            kernel_initializer="he_normal",
             )
             self.features_projector = layers.Conv2D(
    -            attention_units, 3, strides=1, use_bias=True, padding='same', kernel_initializer='he_normal',
    +            attention_units,
    +            3,
    +            strides=1,
    +            use_bias=True,
    +            padding="same",
    +            kernel_initializer="he_normal",
             )
             self.attention_projector = layers.Conv2D(
    -            1, 1, strides=1, use_bias=False, padding="same", kernel_initializer='he_normal',
    +            1,
    +            1,
    +            strides=1,
    +            use_bias=False,
    +            padding="same",
    +            kernel_initializer="he_normal",
             )
             self.flatten = layers.Flatten()
     
    @@ -343,12 +395,12 @@ 

    Source code for doctr.models.recognition.sar.tensorflow

    hidden_state: tf.Tensor, **kwargs: Any, ) -> tf.Tensor: - [H, W] = features.get_shape().as_list()[1:3] - # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units) - hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs) # shape (N, H, W, vgg_units) -> (N, H, W, attention_units) features_projection = self.features_projector(features, **kwargs) + # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units) + hidden_state = tf.expand_dims(tf.expand_dims(hidden_state, axis=1), axis=1) + hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs) projection = tf.math.tanh(hidden_state_projection + features_projection) # shape (N, H, W, attention_units) -> (N, H, W, 1) attention = self.attention_projector(projection, **kwargs) @@ -358,23 +410,25 @@

    Source code for doctr.models.recognition.sar.tensorflow

    # shape (N, H * W) -> (N, H, W, 1) attention_map = tf.reshape(attention, [-1, H, W, 1]) glimpse = tf.math.multiply(features, attention_map) - # shape (N, H * W) -> (N, 1) - glimpse = tf.reduce_sum(glimpse, axis=[1, 2]) - return glimpse + # shape (N, H * W) -> (N, C) + return tf.reduce_sum(glimpse, axis=[1, 2]) class SARDecoder(layers.Layer, NestedObject): """Implements decoder module of the SAR model Args: + ---- rnn_units: number of hidden units in recurrent cells max_length: maximum length of a sequence vocab_size: number of classes in the model alphabet embedding_units: number of hidden embedding units attention_units: number of hidden attention units - num_decoder_layers: number of LSTM layers to stack + num_decoder_cells: number of LSTMCell layers to stack + dropout_prob: dropout probability """ + def __init__( self, rnn_units: int, @@ -382,23 +436,22 @@

    Source code for doctr.models.recognition.sar.tensorflow

    vocab_size: int, embedding_units: int, attention_units: int, - num_decoder_layers: int = 2, - input_shape: Optional[List[Tuple[Optional[int]]]] = None, + num_decoder_cells: int = 2, + dropout_prob: float = 0.0, ) -> None: - super().__init__() self.vocab_size = vocab_size - self.lstm_decoder = layers.StackedRNNCells( - [layers.LSTMCell(rnn_units, dtype=tf.float32, implementation=1) for _ in range(num_decoder_layers)] - ) - self.embed = layers.Dense(embedding_units, use_bias=False, input_shape=(None, self.vocab_size + 1)) - self.attention_module = AttentionModule(attention_units) - self.output_dense = layers.Dense(vocab_size + 1, use_bias=True, input_shape=(None, 2 * rnn_units)) self.max_length = max_length - # Initialize kernels - if input_shape is not None: - self.attention_module.call(layers.Input(input_shape[0][1:]), layers.Input((1, 1, rnn_units))) + self.embed = layers.Dense(embedding_units, use_bias=False) + self.embed_tgt = layers.Embedding(embedding_units, self.vocab_size + 1) + + self.lstm_cells = layers.StackedRNNCells([ + layers.LSTMCell(rnn_units, implementation=1) for _ in range(num_decoder_cells) + ]) + self.attention_module = AttentionModule(attention_units) + self.output_dense = layers.Dense(self.vocab_size + 1, use_bias=True) + self.dropout = layers.Dropout(dropout_prob) def call( self, @@ -407,40 +460,47 @@

    Source code for doctr.models.recognition.sar.tensorflow

    gt: Optional[tf.Tensor] = None, **kwargs: Any, ) -> tf.Tensor: - - # initialize states (each of shape (N, rnn_units)) - states = self.lstm_decoder.get_initial_state( - inputs=None, batch_size=features.shape[0], dtype=tf.float32 - ) - # run first step of lstm - # holistic: shape (N, rnn_units) - _, states = self.lstm_decoder(holistic, states, **kwargs) - # Initialize with the index of virtual START symbol (placed after <eos>) - symbol = tf.fill(features.shape[0], self.vocab_size + 1) - logits_list = [] - if kwargs.get('training') and gt is None: - raise ValueError('Need to provide labels during training for teacher forcing') - for t in range(self.max_length + 1): # keep 1 step for <eos> - # one-hot symbol with depth vocab_size + 1 - # embeded_symbol: shape (N, embedding_units) - embeded_symbol = self.embed(tf.one_hot(symbol, depth=self.vocab_size + 1), **kwargs) - logits, states = self.lstm_decoder(embeded_symbol, states, **kwargs) - glimpse = self.attention_module( - features, tf.expand_dims(tf.expand_dims(logits, axis=1), axis=1), **kwargs, - ) - # logits: shape (N, rnn_units), glimpse: shape (N, 1) - logits = tf.concat([logits, glimpse], axis=-1) - # shape (N, rnn_units + 1) -> (N, vocab_size + 1) - logits = self.output_dense(logits, **kwargs) - # update symbol with predicted logits for t+1 step - if kwargs.get('training'): - symbol = gt[:, t] # type: ignore[index] + if gt is not None: + gt_embedding = self.embed_tgt(gt, **kwargs) + + logits_list: List[tf.Tensor] = [] + + for t in range(self.max_length + 1): # 32 + if t == 0: + # step to init the first states of the LSTMCell + states = self.lstm_cells.get_initial_state( + inputs=None, batch_size=features.shape[0], dtype=features.dtype + ) + prev_symbol = holistic + elif t == 1: + # step to init a 'blank' sequence of length vocab_size + 1 filled with zeros + # (N, vocab_size + 1) --> (N, embedding_units) + prev_symbol = tf.zeros([features.shape[0], self.vocab_size + 1], dtype=features.dtype) + prev_symbol = self.embed(prev_symbol, **kwargs) else: - symbol = tf.argmax(logits, axis=-1) - logits_list.append(logits) - outputs = tf.stack(logits_list, axis=1) # shape (N, max_length + 1, vocab_size + 1) - - return outputs + if gt is not None and kwargs.get("training", False): + # (N, embedding_units) -2 because of <bos> and <eos> (same) + prev_symbol = self.embed(gt_embedding[:, t - 2], **kwargs) + else: + # -1 to start at timestep where prev_symbol was initialized + index = tf.argmax(logits_list[t - 1], axis=-1) + # update prev_symbol with ones at the index of the previous logit vector + prev_symbol = self.embed(self.embed_tgt(index, **kwargs), **kwargs) + + # (N, C), (N, C) take the last hidden state and cell state from current timestep + _, states = self.lstm_cells(prev_symbol, states, **kwargs) + # states = (hidden_state, cell_state) + hidden_state = states[0][0] + # (N, H, W, C), (N, C) --> (N, C) + glimpse = self.attention_module(features, hidden_state, **kwargs) + # (N, C), (N, C) --> (N, 2 * C) + logits = tf.concat([hidden_state, glimpse], axis=1) + logits = self.dropout(logits, **kwargs) + # (N, vocab_size + 1) + logits_list.append(self.output_dense(logits, **kwargs)) + + # (max_length + 1, N, vocab_size + 1) --> (N, max_length + 1, vocab_size + 1) + return tf.transpose(tf.stack(logits_list[1:]), (1, 0, 2)) class SAR(Model, RecognitionModel): @@ -448,17 +508,20 @@

    Source code for doctr.models.recognition.sar.tensorflow

    Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. Args: + ---- feature_extractor: the backbone serving as feature extractor vocab: vocabulary used for encoding rnn_units: number of hidden units in both encoder and decoder LSTM embedding_units: number of embedding units attention_units: number of hidden units in attention module max_length: maximum word length handled by the model - num_decoders: number of LSTM to stack in decoder layer - + num_decoder_cells: number of LSTMCell layers to stack + dropout_prob: dropout probability for the encoder and decoder + exportable: onnx exportable returns only logits + cfg: dictionary containing information about the model """ - _children_names: List[str] = ['feat_extractor', 'encoder', 'decoder', 'postprocessor'] + _children_names: List[str] = ["feat_extractor", "encoder", "decoder", "postprocessor"] def __init__( self, @@ -468,36 +531,34 @@

    Source code for doctr.models.recognition.sar.tensorflow

    embedding_units: int = 512, attention_units: int = 512, max_length: int = 30, - num_decoders: int = 2, + num_decoder_cells: int = 2, + dropout_prob: float = 0.0, + exportable: bool = False, cfg: Optional[Dict[str, Any]] = None, ) -> None: - super().__init__() self.vocab = vocab + self.exportable = exportable self.cfg = cfg - self.max_length = max_length + 1 # Add 1 timestep for EOS after the longest word self.feat_extractor = feature_extractor - self.encoder = Sequential( - [ - layers.LSTM(units=rnn_units, return_sequences=True), - layers.LSTM(units=rnn_units, return_sequences=False) - ] - ) - # Initialize the kernels (watch out for reduce_max) - self.encoder.build(input_shape=(None,) + self.feat_extractor.output_shape[2:]) - + self.encoder = SAREncoder(rnn_units, dropout_prob) self.decoder = SARDecoder( - rnn_units, max_length, len(vocab), embedding_units, attention_units, num_decoders, - input_shape=[self.feat_extractor.output_shape, self.encoder.output_shape] + rnn_units, + self.max_length, + len(vocab), + embedding_units, + attention_units, + num_decoder_cells, + dropout_prob, ) self.postprocessor = SARPostProcessor(vocab=vocab) + @staticmethod def compute_loss( - self, model_output: tf.Tensor, gt: tf.Tensor, seq_len: tf.Tensor, @@ -506,11 +567,13 @@

    Source code for doctr.models.recognition.sar.tensorflow

    Sequences are masked after the EOS character. Args: + ---- gt: the encoded tensor with gt labels model_output: predicted logits of the model seq_len: lengths of each gt word inside the batch Returns: + ------- The loss of the model on the batch """ # Input length : number of timesteps @@ -525,7 +588,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    mask_values = tf.zeros_like(cce) mask_2d = tf.sequence_mask(seq_len, input_len) masked_loss = tf.where(mask_2d, cce, mask_values) - ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32)) + ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, model_output.dtype)) return tf.expand_dims(ce_loss, axis=1) def call( @@ -536,16 +599,28 @@

    Source code for doctr.models.recognition.sar.tensorflow

    return_preds: bool = False, **kwargs: Any, ) -> Dict[str, Any]: - features = self.feat_extractor(x, **kwargs) - pooled_features = tf.reduce_max(features, axis=1) # vertical max pooling + # vertical max pooling --> (N, C, W) + pooled_features = tf.reduce_max(features, axis=1) + # holistic (N, C) encoded = self.encoder(pooled_features, **kwargs) + if target is not None: - gt, seq_len = self.compute_target(target) + gt, seq_len = self.build_target(target) seq_len = tf.cast(seq_len, tf.int32) - decoded_features = self.decoder(features, encoded, gt=None if target is None else gt, **kwargs) + + if kwargs.get("training", False) and target is None: + raise ValueError("Need to provide labels during training for teacher forcing") + + decoded_features = _bf16_to_float32( + self.decoder(features, encoded, gt=None if target is None else gt, **kwargs) + ) out: Dict[str, tf.Tensor] = {} + if self.exportable: + out["logits"] = decoded_features + return out + if return_model_output: out["out_map"] = decoded_features @@ -554,7 +629,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    out["preds"] = self.postprocessor(decoded_features) if target is not None: - out['loss'] = self.compute_loss(decoded_features, gt, seq_len) + out["loss"] = self.compute_loss(decoded_features, gt, seq_len) return out @@ -563,9 +638,8 @@

    Source code for doctr.models.recognition.sar.tensorflow

    """Post processor for SAR architectures Args: + ---- vocab: string containing the ordered sequence of supported characters - ignore_case: if True, ignore case of letters - ignore_accents: if True, ignore accents of letters """ def __call__( @@ -580,95 +654,75 @@

    Source code for doctr.models.recognition.sar.tensorflow

    probs = tf.math.reduce_min(probs, axis=1) # decode raw output of the model with tf_label_to_idx - out_idxs = tf.cast(out_idxs, dtype='int32') + out_idxs = tf.cast(out_idxs, dtype="int32") embedding = tf.constant(self._embedding, dtype=tf.string) decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(embedding, out_idxs), axis=-1) decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>") - decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0] + decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value="not valid")[:, 0] word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] - return list(zip(word_values, probs.numpy().tolist())) + return list(zip(word_values, probs.numpy().clip(0, 1).tolist())) -def _sar(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> SAR: +def _sar( + arch: str, + pretrained: bool, + backbone_fn, + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> SAR: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab']) - _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units']) - _cfg['embedding_units'] = kwargs.get('embedding_units', _cfg['rnn_units']) - _cfg['attention_units'] = kwargs.get('attention_units', _cfg['rnn_units']) - _cfg['max_length'] = kwargs.get('max_length', _cfg['max_length']) - _cfg['num_decoders'] = kwargs.get('num_decoders', _cfg['num_decoders']) + _cfg["input_shape"] = input_shape or _cfg["input_shape"] + _cfg["vocab"] = kwargs.get("vocab", _cfg["vocab"]) # Feature extractor - feat_extractor = backbones.__dict__[default_cfgs[arch]['backbone']]( - input_shape=_cfg['input_shape'], + feat_extractor = backbone_fn( + pretrained=pretrained_backbone, + input_shape=_cfg["input_shape"], include_top=False, ) - kwargs['vocab'] = _cfg['vocab'] - kwargs['rnn_units'] = _cfg['rnn_units'] - kwargs['embedding_units'] = _cfg['embedding_units'] - kwargs['attention_units'] = _cfg['attention_units'] - kwargs['max_length'] = _cfg['max_length'] - kwargs['num_decoders'] = _cfg['num_decoders'] + kwargs["vocab"] = _cfg["vocab"] # Build the model model = SAR(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) # Load pretrained parameters if pretrained: - load_pretrained_params(model, default_cfgs[arch]['url']) + # The given vocab differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, default_cfgs[arch]["url"], skip_mismatch=kwargs["vocab"] != default_cfgs[arch]["vocab"] + ) return model -
    -[docs] -def sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a VGG16 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import sar_vgg16_bn - >>> model = sar_vgg16_bn(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_vgg16_bn', pretrained, **kwargs)
    - - -
    -[docs] +[docs] def sar_resnet31(pretrained: bool = False, **kwargs: Any) -> SAR: """SAR with a resnet-31 feature extractor as described in `"Show, Attend and Read:A Simple and Strong Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - Example: - >>> import tensorflow as tf - >>> from doctr.models import sar_resnet31 - >>> model = sar_resnet31(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import sar_resnet31 + >>> model = sar_resnet31(pretrained=False) + >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the SAR architecture Returns: + ------- text recognition architecture """ - - return _sar('sar_resnet31', pretrained, **kwargs)
    + return _sar("sar_resnet31", pretrained, resnet31, **kwargs)
    @@ -702,8 +756,8 @@

    Source code for doctr.models.recognition.sar.tensorflow

    - +
    + diff --git a/v0.4.1/_modules/doctr/models/recognition/vitstr/tensorflow.html b/v0.4.1/_modules/doctr/models/recognition/vitstr/tensorflow.html index 6e101893bf..c594d40a56 100644 --- a/v0.4.1/_modules/doctr/models/recognition/vitstr/tensorflow.html +++ b/v0.4.1/_modules/doctr/models/recognition/vitstr/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.vitstr.tensorflow - docTR documentation @@ -621,7 +621,7 @@

    Source code for doctr.models.recognition.vitstr.tensorflow

    - + diff --git a/v0.4.1/_modules/doctr/models/recognition/zoo.html b/v0.4.1/_modules/doctr/models/recognition/zoo.html index bf0ae6af6e..f664304019 100644 --- a/v0.4.1/_modules/doctr/models/recognition/zoo.html +++ b/v0.4.1/_modules/doctr/models/recognition/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.zoo - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -from typing import Any
    +from typing import Any, List
     
    -from doctr.file_utils import is_tf_available, is_torch_available
    -from .core import RecognitionPredictor
    -from ..preprocessor import PreProcessor
    -from .. import recognition
    +from doctr.file_utils import is_tf_available
    +from doctr.models.preprocessor import PreProcessor
     
    +from .. import recognition
    +from .predictor import RecognitionPredictor
     
     __all__ = ["recognition_predictor"]
     
     
    -if is_tf_available():
    -    ARCHS = ['crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31', 'master']
    -elif is_torch_available():
    -    ARCHS = ['crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31']
    +ARCHS: List[str] = [
    +    "crnn_vgg16_bn",
    +    "crnn_mobilenet_v3_small",
    +    "crnn_mobilenet_v3_large",
    +    "sar_resnet31",
    +    "master",
    +    "vitstr_small",
    +    "vitstr_base",
    +    "parseq",
    +]
    +
     
    +def _predictor(arch: Any, pretrained: bool, **kwargs: Any) -> RecognitionPredictor:
    +    if isinstance(arch, str):
    +        if arch not in ARCHS:
    +            raise ValueError(f"unknown architecture '{arch}'")
     
    -def _predictor(arch: str, pretrained: bool, **kwargs: Any) -> RecognitionPredictor:
    +        _model = recognition.__dict__[arch](
    +            pretrained=pretrained, pretrained_backbone=kwargs.get("pretrained_backbone", True)
    +        )
    +    else:
    +        if not isinstance(
    +            arch, (recognition.CRNN, recognition.SAR, recognition.MASTER, recognition.ViTSTR, recognition.PARSeq)
    +        ):
    +            raise ValueError(f"unknown architecture: {type(arch)}")
    +        _model = arch
     
    -    if arch not in ARCHS:
    -        raise ValueError(f"unknown architecture '{arch}'")
    +    kwargs.pop("pretrained_backbone", None)
     
    -    _model = recognition.__dict__[arch](pretrained=pretrained)
    -    kwargs['mean'] = kwargs.get('mean', _model.cfg['mean'])
    -    kwargs['std'] = kwargs.get('std', _model.cfg['std'])
    -    kwargs['batch_size'] = kwargs.get('batch_size', 32)
    -    predictor = RecognitionPredictor(
    -        PreProcessor(_model.cfg['input_shape'][:2], preserve_aspect_ratio=True, **kwargs),
    -        _model
    -    )
    +    kwargs["mean"] = kwargs.get("mean", _model.cfg["mean"])
    +    kwargs["std"] = kwargs.get("std", _model.cfg["std"])
    +    kwargs["batch_size"] = kwargs.get("batch_size", 128)
    +    input_shape = _model.cfg["input_shape"][:2] if is_tf_available() else _model.cfg["input_shape"][-2:]
    +    predictor = RecognitionPredictor(PreProcessor(input_shape, preserve_aspect_ratio=True, **kwargs), _model)
     
         return predictor
     
     
     
    -[docs] -def recognition_predictor(arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) -> RecognitionPredictor: +[docs] +def recognition_predictor( + arch: Any = "crnn_vgg16_bn", + pretrained: bool = False, + symmetric_pad: bool = False, + batch_size: int = 128, + **kwargs: Any, +) -> RecognitionPredictor: """Text recognition architecture. Example:: @@ -326,14 +369,18 @@

    Source code for doctr.models.recognition.zoo

            >>> out = model([input_page])
     
         Args:
    -        arch: name of the architecture to use ('crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31')
    +    ----
    +        arch: name of the architecture or model itself to use (e.g. 'crnn_vgg16_bn')
             pretrained: If True, returns a model pre-trained on our text recognition dataset
    +        symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right
    +        batch_size: number of samples the model processes in parallel
    +        **kwargs: optional parameters to be passed to the architecture
     
         Returns:
    +    -------
             Recognition predictor
         """
    -
    -    return _predictor(arch, pretrained, **kwargs)
    + return _predictor(arch=arch, pretrained=pretrained, symmetric_pad=symmetric_pad, batch_size=batch_size, **kwargs)
    @@ -367,8 +414,8 @@

    Source code for doctr.models.recognition.zoo

       
    -
    - +
    + diff --git a/v0.4.1/_modules/doctr/models/zoo.html b/v0.4.1/_modules/doctr/models/zoo.html index dec6857019..d459671648 100644 --- a/v0.4.1/_modules/doctr/models/zoo.html +++ b/v0.4.1/_modules/doctr/models/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.zoo - docTR documentation @@ -225,15 +225,42 @@

    Source code for doctr.models.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     from typing import Any
    -from .core import OCRPredictor
    +
     from .detection.zoo import detection_predictor
    +from .kie_predictor import KIEPredictor
    +from .predictor import OCRPredictor
     from .recognition.zoo import recognition_predictor
     
    +__all__ = ["ocr_predictor", "kie_predictor"]
     
    -__all__ = ["ocr_predictor"]
    -
    -
    -def _predictor(det_arch: str, reco_arch: str, pretrained: bool, det_bs=2, reco_bs=128) -> OCRPredictor:
     
    +def _predictor(
    +    det_arch: Any,
    +    reco_arch: Any,
    +    pretrained: bool,
    +    pretrained_backbone: bool = True,
    +    assume_straight_pages: bool = True,
    +    preserve_aspect_ratio: bool = True,
    +    symmetric_pad: bool = True,
    +    det_bs: int = 2,
    +    reco_bs: int = 128,
    +    detect_orientation: bool = False,
    +    straighten_pages: bool = False,
    +    detect_language: bool = False,
    +    **kwargs,
    +) -> OCRPredictor:
         # Detection
    -    det_predictor = detection_predictor(det_arch, pretrained=pretrained, batch_size=det_bs)
    +    det_predictor = detection_predictor(
    +        det_arch,
    +        pretrained=pretrained,
    +        pretrained_backbone=pretrained_backbone,
    +        batch_size=det_bs,
    +        assume_straight_pages=assume_straight_pages,
    +        preserve_aspect_ratio=preserve_aspect_ratio,
    +        symmetric_pad=symmetric_pad,
    +    )
     
         # Recognition
    -    reco_predictor = recognition_predictor(reco_arch, pretrained=pretrained, batch_size=reco_bs)
    +    reco_predictor = recognition_predictor(
    +        reco_arch,
    +        pretrained=pretrained,
    +        pretrained_backbone=pretrained_backbone,
    +        batch_size=reco_bs,
    +    )
     
    -    return OCRPredictor(det_predictor, reco_predictor)
    +    return OCRPredictor(
    +        det_predictor,
    +        reco_predictor,
    +        assume_straight_pages=assume_straight_pages,
    +        preserve_aspect_ratio=preserve_aspect_ratio,
    +        symmetric_pad=symmetric_pad,
    +        detect_orientation=detect_orientation,
    +        straighten_pages=straighten_pages,
    +        detect_language=detect_language,
    +        **kwargs,
    +    )
     
     
     
    -[docs] +[docs] def ocr_predictor( - det_arch: str = 'db_resnet50', - reco_arch: str = 'crnn_vgg16_bn', + det_arch: Any = "fast_base", + reco_arch: Any = "crnn_vgg16_bn", pretrained: bool = False, - **kwargs: Any + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + export_as_straight_boxes: bool = False, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs: Any, ) -> OCRPredictor: """End-to-end OCR architecture using one model for localization, and another for text recognition. - Example:: - >>> import numpy as np - >>> from doctr.models import ocr_predictor - >>> model = ocr_predictor(pretrained=True) - >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) - >>> out = model([input_page]) + >>> 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(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) Args: - arch: name of the architecture to use ('db_sar_vgg', 'db_sar_resnet', 'db_crnn_vgg', 'db_crnn_resnet') + ---- + det_arch: name of the detection architecture or the model itself to use + (e.g. 'db_resnet50', 'db_mobilenet_v3_large') + reco_arch: name of the recognition architecture or the model itself to use + (e.g. 'crnn_vgg16_bn', 'sar_resnet31') pretrained: If True, returns a model pre-trained on our OCR dataset + pretrained_backbone: If True, returns a model with a pretrained backbone + assume_straight_pages: if True, speeds up the inference by assuming you only pass straight pages + without rotated textual elements. + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it. + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right. + export_as_straight_boxes: when assume_straight_pages is set to False, export final predictions + (potentially rotated) as straight bounding boxes. + detect_orientation: if True, the estimated general page orientation will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + straighten_pages: if True, estimates the page general orientation + based on the segmentation map median line orientation. + Then, rotates page before passing it again to the deep learning detection module. + Doing so will improve performances for documents with page-uniform rotations. + detect_language: if True, the language prediction will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + kwargs: keyword args of `OCRPredictor` Returns: + ------- OCR predictor """ + return _predictor( + det_arch, + reco_arch, + pretrained, + pretrained_backbone=pretrained_backbone, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + export_as_straight_boxes=export_as_straight_boxes, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + )
    + + - return _predictor(det_arch, reco_arch, pretrained, **kwargs)
    +def _kie_predictor( + det_arch: Any, + reco_arch: Any, + pretrained: bool, + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + det_bs: int = 2, + reco_bs: int = 128, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs, +) -> KIEPredictor: + # Detection + det_predictor = detection_predictor( + det_arch, + pretrained=pretrained, + pretrained_backbone=pretrained_backbone, + batch_size=det_bs, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + ) + + # Recognition + reco_predictor = recognition_predictor( + reco_arch, + pretrained=pretrained, + pretrained_backbone=pretrained_backbone, + batch_size=reco_bs, + ) + + return KIEPredictor( + det_predictor, + reco_predictor, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + ) + + +
    +[docs] +def kie_predictor( + det_arch: Any = "fast_base", + reco_arch: Any = "crnn_vgg16_bn", + pretrained: bool = False, + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + export_as_straight_boxes: bool = False, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs: Any, +) -> KIEPredictor: + """End-to-end KIE architecture using one model for localization, and another for text recognition. + + >>> 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(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) + + Args: + ---- + det_arch: name of the detection architecture or the model itself to use + (e.g. 'db_resnet50', 'db_mobilenet_v3_large') + reco_arch: name of the recognition architecture or the model itself to use + (e.g. 'crnn_vgg16_bn', 'sar_resnet31') + pretrained: If True, returns a model pre-trained on our OCR dataset + pretrained_backbone: If True, returns a model with a pretrained backbone + assume_straight_pages: if True, speeds up the inference by assuming you only pass straight pages + without rotated textual elements. + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it. + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right. + export_as_straight_boxes: when assume_straight_pages is set to False, export final predictions + (potentially rotated) as straight bounding boxes. + detect_orientation: if True, the estimated general page orientation will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + straighten_pages: if True, estimates the page general orientation + based on the segmentation map median line orientation. + Then, rotates page before passing it again to the deep learning detection module. + Doing so will improve performances for documents with page-uniform rotations. + detect_language: if True, the language prediction will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + kwargs: keyword args of `OCRPredictor` + + Returns: + ------- + KIE predictor + """ + return _kie_predictor( + det_arch, + reco_arch, + pretrained, + pretrained_backbone=pretrained_backbone, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + export_as_straight_boxes=export_as_straight_boxes, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + )
    @@ -353,8 +575,8 @@

    Source code for doctr.models.zoo

           
         
       
    - - + + diff --git a/v0.4.1/_modules/doctr/transforms/modules.html b/v0.4.1/_modules/doctr/transforms/modules.html deleted file mode 100644 index ba8269e7ef..0000000000 --- a/v0.4.1/_modules/doctr/transforms/modules.html +++ /dev/null @@ -1,734 +0,0 @@ - - - - - - - - - - - - doctr.transforms.modules - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.transforms.modules

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import random
    -import tensorflow as tf
    -from typing import List, Any, Tuple, Callable
    -
    -from doctr.utils.repr import NestedObject
    -from . import functional as F
    -
    -
    -__all__ = ['Compose', 'Resize', 'Normalize', 'LambdaTransformation', 'ToGray', 'ColorInversion',
    -           'RandomBrightness', 'RandomContrast', 'RandomSaturation', 'RandomHue', 'RandomGamma', 'RandomJpegQuality',
    -           'OneOf', 'RandomApply']
    -
    -
    -
    -[docs] -class Compose(NestedObject): - """Implements a wrapper that will apply transformations sequentially - - Example:: - >>> from doctr.transforms import Compose, Resize - >>> import tensorflow as tf - >>> transfos = Compose([Resize((32, 32))]) - >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformation modules - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, x: Any) -> Any: - for t in self.transforms: - x = t(x) - - return x
    - - - -
    -[docs] -class Resize(NestedObject): - """Resizes a tensor to a target size - - Example:: - >>> from doctr.transforms import Resize - >>> import tensorflow as tf - >>> transfo = Resize((32, 32)) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - output_size: expected output size - method: interpolation method - preserve_aspect_ratio: if `True`, preserve aspect ratio and pad the rest with zeros - symmetric_pad: if `True` while preserving aspect ratio, the padding will be done symmetrically - """ - def __init__( - self, - output_size: Tuple[int, int], - method: str = 'bilinear', - preserve_aspect_ratio: bool = False, - symmetric_pad: bool = False, - ) -> None: - self.output_size = output_size - self.method = method - self.preserve_aspect_ratio = preserve_aspect_ratio - self.symmetric_pad = symmetric_pad - - def extra_repr(self) -> str: - _repr = f"output_size={self.output_size}, method='{self.method}'" - if self.preserve_aspect_ratio: - _repr += f", preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}" - return _repr - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img = tf.image.resize(img, self.output_size, self.method, self.preserve_aspect_ratio) - if self.preserve_aspect_ratio: - # pad width - if not self.symmetric_pad: - offset = (0, 0) - elif self.output_size[0] == img.shape[0]: - offset = (0, int((self.output_size[1] - img.shape[1]) / 2)) - else: - offset = (int((self.output_size[0] - img.shape[0]) / 2), 0) - img = tf.image.pad_to_bounding_box(img, *offset, *self.output_size) - return img
    - - - -
    -[docs] -class Normalize(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - mean: average value per channel - std: standard deviation per channel - """ - def __init__(self, mean: Tuple[float, float, float], std: Tuple[float, float, float]) -> None: - self.mean = tf.constant(mean, dtype=tf.float32) - self.std = tf.constant(std, dtype=tf.float32) - - def extra_repr(self) -> str: - return f"mean={self.mean.numpy().tolist()}, std={self.std.numpy().tolist()}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img -= self.mean - img /= self.std - return img
    - - - -
    -[docs] -class LambdaTransformation(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import LambdaTransformation - >>> import tensorflow as tf - >>> transfo = LambdaTransformation(lambda x: x/ 255.) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - fn: the function to be applied to the input tensor - """ - def __init__(self, fn: Callable[[tf.Tensor], tf.Tensor]) -> None: - self.fn = fn - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return self.fn(img)
    - - - -
    -[docs] -class ToGray(NestedObject): - """Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ToGray() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - """ - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.rgb_to_grayscale(img)
    - - - -
    -[docs] -class ColorInversion(NestedObject): - """Applies the following tranformation to a tensor (image or batch of images): - convert to grayscale, colorize (shift 0-values randomly), and then invert colors - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ColorInversion(min_val=0.6) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_val: range [min_val, 1] to colorize RGB pixels - """ - def __init__(self, min_val: float = 0.6) -> None: - self.min_val = min_val - - def extra_repr(self) -> str: - return f"min_val={self.min_val}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return F.invert_colors(img, self.min_val)
    - - - -
    -[docs] -class RandomBrightness(NestedObject): - """Randomly adjust brightness of a tensor (batch of images or image) by adding a delta - to all pixels - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Brightness() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - p: probability to apply transformation - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_brightness(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomContrast(NestedObject): - """Randomly adjust contrast of a tensor (batch of images or image) by adjusting - each pixel: (img - mean) * contrast_factor + mean. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Contrast() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1) - """ - def __init__(self, delta: float = .3) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_contrast(img, lower=1 - self.delta, upper=1 / (1 - self.delta))
    - - - -
    -[docs] -class RandomSaturation(NestedObject): - """Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and - increasing saturation by a factor. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Saturation() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1) - """ - def __init__(self, delta: float = .5) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_saturation(img, lower=1 - self.delta, upper=1 + self.delta)
    - - - -
    -[docs] -class RandomHue(NestedObject): - """Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Hue() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_hue(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomGamma(NestedObject): - """randomly performs gamma correction for a tensor (batch of images or image) - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Gamma() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_gamma: non-negative real number, lower bound for gamma param - max_gamma: non-negative real number, upper bound for gamma - min_gain: lower bound for constant multiplier - max_gain: upper bound for constant multiplier - """ - def __init__( - self, - min_gamma: float = 0.5, - max_gamma: float = 1.5, - min_gain: float = 0.8, - max_gain: float = 1.2, - ) -> None: - self.min_gamma = min_gamma - self.max_gamma = max_gamma - self.min_gain = min_gain - self.max_gain = max_gain - - def extra_repr(self) -> str: - return f"""gamma_range=({self.min_gamma}, {self.max_gamma}), - gain_range=({self.min_gain}, {self.max_gain})""" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - gamma = random.uniform(self.min_gamma, self.max_gamma) - gain = random.uniform(self.min_gain, self.max_gain) - return tf.image.adjust_gamma(img, gamma=gamma, gain=gain)
    - - - -
    -[docs] -class RandomJpegQuality(NestedObject): - """Randomly adjust jpeg quality of a 3 dimensional RGB image - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = JpegQuality() - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - min_quality: int between [0, 100] - max_quality: int between [0, 100] - """ - def __init__(self, min_quality: int = 60, max_quality: int = 100) -> None: - self.min_quality = min_quality - self.max_quality = max_quality - - def extra_repr(self) -> str: - return f"min_quality={self.min_quality}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_jpeg_quality( - img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality - )
    - - - -
    -[docs] -class OneOf(NestedObject): - """Randomly apply one of the input transformations - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = OneOf([JpegQuality(), Gamma()]) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformations, one only will be picked - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - # Pick transformation - transfo = self.transforms[int(random.random() * len(self.transforms))] - # Apply - return transfo(img)
    - - - -
    -[docs] -class RandomApply(NestedObject): - """Apply with a probability p the input transformation - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = RandomApply(Gamma(), p=.5) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transform: transformation to apply - p: probability to apply - """ - def __init__(self, transform: NestedObject, p: float = .5) -> None: - self.transform = transform - self.p = p - - def extra_repr(self) -> str: - return f"transform={self.transform}, p={self.p}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - if random.random() < self.p: - return self.transform(img) - return img
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.1/_modules/doctr/transforms/modules/base.html b/v0.4.1/_modules/doctr/transforms/modules/base.html index c42079a8fd..4596df3848 100644 --- a/v0.4.1/_modules/doctr/transforms/modules/base.html +++ b/v0.4.1/_modules/doctr/transforms/modules/base.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.base - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.transforms.modules.base

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    +import math
     import random
    -from typing import List, Any, Callable
    +from typing import Any, Callable, List, Optional, Tuple, Union
    +
    +import numpy as np
     
     from doctr.utils.repr import NestedObject
    +
     from .. import functional as F
     
    +__all__ = ["SampleCompose", "ImageTransform", "ColorInversion", "OneOf", "RandomApply", "RandomRotate", "RandomCrop"]
    +
    +
    +class SampleCompose(NestedObject):
    +    """Implements a wrapper that will apply transformations sequentially on both image and target
    +
    +    .. tabs::
    +
    +        .. tab:: TensorFlow
    +
    +            .. code:: python
    +
    +                >>> import numpy as np
    +                >>> import tensorflow as tf
    +                >>> from doctr.transforms import SampleCompose, ImageTransform, ColorInversion, RandomRotate
    +                >>> transfo = SampleCompose([ImageTransform(ColorInversion((32, 32))), RandomRotate(30)])
    +                >>> out, out_boxes = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1), np.zeros((2, 4)))
    +
    +        .. tab:: PyTorch
    +
    +            .. code:: python
    +
    +                >>> import numpy as np
    +                >>> import torch
    +                >>> from doctr.transforms import SampleCompose, ImageTransform, ColorInversion, RandomRotate
    +                >>> transfos = SampleCompose([ImageTransform(ColorInversion((32, 32))), RandomRotate(30)])
    +                >>> out, out_boxes = transfos(torch.rand(8, 64, 64, 3), np.zeros((2, 4)))
    +
    +    Args:
    +    ----
    +        transforms: list of transformation modules
    +    """
    +
    +    _children_names: List[str] = ["sample_transforms"]
    +
    +    def __init__(self, transforms: List[Callable[[Any, Any], Tuple[Any, Any]]]) -> None:
    +        self.sample_transforms = transforms
    +
    +    def __call__(self, x: Any, target: Any) -> Tuple[Any, Any]:
    +        for t in self.sample_transforms:
    +            x, target = t(x, target)
    +
    +        return x, target
    +
    +
    +class ImageTransform(NestedObject):
    +    """Implements a transform wrapper to turn an image-only transformation into an image+target transform
    +
    +    .. tabs::
    +
    +        .. tab:: TensorFlow
    +
    +            .. code:: python
    +
    +                >>> import tensorflow as tf
    +                >>> from doctr.transforms import ImageTransform, ColorInversion
    +                >>> transfo = ImageTransform(ColorInversion((32, 32)))
    +                >>> out, _ = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1), None)
    +
    +        .. tab:: PyTorch
    +
    +            .. code:: python
    +
    +                >>> import torch
    +                >>> from doctr.transforms import ImageTransform, ColorInversion
    +                >>> transfo = ImageTransform(ColorInversion((32, 32)))
    +                >>> out, _ = transfo(torch.rand(8, 64, 64, 3), None)
    +
    +    Args:
    +    ----
    +        transform: the image transformation module to wrap
    +    """
    +
    +    _children_names: List[str] = ["img_transform"]
    +
    +    def __init__(self, transform: Callable[[Any], Any]) -> None:
    +        self.img_transform = transform
     
    -__all__ = ['ColorInversion', 'OneOf', 'RandomApply']
    +    def __call__(self, img: Any, target: Any) -> Tuple[Any, Any]:
    +        img = self.img_transform(img)
    +        return img, target
     
     
     
    -[docs] +[docs] class ColorInversion(NestedObject): """Applies the following tranformation to a tensor (image or batch of images): convert to grayscale, colorize (shift 0-values randomly), and then invert colors - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ColorInversion(min_val=0.6) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + .. tabs:: + + .. tab:: TensorFlow + + .. code:: python + + >>> import tensorflow as tf + >>> from doctr.transforms import ColorInversion + >>> transfo = ColorInversion(min_val=0.6) + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + + .. tab:: PyTorch + + .. code:: python + + >>> import torch + >>> from doctr.transforms import ColorInversion + >>> transfo = ColorInversion(min_val=0.6) + >>> out = transfo(torch.rand(8, 64, 64, 3)) Args: + ---- min_val: range [min_val, 1] to colorize RGB pixels """ + def __init__(self, min_val: float = 0.5) -> None: self.min_val = min_val @@ -316,59 +437,178 @@

    Source code for doctr.transforms.modules.base

    -[docs] +[docs] class OneOf(NestedObject): """Randomly apply one of the input transformations - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = OneOf([JpegQuality(), Gamma()]) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + .. tabs:: + + .. tab:: TensorFlow + + .. code:: python + + >>> import tensorflow as tf + >>> from doctr.transforms import OneOf + >>> transfo = OneOf([JpegQuality(), Gamma()]) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + .. tab:: PyTorch + + .. code:: python + + >>> import torch + >>> from doctr.transforms import OneOf + >>> transfo = OneOf([JpegQuality(), Gamma()]) + >>> out = transfo(torch.rand(1, 64, 64, 3)) Args: + ---- transforms: list of transformations, one only will be picked """ - _children_names: List[str] = ['transforms'] + _children_names: List[str] = ["transforms"] def __init__(self, transforms: List[Callable[[Any], Any]]) -> None: self.transforms = transforms - def __call__(self, img: Any) -> Any: + def __call__(self, img: Any, target: Optional[np.ndarray] = None) -> Union[Any, Tuple[Any, np.ndarray]]: # Pick transformation transfo = self.transforms[int(random.random() * len(self.transforms))] # Apply - return transfo(img)
    + return transfo(img) if target is None else transfo(img, target) # type: ignore[call-arg]
    -[docs] +[docs] class RandomApply(NestedObject): """Apply with a probability p the input transformation - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = RandomApply(Gamma(), p=.5) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + .. tabs:: + + .. tab:: TensorFlow + + .. code:: python + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomApply + >>> transfo = RandomApply(Gamma(), p=.5) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + .. tab:: PyTorch + + .. code:: python + + >>> import torch + >>> from doctr.transforms import RandomApply + >>> transfo = RandomApply(Gamma(), p=.5) + >>> out = transfo(torch.rand(1, 64, 64, 3)) Args: + ---- transform: transformation to apply p: probability to apply """ - def __init__(self, transform: Callable[[Any], Any], p: float = .5) -> None: + + def __init__(self, transform: Callable[[Any], Any], p: float = 0.5) -> None: self.transform = transform self.p = p def extra_repr(self) -> str: return f"transform={self.transform}, p={self.p}" - def __call__(self, img: Any) -> Any: + def __call__(self, img: Any, target: Optional[np.ndarray] = None) -> Union[Any, Tuple[Any, np.ndarray]]: if random.random() < self.p: - return self.transform(img) - return img
    + return self.transform(img) if target is None else self.transform(img, target) # type: ignore[call-arg] + return img if target is None else (img, target)
    + + + +
    +[docs] +class RandomRotate(NestedObject): + """Randomly rotate a tensor image and its boxes + + .. image:: https://doctr-static.mindee.com/models?id=v0.4.0/rotation_illustration.png&src=0 + :align: center + + Args: + ---- + max_angle: maximum angle for rotation, in degrees. Angles will be uniformly picked in + [-max_angle, max_angle] + expand: whether the image should be padded before the rotation + """ + + def __init__(self, max_angle: float = 5.0, expand: bool = False) -> None: + self.max_angle = max_angle + self.expand = expand + + def extra_repr(self) -> str: + return f"max_angle={self.max_angle}, expand={self.expand}" + + def __call__(self, img: Any, target: np.ndarray) -> Tuple[Any, np.ndarray]: + angle = random.uniform(-self.max_angle, self.max_angle) + r_img, r_polys = F.rotate_sample(img, target, angle, self.expand) + # Removes deleted boxes + is_kept = (r_polys.max(1) > r_polys.min(1)).sum(1) == 2 + return r_img, r_polys[is_kept]
    + + + +
    +[docs] +class RandomCrop(NestedObject): + """Randomly crop a tensor image and its boxes + + Args: + ---- + scale: tuple of floats, relative (min_area, max_area) of the crop + ratio: tuple of float, relative (min_ratio, max_ratio) where ratio = h/w + """ + + def __init__(self, scale: Tuple[float, float] = (0.08, 1.0), ratio: Tuple[float, float] = (0.75, 1.33)) -> None: + self.scale = scale + self.ratio = ratio + + def extra_repr(self) -> str: + return f"scale={self.scale}, ratio={self.ratio}" + + def __call__(self, img: Any, target: np.ndarray) -> Tuple[Any, np.ndarray]: + scale = random.uniform(self.scale[0], self.scale[1]) + ratio = random.uniform(self.ratio[0], self.ratio[1]) + + height, width = img.shape[:2] + + # Calculate crop size + crop_area = scale * width * height + aspect_ratio = ratio * (width / height) + crop_width = int(round(math.sqrt(crop_area * aspect_ratio))) + crop_height = int(round(math.sqrt(crop_area / aspect_ratio))) + + # Ensure crop size does not exceed image dimensions + crop_width = min(crop_width, width) + crop_height = min(crop_height, height) + + # Randomly select crop position + x = random.randint(0, width - crop_width) + y = random.randint(0, height - crop_height) + + # relative crop box + crop_box = (x / width, y / height, (x + crop_width) / width, (y + crop_height) / height) + if target.shape[1:] == (4, 2): + min_xy = np.min(target, axis=1) + max_xy = np.max(target, axis=1) + _target = np.concatenate((min_xy, max_xy), axis=1) + else: + _target = target + + # Crop image and targets + croped_img, crop_boxes = F.crop_detection(img, _target, crop_box) + # hard fallback if no box is kept + if crop_boxes.shape[0] == 0: + return img, target + # clip boxes + return croped_img, np.clip(crop_boxes, 0, 1)
    @@ -402,8 +642,8 @@

    Source code for doctr.transforms.modules.base

    - - + + diff --git a/v0.4.1/_modules/doctr/transforms/modules/tensorflow.html b/v0.4.1/_modules/doctr/transforms/modules/tensorflow.html index 1d192a876b..acbbe96225 100644 --- a/v0.4.1/_modules/doctr/transforms/modules/tensorflow.html +++ b/v0.4.1/_modules/doctr/transforms/modules/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.transforms.modules.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import random
    +from typing import Any, Callable, Iterable, List, Optional, Tuple, Union
    +
    +import numpy as np
     import tensorflow as tf
    -from typing import List, Any, Tuple, Callable
     
     from doctr.utils.repr import NestedObject
     
    -
    -__all__ = ['Compose', 'Resize', 'Normalize', 'LambdaTransformation', 'ToGray', 'RandomBrightness',
    -           'RandomContrast', 'RandomSaturation', 'RandomHue', 'RandomGamma', 'RandomJpegQuality']
    +from ..functional.tensorflow import _gaussian_filter, random_shadow
    +
    +__all__ = [
    +    "Compose",
    +    "Resize",
    +    "Normalize",
    +    "LambdaTransformation",
    +    "ToGray",
    +    "RandomBrightness",
    +    "RandomContrast",
    +    "RandomSaturation",
    +    "RandomHue",
    +    "RandomGamma",
    +    "RandomJpegQuality",
    +    "GaussianBlur",
    +    "ChannelShuffle",
    +    "GaussianNoise",
    +    "RandomHorizontalFlip",
    +    "RandomShadow",
    +    "RandomResize",
    +]
     
     
     
    -[docs] +[docs] class Compose(NestedObject): """Implements a wrapper that will apply transformations sequentially - Example:: - >>> from doctr.transforms import Compose, Resize - >>> import tensorflow as tf - >>> transfos = Compose([Resize((32, 32))]) - >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import Compose, Resize + >>> transfos = Compose([Resize((32, 32))]) + >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) Args: + ---- transforms: list of transformation modules """ - _children_names: List[str] = ['transforms'] + _children_names: List[str] = ["transforms"] def __init__(self, transforms: List[Callable[[Any], Any]]) -> None: self.transforms = transforms @@ -319,26 +361,27 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class Resize(NestedObject): """Resizes a tensor to a target size - Example:: - >>> from doctr.transforms import Resize - >>> import tensorflow as tf - >>> transfo = Resize((32, 32)) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import Resize + >>> transfo = Resize((32, 32)) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) Args: + ---- output_size: expected output size method: interpolation method preserve_aspect_ratio: if `True`, preserve aspect ratio and pad the rest with zeros symmetric_pad: if `True` while preserving aspect ratio, the padding will be done symmetrically """ + def __init__( self, - output_size: Tuple[int, int], - method: str = 'bilinear', + output_size: Union[int, Tuple[int, int]], + method: str = "bilinear", preserve_aspect_ratio: bool = False, symmetric_pad: bool = False, ) -> None: @@ -346,6 +389,14 @@

    Source code for doctr.transforms.modules.tensorflow

    self.method = method self.preserve_aspect_ratio = preserve_aspect_ratio self.symmetric_pad = symmetric_pad + self.antialias = True + + if isinstance(self.output_size, int): + self.wanted_size = (self.output_size, self.output_size) + elif isinstance(self.output_size, (tuple, list)): + self.wanted_size = self.output_size + else: + raise AssertionError("Output size should be either a list, a tuple or an int") def extra_repr(self) -> str: _repr = f"output_size={self.output_size}, method='{self.method}'" @@ -353,64 +404,106 @@

    Source code for doctr.transforms.modules.tensorflow

    _repr += f", preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}" return _repr - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img = tf.image.resize(img, self.output_size, self.method, self.preserve_aspect_ratio) + def __call__( + self, + img: tf.Tensor, + target: Optional[np.ndarray] = None, + ) -> Union[tf.Tensor, Tuple[tf.Tensor, np.ndarray]]: + input_dtype = img.dtype + self.output_size = ( + (self.output_size, self.output_size) if isinstance(self.output_size, int) else self.output_size + ) + + img = tf.image.resize(img, self.wanted_size, self.method, self.preserve_aspect_ratio, self.antialias) + # It will produce an un-padded resized image, with a side shorter than wanted if we preserve aspect ratio + raw_shape = img.shape[:2] + if self.symmetric_pad: + half_pad = (int((self.output_size[0] - img.shape[0]) / 2), 0) if self.preserve_aspect_ratio: - # pad width - if not self.symmetric_pad: - offset = (0, 0) - elif self.output_size[0] == img.shape[0]: - offset = (0, int((self.output_size[1] - img.shape[1]) / 2)) - else: - offset = (int((self.output_size[0] - img.shape[0]) / 2), 0) - img = tf.image.pad_to_bounding_box(img, *offset, *self.output_size) - return img
    + if isinstance(self.output_size, (tuple, list)): + # In that case we need to pad because we want to enforce both width and height + if not self.symmetric_pad: + half_pad = (0, 0) + elif self.output_size[0] == img.shape[0]: + half_pad = (0, int((self.output_size[1] - img.shape[1]) / 2)) + # Pad image + img = tf.image.pad_to_bounding_box(img, *half_pad, *self.output_size) + + # In case boxes are provided, resize boxes if needed (for detection task if preserve aspect ratio) + if target is not None: + if self.symmetric_pad: + offset = half_pad[0] / img.shape[0], half_pad[1] / img.shape[1] + + if self.preserve_aspect_ratio: + # Get absolute coords + if target.shape[1:] == (4,): + if isinstance(self.output_size, (tuple, list)) and self.symmetric_pad: + target[:, [0, 2]] = offset[1] + target[:, [0, 2]] * raw_shape[1] / img.shape[1] + target[:, [1, 3]] = offset[0] + target[:, [1, 3]] * raw_shape[0] / img.shape[0] + else: + target[:, [0, 2]] *= raw_shape[1] / img.shape[1] + target[:, [1, 3]] *= raw_shape[0] / img.shape[0] + elif target.shape[1:] == (4, 2): + if isinstance(self.output_size, (tuple, list)) and self.symmetric_pad: + target[..., 0] = offset[1] + target[..., 0] * raw_shape[1] / img.shape[1] + target[..., 1] = offset[0] + target[..., 1] * raw_shape[0] / img.shape[0] + else: + target[..., 0] *= raw_shape[1] / img.shape[1] + target[..., 1] *= raw_shape[0] / img.shape[0] + else: + raise AssertionError("Boxes should be in the format (n_boxes, 4, 2) or (n_boxes, 4)") + + return tf.cast(img, dtype=input_dtype), np.clip(target, 0, 1) + + return tf.cast(img, dtype=input_dtype)
    -[docs] +[docs] class Normalize(NestedObject): """Normalize a tensor to a Gaussian distribution for each channel - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import Normalize + >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- mean: average value per channel std: standard deviation per channel """ + def __init__(self, mean: Tuple[float, float, float], std: Tuple[float, float, float]) -> None: - self.mean = tf.constant(mean, dtype=tf.float32) - self.std = tf.constant(std, dtype=tf.float32) + self.mean = tf.constant(mean) + self.std = tf.constant(std) def extra_repr(self) -> str: return f"mean={self.mean.numpy().tolist()}, std={self.std.numpy().tolist()}" def __call__(self, img: tf.Tensor) -> tf.Tensor: - img -= self.mean - img /= self.std + img -= tf.cast(self.mean, dtype=img.dtype) + img /= tf.cast(self.std, dtype=img.dtype) return img
    -[docs] +[docs] class LambdaTransformation(NestedObject): """Normalize a tensor to a Gaussian distribution for each channel - Example:: - >>> from doctr.transforms import LambdaTransformation - >>> import tensorflow as tf - >>> transfo = LambdaTransformation(lambda x: x/ 255.) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import LambdaTransformation + >>> transfo = LambdaTransformation(lambda x: x/ 255.) + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- fn: the function to be applied to the input tensor """ + def __init__(self, fn: Callable[[tf.Tensor], tf.Tensor]) -> None: self.fn = fn @@ -420,37 +513,42 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class ToGray(NestedObject): """Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ToGray() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import ToGray + >>> transfo = ToGray() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) """ + + def __init__(self, num_output_channels: int = 1): + self.num_output_channels = num_output_channels + def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.rgb_to_grayscale(img)
    + img = tf.image.rgb_to_grayscale(img) + return img if self.num_output_channels == 1 else tf.repeat(img, self.num_output_channels, axis=-1)
    -[docs] +[docs] class RandomBrightness(NestedObject): """Randomly adjust brightness of a tensor (batch of images or image) by adding a delta to all pixels - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Brightness() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomBrightness + >>> transfo = RandomBrightness() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] p: probability to apply transformation """ + def __init__(self, max_delta: float = 0.3) -> None: self.max_delta = max_delta @@ -463,21 +561,22 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomContrast(NestedObject): """Randomly adjust contrast of a tensor (batch of images or image) by adjusting each pixel: (img - mean) * contrast_factor + mean. - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Contrast() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomContrast + >>> transfo = RandomContrast() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1) """ - def __init__(self, delta: float = .3) -> None: + + def __init__(self, delta: float = 0.3) -> None: self.delta = delta def extra_repr(self) -> str: @@ -489,21 +588,22 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomSaturation(NestedObject): """Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and increasing saturation by a factor. - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Saturation() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomSaturation + >>> transfo = RandomSaturation() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1) """ - def __init__(self, delta: float = .5) -> None: + + def __init__(self, delta: float = 0.5) -> None: self.delta = delta def extra_repr(self) -> str: @@ -515,19 +615,20 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomHue(NestedObject): """Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Hue() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomHue + >>> transfo = RandomHue() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] """ + def __init__(self, max_delta: float = 0.3) -> None: self.max_delta = max_delta @@ -540,22 +641,23 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomGamma(NestedObject): """randomly performs gamma correction for a tensor (batch of images or image) - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Gamma() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomGamma + >>> transfo = RandomGamma() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- min_gamma: non-negative real number, lower bound for gamma param max_gamma: non-negative real number, upper bound for gamma min_gain: lower bound for constant multiplier max_gain: upper bound for constant multiplier """ + def __init__( self, min_gamma: float = 0.5, @@ -580,20 +682,21 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomJpegQuality(NestedObject): """Randomly adjust jpeg quality of a 3 dimensional RGB image - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = JpegQuality() - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomJpegQuality + >>> transfo = RandomJpegQuality() + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) Args: + ---- min_quality: int between [0, 100] max_quality: int between [0, 100] """ + def __init__(self, min_quality: int = 60, max_quality: int = 100) -> None: self.min_quality = min_quality self.max_quality = max_quality @@ -602,10 +705,224 @@

    Source code for doctr.transforms.modules.tensorflow

    return f"min_quality={self.min_quality}" def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_jpeg_quality( - img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality + return tf.image.random_jpeg_quality(img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality)
    + + + +
    +[docs] +class GaussianBlur(NestedObject): + """Randomly adjust jpeg quality of a 3 dimensional RGB image + + >>> import tensorflow as tf + >>> from doctr.transforms import GaussianBlur + >>> transfo = GaussianBlur(3, (.1, 5)) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + kernel_shape: size of the blurring kernel + std: min and max value of the standard deviation + """ + + def __init__(self, kernel_shape: Union[int, Iterable[int]], std: Tuple[float, float]) -> None: + self.kernel_shape = kernel_shape + self.std = std + + def extra_repr(self) -> str: + return f"kernel_shape={self.kernel_shape}, std={self.std}" + + def __call__(self, img: tf.Tensor) -> tf.Tensor: + return tf.squeeze( + _gaussian_filter( + img[tf.newaxis, ...], + kernel_size=self.kernel_shape, + sigma=random.uniform(self.std[0], self.std[1]), + mode="REFLECT", + ), + axis=0, )
    + + +
    +[docs] +class ChannelShuffle(NestedObject): + """Randomly shuffle channel order of a given image""" + + def __init__(self): + pass + + def __call__(self, img: tf.Tensor) -> tf.Tensor: + return tf.transpose(tf.random.shuffle(tf.transpose(img, perm=[2, 0, 1])), perm=[1, 2, 0])
    + + + +
    +[docs] +class GaussianNoise(NestedObject): + """Adds Gaussian Noise to the input tensor + + >>> import tensorflow as tf + >>> from doctr.transforms import GaussianNoise + >>> transfo = GaussianNoise(0., 1.) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + mean : mean of the gaussian distribution + std : std of the gaussian distribution + """ + + def __init__(self, mean: float = 0.0, std: float = 1.0) -> None: + super().__init__() + self.std = std + self.mean = mean + + def __call__(self, x: tf.Tensor) -> tf.Tensor: + # Reshape the distribution + noise = self.mean + 2 * self.std * tf.random.uniform(x.shape) - self.std + if x.dtype == tf.uint8: + return tf.cast( + tf.clip_by_value(tf.math.round(tf.cast(x, dtype=tf.float32) + 255 * noise), 0, 255), dtype=tf.uint8 + ) + else: + return tf.cast(tf.clip_by_value(x + noise, 0, 1), dtype=x.dtype) + + def extra_repr(self) -> str: + return f"mean={self.mean}, std={self.std}"
    + + + +
    +[docs] +class RandomHorizontalFlip(NestedObject): + """Adds random horizontal flip to the input tensor/np.ndarray + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomHorizontalFlip + >>> transfo = RandomHorizontalFlip(p=0.5) + >>> image = tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1) + >>> target = np.array([[0.1, 0.1, 0.4, 0.5] ], dtype= np.float32) + >>> out = transfo(image, target) + + Args: + ---- + p : probability of Horizontal Flip + """ + + def __init__(self, p: float) -> None: + super().__init__() + self.p = p + + def __call__(self, img: Union[tf.Tensor, np.ndarray], target: np.ndarray) -> Tuple[tf.Tensor, np.ndarray]: + if np.random.rand(1) <= self.p: + _img = tf.image.flip_left_right(img) + _target = target.copy() + # Changing the relative bbox coordinates + if target.shape[1:] == (4,): + _target[:, ::2] = 1 - target[:, [2, 0]] + else: + _target[..., 0] = 1 - target[..., 0] + return _img, _target + return img, target
    + + + +
    +[docs] +class RandomShadow(NestedObject): + """Adds random shade to the input image + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomShadow + >>> transfo = RandomShadow(0., 1.) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + opacity_range : minimum and maximum opacity of the shade + """ + + def __init__(self, opacity_range: Optional[Tuple[float, float]] = None) -> None: + super().__init__() + self.opacity_range = opacity_range if isinstance(opacity_range, tuple) else (0.2, 0.8) + + def __call__(self, x: tf.Tensor) -> tf.Tensor: + # Reshape the distribution + if x.dtype == tf.uint8: + return tf.cast( + tf.clip_by_value( + tf.math.round(255 * random_shadow(tf.cast(x, dtype=tf.float32) / 255, self.opacity_range)), + 0, + 255, + ), + dtype=tf.uint8, + ) + else: + return tf.clip_by_value(random_shadow(x, self.opacity_range), 0, 1) + + def extra_repr(self) -> str: + return f"opacity_range={self.opacity_range}"
    + + + +
    +[docs] +class RandomResize(NestedObject): + """Randomly resize the input image and align corresponding targets + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomResize + >>> transfo = RandomResize((0.3, 0.9), preserve_aspect_ratio=True, symmetric_pad=True, p=0.5) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + scale_range: range of the resizing factor for width and height (independently) + preserve_aspect_ratio: whether to preserve the aspect ratio of the image, + given a float value, the aspect ratio will be preserved with this probability + symmetric_pad: whether to symmetrically pad the image, + given a float value, the symmetric padding will be applied with this probability + p: probability to apply the transformation + """ + + def __init__( + self, + scale_range: Tuple[float, float] = (0.3, 0.9), + preserve_aspect_ratio: Union[bool, float] = False, + symmetric_pad: Union[bool, float] = False, + p: float = 0.5, + ): + super().__init__() + self.scale_range = scale_range + self.preserve_aspect_ratio = preserve_aspect_ratio + self.symmetric_pad = symmetric_pad + self.p = p + self._resize = Resize + + def __call__(self, img: tf.Tensor, target: np.ndarray) -> Tuple[tf.Tensor, np.ndarray]: + if np.random.rand(1) <= self.p: + scale_h = random.uniform(*self.scale_range) + scale_w = random.uniform(*self.scale_range) + new_size = (int(img.shape[-3] * scale_h), int(img.shape[-2] * scale_w)) + + _img, _target = self._resize( + new_size, + preserve_aspect_ratio=self.preserve_aspect_ratio + if isinstance(self.preserve_aspect_ratio, bool) + else bool(np.random.rand(1) <= self.symmetric_pad), + symmetric_pad=self.symmetric_pad + if isinstance(self.symmetric_pad, bool) + else bool(np.random.rand(1) <= self.symmetric_pad), + )(img, target) + + return _img, _target + return img, target + + def extra_repr(self) -> str: + return f"scale_range={self.scale_range}, preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}, p={self.p}" # noqa: E501
    +
    @@ -638,8 +955,8 @@

    Source code for doctr.transforms.modules.tensorflow

    - +
    + diff --git a/v0.4.1/_modules/doctr/utils/metrics.html b/v0.4.1/_modules/doctr/utils/metrics.html index 460c64a385..8a37d5949a 100644 --- a/v0.4.1/_modules/doctr/utils/metrics.html +++ b/v0.4.1/_modules/doctr/utils/metrics.html @@ -13,7 +13,7 @@ - + doctr.utils.metrics - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.utils.metrics

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
    +
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +from typing import Dict, List, Optional, Tuple
     
     import numpy as np
    -import cv2
    -from typing import List, Tuple, Dict, Optional
    -from unidecode import unidecode
    +from anyascii import anyascii
     from scipy.optimize import linear_sum_assignment
    -from doctr.utils.geometry import rbbox_to_polygon
    +from shapely.geometry import Polygon
     
    -__all__ = ['TextMatch', 'box_iou', 'box_ioa', 'mask_iou', 'rbox_to_mask',
    -           'nms', 'LocalizationConfusion', 'OCRMetric']
    +__all__ = [
    +    "TextMatch",
    +    "box_iou",
    +    "polygon_iou",
    +    "nms",
    +    "LocalizationConfusion",
    +    "OCRMetric",
    +    "DetectionMetric",
    +]
     
     
     def string_match(word1: str, word2: str) -> Tuple[bool, bool, bool, bool]:
    -    """Perform string comparison with multiple levels of tolerance
    +    """Performs string comparison with multiple levels of tolerance
     
         Args:
    +    ----
             word1: a string
             word2: another string
     
         Returns:
    +    -------
             a tuple with booleans specifying respectively whether the raw strings, their lower-case counterparts, their
    -            unidecode counterparts and their lower-case unidecode counterparts match
    +            anyascii counterparts and their lower-case anyascii counterparts match
         """
    -    raw_match = (word1 == word2)
    -    caseless_match = (word1.lower() == word2.lower())
    -    unidecode_match = (unidecode(word1) == unidecode(word2))
    +    raw_match = word1 == word2
    +    caseless_match = word1.lower() == word2.lower()
    +    anyascii_match = anyascii(word1) == anyascii(word2)
     
         # Warning: the order is important here otherwise the pair ("EUR", "€") cannot be matched
    -    unicase_match = (unidecode(word1).lower() == unidecode(word2).lower())
    +    unicase_match = anyascii(word1).lower() == anyascii(word2).lower()
     
    -    return raw_match, caseless_match, unidecode_match, unicase_match
    +    return raw_match, caseless_match, anyascii_match, unicase_match
     
     
     
    -[docs] +[docs] class TextMatch: - """Implements text match metric (word-level accuracy) for recognition task. + r"""Implements text match metric (word-level accuracy) for recognition task. The raw aggregated metric is computed as follows: .. math:: - \\forall X, Y \\in \\mathcal{W}^N, - TextMatch(X, Y) = \\frac{1}{N} \\sum\\limits_{i=1}^N f_{Y_i}(X_i) + \forall X, Y \in \mathcal{W}^N, + TextMatch(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N f_{Y_i}(X_i) with the indicator function :math:`f_{a}` defined as: .. math:: - \\forall a, x \\in \\mathcal{W}, - f_a(x) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } x = a \\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{W}` is the set of all possible character sequences, + \forall a, x \in \mathcal{W}, + f_a(x) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } x = a \\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{W}` is the set of all possible character sequences, :math:`N` is a strictly positive integer. - Example:: - >>> from doctr.utils import TextMatch - >>> metric = TextMatch() - >>> metric.update(['Hello', 'world'], ['hello', 'world']) - >>> metric.summary() + >>> from doctr.utils import TextMatch + >>> metric = TextMatch() + >>> metric.update(['Hello', 'world'], ['hello', 'world']) + >>> metric.summary() """ def __init__(self) -> None: self.reset() +
    +[docs] def update( self, gt: List[str], @@ -354,29 +386,32 @@

    Source code for doctr.utils.metrics

             """Update the state of the metric with new predictions
     
             Args:
    +        ----
                 gt: list of groung-truth character sequences
    -            pred: list of predicted character sequences"""
    -
    +            pred: list of predicted character sequences
    +        """
             if len(gt) != len(pred):
                 raise AssertionError("prediction size does not match with ground-truth labels size")
     
             for gt_word, pred_word in zip(gt, pred):
    -            _raw, _caseless, _unidecode, _unicase = string_match(gt_word, pred_word)
    +            _raw, _caseless, _anyascii, _unicase = string_match(gt_word, pred_word)
                 self.raw += int(_raw)
                 self.caseless += int(_caseless)
    -            self.unidecode += int(_unidecode)
    +            self.anyascii += int(_anyascii)
                 self.unicase += int(_unicase)
     
    -        self.total += len(gt)
    +        self.total += len(gt)
    +
    -[docs] +[docs] def summary(self) -> Dict[str, float]: """Computes the aggregated metrics - Returns: - a dictionary with the exact match score for the raw data, its lower-case counterpart, its unidecode - counterpart and its lower-case unidecode counterpart + Returns + ------- + a dictionary with the exact match score for the raw data, its lower-case counterpart, its anyascii + counterpart and its lower-case anyascii counterpart """ if self.total == 0: raise AssertionError("you need to update the metric before getting the summary") @@ -384,7 +419,7 @@

    Source code for doctr.utils.metrics

             return dict(
                 raw=self.raw / self.total,
                 caseless=self.caseless / self.total,
    -            unidecode=self.unidecode / self.total,
    +            anyascii=self.anyascii / self.total,
                 unicase=self.unicase / self.total,
             )
    @@ -392,23 +427,25 @@

    Source code for doctr.utils.metrics

         def reset(self) -> None:
             self.raw = 0
             self.caseless = 0
    -        self.unidecode = 0
    +        self.anyascii = 0
             self.unicase = 0
             self.total = 0
    def box_iou(boxes_1: np.ndarray, boxes_2: np.ndarray) -> np.ndarray: - """Compute the IoU between two sets of bounding boxes + """Computes the IoU between two sets of bounding boxes Args: + ---- boxes_1: bounding boxes of shape (N, 4) in format (xmin, ymin, xmax, ymax) boxes_2: bounding boxes of shape (M, 4) in format (xmin, ymin, xmax, ymax) + Returns: + ------- the IoU matrix of shape (N, M) """ - - iou_mat = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32) + iou_mat: np.ndarray = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32) if boxes_1.shape[0] > 0 and boxes_2.shape[0] > 0: l1, t1, r1, b1 = np.split(boxes_1, 4, axis=1) @@ -419,107 +456,54 @@

    Source code for doctr.utils.metrics

             right = np.minimum(r1, r2.T)
             bot = np.minimum(b1, b2.T)
     
    -        intersection = np.clip(right - left, 0, np.Inf) * np.clip(bot - top, 0, np.Inf)
    +        intersection = np.clip(right - left, 0, np.inf) * np.clip(bot - top, 0, np.inf)
             union = (r1 - l1) * (b1 - t1) + ((r2 - l2) * (b2 - t2)).T - intersection
             iou_mat = intersection / union
     
         return iou_mat
     
     
    -def box_ioa(boxes_1: np.ndarray, boxes_2: np.ndarray) -> np.ndarray:
    -    """Compute the IoA (intersection over area) between two sets of bounding boxes:
    -    ioa(i, j) = inter(i, j) / area(i)
    -
    -    Args:
    -        boxes_1: bounding boxes of shape (N, 4) in format (xmin, ymin, xmax, ymax)
    -        boxes_2: bounding boxes of shape (M, 4) in format (xmin, ymin, xmax, ymax)
    -    Returns:
    -        the IoA matrix of shape (N, M)
    -    """
    -
    -    ioa_mat = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32)
    -
    -    if boxes_1.shape[0] > 0 and boxes_2.shape[0] > 0:
    -        l1, t1, r1, b1 = np.split(boxes_1, 4, axis=1)
    -        l2, t2, r2, b2 = np.split(boxes_2, 4, axis=1)
    -
    -        left = np.maximum(l1, l2.T)
    -        top = np.maximum(t1, t2.T)
    -        right = np.minimum(r1, r2.T)
    -        bot = np.minimum(b1, b2.T)
    -
    -        intersection = np.clip(right - left, 0, np.Inf) * np.clip(bot - top, 0, np.Inf)
    -        area = (r1 - l1) * (b1 - t1)
    -        ioa_mat = intersection / area
    -
    -    return ioa_mat
    -
    -
    -def mask_iou(masks_1: np.ndarray, masks_2: np.ndarray) -> np.ndarray:
    -    """Compute the IoU between two sets of boolean masks
    +def polygon_iou(polys_1: np.ndarray, polys_2: np.ndarray) -> np.ndarray:
    +    """Computes the IoU between two sets of rotated bounding boxes
     
         Args:
    -        masks_1: boolean masks of shape (N, H, W)
    -        masks_2: boolean masks of shape (M, H, W)
    +    ----
    +        polys_1: rotated bounding boxes of shape (N, 4, 2)
    +        polys_2: rotated bounding boxes of shape (M, 4, 2)
    +        mask_shape: spatial shape of the intermediate masks
    +        use_broadcasting: if set to True, leverage broadcasting speedup by consuming more memory
     
         Returns:
    +    -------
             the IoU matrix of shape (N, M)
         """
    +    if polys_1.ndim != 3 or polys_2.ndim != 3:
    +        raise AssertionError("expects boxes to be in format (N, 4, 2)")
     
    -    if masks_1.shape[1:] != masks_2.shape[1:]:
    -        raise AssertionError("both boolean masks should have the same spatial shape")
    +    iou_mat = np.zeros((polys_1.shape[0], polys_2.shape[0]), dtype=np.float32)
     
    -    iou_mat = np.zeros((masks_1.shape[0], masks_2.shape[0]), dtype=np.float32)
    +    shapely_polys_1 = [Polygon(poly) for poly in polys_1]
    +    shapely_polys_2 = [Polygon(poly) for poly in polys_2]
     
    -    if masks_1.shape[0] > 0 and masks_2.shape[0] > 0:
    -        intersection = np.logical_and(masks_1[:, None, ...], masks_2[None, ...])
    -        union = np.logical_or(masks_1[:, None, ...], masks_2[None, ...])
    -        axes = tuple(range(2, masks_1.ndim + 1))
    -        iou_mat = intersection.sum(axis=axes) / union.sum(axis=axes)
    +    for i, poly1 in enumerate(shapely_polys_1):
    +        for j, poly2 in enumerate(shapely_polys_2):
    +            intersection_area = poly1.intersection(poly2).area
    +            union_area = poly1.area + poly2.area - intersection_area
    +            iou_mat[i, j] = intersection_area / union_area
     
         return iou_mat
     
     
    -def rbox_to_mask(boxes: np.ndarray, shape: Tuple[int, int]) -> np.ndarray:
    -    """Convert boxes to masks
    -
    -    Args:
    -        boxes: rotated bounding boxes of shape (N, 5) in format (x, y, w, h, alpha)
    -        shape: spatial shapes of the output masks
    -
    -    Returns:
    -        the boolean masks of shape (N, H, W)
    -    """
    -
    -    masks = np.zeros((boxes.shape[0], *shape), dtype=np.uint8)
    -
    -    if boxes.shape[0] > 0:
    -        # Get absolute coordinates
    -        if boxes.dtype != np.int:
    -            abs_boxes = boxes.copy()
    -            abs_boxes[:, [0, 2]] = abs_boxes[:, [0, 2]] * shape[1]
    -            abs_boxes[:, [1, 3]] = abs_boxes[:, [1, 3]] * shape[0]
    -            abs_boxes = abs_boxes.round().astype(np.int)
    -        else:
    -            abs_boxes = boxes
    -            abs_boxes[:, 2:] = abs_boxes[:, 2:] + 1
    -
    -        # TODO: optimize slicing to improve vectorization
    -        for idx, _box in enumerate(abs_boxes):
    -            box = rbbox_to_polygon(_box)
    -            cv2.fillPoly(masks[idx], [np.array(box, np.int32)], 1)
    -
    -    return masks.astype(bool)
    -
    -
    -def nms(boxes: np.ndarray, thresh: float = .5) -> List[int]:
    +def nms(boxes: np.ndarray, thresh: float = 0.5) -> List[int]:
         """Perform non-max suppression, borrowed from <https://github.com/rbgirshick/fast-rcnn>`_.
     
         Args:
    +    ----
             boxes: np array of straight boxes: (*, 5), (xmin, ymin, xmax, ymax, score)
             thresh: iou threshold to perform box suppression.
     
         Returns:
    +    -------
             A list of box indexes to keep
         """
         x1 = boxes[:, 0]
    @@ -551,66 +535,71 @@ 

    Source code for doctr.utils.metrics

     
     
     
    -[docs] +[docs] class LocalizationConfusion: - """Implements common confusion metrics and mean IoU for localization evaluation. + r"""Implements common confusion metrics and mean IoU for localization evaluation. The aggregated metrics are computed as follows: .. math:: - \\forall Y \\in \\mathcal{B}^N, \\forall X \\in \\mathcal{B}^M, \\\\ - Recall(X, Y) = \\frac{1}{N} \\sum\\limits_{i=1}^N g_{X}(Y_i) \\\\ - Precision(X, Y) = \\frac{1}{M} \\sum\\limits_{i=1}^N g_{X}(Y_i) \\\\ - meanIoU(X, Y) = \\frac{1}{M} \\sum\\limits_{i=1}^M \\max\\limits_{j \\in [1, N]} IoU(X_i, Y_j) + \forall Y \in \mathcal{B}^N, \forall X \in \mathcal{B}^M, \\ + Recall(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N g_{X}(Y_i) \\ + Precision(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M g_{X}(Y_i) \\ + meanIoU(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(X_i, Y_j) with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and :math:`y`, and the function :math:`g_{X}` defined as: .. math:: - \\forall y \\in \\mathcal{B}, - g_X(y) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } y\\mbox{ has been assigned to any }(X_i)_i\\mbox{ with an }IoU \\geq 0.5 \\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{B}` is the set of possible bounding boxes, + \forall y \in \mathcal{B}, + g_X(y) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } y\mbox{ has been assigned to any }(X_i)_i\mbox{ with an }IoU \geq 0.5 \\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. - Example:: - >>> import numpy as np - >>> from doctr.utils import LocalizationConfusion - >>> metric = LocalizationConfusion(iou_thresh=0.5) - >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]])) - >>> metric.summary() + >>> import numpy as np + >>> from doctr.utils import LocalizationConfusion + >>> metric = LocalizationConfusion(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]])) + >>> metric.summary() Args: + ---- iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format """ def __init__( self, iou_thresh: float = 0.5, - rotated_bbox: bool = False, - mask_shape: Tuple[int, int] = (1024, 1024), + use_polygons: bool = False, ) -> None: self.iou_thresh = iou_thresh - self.rotated_bbox = rotated_bbox - self.mask_shape = mask_shape + self.use_polygons = use_polygons self.reset() +
    +[docs] def update(self, gts: np.ndarray, preds: np.ndarray) -> None: + """Updates the metric + Args: + ---- + gts: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones + preds: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones + """ if preds.shape[0] > 0: # Compute IoU - if self.rotated_bbox: - mask_gts = rbox_to_mask(gts, shape=self.mask_shape) - mask_preds = rbox_to_mask(preds, shape=self.mask_shape) - iou_mat = mask_iou(mask_gts, mask_preds) + if self.use_polygons: + iou_mat = polygon_iou(gts, preds) else: iou_mat = box_iou(gts, preds) - self.tot_iou += float(iou_mat.max(axis=1).sum()) + self.tot_iou += float(iou_mat.max(axis=0).sum()) # Assign pairs gt_indices, pred_indices = linear_sum_assignment(-iou_mat) @@ -618,17 +607,18 @@

    Source code for doctr.utils.metrics

     
             # Update counts
             self.num_gts += gts.shape[0]
    -        self.num_preds += preds.shape[0]
    +        self.num_preds += preds.shape[0]
    +
    -[docs] +[docs] def summary(self) -> Tuple[Optional[float], Optional[float], Optional[float]]: """Computes the aggregated metrics - Returns: + Returns + ------- a tuple with the recall, precision and meanIoU scores """ - # Recall recall = self.matches / self.num_gts if self.num_gts > 0 else None @@ -636,7 +626,7 @@

    Source code for doctr.utils.metrics

             precision = self.matches / self.num_preds if self.num_preds > 0 else None
     
             # mean IoU
    -        mean_iou = self.tot_iou / self.num_preds if self.num_preds > 0 else None
    +        mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None
     
             return recall, precision, mean_iou
    @@ -645,64 +635,65 @@

    Source code for doctr.utils.metrics

             self.num_gts = 0
             self.num_preds = 0
             self.matches = 0
    -        self.tot_iou = 0.
    + self.tot_iou = 0.0
    -[docs] +[docs] class OCRMetric: - """Implements end-to-end OCR metric. + r"""Implements an end-to-end OCR metric. The aggregated metrics are computed as follows: .. math:: - \\forall (B, L) \\in \\mathcal{B}^N \\times \\mathcal{L}^N, - \\forall (\\hat{B}, \\hat{L}) \\in \\mathcal{B}^M \\times \\mathcal{L}^M, \\\\ - Recall(B, \\hat{B}, L, \\hat{L}) = \\frac{1}{N} \\sum\\limits_{i=1}^N h_{B,L}(\\hat{B}_i, \\hat{L}_i) \\\\ - Precision(B, \\hat{B}, L, \\hat{L}) = \\frac{1}{M} \\sum\\limits_{i=1}^N h_{B,L}(\\hat{B}_i, \\hat{L}_i) \\\\ - meanIoU(B, \\hat{B}) = \\frac{1}{M} \\sum\\limits_{i=1}^M \\max\\limits_{j \\in [1, N]} IoU(\\hat{B}_i, B_j) + \forall (B, L) \in \mathcal{B}^N \times \mathcal{L}^N, + \forall (\hat{B}, \hat{L}) \in \mathcal{B}^M \times \mathcal{L}^M, \\ + Recall(B, \hat{B}, L, \hat{L}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ + Precision(B, \hat{B}, L, \hat{L}) = \frac{1}{M} \sum\limits_{i=1}^M h_{B,L}(\hat{B}_i, \hat{L}_i) \\ + meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j) with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and :math:`y`, and the function :math:`h_{B, L}` defined as: .. math:: - \\forall (b, l) \\in \\mathcal{B} \\times \\mathcal{L}, - h_{B,L}(b, l) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } b\\mbox{ has been assigned to a given }B_j\\mbox{ with an } \\\\ - & IoU \\geq 0.5 \\mbox{ and that for this assignment, } l = L_j\\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{B}` is the set of possible bounding boxes, - :math:`\\mathcal{L}` is the set of possible character sequences, + \forall (b, l) \in \mathcal{B} \times \mathcal{L}, + h_{B,L}(b, l) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ + & IoU \geq 0.5 \mbox{ and that for this assignment, } l = L_j\\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, + :math:`\mathcal{L}` is the set of possible character sequences, :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. - Example:: - >>> import numpy as np - >>> from doctr.utils import OCRMetric - >>> metric = OCRMetric(iou_thresh=0.5) - >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), - ['hello'], ['hello', 'world']) - >>> metric.summary() + >>> import numpy as np + >>> from doctr.utils import OCRMetric + >>> metric = OCRMetric(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), + >>> ['hello'], ['hello', 'world']) + >>> metric.summary() Args: + ---- iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format """ def __init__( self, iou_thresh: float = 0.5, - rotated_bbox: bool = False, - mask_shape: Tuple[int, int] = (1024, 1024), + use_polygons: bool = False, ) -> None: self.iou_thresh = iou_thresh - self.rotated_bbox = rotated_bbox - self.mask_shape = mask_shape + self.use_polygons = use_polygons self.reset() +
    +[docs] def update( self, gt_boxes: np.ndarray, @@ -710,50 +701,58 @@

    Source code for doctr.utils.metrics

             gt_labels: List[str],
             pred_labels: List[str],
         ) -> None:
    +        """Updates the metric
     
    +        Args:
    +        ----
    +            gt_boxes: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones
    +            pred_boxes: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones
    +            gt_labels: a list of N string labels
    +            pred_labels: a list of M string labels
    +        """
             if gt_boxes.shape[0] != len(gt_labels) or pred_boxes.shape[0] != len(pred_labels):
    -            raise AssertionError("there should be the same number of boxes and string both for the ground truth "
    -                                 "and the predictions")
    +            raise AssertionError(
    +                "there should be the same number of boxes and string both for the ground truth and the predictions"
    +            )
     
             # Compute IoU
             if pred_boxes.shape[0] > 0:
    -            if self.rotated_bbox:
    -                mask_gts = rbox_to_mask(gt_boxes, shape=self.mask_shape)
    -                mask_preds = rbox_to_mask(pred_boxes, shape=self.mask_shape)
    -                iou_mat = mask_iou(mask_gts, mask_preds)
    +            if self.use_polygons:
    +                iou_mat = polygon_iou(gt_boxes, pred_boxes)
                 else:
                     iou_mat = box_iou(gt_boxes, pred_boxes)
     
    -            self.tot_iou += float(iou_mat.max(axis=1).sum())
    +            self.tot_iou += float(iou_mat.max(axis=0).sum())
     
                 # Assign pairs
                 gt_indices, pred_indices = linear_sum_assignment(-iou_mat)
                 is_kept = iou_mat[gt_indices, pred_indices] >= self.iou_thresh
                 # String comparison
                 for gt_idx, pred_idx in zip(gt_indices[is_kept], pred_indices[is_kept]):
    -                _raw, _caseless, _unidecode, _unicase = string_match(gt_labels[gt_idx], pred_labels[pred_idx])
    +                _raw, _caseless, _anyascii, _unicase = string_match(gt_labels[gt_idx], pred_labels[pred_idx])
                     self.raw_matches += int(_raw)
                     self.caseless_matches += int(_caseless)
    -                self.unidecode_matches += int(_unidecode)
    +                self.anyascii_matches += int(_anyascii)
                     self.unicase_matches += int(_unicase)
     
             self.num_gts += gt_boxes.shape[0]
    -        self.num_preds += pred_boxes.shape[0]
    +        self.num_preds += pred_boxes.shape[0]
    +
    -[docs] +[docs] def summary(self) -> Tuple[Dict[str, Optional[float]], Dict[str, Optional[float]], Optional[float]]: """Computes the aggregated metrics - Returns: - a tuple with the recall & precision for each string comparison flexibility and the mean IoU + Returns + ------- + a tuple with the recall & precision for each string comparison and the mean IoU """ - # Recall recall = dict( raw=self.raw_matches / self.num_gts if self.num_gts > 0 else None, caseless=self.caseless_matches / self.num_gts if self.num_gts > 0 else None, - unidecode=self.unidecode_matches / self.num_gts if self.num_gts > 0 else None, + anyascii=self.anyascii_matches / self.num_gts if self.num_gts > 0 else None, unicase=self.unicase_matches / self.num_gts if self.num_gts > 0 else None, ) @@ -761,12 +760,12 @@

    Source code for doctr.utils.metrics

             precision = dict(
                 raw=self.raw_matches / self.num_preds if self.num_preds > 0 else None,
                 caseless=self.caseless_matches / self.num_preds if self.num_preds > 0 else None,
    -            unidecode=self.unidecode_matches / self.num_preds if self.num_preds > 0 else None,
    +            anyascii=self.anyascii_matches / self.num_preds if self.num_preds > 0 else None,
                 unicase=self.unicase_matches / self.num_preds if self.num_preds > 0 else None,
             )
     
             # mean IoU (overall detected boxes)
    -        mean_iou = self.tot_iou / self.num_preds if self.num_preds > 0 else None
    +        mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None
     
             return recall, precision, mean_iou
    @@ -774,12 +773,136 @@

    Source code for doctr.utils.metrics

         def reset(self) -> None:
             self.num_gts = 0
             self.num_preds = 0
    -        self.tot_iou = 0.
    +        self.tot_iou = 0.0
             self.raw_matches = 0
             self.caseless_matches = 0
    -        self.unidecode_matches = 0
    +        self.anyascii_matches = 0
             self.unicase_matches = 0
    + + +
    +[docs] +class DetectionMetric: + r"""Implements an object detection metric. + + The aggregated metrics are computed as follows: + + .. math:: + \forall (B, C) \in \mathcal{B}^N \times \mathcal{C}^N, + \forall (\hat{B}, \hat{C}) \in \mathcal{B}^M \times \mathcal{C}^M, \\ + Recall(B, \hat{B}, C, \hat{C}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,C}(\hat{B}_i, \hat{C}_i) \\ + Precision(B, \hat{B}, C, \hat{C}) = \frac{1}{M} \sum\limits_{i=1}^M h_{B,C}(\hat{B}_i, \hat{C}_i) \\ + meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j) + + with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and + :math:`y`, and the function :math:`h_{B, C}` defined as: + + .. math:: + \forall (b, c) \in \mathcal{B} \times \mathcal{C}, + h_{B,C}(b, c) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ + & IoU \geq 0.5 \mbox{ and that for this assignment, } c = C_j\\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, + :math:`\mathcal{C}` is the set of possible class indices, + :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. + + >>> import numpy as np + >>> from doctr.utils import DetectionMetric + >>> metric = DetectionMetric(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), + >>> np.zeros(1, dtype=np.int64), np.array([0, 1], dtype=np.int64)) + >>> metric.summary() + + Args: + ---- + iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format + """ + + def __init__( + self, + iou_thresh: float = 0.5, + use_polygons: bool = False, + ) -> None: + self.iou_thresh = iou_thresh + self.use_polygons = use_polygons + self.reset() + +
    +[docs] + def update( + self, + gt_boxes: np.ndarray, + pred_boxes: np.ndarray, + gt_labels: np.ndarray, + pred_labels: np.ndarray, + ) -> None: + """Updates the metric + + Args: + ---- + gt_boxes: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones + pred_boxes: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones + gt_labels: an array of class indices of shape (N,) + pred_labels: an array of class indices of shape (M,) + """ + if gt_boxes.shape[0] != gt_labels.shape[0] or pred_boxes.shape[0] != pred_labels.shape[0]: + raise AssertionError( + "there should be the same number of boxes and string both for the ground truth and the predictions" + ) + + # Compute IoU + if pred_boxes.shape[0] > 0: + if self.use_polygons: + iou_mat = polygon_iou(gt_boxes, pred_boxes) + else: + iou_mat = box_iou(gt_boxes, pred_boxes) + + self.tot_iou += float(iou_mat.max(axis=0).sum()) + + # Assign pairs + gt_indices, pred_indices = linear_sum_assignment(-iou_mat) + is_kept = iou_mat[gt_indices, pred_indices] >= self.iou_thresh + # Category comparison + self.num_matches += int((gt_labels[gt_indices[is_kept]] == pred_labels[pred_indices[is_kept]]).sum()) + + self.num_gts += gt_boxes.shape[0] + self.num_preds += pred_boxes.shape[0]
    + + +
    +[docs] + def summary(self) -> Tuple[Optional[float], Optional[float], Optional[float]]: + """Computes the aggregated metrics + + Returns + ------- + a tuple with the recall & precision for each class prediction and the mean IoU + """ + # Recall + recall = self.num_matches / self.num_gts if self.num_gts > 0 else None + + # Precision + precision = self.num_matches / self.num_preds if self.num_preds > 0 else None + + # mean IoU (overall detected boxes) + mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None + + return recall, precision, mean_iou
    + + + def reset(self) -> None: + self.num_gts = 0 + self.num_preds = 0 + self.tot_iou = 0.0 + self.num_matches = 0
    +
    @@ -812,8 +935,8 @@

    Source code for doctr.utils.metrics

           
         
       
    - - + + diff --git a/v0.4.1/_modules/doctr/utils/visualization.html b/v0.4.1/_modules/doctr/utils/visualization.html index 8e7dcca811..c818be6d7b 100644 --- a/v0.4.1/_modules/doctr/utils/visualization.html +++ b/v0.4.1/_modules/doctr/utils/visualization.html @@ -13,7 +13,7 @@ - + doctr.utils.visualization - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.utils.visualization

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
    +import colorsys
    +from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple, Union
     
    -import matplotlib.pyplot as plt
    -from matplotlib.figure import Figure
    +import cv2
     import matplotlib.patches as patches
    -import mplcursors
    -from PIL import ImageFont, ImageDraw, Image
    +import matplotlib.pyplot as plt
     import numpy as np
    -import cv2
    -from typing import Tuple, List, Dict, Any, Union
    +from matplotlib.figure import Figure
     
    -from .common_types import BoundingBox, RotatedBbox
    +from .common_types import BoundingBox, Polygon4P
     
    -__all__ = ['visualize_page', 'synthetize_page']
    +__all__ = ["visualize_page", "visualize_kie_page", "draw_boxes"]
     
     
    -def create_rect_patch(
    -    geometry: Union[BoundingBox, RotatedBbox],
    -    label: str,
    +def rect_patch(
    +    geometry: BoundingBox,
         page_dimensions: Tuple[int, int],
    -    color: Tuple[int, int, int],
    +    label: Optional[str] = None,
    +    color: Tuple[float, float, float] = (0, 0, 0),
         alpha: float = 0.3,
         linewidth: int = 2,
         fill: bool = True,
    -) -> patches.Patch:
    -    """Create a matplotlib patch (rectangle) bounding the element
    +    preserve_aspect_ratio: bool = False,
    +) -> patches.Rectangle:
    +    """Create a matplotlib rectangular patch for the element
     
         Args:
    +    ----
             geometry: bounding box of the element
    +        page_dimensions: dimensions of the Page in format (height, width)
             label: label to display when hovered
    -        page_dimensions: dimensions of the Page
             color: color to draw box
             alpha: opacity parameter to fill the boxes, 0 = transparent
             linewidth: line width
    +        fill: whether the patch should be filled
    +        preserve_aspect_ratio: pass True if you passed True to the predictor
     
         Returns:
    +    -------
             a rectangular Patch
         """
    +    if len(geometry) != 2 or any(not isinstance(elt, tuple) or len(elt) != 2 for elt in geometry):
    +        raise ValueError("invalid geometry format")
    +
    +    # Unpack
         height, width = page_dimensions
    -    if len(geometry) == 5:
    -        x, y, w, h, a = geometry  # type: ignore[misc]
    -        x, w = x * width, w * width
    -        y, h = y * height, h * height
    -        points = cv2.boxPoints(((x, y), (w, h), a))
    -        return patches.Polygon(
    -            points,
    -            fill=fill,
    -            linewidth=linewidth,
    -            edgecolor=(*color, alpha),
    -            facecolor=(*color, alpha),
    -            label=label
    -        )
    -    else:
    -        (xmin, ymin), (xmax, ymax) = geometry  # type: ignore[misc]
    -        xmin, xmax = xmin * width, xmax * width
    -        ymin, ymax = ymin * height, ymax * height
    -        return patches.Rectangle(
    -            (xmin, ymin),
    -            xmax - xmin,
    -            ymax - ymin,
    -            fill=fill,
    -            linewidth=linewidth,
    -            edgecolor=(*color, alpha),
    -            facecolor=(*color, alpha),
    -            label=label
    -        )
    +    (xmin, ymin), (xmax, ymax) = geometry
    +    # Switch to absolute coords
    +    if preserve_aspect_ratio:
    +        width = height = max(height, width)
    +    xmin, w = xmin * width, (xmax - xmin) * width
    +    ymin, h = ymin * height, (ymax - ymin) * height
    +
    +    return patches.Rectangle(
    +        (xmin, ymin),
    +        w,
    +        h,
    +        fill=fill,
    +        linewidth=linewidth,
    +        edgecolor=(*color, alpha),
    +        facecolor=(*color, alpha),
    +        label=label,
    +    )
    +
    +
    +def polygon_patch(
    +    geometry: np.ndarray,
    +    page_dimensions: Tuple[int, int],
    +    label: Optional[str] = None,
    +    color: Tuple[float, float, float] = (0, 0, 0),
    +    alpha: float = 0.3,
    +    linewidth: int = 2,
    +    fill: bool = True,
    +    preserve_aspect_ratio: bool = False,
    +) -> patches.Polygon:
    +    """Create a matplotlib polygon patch for the element
    +
    +    Args:
    +    ----
    +        geometry: bounding box of the element
    +        page_dimensions: dimensions of the Page in format (height, width)
    +        label: label to display when hovered
    +        color: color to draw box
    +        alpha: opacity parameter to fill the boxes, 0 = transparent
    +        linewidth: line width
    +        fill: whether the patch should be filled
    +        preserve_aspect_ratio: pass True if you passed True to the predictor
    +
    +    Returns:
    +    -------
    +        a polygon Patch
    +    """
    +    if not geometry.shape == (4, 2):
    +        raise ValueError("invalid geometry format")
    +
    +    # Unpack
    +    height, width = page_dimensions
    +    geometry[:, 0] = geometry[:, 0] * (max(width, height) if preserve_aspect_ratio else width)
    +    geometry[:, 1] = geometry[:, 1] * (max(width, height) if preserve_aspect_ratio else height)
    +
    +    return patches.Polygon(
    +        geometry,
    +        fill=fill,
    +        linewidth=linewidth,
    +        edgecolor=(*color, alpha),
    +        facecolor=(*color, alpha),
    +        label=label,
    +    )
    +
    +
    +def create_obj_patch(
    +    geometry: Union[BoundingBox, Polygon4P, np.ndarray],
    +    page_dimensions: Tuple[int, int],
    +    **kwargs: Any,
    +) -> patches.Patch:
    +    """Create a matplotlib patch for the element
    +
    +    Args:
    +    ----
    +        geometry: bounding box (straight or rotated) of the element
    +        page_dimensions: dimensions of the page in format (height, width)
    +        **kwargs: keyword arguments for the patch
    +
    +    Returns:
    +    -------
    +        a matplotlib Patch
    +    """
    +    if isinstance(geometry, tuple):
    +        if len(geometry) == 2:  # straight word BB (2 pts)
    +            return rect_patch(geometry, page_dimensions, **kwargs)
    +        elif len(geometry) == 4:  # rotated word BB (4 pts)
    +            return polygon_patch(np.asarray(geometry), page_dimensions, **kwargs)
    +    elif isinstance(geometry, np.ndarray) and geometry.shape == (4, 2):  # rotated line
    +        return polygon_patch(geometry, page_dimensions, **kwargs)
    +    raise ValueError("invalid geometry format")
    +
    +
    +def get_colors(num_colors: int) -> List[Tuple[float, float, float]]:
    +    """Generate num_colors color for matplotlib
    +
    +    Args:
    +    ----
    +        num_colors: number of colors to generate
    +
    +    Returns:
    +    -------
    +        colors: list of generated colors
    +    """
    +    colors = []
    +    for i in np.arange(0.0, 360.0, 360.0 / num_colors):
    +        hue = i / 360.0
    +        lightness = (50 + np.random.rand() * 10) / 100.0
    +        saturation = (90 + np.random.rand() * 10) / 100.0
    +        colors.append(colorsys.hls_to_rgb(hue, lightness, saturation))
    +    return colors
     
     
     
    -[docs] +[docs] def visualize_page( page: Dict[str, Any], image: np.ndarray, @@ -359,18 +472,18 @@

    Source code for doctr.utils.visualization

     ) -> Figure:
         """Visualize a full page with predicted blocks, lines and words
     
    -    Example::
    -        >>> import numpy as np
    -        >>> import matplotlib.pyplot as plt
    -        >>> from doctr.utils.visualization import visualize_page
    -        >>> from doctr.models import ocr_db_crnn
    -        >>> model = ocr_db_crnn(pretrained=True)
    -        >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    -        >>> out = model([[input_page]])
    -        >>> visualize_page(out[0].pages[0].export(), input_page)
    -        >>> plt.show()
    +    >>> import numpy as np
    +    >>> import matplotlib.pyplot as plt
    +    >>> from doctr.utils.visualization import visualize_page
    +    >>> from doctr.models import ocr_db_crnn
    +    >>> model = ocr_db_crnn(pretrained=True)
    +    >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    +    >>> out = model([[input_page]])
    +    >>> visualize_page(out[0].pages[0].export(), input_page)
    +    >>> plt.show()
     
         Args:
    +    ----
             page: the exported Page of a Document
             image: np array of the page, needs to have the same shape than page['dimensions']
             words_only: whether only words should be displayed
    @@ -378,6 +491,11 @@ 

    Source code for doctr.utils.visualization

             scale: figsize of the largest windows side
             interactive: whether the plot should be interactive
             add_labels: for static plot, adds text labels on top of bounding box
    +        **kwargs: keyword arguments for the polygon patch
    +
    +    Returns:
    +    -------
    +        the matplotlib figure
         """
         # Get proper scale and aspect ratio
         h, w = image.shape[:2]
    @@ -386,128 +504,189 @@ 

    Source code for doctr.utils.visualization

         # Display the image
         ax.imshow(image)
         # hide both axis
    -    ax.axis('off')
    +    ax.axis("off")
     
         if interactive:
             artists: List[patches.Patch] = []  # instantiate an empty list of patches (to be drawn on the page)
     
    -    for block in page['blocks']:
    +    for block in page["blocks"]:
             if not words_only:
    -            rect = create_rect_patch(block['geometry'], 'block', page['dimensions'], (0, 1, 0), linewidth=1, **kwargs)
    +            rect = create_obj_patch(
    +                block["geometry"], page["dimensions"], label="block", color=(0, 1, 0), linewidth=1, **kwargs
    +            )
                 # add patch on figure
                 ax.add_patch(rect)
                 if interactive:
                     # add patch to cursor's artists
                     artists.append(rect)
     
    -        for line in block['lines']:
    +        for line in block["lines"]:
                 if not words_only:
    -                rect = create_rect_patch(line['geometry'], 'line', page['dimensions'], (1, 0, 0), linewidth=1, **kwargs)
    +                rect = create_obj_patch(
    +                    line["geometry"], page["dimensions"], label="line", color=(1, 0, 0), linewidth=1, **kwargs
    +                )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
     
    -            for word in line['words']:
    -                rect = create_rect_patch(word['geometry'], f"{word['value']} (confidence: {word['confidence']:.2%})",
    -                                         page['dimensions'], (0, 0, 1), **kwargs)
    +            for word in line["words"]:
    +                rect = create_obj_patch(
    +                    word["geometry"],
    +                    page["dimensions"],
    +                    label=f"{word['value']} (confidence: {word['confidence']:.2%})",
    +                    color=(0, 0, 1),
    +                    **kwargs,
    +                )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
                     elif add_labels:
    -                    if len(word['geometry']) == 5:
    +                    if len(word["geometry"]) == 5:
                             text_loc = (
    -                            int(page['dimensions'][1] * (word['geometry'][0] - word['geometry'][2] / 2)),
    -                            int(page['dimensions'][0] * (word['geometry'][1] - word['geometry'][3] / 2))
    +                            int(page["dimensions"][1] * (word["geometry"][0] - word["geometry"][2] / 2)),
    +                            int(page["dimensions"][0] * (word["geometry"][1] - word["geometry"][3] / 2)),
                             )
                         else:
                             text_loc = (
    -                            int(page['dimensions'][1] * word['geometry'][0][0]),
    -                            int(page['dimensions'][0] * word['geometry'][0][1])
    +                            int(page["dimensions"][1] * word["geometry"][0][0]),
    +                            int(page["dimensions"][0] * word["geometry"][0][1]),
    +                        )
    +
    +                    if len(word["geometry"]) == 2:
    +                        # We draw only if boxes are in straight format
    +                        ax.text(
    +                            *text_loc,
    +                            word["value"],
    +                            size=10,
    +                            alpha=0.5,
    +                            color=(0, 0, 1),
                             )
    -                    ax.text(
    -                        *text_loc,
    -                        word['value'],
    -                        size=10,
    -                        alpha=0.5,
    -                        color=(0, 0, 1),
    -                    )
     
             if display_artefacts:
    -            for artefact in block['artefacts']:
    -                rect = create_rect_patch(
    -                    artefact['geometry'],
    -                    'artefact',
    -                    page['dimensions'],
    -                    (0.5, 0.5, 0.5),  # type: ignore[arg-type]
    +            for artefact in block["artefacts"]:
    +                rect = create_obj_patch(
    +                    artefact["geometry"],
    +                    page["dimensions"],
    +                    label="artefact",
    +                    color=(0.5, 0.5, 0.5),
                         linewidth=1,
    -                    **kwargs
    +                    **kwargs,
                     )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
     
         if interactive:
    +        import mplcursors
    +
             # Create mlp Cursor to hover patches in artists
             mplcursors.Cursor(artists, hover=2).connect("add", lambda sel: sel.annotation.set_text(sel.artist.get_label()))
    -    fig.tight_layout(pad=0.)
    +    fig.tight_layout(pad=0.0)
     
         return fig
    -def synthetize_page( +def visualize_kie_page( page: Dict[str, Any], - draw_proba: bool = False, - font_size: int = 13, -) -> np.ndarray: - """Draw a the content of the element page (OCR response) on a blank page. + image: np.ndarray, + words_only: bool = False, + display_artefacts: bool = True, + scale: float = 10, + interactive: bool = True, + add_labels: bool = True, + **kwargs: Any, +) -> Figure: + """Visualize a full page with predicted blocks, lines and words + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from doctr.utils.visualization import visualize_page + >>> from doctr.models import ocr_db_crnn + >>> model = ocr_db_crnn(pretrained=True) + >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) + >>> out = model([[input_page]]) + >>> visualize_kie_page(out[0].pages[0].export(), input_page) + >>> plt.show() Args: - page: exported Page object to represent - draw_proba: if True, draw words in colors to represent confidence. Blue: p=1, red: p=0 - font_size: size of the font, default font = 13 + ---- + page: the exported Page of a Document + image: np array of the page, needs to have the same shape than page['dimensions'] + words_only: whether only words should be displayed + display_artefacts: whether artefacts should be displayed + scale: figsize of the largest windows side + interactive: whether the plot should be interactive + add_labels: for static plot, adds text labels on top of bounding box + **kwargs: keyword arguments for the polygon patch - Return: - A np array (drawn page) + Returns: + ------- + the matplotlib figure """ - # Draw template - h, w = page["dimensions"] - response = 255 * np.ones((h, w, 3), dtype=np.int32) + # Get proper scale and aspect ratio + h, w = image.shape[:2] + size = (scale * w / h, scale) if h > w else (scale, h / w * scale) + fig, ax = plt.subplots(figsize=size) + # Display the image + ax.imshow(image) + # hide both axis + ax.axis("off") - # Draw each word - for block in page["blocks"]: - for line in block["lines"]: - for word in line["words"]: - # Get aboslute word geometry - (xmin, ymin), (xmax, ymax) = word["geometry"] - xmin, xmax = int(w * xmin), int(w * xmax) - ymin, ymax = int(h * ymin), int(h * ymax) - - # White drawing context adapted to font size, 0.75 factor to convert pts --> pix - h_box, w_box = ymax - ymin, xmax - xmin - h_font, w_font = font_size, int(font_size * w_box / (h_box * 0.75)) - img = Image.new('RGB', (w_font, h_font), color=(255, 255, 255)) - d = ImageDraw.Draw(img) - - # Draw in black the value of the word - d.text((0, 0), word["value"], font=ImageFont.load_default(), fill=(0, 0, 0)) - - # Resize back to box size - img = img.resize((w_box, h_box), Image.NEAREST) - - # Colorize if draw_proba - if draw_proba: - p = int(255 * word["confidence"]) - mask = np.where(np.array(img) == 0, 1, 0) - proba = np.array([255 - p, 0, p]) - color = mask * proba[np.newaxis, np.newaxis, :] - white_mask = 255 * (1 - mask) - img = color + white_mask - - # Write to response page - response[ymin:ymax, xmin:xmax, :] = np.array(img) - - return response + if interactive: + artists: List[patches.Patch] = [] # instantiate an empty list of patches (to be drawn on the page) + + colors = {k: color for color, k in zip(get_colors(len(page["predictions"])), page["predictions"])} + for key, value in page["predictions"].items(): + for prediction in value: + if not words_only: + rect = create_obj_patch( + prediction["geometry"], + page["dimensions"], + label=f"{key} \n {prediction['value']} (confidence: {prediction['confidence']:.2%}", + color=colors[key], + linewidth=1, + **kwargs, + ) + # add patch on figure + ax.add_patch(rect) + if interactive: + # add patch to cursor's artists + artists.append(rect) + + if interactive: + import mplcursors + + # Create mlp Cursor to hover patches in artists + mplcursors.Cursor(artists, hover=2).connect("add", lambda sel: sel.annotation.set_text(sel.artist.get_label())) + fig.tight_layout(pad=0.0) + + return fig + + +def draw_boxes(boxes: np.ndarray, image: np.ndarray, color: Optional[Tuple[int, int, int]] = None, **kwargs) -> None: + """Draw an array of relative straight boxes on an image + + Args: + ---- + boxes: array of relative boxes, of shape (*, 4) + image: np array, float32 or uint8 + color: color to use for bounding box edges + **kwargs: keyword arguments from `matplotlib.pyplot.plot` + """ + h, w = image.shape[:2] + # Convert boxes to absolute coords + _boxes = deepcopy(boxes) + _boxes[:, [0, 2]] *= w + _boxes[:, [1, 3]] *= h + _boxes = _boxes.astype(np.int32) + for box in _boxes.tolist(): + xmin, ymin, xmax, ymax = box + image = cv2.rectangle( + image, (xmin, ymin), (xmax, ymax), color=color if isinstance(color, tuple) else (0, 0, 255), thickness=2 + ) + plt.imshow(image) + plt.plot(**kwargs)
    @@ -540,8 +719,8 @@

    Source code for doctr.utils.visualization

           
         
       
    - - + + diff --git a/v0.4.1/_modules/index.html b/v0.4.1/_modules/index.html index e86abcd4d4..5793c44f20 100644 --- a/v0.4.1/_modules/index.html +++ b/v0.4.1/_modules/index.html @@ -13,7 +13,7 @@ - + Overview: module code - docTR documentation @@ -225,20 +225,42 @@ - - + + diff --git a/v0.4.1/_sources/changelog.rst.txt b/v0.4.1/_sources/changelog.rst.txt index 430097d6c8..35befe7b96 100644 --- a/v0.4.1/_sources/changelog.rst.txt +++ b/v0.4.1/_sources/changelog.rst.txt @@ -1,6 +1,54 @@ Changelog ========= +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 `_ diff --git a/v0.4.1/_sources/datasets.rst.txt b/v0.4.1/_sources/datasets.rst.txt deleted file mode 100644 index 354122f1e5..0000000000 --- a/v0.4.1/_sources/datasets.rst.txt +++ /dev/null @@ -1,68 +0,0 @@ -doctr.datasets -============== - -.. currentmodule:: doctr.datasets - -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. - - -.. _datasets: - -Available Datasets ------------------- -The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL. - -.. autoclass:: doctr.datasets.datasets.VisionDataset - - -Here are all datasets that are available through DocTR: - -.. autoclass:: FUNSD -.. autoclass:: SROIE -.. autoclass:: CORD -.. autoclass:: OCRDataset - - -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. - -.. autoclass:: doctr.datasets.loader.DataLoader - - -.. _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 - * - digits - - 10 - - 0123456789 - * - ascii_letters - - 52 - - abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ - * - punctuation - - 32 - - !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ - * - currency - - 5 - - £€¥¢฿ - * - latin - - 96 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~° - * - french - - 154 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿ - -.. autofunction:: encode_sequences diff --git a/v0.4.1/_sources/documents.rst.txt b/v0.4.1/_sources/documents.rst.txt deleted file mode 100644 index 655730073e..0000000000 --- a/v0.4.1/_sources/documents.rst.txt +++ /dev/null @@ -1,87 +0,0 @@ -doctr.documents -=============== - - -.. currentmodule:: doctr.documents - -The documents module enables users to easily access content from documents and export analysis -results to structured formats. - - -Document structure ------------------- - -Structural organization of the documents. - -Word -^^^^ -A Word is an uninterrupted sequence of characters. - -.. autoclass:: Word - -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 - -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 - - -Document -^^^^^^^^ - -A Document is a collection of Pages. - -.. autoclass:: Document - - .. automethod:: show - - -File reading ------------- - -High-performance file reading and conversion to processable structured data. - -.. autofunction:: read_pdf - -.. autofunction:: read_img - -.. autofunction:: read_html - - -.. autoclass:: DocumentFile - - .. automethod:: from_pdf - - .. automethod:: from_url - - .. automethod:: from_images - -.. autoclass:: PDF - - .. automethod:: as_images - - .. automethod:: get_words - - .. automethod:: get_artefacts diff --git a/v0.4.1/_sources/getting_started/installing.rst.txt b/v0.4.1/_sources/getting_started/installing.rst.txt index e764e734a7..39e79aa3dd 100644 --- a/v0.4.1/_sources/getting_started/installing.rst.txt +++ b/v0.4.1/_sources/getting_started/installing.rst.txt @@ -3,7 +3,7 @@ Installation ************ -This library requires `Python `_ 3.9 or higher. +This library requires `Python `_ 3.10 or higher. Prerequisites diff --git a/v0.4.1/_sources/index.rst.txt b/v0.4.1/_sources/index.rst.txt index fc3ff89fdf..53251db142 100644 --- a/v0.4.1/_sources/index.rst.txt +++ b/v0.4.1/_sources/index.rst.txt @@ -1,7 +1,8 @@ -DocTR: Document Text Recognition -================================ +******************************** +docTR: Document Text Recognition +******************************** -State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 (PyTorch now in beta) +State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 & PyTorch .. image:: https://github.com/mindee/doctr/releases/download/v0.2.0/ocr.png :align: center @@ -9,38 +10,29 @@ State-of-the-art Optical Character Recognition made seamless & accessible to any DocTR provides an easy and powerful way to extract valuable information from your documents: -* |:receipt:| **for automation**: seemlessly process documents for Natural Language Understanding tasks: we provide OCR predictors to parse textual information (localize and identify each word) 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. -Welcome to the documentation of `DocTR `_! - - Main Features ------------- * |:robot:| Robust 2-stage (detection + recognition) OCR predictors with pretrained parameters * |:zap:| User-friendly, 3 lines of code to load a document and extract text with a predictor -* |:rocket:| State-of-the-art performances on public document datasets, comparable with GoogleVision/AWS Textract +* |: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, small dependencies -* |:tools:| Daily maintained -* |:factory:| Easy integration - +* |:bird:| Light package, minimal dependencies +* |:tools:| Actively maintained by Mindee +* |:factory:| Easy integration (available templates for browser demo & API deployment) -Getting Started ---------------- .. toctree:: :maxdepth: 2 + :caption: Getting started + :hidden: - installing - - -Build & train your predictor -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* Compose your own end-to-end OCR predictor: mix and match detection & recognition predictors (all-pretrained) -* Fine-tune or train from scratch any detection or recognition model to specialize on your data + getting_started/installing + notebooks Model zoo @@ -48,36 +40,83 @@ Model zoo Text detection models """"""""""""""""""""" - * `DBNet `_ (Differentiable Binarization) - * `LinkNet `_ +* 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 `_ (Show, Attend and Read) - * `CRNN `_ (Convolutional Recurrent Neural Network) - * `MASTER `_ (Multi-Aspect Non-local Network for Scene Text Recognition) +* 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" `_ 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 `_. +* 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" `_. .. toctree:: :maxdepth: 2 - :caption: Notes + :caption: Using docTR + :hidden: - changelog + 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 + + +.. toctree:: + :maxdepth: 2 + :caption: Community + :hidden: + + community/resources .. toctree:: :maxdepth: 2 :caption: Package Reference + :hidden: - datasets - documents - models - transforms - utils + 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/v0.4.1/_sources/installing.rst.txt b/v0.4.1/_sources/installing.rst.txt deleted file mode 100644 index 5c8779dc1c..0000000000 --- a/v0.4.1/_sources/installing.rst.txt +++ /dev/null @@ -1,46 +0,0 @@ - -************ -Installation -************ - -This library requires Python 3.6 or higher. - - -Prerequisites -============= - -Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so: - -* TensorFlow: `installation page `_. -* PyTorch: `installation page `_. - -If you are running another OS than Linux, you will need a few extra dependencies. - -For MacOS users, you can install them as follows: - -.. code:: shell - - brew install cairo pango gdk-pixbuf libffi - -For Windows users, those dependencies are included in GTK. You can find the latest installer over `here `_. - - -Via Python Package -================== - -Install the last stable release of the package using pip: - -.. code:: bash - - pip install python-doctr - - -Via Git -======= - -Install the library in developper mode: - -.. code:: bash - - git clone https://github.com/mindee/doctr.git - pip install -e doctr/. diff --git a/v0.4.1/_sources/models.rst.txt b/v0.4.1/_sources/models.rst.txt deleted file mode 100644 index 9830c6c153..0000000000 --- a/v0.4.1/_sources/models.rst.txt +++ /dev/null @@ -1,215 +0,0 @@ -doctr.models -============ - -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. - -.. currentmodule:: doctr.models - -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 TensorFlow model. -* Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable. - - -Text Detection --------------- -Localizing text elements in images - -+---------------------------------------------------+----------------------------+----------------------------+---------+ -| | FUNSD | CORD | | -+==================+=================+==============+============+===============+============+===============+=========+ -| **Architecture** | **Input shape** | **# params** | **Recall** | **Precision** | **Recall** | **Precision** | **FPS** | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ -| db_resnet50 | (1024, 1024, 3) | 25.2 M | 82.14 | 87.64 | 92.49 | 89.66 | 2.1 | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ - -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 combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for detection -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for detection is the following: - -1. resize each input image to the target size (bilinear interpolation by default) with potential deformation. -2. batch images together -3. normalize the batch using the training data statistics - - -Detection models -^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - -.. autofunction:: doctr.models.detection.db_resnet50 -.. autofunction:: doctr.models.detection.linknet16 - -Detection predictors -^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information. - -.. autofunction:: doctr.models.detection.detection_predictor - - -Text Recognition ----------------- -Identifying strings in images - -.. list-table:: Text recognition model zoo - :widths: 20 20 15 10 10 10 - :header-rows: 1 - - * - Architecture - - Input shape - - # params - - FUNSD - - CORD - - FPS - * - crnn_vgg16_bn - - (32, 128, 3) - - 15.8M - - 86.02 - - 91.3 - - 12.8 - * - sar_vgg16_bn - - (32, 128, 3) - - 21.5M - - 86.2 - - 91.7 - - 3.3 - * - sar_resnet31 - - (32, 128, 3) - - 53.1M - - **86.3** - - **92.1** - - 2.7 - -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 metrics being used are available in :ref:`metrics`. - -All these recognition models are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for recognition -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for recognition is the following: - -1. resize each input image to the target size (bilinear interpolation by default) without deformation. -2. pad the image to the target size (with zeros by default) -3. batch images together -4. normalize the batch using the training data statistics - -Recognition models -^^^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - - -.. autofunction:: doctr.models.recognition.crnn_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_resnet31 -.. autofunction:: doctr.models.recognition.master - - -Recognition predictors -^^^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage. - -.. autofunction:: doctr.models.recognition.recognition_predictor - - -End-to-End OCR --------------- -Predictors that localize and identify text elements in images - -+-----------------------------+--------------------------------------+--------------------------------------+ -| | FUNSD | CORD | -+=============================+============+===============+=========+============+===============+=========+ -| **Architecture** | **Recall** | **Precision** | **FPS** | **Recall** | **Precision** | **FPS** | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + crnn_vgg16_bn | 70.08 | 74.77 | 0.85 | 82.19 | **79.67** | 1.6 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_vgg16_bn | N/A | N/A | 0.49 | N/A | N/A | 1.0 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_resnet31 | N/A | N/A | 0.27 | N/A | N/A | 0.83 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision text detection | 59.50 | 62.50 | | 75.30 | 70.00 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision doc. text detection | 64.00 | 53.30 | | 68.90 | 61.10 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| AWS textract | **78.10** | **83.00** | | **87.50** | 66.00 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ - -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`. - -All recognition models of predictors are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Results on private ocr datasets - -+------------------------------------+----------------------------+----------------------------+----------------------------+ -| | Receipts | Invoices | IDs | -+====================================+============+===============+============+===============+============+===============+ -| **Architecture** | **Recall** | **Precision** | **Recall** | **Precision** | **Recall** | **Precision** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| db_resnet50 + crnn_vgg16_bn (ours) | **78.90** | **81.01** | 65.68 | **69.86** | **49.48** | **50.46** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| Gvision doc. text detection | 68.91 | 59.89 | 63.20 | 52.85 | 43.70 | 29.21 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| AWS textract | 75.77 | 77.70 | **70.47** | 69.13 | 46.39 | 43.32 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ - - -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. - -.. autofunction:: doctr.models.zoo.ocr_predictor - - -Model export ------------- -Utility functions to make the most of document analysis models. - -.. currentmodule:: doctr.models.export - -Model compression -^^^^^^^^^^^^^^^^^ - -.. autofunction:: convert_to_tflite - -.. autofunction:: convert_to_fp16 - -.. autofunction:: quantize_model - -Using SavedModel -^^^^^^^^^^^^^^^^ - -Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -`SavedModel `_ format as follows: - - - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> _ = model(input_t, training=False) - >>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/') - -And loaded just as easily: - - - >>> import tensorflow as tf - >>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/') diff --git a/v0.4.1/_sources/transforms.rst.txt b/v0.4.1/_sources/transforms.rst.txt deleted file mode 100644 index 0230fe75f5..0000000000 --- a/v0.4.1/_sources/transforms.rst.txt +++ /dev/null @@ -1,32 +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: - -.. autoclass:: Resize -.. autoclass:: Normalize -.. autoclass:: LambdaTransformation -.. autoclass:: ToGray -.. autoclass:: ColorInversion -.. autoclass:: RandomBrightness -.. autoclass:: RandomContrast -.. autoclass:: RandomSaturation -.. autoclass:: RandomHue -.. autoclass:: RandomGamma -.. autoclass:: RandomJpegQuality - - -Composing transformations ---------------------------------------------- -It is common to require several transformations to be performed consecutively. - -.. autoclass:: Compose -.. autoclass:: OneOf -.. autoclass:: RandomApply diff --git a/v0.4.1/_sources/utils.rst.txt b/v0.4.1/_sources/utils.rst.txt deleted file mode 100644 index 69c1abe0eb..0000000000 --- a/v0.4.1/_sources/utils.rst.txt +++ /dev/null @@ -1,36 +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 - - -.. _metrics: - -Task evaluation ---------------- -Implementations of task-specific metrics to easily assess your model performances. - -.. currentmodule:: doctr.utils.metrics - -.. autoclass:: TextMatch - - .. automethod:: summary - -.. autoclass:: LocalizationConfusion - - .. automethod:: summary - -.. autoclass:: OCRMetric - - .. automethod:: summary diff --git a/v0.4.1/_static/basic.css b/v0.4.1/_static/basic.css index f316efcb47..7ebbd6d07b 100644 --- a/v0.4.1/_static/basic.css +++ b/v0.4.1/_static/basic.css @@ -1,12 +1,5 @@ /* - * basic.css - * ~~~~~~~~~ - * * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ /* -- main layout ----------------------------------------------------------- */ @@ -115,15 +108,11 @@ img { /* -- search page ----------------------------------------------------------- */ ul.search { - margin: 10px 0 0 20px; - padding: 0; + margin-top: 10px; } ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; + padding: 5px 0; } ul.search li a { diff --git a/v0.4.1/_static/doctools.js b/v0.4.1/_static/doctools.js index 4d67807d17..0398ebb9f0 100644 --- a/v0.4.1/_static/doctools.js +++ b/v0.4.1/_static/doctools.js @@ -1,12 +1,5 @@ /* - * doctools.js - * ~~~~~~~~~~~ - * * Base JavaScript utilities for all Sphinx HTML documentation. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; diff --git a/v0.4.1/_static/documentation_options.js b/v0.4.1/_static/documentation_options.js index a7b5cbe04a..4f656fdbea 100644 --- a/v0.4.1/_static/documentation_options.js +++ b/v0.4.1/_static/documentation_options.js @@ -1,5 +1,5 @@ const DOCUMENTATION_OPTIONS = { - VERSION: '0.3.0a0-git', + VERSION: '0.10.1a0-git', LANGUAGE: 'en', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/v0.4.1/_static/language_data.js b/v0.4.1/_static/language_data.js index 367b8ed81b..c7fe6c6faf 100644 --- a/v0.4.1/_static/language_data.js +++ b/v0.4.1/_static/language_data.js @@ -1,13 +1,6 @@ /* - * language_data.js - * ~~~~~~~~~~~~~~~~ - * * This script contains the language-specific data used by searchtools.js, * namely the list of stopwords, stemmer, scorer and splitter. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; diff --git a/v0.4.1/_static/searchtools.js b/v0.4.1/_static/searchtools.js index b08d58c9b9..2c774d17af 100644 --- a/v0.4.1/_static/searchtools.js +++ b/v0.4.1/_static/searchtools.js @@ -1,12 +1,5 @@ /* - * searchtools.js - * ~~~~~~~~~~~~~~~~ - * * Sphinx JavaScript utilities for the full-text search. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; @@ -20,7 +13,7 @@ if (typeof Scorer === "undefined") { // and returns the new score. /* score: result => { - const [docname, title, anchor, descr, score, filename] = result + const [docname, title, anchor, descr, score, filename, kind] = result return score }, */ @@ -47,6 +40,14 @@ if (typeof Scorer === "undefined") { }; } +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + const _removeChildren = (element) => { while (element && element.lastChild) element.removeChild(element.lastChild); }; @@ -64,9 +65,13 @@ const _displayItem = (item, searchTerms, highlightTerms) => { const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; const contentRoot = document.documentElement.dataset.content_root; - const [docName, title, anchor, descr, score, _filename] = item; + const [docName, title, anchor, descr, score, _filename, kind] = item; let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); let requestUrl; let linkUrl; if (docBuilder === "dirhtml") { @@ -115,8 +120,10 @@ const _finishSearch = (resultCount) => { "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." ); else - Search.status.innerText = _( - "Search finished, found ${resultCount} page(s) matching the search query." + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, ).replace('${resultCount}', resultCount); }; const _displayNextItem = ( @@ -138,7 +145,7 @@ const _displayNextItem = ( else _finishSearch(resultCount); }; // Helper function used by query() to order search results. -// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. // Order the results by score (in opposite order of appearance, since the // `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. const _orderResultsByScoreThenName = (a, b) => { @@ -248,6 +255,7 @@ const Search = { searchSummary.classList.add("search-summary"); searchSummary.innerText = ""; const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); searchList.classList.add("search"); const out = document.getElementById("search-results"); @@ -318,7 +326,7 @@ const Search = { const indexEntries = Search._index.indexentries; // Collect multiple result groups to be sorted separately and then ordered. - // Each is an array of [docname, title, anchor, descr, score, filename]. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. const normalResults = []; const nonMainIndexResults = []; @@ -337,6 +345,7 @@ const Search = { null, score + boost, filenames[file], + SearchResultKind.title, ]); } } @@ -354,6 +363,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.index, ]; if (isMain) { normalResults.push(result); @@ -475,6 +485,7 @@ const Search = { descr, score, filenames[match[0]], + SearchResultKind.object, ]); }; Object.keys(objects).forEach((prefix) => @@ -585,6 +596,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.text, ]); } return results; diff --git a/v0.4.1/changelog.html b/v0.4.1/changelog.html index eafac3a877..fc45a50384 100644 --- a/v0.4.1/changelog.html +++ b/v0.4.1/changelog.html @@ -12,9 +12,9 @@ gtag('js', new Date()); gtag('config', 'G-40DVRMX8T4'); - + - + Changelog - docTR documentation @@ -226,20 +226,42 @@ + diff --git a/v0.4.1/community/resources.html b/v0.4.1/community/resources.html index 2564037893..9a1988258c 100644 --- a/v0.4.1/community/resources.html +++ b/v0.4.1/community/resources.html @@ -14,7 +14,7 @@ - + Community resources - docTR documentation @@ -389,7 +389,7 @@

    Community resources - + diff --git a/v0.4.1/contributing/code_of_conduct.html b/v0.4.1/contributing/code_of_conduct.html index 5ea4a1f99d..03422dbb4d 100644 --- a/v0.4.1/contributing/code_of_conduct.html +++ b/v0.4.1/contributing/code_of_conduct.html @@ -14,7 +14,7 @@ - + Contributor Covenant Code of Conduct - docTR documentation @@ -504,7 +504,7 @@

    Attribution - + diff --git a/v0.4.1/contributing/contributing.html b/v0.4.1/contributing/contributing.html index e5a85682c6..05e2b3641b 100644 --- a/v0.4.1/contributing/contributing.html +++ b/v0.4.1/contributing/contributing.html @@ -14,7 +14,7 @@ - + Contributing to docTR - docTR documentation @@ -481,7 +481,7 @@

    Let’s connect - + diff --git a/v0.4.1/datasets.html b/v0.4.1/datasets.html deleted file mode 100644 index 193e576c57..0000000000 --- a/v0.4.1/datasets.html +++ /dev/null @@ -1,578 +0,0 @@ - - - - - - - - - - - - - doctr.datasets - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.datasets

    -

    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.

    -
    -

    Available Datasets

    -

    The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL.

    -
    -
    -class doctr.datasets.datasets.VisionDataset(url: str, file_name: str | None = None, file_hash: str | None = None, extract_archive: bool = False, download: bool = False, overwrite: bool = False)[source]
    -
    - -

    Here are all datasets that are available through DocTR:

    -
    -
    -class doctr.datasets.FUNSD(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    FUNSD dataset from “FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents”.

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD
    ->>> train_set = FUNSD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.SROIE(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    SROIE dataset from “ICDAR2019 Competition on Scanned Receipt OCR and Information Extraction”.

    -
    -
    Example::
    >>> from doctr.datasets import SROIE
    ->>> train_set = SROIE(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.CORD(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    CORD dataset from “CORD: A Consolidated Receipt Dataset forPost-OCR Parsing”.

    -
    -
    Example::
    >>> from doctr.datasets import CORD
    ->>> train_set = CORD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.OCRDataset(img_folder: str, label_file: str, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    Implements an OCR dataset

    -
    -
    Parameters:
    -
      -
    • img_folder – local path to image folder (all jpg at the root)

    • -
    • label_file – local path to the label file

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -

    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.

    -
    -
    -class doctr.datasets.loader.DataLoader(dataset, shuffle: bool = True, batch_size: int = 1, drop_last: bool = False, workers: int | None = None)[source]
    -

    Implements a dataset wrapper for fast data loading

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD, 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)
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • dataset – the dataset

    • -
    • shuffle – whether the samples should be shuffled before passing it to the iterator

    • -
    • batch_size – number of elements in each batch

    • -
    • drop_last – if True, drops the last batch if it isn’t full

    • -
    • workers – number of workers to use for data loading

    • -
    -
    -
    -
    - -
    -
    -

    Supported Vocabs

    -

    Since textual content has to be encoded properly for models to interpret them efficiently, DocTR supports multiple sets -of vocabs.

    -
    - - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    DocTR Vocabs

    Name

    size

    characters

    digits

    10

    0123456789

    ascii_letters

    52

    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

    punctuation

    32

    !”#$%&'()*+,-./:;<=>?@[\]^_`{|}~

    currency

    5

    £€¥¢฿

    latin

    96

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°

    french

    154

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿

    -
    -
    -
    -doctr.datasets.encode_sequences(sequences: List[str], vocab: str, target_size: int | None = None, eos: int = -1, sos: int | None = None, pad: int | None = None, **kwargs: Any) ndarray[source]
    -

    Encode character sequences using a given vocab as mapping

    -
    -
    Parameters:
    -
      -
    • sequences – the list of character sequences of size N

    • -
    • vocab – the ordered vocab to use for encoding

    • -
    • target_size – maximum length of the encoded data

    • -
    • eos – encoding of End Of String

    • -
    • sos – optional encoding of Start Of String

    • -
    • pad – optional encoding for padding. In case of padding, all sequences are followed by 1 EOS then PAD

    • -
    -
    -
    Returns:
    -

    the padded encoded data as a tensor

    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.1/documents.html b/v0.4.1/documents.html deleted file mode 100644 index 98cbb2c5ef..0000000000 --- a/v0.4.1/documents.html +++ /dev/null @@ -1,772 +0,0 @@ - - - - - - - - - - - - - doctr.documents - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.documents

    -

    The documents module enables users to easily access content from documents and export analysis -results to structured formats.

    -
    -

    Document structure

    -

    Structural organization of the documents.

    -
    -

    Word

    -

    A Word is an uninterrupted sequence of characters.

    -
    -
    -class doctr.documents.Word(value: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float])[source]
    -

    Implements a word element

    -
    -
    Parameters:
    -
      -
    • value – the text string of the word

    • -
    • confidence – the confidence associated with the text prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to

    • -
    • size (the page's)

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Line(words: List[Word], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float] | None = None)[source]
    -

    Implements a line element as a collection of words

    -
    -
    Parameters:
    -
      -
    • words – list of word elements

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all words in it.

    • -
    -
    -
    -
    - -
    -
    -

    Artefact

    -

    An Artefact is a non-textual element (e.g. QR code, picture, chart, signature, logo, etc.).

    -
    -
    -class doctr.documents.Artefact(artefact_type: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]])[source]
    -

    Implements a non-textual element

    -
    -
    Parameters:
    -
      -
    • artefact_type – the type of artefact

    • -
    • confidence – the confidence of the type prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size.

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Block(lines: List[Line] = [], artefacts: List[Artefact] = [], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float] | None = None)[source]
    -

    Implements a block element as a collection of lines and artefacts

    -
    -
    Parameters:
    -
      -
    • lines – list of line elements

    • -
    • artefacts – list of artefacts

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all lines and artefacts in it.

    • -
    -
    -
    -
    - -
    -
    -

    Page

    -

    A Page is a collection of Blocks that were on the same physical page.

    -
    -
    -class doctr.documents.Page(blocks: List[Block], page_idx: int, dimensions: Tuple[int, int], orientation: Dict[str, Any] | None = None, language: Dict[str, Any] | None = None)[source]
    -

    Implements a page element as a collection of blocks

    -
    -
    Parameters:
    -
      -
    • blocks – list of block elements

    • -
    • page_idx – the index of the page in the input raw document

    • -
    • dimensions – the page size in pixels in format (width, height)

    • -
    • orientation – a dictionary with the value of the rotation angle in degress and confidence of the prediction

    • -
    • language – a dictionary with the language value and confidence of the prediction

    • -
    -
    -
    -
    -
    -show(page: ndarray, interactive: bool = True, **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -
      -
    • page – image encoded as a numpy array in uint8

    • -
    • interactive – whether the display should be interactive

    • -
    -
    -
    -
    - -
    - -
    -
    -

    Document

    -

    A Document is a collection of Pages.

    -
    -
    -class doctr.documents.Document(pages: List[Page])[source]
    -

    Implements a document element as a collection of pages

    -
    -
    Parameters:
    -

    pages – list of page elements

    -
    -
    -
    -
    -show(pages: List[ndarray], **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -

    pages – list of images encoded as numpy arrays in uint8

    -
    -
    -
    - -
    - -
    -
    -
    -

    File reading

    -

    High-performance file reading and conversion to processable structured data.

    -
    -
    -doctr.documents.read_pdf(file: str | Path | bytes, **kwargs: Any) Document[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_pdf
    ->>> doc = read_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_img(file: str | Path | bytes, output_size: Tuple[int, int] | None = None, rgb_output: bool = True) ndarray[source]
    -

    Read an image file into numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_img
    ->>> page = read_img("path/to/your/doc.jpg")
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • file – the path to the image file

    • -
    • output_size – the expected output size of each page in format H x W

    • -
    • rgb_output – whether the output ndarray channel order should be RGB instead of BGR.

    • -
    -
    -
    Returns:
    -

    the page decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_html(url: str, **kwargs: Any) bytes[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_html
    ->>> doc = read_html("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – URL of the target web page

    -
    -
    Returns:
    -

    decoded PDF file as a bytes stream

    -
    -
    -
    - -
    -
    -class doctr.documents.DocumentFile[source]
    -

    Read a document from multiple extensions

    -
    -
    -classmethod from_pdf(file: str | Path | bytes, **kwargs) PDF[source]
    -

    Read a PDF file

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file or a binary stream

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_url(url: str, **kwargs) PDF[source]
    -

    Interpret a web page as a PDF document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_url("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – the URL of the target web page

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_images(files: Sequence[str | Path | bytes] | str | Path | bytes, **kwargs) List[ndarray][source]
    -

    Read an image file (or a collection of image files) and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"])
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    files – the path to the image file or a binary stream, or a collection of those

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    - -
    -
    -class doctr.documents.PDF(doc: Document)[source]
    -

    PDF document template

    -
    -
    Parameters:
    -

    doc – input PDF document

    -
    -
    -
    -
    -as_images(**kwargs) List[ndarray][source]
    -

    Convert all document pages to images

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of convert_page_to_numpy

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -get_words(**kwargs) List[List[Tuple[Tuple[float, float, float, float], str]]][source]
    -

    Get the annotations for all words in the document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of fitz.Page.getTextWords

    -
    -
    Returns:
    -

    the list of pages annotations, represented as a list of tuple (bounding box, value)

    -
    -
    -
    - -
    -
    -get_artefacts() List[List[Tuple[float, float, float, float]]][source]
    -

    Get the artefacts for the entire document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts()
    -
    -
    -
    -
    -
    -
    Returns:
    -

    the list of pages artefacts, represented as a list of bounding boxes

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.1/genindex.html b/v0.4.1/genindex.html index a19b433943..21520455b4 100644 --- a/v0.4.1/genindex.html +++ b/v0.4.1/genindex.html @@ -13,7 +13,7 @@ - Index - docTR documentation + Index - docTR documentation @@ -224,20 +224,42 @@

    +
    +

    U

    + + +
    +
    +

    V

    @@ -561,7 +711,13 @@

    V

    W

    +
    @@ -599,8 +755,8 @@

    W

    - - + + diff --git a/v0.4.1/getting_started/installing.html b/v0.4.1/getting_started/installing.html index a488e9a030..af3b58193e 100644 --- a/v0.4.1/getting_started/installing.html +++ b/v0.4.1/getting_started/installing.html @@ -14,7 +14,7 @@ - + Installation - docTR documentation @@ -305,7 +305,7 @@

    Installation

    -

    This library requires Python 3.9 or higher.

    +

    This library requires Python 3.10 or higher.

    Prerequisites

    Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so:

    @@ -435,7 +435,7 @@

    Via Git - + diff --git a/v0.4.1/index.html b/v0.4.1/index.html index 4c6a28c66a..3a06afc6d9 100644 --- a/v0.4.1/index.html +++ b/v0.4.1/index.html @@ -12,9 +12,9 @@ gtag('js', new Date()); gtag('config', 'G-40DVRMX8T4'); - + - + docTR documentation @@ -226,20 +226,42 @@
    -

    DocTR: Document Text Recognition

    -

    State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 (PyTorch now in beta)

    +

    docTR: Document Text Recognition

    +

    State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 & PyTorch

    https://github.com/mindee/doctr/releases/download/v0.2.0/ocr.png

    DocTR provides an easy and powerful way to extract valuable information from your documents:

      -
    • 🧾 for automation: seemlessly process documents for Natural Language Understanding tasks: we provide OCR predictors to parse textual information (localize and identify each word) from your documents.

    • +
    • 🧾 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.

    • 👩‍🔬 for research: quickly compare your own architectures speed & performances with state-of-art models on public datasets.

    -

    Welcome to the documentation of DocTR!

    Main Features

    • 🤖 Robust 2-stage (detection + recognition) OCR predictors with pretrained parameters

    • ⚡ User-friendly, 3 lines of code to load a document and extract text with a predictor

    • -
    • 🚀 State-of-the-art performances on public document datasets, comparable with GoogleVision/AWS Textract

    • +
    • 🚀 State-of-the-art performance on public document datasets, comparable with GoogleVision/AWS Textract

    • ⚡ Optimized for inference speed on both CPU & GPU

    • -
    • 🐦 Light package, small dependencies

    • -
    • 🛠️ Daily maintained

    • -
    • 🏭 Easy integration

    • +
    • 🐦 Light package, minimal dependencies

    • +
    • 🛠️ Actively maintained by Mindee

    • +
    • 🏭 Easy integration (available templates for browser demo & API deployment)

    -
    -
    -

    Getting Started

    -
    -

    Build & train your predictor

    -
      -
    • Compose your own end-to-end OCR predictor: mix and match detection & recognition predictors (all-pretrained)

    • -
    • Fine-tune or train from scratch any detection or recognition model to specialize on your data

    • -
    -

    Model zoo

    Text detection models

    -
    -

    Text recognition models

    -
    -

    Supported datasets

    -
    -
    +
    +
    +
    +
    +
    @@ -406,7 +381,7 @@

    Supported datasets - +
    Next @@ -446,10 +421,8 @@

    Supported datasets + diff --git a/v0.4.1/installing.html b/v0.4.1/installing.html deleted file mode 100644 index b61c60134b..0000000000 --- a/v0.4.1/installing.html +++ /dev/null @@ -1,395 +0,0 @@ - - - - - - - - - - - - - Installation - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    Installation

    -

    This library requires Python 3.6 or higher.

    -
    -

    Prerequisites

    -

    Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so:

    - -

    If you are running another OS than Linux, you will need a few extra dependencies.

    -

    For MacOS users, you can install them as follows:

    -
    brew install cairo pango gdk-pixbuf libffi
    -
    -
    -

    For Windows users, those dependencies are included in GTK. You can find the latest installer over here.

    -
    -
    -

    Via Python Package

    -

    Install the last stable release of the package using pip:

    -
    pip install python-doctr
    -
    -
    -
    -
    -

    Via Git

    -

    Install the library in developper mode:

    -
    git clone https://github.com/mindee/doctr.git
    -pip install -e doctr/.
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.1/models.html b/v0.4.1/models.html deleted file mode 100644 index b5cd44c9fa..0000000000 --- a/v0.4.1/models.html +++ /dev/null @@ -1,1002 +0,0 @@ - - - - - - - - - - - - - doctr.models - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.models

    -

    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 TensorFlow model.

    • -
    • Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable.

    • -
    -
    -

    Text Detection

    -

    Localizing text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Input shape

    # params

    Recall

    Precision

    Recall

    Precision

    FPS

    db_resnet50

    (1024, 1024, 3)

    25.2 M

    82.14

    87.64

    92.49

    89.66

    2.1

    -
    -

    All text detection models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for detection

    -

    In DocTR, the pre-processing scheme for detection is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) with potential deformation.

    2. -
    3. batch images together

    4. -
    5. normalize the batch using the training data statistics

    6. -
    -
    -
    -

    Detection models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.detection.db_resnet50(pretrained: bool = False, **kwargs: Any) DBNet[source]
    -

    DBNet as described in “Real-time Scene Text Detection with Differentiable Binarization”, using a ResNet-50 backbone.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -doctr.models.detection.linknet16(pretrained: bool = False, **kwargs: Any) LinkNet[source]
    -

    LinkNet as described in “LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import linknet16
    ->>> model = linknet16(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -

    Detection predictors

    -

    Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information.

    -
    -
    -doctr.models.detection.detection_predictor(arch: str = 'db_resnet50', pretrained: bool = False, **kwargs: Any) DetectionPredictor[source]
    -

    Text detection architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import detection_predictor
    ->>> model = detection_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_resnet50’)

    • -
    • pretrained – If True, returns a model pre-trained on our text detection dataset

    • -
    -
    -
    Returns:
    -

    Detection predictor

    -
    -
    -
    - -
    -
    -
    -

    Text Recognition

    -

    Identifying strings in images

    -
    - - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Text recognition model zoo

    Architecture

    Input shape

    # params

    FUNSD

    CORD

    FPS

    crnn_vgg16_bn

    (32, 128, 3)

    15.8M

    86.02

    91.3

    12.8

    sar_vgg16_bn

    (32, 128, 3)

    21.5M

    86.2

    91.7

    3.3

    sar_resnet31

    (32, 128, 3)

    53.1M

    86.3

    92.1

    2.7

    -
    -

    All text recognition models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All these recognition models are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for recognition

    -

    In DocTR, the pre-processing scheme for recognition is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) without deformation.

    2. -
    3. pad the image to the target size (with zeros by default)

    4. -
    5. batch images together

    6. -
    7. normalize the batch using the training data statistics

    8. -
    -
    -
    -

    Recognition models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.recognition.crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) CRNN[source]
    -

    CRNN with a VGG-16 backbone as described in “An End-to-End Trainable Neural Network for Image-based -Sequence Recognition and Its Application to Scene Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import crnn_vgg16_bn
    ->>> model = crnn_vgg16_bn(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a VGG16 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_vgg16_bn
    ->>> model = sar_vgg16_bn(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_resnet31(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a resnet-31 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -

    Example

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_resnet31
    ->>> model = sar_resnet31(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.master(pretrained: bool = False, **kwargs: Any) MASTER[source]
    -

    MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_. -Example:

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import master
    ->>> model = master(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 48, 160, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -

    Recognition predictors

    -

    Combining the right components around a given architecture for easier usage.

    -
    -
    -doctr.models.recognition.recognition_predictor(arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) RecognitionPredictor[source]
    -

    Text recognition architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import recognition_predictor
    ->>> model = recognition_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(32, 128, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘crnn_vgg16_bn’, ‘crnn_resnet31’, ‘sar_vgg16_bn’, ‘sar_resnet31’)

    • -
    • pretrained – If True, returns a model pre-trained on our text recognition dataset

    • -
    -
    -
    Returns:
    -

    Recognition predictor

    -
    -
    -
    - -
    -
    -
    -

    End-to-End OCR

    -

    Predictors that localize and identify text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Recall

    Precision

    FPS

    Recall

    Precision

    FPS

    db_resnet50 + crnn_vgg16_bn

    70.08

    74.77

    0.85

    82.19

    79.67

    1.6

    db_resnet50 + sar_vgg16_bn

    N/A

    N/A

    0.49

    N/A

    N/A

    1.0

    db_resnet50 + sar_resnet31

    N/A

    N/A

    0.27

    N/A

    N/A

    0.83

    Gvision text detection

    59.50

    62.50

    75.30

    70.00

    Gvision doc. text detection

    64.00

    53.30

    68.90

    61.10

    AWS textract

    78.10

    83.00

    87.50

    66.00

    -
    -

    All OCR models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All recognition models of predictors are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -

    Results on private ocr datasets

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Receipts

    Invoices

    IDs

    Architecture

    Recall

    Precision

    Recall

    Precision

    Recall

    Precision

    db_resnet50 + crnn_vgg16_bn (ours)

    78.90

    81.01

    65.68

    69.86

    49.48

    50.46

    Gvision doc. text detection

    68.91

    59.89

    63.20

    52.85

    43.70

    29.21

    AWS textract

    75.77

    77.70

    70.47

    69.13

    46.39

    43.32

    -
    -
    -

    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.

    -
    -
    -doctr.models.zoo.ocr_predictor(det_arch: str = 'db_resnet50', reco_arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) OCRPredictor[source]
    -

    End-to-end OCR architecture using one model for localization, and another for text recognition.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import ocr_predictor
    ->>> model = ocr_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_sar_vgg’, ‘db_sar_resnet’, ‘db_crnn_vgg’, ‘db_crnn_resnet’)

    • -
    • pretrained – If True, returns a model pre-trained on our OCR dataset

    • -
    -
    -
    Returns:
    -

    OCR predictor

    -
    -
    -
    - -
    -
    -
    -

    Model export

    -

    Utility functions to make the most of document analysis models.

    -
    -

    Model compression

    -
    -
    -doctr.models.export.convert_to_tflite(tf_model: Model) bytes[source]
    -

    Converts a model to TFLite format

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_tflite, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_tflite(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.convert_to_fp16(tf_model: Model) bytes[source]
    -

    Converts a model to half precision

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_fp16, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_fp16(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the serialized FP16 model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) bytes[source]
    -

    Quantize a Tensorflow model

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import quantize_model, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = quantize_model(model, (224, 224, 3))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • tf_model – a keras model

    • -
    • input_shape – shape of the expected input tensor (excluding batch dimension) with channel last order

    • -
    -
    -
    Returns:
    -

    the serialized quantized model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -

    Using SavedModel

    -

    Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -SavedModel format as follows:

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> _ = model(input_t, training=False)
    ->>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/')
    -
    -
    -

    And loaded just as easily:

    -
    >>> import tensorflow as tf
    ->>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/')
    -
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.1/modules/contrib.html b/v0.4.1/modules/contrib.html index e99f6b3f74..7fb86b8b38 100644 --- a/v0.4.1/modules/contrib.html +++ b/v0.4.1/modules/contrib.html @@ -14,7 +14,7 @@ - + doctr.contrib - docTR documentation @@ -380,7 +380,7 @@

    Supported contribution modules - + diff --git a/v0.4.1/modules/datasets.html b/v0.4.1/modules/datasets.html index 456e10b172..380a986793 100644 --- a/v0.4.1/modules/datasets.html +++ b/v0.4.1/modules/datasets.html @@ -14,7 +14,7 @@ - + doctr.datasets - docTR documentation @@ -1081,7 +1081,7 @@

    Returns:

    - + diff --git a/v0.4.1/modules/io.html b/v0.4.1/modules/io.html index 01eadaa4b8..24c41954be 100644 --- a/v0.4.1/modules/io.html +++ b/v0.4.1/modules/io.html @@ -14,7 +14,7 @@ - + doctr.io - docTR documentation @@ -760,7 +760,7 @@

    Returns: - + diff --git a/v0.4.1/modules/models.html b/v0.4.1/modules/models.html index c465cc0586..91b8810a6a 100644 --- a/v0.4.1/modules/models.html +++ b/v0.4.1/modules/models.html @@ -14,7 +14,7 @@ - + doctr.models - docTR documentation @@ -1612,7 +1612,7 @@

    Args: - + diff --git a/v0.4.1/modules/transforms.html b/v0.4.1/modules/transforms.html index 30f7a2631a..c5ead3f3ce 100644 --- a/v0.4.1/modules/transforms.html +++ b/v0.4.1/modules/transforms.html @@ -14,7 +14,7 @@ - + doctr.transforms - docTR documentation @@ -835,7 +835,7 @@

    Args:< - + diff --git a/v0.4.1/modules/utils.html b/v0.4.1/modules/utils.html index 888a32c321..b7f6fc570b 100644 --- a/v0.4.1/modules/utils.html +++ b/v0.4.1/modules/utils.html @@ -14,7 +14,7 @@ - + doctr.utils - docTR documentation @@ -715,7 +715,7 @@

    Args: - + diff --git a/v0.4.1/notebooks.html b/v0.4.1/notebooks.html index f97771aebb..d36539f59e 100644 --- a/v0.4.1/notebooks.html +++ b/v0.4.1/notebooks.html @@ -14,7 +14,7 @@ - + docTR Notebooks - docTR documentation @@ -391,7 +391,7 @@

    docTR Notebooks - + diff --git a/v0.4.1/objects.inv b/v0.4.1/objects.inv index a22d2ce821026792e7b2f75cbac9aecf06cc0d89..c1700f291b7b6bc7252a625eb1acde3a63b67eca 100644 GIT binary patch delta 4090 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 delta 1868 zcmV-S2ebIcAgT|LI|DN=Fp)nzf2CSYZ`(K!z4KQH*kG}Hpb@8NQRLRfO*V_9$=YuA zXwVYv2$4jUq@1R|zWk6VlKNIpp``cTkeuOgNGBp8r;7-2#4u-ztB3U}$lor*1ThvY z1M}gTr^z2@0R)jtxc>HaGmY_ZwO;A=B-&>EaQkHvBP2BP1_XUt)H3{re~@WB#VjV- zoZg!T#~CB^kdW6dwV^(C^rm4FXCaC3j^XcxXksQU9EvRDf;6Vf0?Q)bzeAnV@P<}G zP=x71_VrFRCrus+X=~IBb;jZ}G#Mo^_Je9jP{WND35yhG;{7Me@d1TyNSLqwu*`?g z2?<{&a#m$)CT+o<$*bh1f0#d$Xd3xCPVh{-lDarhlJ4RZ9d$6y?Sj_Hqr>lu6f~JC zau;V)C*g5*J)N;YZ01}^@)7eLDx-3?z^h20)5)UCQ%5T(vjX#f(ZPwfaDuzmENlpL zuJayUZ&446YC?};d}I4?+)uJe|%GqVifHp!QTLf zgGEu}^f;4Qrl{#mxmC92-0_ZAyiRo|BaQHsfLB_nC@Ki`fZNzT;$f$bVOXACj2k85-XU zl=1i{-l^A91Sk&4e=&>IJcKmMcBkWU`C+%u=8B|x+F}gRGQ{33e@A1^ zl>8(_)}Ipx!70kvfzVVODM&)-tqcuWt=qem1?r=xIbnD*?+&H=2yLquh|e+pRWcJ1 zkCT}X8GblSX01^ck@QoZCvP*kpJ{x2<{4&eroa`+#5=}kf6{MInxdK9e+oR4`EQPo z{}sYheD1t$5HIMwAX#HJCqlO5hN9`+73-}?Fk9@!iL7&N!KT>Ix*Rg)1@ssTnldEZ z8uMf1ZDNlR>%yUtOUBKUCXF-EpLWLBYgHI$yd_ikkw(3wJj#^jj5FY;?=c2S zPGz9rv8bHHd7s9iSob89i%<c7_oe3s#6wq;JmU!U(a4tM$EVPV9u? z%{9EmKlnZ3;qyephBwvlDQ1P4H}iydd3m6>%3V*Tf4X7{7>_O!v=?U<*mBCP?o9wX zp;*Ag(X_f#^_b4>t3gA9{$vEon_UL>>i(p^1L}?i?29;wf$tPIhqf5m4zZj>hZeY3^uXK( zlqWOSqJ3I3NLV6Q9@Ww^ZXKm(p;s*u-cJ#|Gb)jF^f&Hvdc(dX8?1Y>?HP#Tm8j-! z&>vr#Y@wZ0<8rNJqG=jaGA_+{0xmLJV4dbWe;2DeWGH#i=B-G$U0(*~c2uU!U|j=% zG1;F_Dghf$i%@LRc*rHXXEl7)PynubtEhLB0xu8%&P0+TQ2Yro~2f=5J$*@C6wV@5Y_sON4zSSI3 zDpB_qrR4u4q^D-rh<7Fe0B^oM0Ff3UNkgtmlf{syZLnscuz-!foTJ=E_%`I`n1 zvmo{W63V#{;UV&1P;Vujli{+UV8NR8*dy6yuQOQ=ShRVMy|U6O#ovpV#k?7%{?d80 z!N8j4f%$)Y6Xk2>y20+_&|YR?s~v6KpgW3Vwt8FD!mnFp0K*8d_ue*|J2o z?K;3K0?Qoh#ZKTCjDWuqxp|A^e~EIDP*C`cdlr{LLkqoakpP9tOAMWGr1y(XO@)LO z@|&V=r!b_=gb9mIRfOCz}Q~EsFI53>6&YH - - - - - - - - - - Python Module Index - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    - -
    -

    Python Module Index

    - -
    - - - - - - - - - - - -
     
    d
    - doctr -
    - -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.4.1/search.html b/v0.4.1/search.html index 73772822d2..d050f5eac7 100644 --- a/v0.4.1/search.html +++ b/v0.4.1/search.html @@ -14,7 +14,7 @@ - + Search - docTR documentation @@ -226,20 +226,42 @@ - - + + diff --git a/v0.4.1/searchindex.js b/v0.4.1/searchindex.js index 803f4f4bcf..6f154115ab 100644 --- a/v0.4.1/searchindex.js +++ b/v0.4.1/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles": {"Artefact": [[2, "artefact"]], "Available Datasets": [[1, "available-datasets"]], "Block": [[2, "block"]], "Build & train your predictor": [[3, "build-train-your-predictor"]], "Changelog": [[0, null]], "Composing transformations": [[6, "composing-transformations"]], "Data Loading": [[1, "data-loading"]], "Detection models": [[5, "detection-models"]], "Detection predictors": [[5, "detection-predictors"]], "DocTR Vocabs": [[1, "id1"]], "DocTR: Document Text Recognition": [[3, null]], "Document": [[2, "document"]], "Document structure": [[2, "document-structure"]], "End-to-End OCR": [[5, "end-to-end-ocr"]], "File reading": [[2, "file-reading"]], "Getting Started": [[3, "getting-started"]], "Installation": [[4, null]], "Line": [[2, "line"]], "Main Features": [[3, "main-features"]], "Model compression": [[5, "model-compression"]], "Model export": [[5, "model-export"]], "Model zoo": [[3, "model-zoo"]], "Notes": [[3, null]], "Package Reference": [[3, null]], "Page": [[2, "page"]], "Pre-processing for detection": [[5, "pre-processing-for-detection"]], "Pre-processing for recognition": [[5, "pre-processing-for-recognition"]], "Prerequisites": [[4, "prerequisites"]], "Recognition models": [[5, "recognition-models"]], "Recognition predictors": [[5, "recognition-predictors"]], "Supported Vocabs": [[1, "supported-vocabs"]], "Supported datasets": [[3, "supported-datasets"]], "Supported transformations": [[6, "supported-transformations"]], "Task evaluation": [[7, "task-evaluation"]], "Text Detection": [[5, "text-detection"]], "Text Recognition": [[5, "text-recognition"]], "Text detection models": [[3, "text-detection-models"]], "Text recognition model zoo": [[5, "id2"]], "Text recognition models": [[3, "text-recognition-models"]], "Two-stage approaches": [[5, "two-stage-approaches"]], "Using SavedModel": [[5, "using-savedmodel"]], "Via Git": [[4, "via-git"]], "Via Python Package": [[4, "via-python-package"]], "Visualization": [[7, "visualization"]], "Word": [[2, "word"]], "doctr.datasets": [[1, null]], "doctr.documents": [[2, null]], "doctr.models": [[5, null]], "doctr.transforms": [[6, null]], "doctr.utils": [[7, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]]}, "docnames": ["changelog", "datasets", "documents", "index", "installing", "models", "transforms", "utils"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "datasets.rst", "documents.rst", "index.rst", "installing.rst", "models.rst", "transforms.rst", "utils.rst"], "indexentries": {"artefact (class in doctr.documents)": [[2, "doctr.documents.Artefact", false]], "as_images() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.as_images", false]], "block (class in doctr.documents)": [[2, "doctr.documents.Block", false]], "colorinversion (class in doctr.transforms)": [[6, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[6, "doctr.transforms.Compose", false]], "convert_to_fp16() (in module doctr.models.export)": [[5, "doctr.models.export.convert_to_fp16", false]], "convert_to_tflite() (in module doctr.models.export)": [[5, "doctr.models.export.convert_to_tflite", false]], "cord (class in doctr.datasets)": [[1, "doctr.datasets.CORD", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.crnn_vgg16_bn", false]], "dataloader (class in doctr.datasets.loader)": [[1, "doctr.datasets.loader.DataLoader", false]], "db_resnet50() (in module doctr.models.detection)": [[5, "doctr.models.detection.db_resnet50", false]], "detection_predictor() (in module doctr.models.detection)": [[5, "doctr.models.detection.detection_predictor", false]], "document (class in doctr.documents)": [[2, "doctr.documents.Document", false]], "documentfile (class in doctr.documents)": [[2, "doctr.documents.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[1, "doctr.datasets.encode_sequences", false]], "from_images() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_images", false]], "from_pdf() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_pdf", false]], "from_url() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[1, "doctr.datasets.FUNSD", false]], "get_artefacts() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.get_artefacts", false]], "get_words() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.get_words", false]], "lambdatransformation (class in doctr.transforms)": [[6, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.documents)": [[2, "doctr.documents.Line", false]], "linknet16() (in module doctr.models.detection)": [[5, "doctr.models.detection.linknet16", false]], "localizationconfusion (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.LocalizationConfusion", false]], "master() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.master", false]], "normalize (class in doctr.transforms)": [[6, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models.zoo)": [[5, "doctr.models.zoo.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[1, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[6, "doctr.transforms.OneOf", false]], "page (class in doctr.documents)": [[2, "doctr.documents.Page", false]], "pdf (class in doctr.documents)": [[2, "doctr.documents.PDF", false]], "quantize_model() (in module doctr.models.export)": [[5, "doctr.models.export.quantize_model", false]], "randomapply (class in doctr.transforms)": [[6, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[6, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[6, "doctr.transforms.RandomContrast", false]], "randomgamma (class in doctr.transforms)": [[6, "doctr.transforms.RandomGamma", false]], "randomhue (class in doctr.transforms)": [[6, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[6, "doctr.transforms.RandomJpegQuality", false]], "randomsaturation (class in doctr.transforms)": [[6, "doctr.transforms.RandomSaturation", false]], "read_html() (in module doctr.documents)": [[2, "doctr.documents.read_html", false]], "read_img() (in module doctr.documents)": [[2, "doctr.documents.read_img", false]], "read_pdf() (in module doctr.documents)": [[2, "doctr.documents.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.recognition_predictor", false]], "resize (class in doctr.transforms)": [[6, "doctr.transforms.Resize", false]], "sar_resnet31() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.sar_resnet31", false]], "sar_vgg16_bn() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.sar_vgg16_bn", false]], "show() (doctr.documents.document method)": [[2, "doctr.documents.Document.show", false]], "show() (doctr.documents.page method)": [[2, "doctr.documents.Page.show", false]], "sroie (class in doctr.datasets)": [[1, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[7, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[7, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[7, "doctr.utils.metrics.TextMatch.summary", false]], "textmatch (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.TextMatch", false]], "togray (class in doctr.transforms)": [[6, "doctr.transforms.ToGray", false]], "visiondataset (class in doctr.datasets.datasets)": [[1, "doctr.datasets.datasets.VisionDataset", false]], "visualize_page() (in module doctr.utils.visualization)": [[7, "doctr.utils.visualization.visualize_page", false]], "word (class in doctr.documents)": [[2, "doctr.documents.Word", false]]}, "objects": {"doctr.datasets": [[1, 0, 1, "", "CORD"], [1, 0, 1, "", "FUNSD"], [1, 0, 1, "", "OCRDataset"], [1, 0, 1, "", "SROIE"], [1, 1, 1, "", "encode_sequences"]], "doctr.datasets.datasets": [[1, 0, 1, "", "VisionDataset"]], "doctr.datasets.loader": [[1, 0, 1, "", "DataLoader"]], "doctr.documents": [[2, 0, 1, "", "Artefact"], [2, 0, 1, "", "Block"], [2, 0, 1, "", "Document"], [2, 0, 1, "", "DocumentFile"], [2, 0, 1, "", "Line"], [2, 0, 1, "", "PDF"], [2, 0, 1, "", "Page"], [2, 0, 1, "", "Word"], [2, 1, 1, "", "read_html"], [2, 1, 1, "", "read_img"], [2, 1, 1, "", "read_pdf"]], "doctr.documents.Document": [[2, 2, 1, "", "show"]], "doctr.documents.DocumentFile": [[2, 2, 1, "", "from_images"], [2, 2, 1, "", "from_pdf"], [2, 2, 1, "", "from_url"]], "doctr.documents.PDF": [[2, 2, 1, "", "as_images"], [2, 2, 1, "", "get_artefacts"], [2, 2, 1, "", "get_words"]], "doctr.documents.Page": [[2, 2, 1, "", "show"]], "doctr.models.detection": [[5, 1, 1, "", "db_resnet50"], [5, 1, 1, "", "detection_predictor"], [5, 1, 1, "", "linknet16"]], "doctr.models.export": [[5, 1, 1, "", "convert_to_fp16"], [5, 1, 1, "", "convert_to_tflite"], [5, 1, 1, "", "quantize_model"]], "doctr.models.recognition": [[5, 1, 1, "", "crnn_vgg16_bn"], [5, 1, 1, "", "master"], [5, 1, 1, "", "recognition_predictor"], [5, 1, 1, "", "sar_resnet31"], [5, 1, 1, "", "sar_vgg16_bn"]], "doctr.models.zoo": [[5, 1, 1, "", "ocr_predictor"]], "doctr.transforms": [[6, 0, 1, "", "ColorInversion"], [6, 0, 1, "", "Compose"], [6, 0, 1, "", "LambdaTransformation"], [6, 0, 1, "", "Normalize"], [6, 0, 1, "", "OneOf"], [6, 0, 1, "", "RandomApply"], [6, 0, 1, "", "RandomBrightness"], [6, 0, 1, "", "RandomContrast"], [6, 0, 1, "", "RandomGamma"], [6, 0, 1, "", "RandomHue"], [6, 0, 1, "", "RandomJpegQuality"], [6, 0, 1, "", "RandomSaturation"], [6, 0, 1, "", "Resize"], [6, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[7, 0, 1, "", "LocalizationConfusion"], [7, 0, 1, "", "OCRMetric"], [7, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.LocalizationConfusion": [[7, 2, 1, "", "summary"]], "doctr.utils.metrics.OCRMetric": [[7, 2, 1, "", "summary"]], "doctr.utils.metrics.TextMatch": [[7, 2, 1, "", "summary"]], "doctr.utils.visualization": [[7, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [2, 7], "0": [1, 3, 5, 6, 7], "00": 5, "01": 5, "0123456789": 1, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 1, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": [], "02": 5, "02562": 5, "03": 3, "035": [], "0361328125": [], "04": [], "05": 3, "06": [], "06640625": [], "07": [], "08": 5, "09": [], "0966796875": [], "1": [1, 3, 5, 6, 7], "10": [1, 5, 7], "100": [5, 6, 7], "1000": 5, "101": [], "1024": [5, 7], "104": [], "106": [], "108": [], "1095": [], "11": 3, "110": 7, "1107": [], "114": [], "115": [], "1156": [], "116": [], "118": [], "11800h": [], "11th": [], "12": 5, "120": [], "123": [], "126": [], "1268": [], "128": 5, "13": 5, "130": [], "13068": [], "131": [], "1337891": [], "1357421875": [], "1396484375": [], "14": 5, "1420": [], "14470v1": [], "149": [], "15": 5, "150": 7, "154": 1, "1552": [], "16": 5, "160": 5, "1630859375": [], "1684": [], "16x16": [], "17": [], "1778": [], "1782": [], "18": 3, "185546875": [], "19": 5, "1900": [], "1910": 5, "19342": [], "19370": [], "195": [], "19598": [], "199": 5, "1999": [], "1m": 5, "2": [3, 5, 6], "20": 5, "200": 7, "2000": [], "2003": [], "2012": [], "2013": [], "2015": [], "2019": 3, "2021": 3, "2023": [], "207901": [], "21": 5, "2103": [], "2186": [], "21888": [], "22": [], "224": [5, 6], "225": 6, "22672": [], "229": 6, "23": [], "233": [], "236": [], "24": [], "246": [], "249": [], "25": 5, "2504": [], "255": [5, 6, 7], "256": 5, "257": [], "26": [], "26032": [], "264": [], "27": 5, "2700": [], "2710": [], "2749": [], "28": 3, "287": [], "29": 5, "296": [], "299": [], "2d": [], "3": [2, 3, 4, 5, 6, 7], "30": 5, "300": [], "3000": [], "301": [], "30595": 5, "30ghz": [], "31": 5, "32": [1, 5, 6], "3232421875": [], "33": [], "33402": [], "33608": [], "34": [], "340": [], "3456": [], "3515625": [], "36": [], "360": [], "37": [], "38": [], "39": 5, "4": [], "40": [], "406": 6, "41": [], "42": [], "43": 5, "44": [], "45": [], "456": 6, "46": 5, "47": 5, "472": [], "48": 5, "485": 6, "49": 5, "49377": [], "5": [1, 6, 7], "50": 5, "51": [], "51171875": [], "512": [], "52": [1, 5], "529": [], "53": 5, "533": [], "54": [], "540": [], "5478515625": [], "55": [], "56": [], "57": [], "58": [], "580": [], "5810546875": [], "583": [], "59": 5, "595": [], "597": [], "5k": [], "5m": 5, "6": [4, 5, 6], "60": 6, "600": [5, 7], "61": 5, "611": [], "62": 5, "625": [], "626": [], "629": [], "63": 5, "630": [], "64": [5, 6], "640": [], "641": [], "647": [], "65": 5, "66": 5, "660": [], "664": [], "666": [], "67": 5, "672": [], "68": 5, "689": [], "69": 5, "693": [], "694": [], "695": [], "6m": [], "7": 5, "70": [5, 7], "700": [], "701": [], "702": [], "707470": [], "71": [], "7100000": [], "713": [], "7141797": [], "7149": [], "72": [], "72dpi": [], "73": [], "73257": [], "733": [], "74": 5, "745": [], "75": 5, "753": [], "7581382": [], "76": [], "77": 5, "772": [], "772875": [], "78": 5, "780": [], "781": [], "783": [], "785": [], "789": [], "79": 5, "793533": [], "796": [], "798": [], "7m": [], "8": [5, 6], "80": [], "800": [5, 7], "81": 5, "817": [], "82": 5, "8275l": 5, "83": 5, "830": [], "84": [], "849": [], "85": 5, "8564453125": [], "857": [], "85875": [], "86": 5, "860": [], "8603515625": [], "862": [], "863": [], "87": 5, "8707": [], "875": [], "88": [], "89": 5, "8m": 5, "9": [], "90": 5, "90k": [], "90kdict32px": [], "91": 5, "913": [], "914085328578949": [], "917": [], "92": 5, "921": [], "93": [], "94": [], "95": 7, "9578408598899841": [], "96": 1, "97": [], "98": [], "99": [], "9949972033500671": [], "A": [1, 2, 3, 5], "And": 5, "As": [], "Be": [], "Being": [], "By": [], "For": [4, 5], "If": [2, 4, 5], "In": [1, 5], "It": 6, "Its": 5, "No": [], "Of": 1, "Or": [], "The": [1, 2, 5, 7], "Then": 5, "To": [], "_": [1, 5], "__call__": [], "_build": [], "_i": 7, "ab": [], "abc": [], "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 1, "abdef": [], "abl": [], "about": 5, "abov": 5, "abstract": 1, "abstractdataset": [], "abus": [], "accent": [], "accept": [], "access": [1, 2, 3], "account": [], "accur": [], "accuraci": 7, "achiev": [], "act": [], "action": [], "activ": [], "ad": 6, "adapt": [], "add": [6, 7], "add_hook": [], "add_label": 7, "addit": [], "addition": 5, "address": 2, "adjust": 6, "advanc": [], "advantag": [], "advis": [], "aesthet": [], "affect": [], "after": [], "ag": [], "again": [], "aggreg": [1, 7], "aggress": [], "align": 2, "all": [1, 2, 3, 5, 6, 7], "allow": [], "along": 5, "alreadi": [], "also": [], "alwai": [], "an": [1, 2, 3, 5, 7], "analysi": [2, 5], "ancient_greek": [], "andrej": [], "angl": 2, "ani": [1, 2, 3, 5, 6, 7], "annot": 2, "anot": [], "anoth": [1, 4, 5], "answer": [], "anyascii": [], "anyon": 3, "anyth": [], "api": [], "apolog": [], "apologi": [], "app": [], "appear": [], "appli": [1, 6], "applic": 5, "appoint": [], "appreci": [], "appropri": [], "ar": [1, 2, 4, 5, 6, 7], "arab": [], "arabic_diacrit": [], "arabic_lett": [], "arabic_punctu": [], "arbitrarili": [], "arch": 5, "architectur": [3, 5], "archiv": [], "area": [], "argument": [1, 2], "around": 5, "arrai": [2, 7], "art": 3, "artefact": 7, "artefact_typ": 2, "articl": [], "artifici": [], "arxiv": 5, "as_imag": 2, "asarrai": 7, "ascii_lett": 1, "aspect": [3, 6], "assess": 7, "assign": 7, "associ": 2, "assum": [], "assume_straight_pag": [], "astyp": [5, 7], "attack": [], "attend": [3, 5], "attent": [], "autoclass": [], "autom": 3, "automat": [], "autoregress": [], "avail": [3, 5, 6], "averag": [5, 6], "avoid": [], "aw": [3, 5], "awar": [], "azur": [], "b": 7, "b_j": 7, "back": [], "backbon": 5, "backend": 5, "background": [], "bangla": [], "bar": [], "bar_cod": [], "baranovskij": [], "base": 5, "baselin": 5, "batch": [1, 5, 6], "batch_siz": 1, "bblanchon": [], "bbox": [], "becaus": [], "been": [5, 7], "befor": 1, "begin": 7, "behavior": [], "being": [5, 7], "belong": [], "benchmark": [], "best": [], "beta": 3, "better": [], "between": [6, 7], "bgr": 2, "bilinear": [5, 6], "bin_thresh": [], "binar": [3, 5], "binari": 2, "bit": [], "block": [5, 7], "block_1_1": [], "blur": [], "bmvc": [], "bn": [], "bodi": [], "bool": [1, 2, 5, 6, 7], "boolean": [], "both": [3, 5, 6], "bottom": [], "bound": [1, 2, 6, 7], "box": [1, 2, 7], "box_thresh": [], "brew": 4, "bright": 6, "browser": [], "build": [], "built": [], "byte": [2, 5], "c": [], "c5": 5, "c_j": [], "cach": [], "cache_sampl": [], "cairo": 4, "call": [], "callabl": [1, 6], "can": [1, 4, 5], "capabl": 5, "case": [1, 7], "cf": 5, "cfg": [], "challeng": [], "challenge2_test_task12_imag": [], "challenge2_test_task1_gt": [], "challenge2_training_task12_imag": [], "challenge2_training_task1_gt": [], "chang": [], "changelog": 3, "channel": [2, 5, 6], "channel_prior": [], "channelshuffl": [], "charact": [1, 2, 3, 5, 7], "charactergener": [], "characterist": [], "charg": 5, "charset": [], "chart": 2, "check": [], "checkpoint": [], "chip": [], "christian": [], "ci": [], "clarifi": [], "clariti": [], "class": [1, 2, 6, 7], "class_nam": [], "classif": [], "classmethod": 2, "clear": [], "clone": 4, "close": [], "co": [], "code": [2, 3], "codecov": [], "colab": [], "collate_fn": [], "collect": 2, "color": 6, "colorinvers": 6, "column": 2, "com": [2, 4], "combin": 5, "command": [], "comment": [], "commit": [], "common": [6, 7], "commun": [], "compar": 3, "comparison": 7, "competit": 1, "compil": [], "complaint": [], "complementari": 7, "complet": [], "compon": 5, "compos": [1, 3, 5], "comprehens": [], "comput": [5, 7], "conf_threshold": [], "confid": 2, "config": [], "configur": [], "confus": 7, "consecut": [5, 6], "consequ": [], "consid": [1, 2, 7], "consist": [], "consolid": [1, 3], "constant": 6, "construct": [], "contact": [], "contain": [], "content": [1, 2], "context": [], "contib": [], "continu": [], "contrast": 6, "contrast_factor": 6, "contrib": [], "contribut": [], "contributor": [], "conv_sequ": 5, "convers": 2, "convert": [2, 5, 6], "convert_page_to_numpi": 2, "convert_to_fp16": 5, "convert_to_tflit": 5, "convolut": 3, "cool": [], "coordin": 2, "cord": [1, 3, 5], "core": 7, "corner": [], "correct": 6, "correspond": [4, 5], "could": [], "counterpart": 7, "cover": [], "coverag": [], "cpu": [3, 5], "creat": [], "crnn": [3, 5], "crnn_mobilenet_v3_larg": [], "crnn_mobilenet_v3_smal": [], "crnn_resnet31": 5, "crnn_vgg16_bn": 5, "crop": 5, "crop_orient": [], "crop_orientation_predictor": [], "crop_param": [], "cuda": [], "currenc": 1, "current": [], "custom": [], "custom_crop_orientation_model": [], "custom_page_orientation_model": [], "customhook": [], "cvit": [], "czczup": [], "czech": [], "d": [], "daili": 3, "danish": [], "data": [2, 3, 5, 6, 7], "dataload": 1, "dataset": 5, "dataset_info": [], "date": [], "db": [], "db_crnn_resnet": 5, "db_crnn_vgg": 5, "db_mobilenet_v3_larg": [], "db_resnet34": [], "db_resnet50": 5, "db_sar_resnet": 5, "db_sar_vgg": 5, "dbnet": [3, 5], "deal": [], "decis": [], "decod": 2, "decode_img_as_tensor": [], "dedic": [], "deem": [], "deep": 5, "def": [], "default": [2, 5], "defer": 1, "defin": 7, "deform": 5, "degre": [], "degress": 2, "delet": [], "delimit": [], "delta": 6, "demo": [], "demonstr": [], "depend": [3, 4], "deploi": [], "deploy": [], "derogatori": [], "describ": 5, "descript": [], "design": 6, "desir": [], "det_arch": 5, "det_b": [], "det_model": [], "det_param": [], "det_predictor": [], "detail": [], "detect": [], "detect_languag": [], "detect_orient": [], "detection_predictor": 5, "detection_task": [], "detectiondataset": [], "detectionmetr": [], "detectionpredictor": 5, "detector": [], "deterior": [], "determin": [], "dev": [], "develop": [], "developp": 4, "deviat": 6, "devic": [], "dict": [2, 7], "dictionari": [2, 7], "differ": [], "differenti": [3, 5], "digit": 1, "dimens": [2, 5, 7], "dimension": 6, "direct": [], "directli": 5, "directori": [], "disabl": [], "disable_crop_orient": [], "disable_page_orient": [], "disclaim": 5, "discuss": [], "disk": [], "disparag": [], "displai": [2, 7], "display_artefact": 7, "distanc": [], "distribut": 6, "div": [], "divers": [], "divid": [], "do": 4, "doc": [2, 5], "docartefact": [], "docstr": [], "doctr": 4, "doctr_cache_dir": [], "doctr_multiprocessing_dis": [], "document": [1, 5, 7], "documentbuild": [], "documentfil": 2, "doesn": [], "don": [], "done": 6, "download": 1, "downsiz": [], "draw": 6, "drop": 1, "drop_last": 1, "dtype": 5, "dual": [], "dummi": [], "dummy_img": [], "dummy_input": [], "dure": [], "dutch": [], "dynam": [], "dynamic_seq_length": [], "e": [2, 4], "each": [1, 2, 3, 5, 6, 7], "eas": [], "easi": [3, 7], "easier": 5, "easili": [2, 5, 7], "econom": [], "edit": [], "educ": [], "effect": [], "effici": [1, 5], "either": 5, "element": [1, 2, 5], "els": [], "email": [], "empathi": [], "en": [], "enabl": 2, "enclos": 2, "encod": [1, 2, 5], "encode_sequ": 1, "encount": [], "encrypt": [], "end": [1, 3, 7], "english": [], "enough": 5, "ensur": [], "entir": 2, "entri": [], "environ": [], "eo": 1, "equiv": [], "error": [], "estim": [], "etc": 2, "ethnic": [], "evalu": [1, 3, 5], "event": [], "everyon": [], "everyth": [], "exact": 7, "exactmatch": [], "exampl": [1, 2, 5, 6, 7], "exchang": [], "exclud": 5, "execut": [], "exist": [], "expand": [], "expect": [2, 5, 6], "experi": 5, "explan": 5, "explicit": [], "exploit": 5, "export": [2, 3, 7], "export_as_straight_box": [], "export_as_xml": [], "export_model_to_onnx": [], "express": 6, "extens": 2, "extern": [], "extra": 4, "extract": [1, 3], "extract_arch": 1, "extractor": 5, "f_": 7, "f_a": 7, "factor": 6, "fair": [], "fairli": [], "fals": [1, 5, 6, 7], "faq": [], "fascan": [], "fast": 1, "fast_bas": [], "fast_smal": [], "fast_tini": [], "faster": [], "fasterrcnn_mobilenet_v3_large_fpn": [], "favorit": [], "featur": [5, 7], "feed": 5, "feedback": [], "feel": [], "felix92": [], "few": 4, "figsiz": 7, "figur": 7, "file": [1, 3], "file_hash": 1, "file_nam": 1, "final": [], "find": 4, "fine": 3, "finnish": [], "first": [], "firsthand": [], "fit": [], "fitz": 2, "flag": [], "flexibl": 7, "flip": [], "float": [2, 6, 7], "float32": 5, "fn": 6, "focu": [], "focus": [], "folder": [1, 5], "follow": [1, 4, 5, 6, 7], "font": [], "font_famili": [], "foral": 7, "forc": [], "forg": [], "form": [1, 3], "format": [2, 5], "forpost": [1, 3], "forum": [], "found": [], "fp": 5, "fp16": 5, "frac": 7, "frame": 5, "framework": 1, "free": [], "french": [1, 5], "friendli": 3, "from": [1, 2, 3, 5, 6, 7], "from_hub": [], "from_imag": 2, "from_pdf": 2, "from_url": 2, "full": [1, 5, 7], "fulli": [], "function": [5, 6, 7], "funsd": [1, 3, 5], "further": [], "futur": [], "g": 2, "g_": 7, "g_x": 7, "gallagh": [], "gamma": 6, "gaussian": 6, "gaussianblur": [], "gaussiannois": [], "gdk": 4, "gen": [], "gender": [], "gener": [], "generic_cyrillic_lett": [], "geometri": 2, "geq": 7, "german": [], "get": 2, "get_artefact": 2, "get_word": 2, "gettextword": 2, "git": 3, "github": 4, "give": [], "given": [1, 2, 5, 7], "global": [], "go": [], "good": [], "googl": [], "googlevis": 3, "gpu": 3, "gracefulli": [], "graph": 2, "grayscal": 6, "ground": 7, "groung": [], "group": [], "gt": [], "gt_box": [], "gt_label": [], "gtk": 4, "guid": [], "guidanc": [], "gvision": 5, "h": 2, "h_": 7, "ha": [1, 7], "half": 5, "handl": 1, "handwrit": [], "handwritten": [], "harass": [], "hardwar": [], "harm": [], "hat": 7, "have": [1, 5, 7], "head": [], "healthi": [], "hebrew": [], "height": 2, "hello": 7, "help": [], "here": [1, 4, 6], "hf": [], "hf_hub_download": [], "high": 2, "higher": 4, "hindi": [], "hindi_digit": [], "hocr": [], "hook": [], "horizont": 2, "hous": [], "how": [], "howev": [], "hsv": 6, "html": [], "http": [2, 4, 5], "hub": [], "hue": 6, "huggingfac": [], "hw": [], "i": [1, 2, 5, 6, 7], "i7": [], "ibrahimov": [], "ic03": [], "ic13": [], "icdar": 3, "icdar2019": 1, "id": 5, "ident": [], "identifi": [3, 5], "ignor": [], "ignore_acc": [], "ignore_cas": [], "iiit": [], "iiit5k": [], "iiithw": [], "imag": [1, 2, 5, 6, 7], "imagenet": [], "imageri": [], "images_90k_norm": [], "img": [1, 6], "img_cont": [], "img_fold": 1, "img_path": [], "img_transform": [], "imgur5k": [], "imgur5k_annot": [], "imlist": [], "impact": [], "implement": [1, 2, 5, 6, 7], "import": [1, 2, 5, 6, 7], "improv": [], "inappropri": [], "incid": [], "includ": [4, 5], "inclus": [], "increas": 6, "independ": [], "index": 2, "indic": 7, "individu": [], "infer": [3, 6], "inform": [1, 3, 5], "inherit": [1, 5], "input": [2, 5, 6], "input_crop": [], "input_pag": [5, 7], "input_shap": 5, "input_t": 5, "input_tensor": 5, "inspir": 6, "instal": 3, "instanc": 5, "instanti": 5, "instead": [1, 2], "insult": [], "int": [1, 2, 5, 6, 7], "int64": [], "integ": 7, "integr": 3, "intel": [], "interact": [2, 7], "interfac": [], "interoper": [], "interpol": [5, 6], "interpret": [1, 2], "intersect": 7, "invert": 6, "investig": [], "invis": [], "invoic": 5, "involv": 5, "io": [], "iou": 7, "iou_thresh": 7, "iou_threshold": [], "irregular": 5, "isn": 1, "issu": [], "italian": [], "iter": 1, "its": [1, 2, 5, 7], "itself": [], "j": 7, "jame": [], "job": [], "join": [], "jpeg": 6, "jpegqual": 6, "jpg": [1, 2], "json": [], "json_output": [], "jump": [], "just": 5, "kei": [], "kera": 5, "kernel": [], "kernel_s": 5, "kernel_shap": [], "keywoard": [], "keyword": [1, 2], "kie": [], "kie_predictor": [], "kiepredictor": [], "kind": [], "know": [], "kwarg": [1, 2, 5, 7], "l": 7, "l_j": 7, "label": [1, 7], "label_fil": 1, "label_fold": [], "label_path": [], "labels_path": [], "ladder": [], "lambda": 6, "lambdatransform": 6, "lang": [], "languag": [2, 3], "larg": [], "largest": 7, "last": [1, 4, 5], "latenc": [], "later": [], "latest": 4, "latin": 1, "layer": [], "layout": [], "lead": [], "leader": [], "learn": 5, "least": 4, "left": 7, "legacy_french": [], "length": 1, "less": [], "let": 5, "letter": [], "level": [5, 7], "levenshtein": [], "leverag": [], "lf": [], "libffi": 4, "librari": 4, "light": 3, "lightweight": [], "like": [], "limits_": 7, "line": [3, 7], "line_1_1": [], "link": [], "linknet": [3, 5], "linknet16": 5, "linknet_resnet18": [], "linknet_resnet34": [], "linknet_resnet50": [], "linux": 4, "list": [1, 2, 6], "ll": 7, "load": [3, 5], "load_state_dict": [], "load_weight": [], "loader": 1, "loc_pr": [], "local": [1, 3, 5, 7], "localis": [], "localizationconfus": 7, "locat": [], "login": [], "login_to_hub": [], "logo": 2, "love": [], "lower": [6, 7], "m": [5, 7], "m1": [], "macbook": [], "machin": [], "maco": 4, "made": 3, "magc_resnet31": [], "mai": [], "mail": [], "main": [], "maintain": 3, "mainten": [], "make": [5, 7], "mani": [], "manipul": [], "map": 1, "map_loc": [], "mask_shap": 7, "master": [3, 5], "match": [3, 7], "mathcal": 7, "matplotlib": 7, "max": 7, "max_angl": [], "max_area": [], "max_char": [], "max_delta": 6, "max_dist": [], "max_gain": 6, "max_gamma": 6, "max_qual": 6, "max_ratio": [], "maximum": 1, "maxval": [5, 6], "mbox": 7, "mean": [6, 7], "meaniou": 7, "meant": 2, "measur": 5, "media": [], "median": [], "meet": [], "member": [], "memori": [], "mention": [], "merg": [], "messag": [], "meta": [], "metadata": [], "metal": [], "method": 6, "metric": [5, 7], "middl": [], "might": 5, "min": [], "min_area": [], "min_char": [], "min_gain": 6, "min_gamma": 6, "min_qual": 6, "min_ratio": [], "min_val": 6, "minde": 4, "minim": [], "minimalist": [], "minimum": 7, "minval": 6, "miss": [], "mistak": [], "mix": 3, "mixed_float16": [], "mixed_precis": [], "mjsynth": [], "mnt": [], "mobilenet": [], "mobilenet_v3_larg": [], "mobilenet_v3_large_r": [], "mobilenet_v3_smal": [], "mobilenet_v3_small_crop_orient": [], "mobilenet_v3_small_page_orient": [], "mobilenet_v3_small_r": [], "mobilenetv3": [], "modal": [], "mode": 4, "model": [1, 7], "model_nam": [], "model_path": [], "moder": [], "modif": [], "modifi": [], "modul": [2, 5, 6, 7], "more": [], "moscardi": [], "most": 5, "mozilla": [], "multi": 3, "multilingu": [], "multipl": [1, 2, 6], "multipli": 6, "multiprocess": [], "my": [], "my_awesome_model": [], "my_hook": [], "n": [1, 5, 7], "na": [], "name": [1, 5], "nation": [], "natur": 3, "ndarrai": [1, 2, 7], "necessari": [], "need": [4, 7], "neg": 6, "nest": [], "nestedobject": [], "netraj": [], "network": [3, 5], "neural": [3, 5], "new": [], "newer": [], "next": 1, "nois": [], "noisi": [1, 3], "non": [2, 3, 6, 7], "none": [1, 2, 7], "normal": [5, 6], "norwegian": [], "note": 0, "now": 3, "np": [5, 7], "num_output_channel": [], "num_sampl": [], "number": [1, 6, 7], "numpi": [2, 5, 7], "o": 4, "obb": [], "obj_detect": [], "object": 1, "objectness_scor": [], "oblig": [], "obtain": [], "occupi": [], "ocr": [1, 3, 7], "ocr_carea": [], "ocr_db_crnn": 7, "ocr_lin": [], "ocr_pag": [], "ocr_par": [], "ocr_predictor": 5, "ocrdataset": 1, "ocrmetr": 7, "ocrpredictor": 5, "ocrx_word": [], "offens": [], "offici": [], "offlin": [], "offset": 6, "onc": 5, "one": [1, 5, 6], "oneof": 6, "ones": 1, "onli": [6, 7], "onlin": [], "onnx": [], "onnxruntim": [], "onnxtr": [], "opac": [], "opacity_rang": [], "open": [], "opinion": [], "optic": [3, 5], "optim": 3, "option": 1, "order": [1, 2, 5], "org": 5, "organ": 2, "orient": 2, "orientationpredictor": [], "other": [], "otherwis": 7, "our": 5, "out": [5, 6, 7], "outpout": [], "output": [2, 5, 6], "output_s": [2, 6], "outsid": [], "over": [4, 7], "overal": [], "overlai": 2, "overview": [], "overwrit": 1, "overwritten": [], "own": 3, "p": 6, "packag": 7, "pad": [1, 5, 6], "page": [4, 5, 7], "page1": 2, "page2": 2, "page_1": [], "page_idx": 2, "page_orientation_predictor": [], "page_param": [], "pair": 7, "pango": 4, "paper": 5, "par_1_1": [], "paragraph": [], "paragraph_break": [], "parallel": [], "param": [5, 6], "paramet": [1, 2, 3, 5, 6, 7], "pars": [1, 3], "parseq": [], "part": 6, "parti": [], "partial": [], "particip": [], "pass": [1, 5], "password": [], "patch": [], "path": [1, 2, 5], "path_to_checkpoint": [], "path_to_custom_model": [], "path_to_pt": [], "patil": [], "pattern": [], "pdf": [2, 5], "pdfpage": [], "peopl": [], "per": [5, 6], "perform": [2, 3, 5, 6, 7], "period": [], "permiss": [], "permut": [], "persian_lett": [], "person": [], "phase": [], "photo": [], "physic": 2, "pick": 6, "pictur": 2, "pip": 4, "pipelin": [], "pixbuf": 4, "pixel": [2, 6], "platinum": 5, "pleas": [], "plot": 7, "plt": 7, "plug": [], "plugin": [], "png": 2, "point": [], "polici": [], "polish": [], "polit": [], "polygon": 1, "pool": [], "portugues": [], "posit": 7, "possibl": 7, "post": 5, "postprocessor": [], "potenti": 5, "power": 3, "ppageno": [], "pre": [], "precis": [5, 7], "pred": [], "pred_box": [], "pred_label": [], "predefin": 1, "predict": [2, 7], "predictor": [], "prefer": 1, "preinstal": [], "preprocessor": 5, "prerequisit": 3, "present": [], "preserv": 6, "preserve_aspect_ratio": 6, "pretrain": [3, 5, 7], "pretrained_backbon": [], "print": [], "prior": [], "privaci": [], "privat": 5, "probabl": 6, "problem": [], "procedur": 6, "process": [2, 3], "processor": 5, "produc": 5, "product": [], "profession": [], "project": [], "promptli": [], "proper": [], "properli": 1, "properti": 5, "provid": [3, 5], "public": 3, "publicli": [], "publish": [], "pull": [], "punctuat": 1, "pure": [], "purpos": [], "push_to_hf_hub": [], "py": [], "pypdfium2": [], "pyplot": 7, "python": 3, "python3": [], "pytorch": [3, 4], "q": [], "qr": 2, "qr_code": [], "qualiti": 6, "quantiz": 5, "quantize_model": 5, "question": [], "quickli": 3, "quicktour": [], "r": [], "race": [], "ramdisk": [], "rand": [5, 7], "random": [5, 6, 7], "randomappli": 6, "randombright": 6, "randomcontrast": 6, "randomcrop": [], "randomgamma": 6, "randomhorizontalflip": [], "randomhu": 6, "randomjpegqu": 6, "randomli": 6, "randomres": [], "randomrot": [], "randomsatur": 6, "randomshadow": [], "rang": 6, "rassi": [], "ratio": 6, "raw": [2, 7], "re": [], "read": [3, 5], "read_html": 2, "read_img": 2, "read_img_as_numpi": [], "read_img_as_tensor": [], "read_pdf": 2, "readi": [], "real": [5, 6], "realli": [], "reason": [], "rebuild": [], "rebuilt": [], "recal": [5, 7], "receipt": [1, 3, 5], "reco_arch": 5, "reco_b": [], "reco_model": [], "reco_param": [], "reco_predictor": [], "recogn": [], "recognit": 7, "recognition_predictor": 5, "recognition_task": [], "recognitiondataset": [], "recognitionpredictor": 5, "rectangular": [], "recurr": 3, "reduc": 6, "refer": 4, "regardless": [], "region": [], "regroup": 7, "regular": [], "reject": [], "rel": 2, "relat": [], "releas": [0, 4], "relev": [], "religion": [], "relu": 5, "remov": [], "render": [], "repo": [], "repo_id": [], "report": [], "repositori": [], "repres": [2, 5], "represent": 5, "request": [], "requir": [4, 6], "research": 3, "residu": [], "resiz": [5, 6], "resnet": 5, "resnet18": [], "resnet31": [], "resnet34": [], "resnet50": [], "resolv": 2, "resolve_block": [], "resolve_lin": [], "resourc": [], "respect": [], "rest": [6, 7], "restrict": [], "result": [2, 5], "return": [1, 2, 5, 7], "reusabl": 5, "review": [], "rgb": [2, 6], "rgb_mode": [], "rgb_output": 2, "right": [5, 7], "roboflow": [], "robust": 3, "root": 1, "rotat": [1, 2], "rotated_bbox": [1, 7], "run": 4, "same": [2, 7], "sampl": 1, "sample_transform": 1, "sanjin": [], "sar": [3, 5], "sar_resnet31": 5, "sar_vgg16_bn": 5, "satur": 6, "save": [1, 5], "saved_model": 5, "scale": 7, "scale_rang": [], "scan": [1, 3], "scene": [3, 5], "scheme": 5, "score": 7, "scratch": 3, "script": [], "seamless": 3, "seamlessli": [], "search": [], "searchabl": [], "sec": [], "second": 5, "section": [], "secur": [], "see": [], "seemlessli": 3, "seen": 5, "segment": 5, "self": [], "semant": 5, "send": [], "sens": 7, "sensit": [], "separ": 5, "sequenc": [1, 2, 5, 7], "sequenti": [5, 6], "seri": [], "serial": 5, "serialized_model": 5, "seriou": [], "set": [1, 5, 7], "set_global_polici": [], "sever": [2, 6], "sex": [], "sexual": [], "sha256": [], "shade": [], "shape": [2, 5, 6, 7], "share": [], "shift": 6, "shm": [], "should": [1, 2, 7], "show": [2, 3, 5, 7], "showcas": [], "shuffl": 1, "side": 7, "signatur": 2, "signific": 1, "simpl": 5, "simpler": [], "sinc": 1, "singl": [], "single_img_doc": [], "size": [1, 2, 5, 6], "skew": [], "slack": [], "slightli": [], "small": 3, "smallest": 2, "snapshot_download": [], "snippet": [], "so": [1, 4], "social": [], "socio": [], "some": [], "someth": [], "somewher": [], "sort": [], "sourc": [1, 2, 5, 6, 7], "space": [], "span": [], "spanish": [], "spatial": 2, "special": 3, "specif": [1, 5, 7], "specifi": 2, "speed": [3, 5], "sphinx": [], "sroie": [1, 3], "stabl": 4, "stackoverflow": [], "stage": 3, "standalon": [], "standard": 6, "start": 1, "state": 3, "static": 7, "statist": 5, "statu": [], "std": 6, "step": [], "still": [], "str": [1, 2, 5, 6, 7], "straight": 1, "straighten": [], "straighten_pag": [], "straigten_pag": [], "stream": 2, "street": [], "strict": [], "strictli": 7, "string": [1, 2, 5, 7], "strive": [], "strong": 5, "structur": [3, 5], "subset": [1, 5], "suggest": [], "sum": 7, "summari": 7, "support": 5, "sustain": [], "svhn": [], "svt": [], "swedish": [], "symbol": [], "symmetr": 6, "symmetric_pad": 6, "synthet": [], "synthtext": [], "system": [], "t": 1, "tabl": [], "take": [], "target": [1, 2, 5, 6], "target_s": 1, "task": [1, 3, 5], "task2": [], "team": [], "techminde": [], "templat": 2, "tensor": [1, 5, 6], "tensorflow": [3, 4, 5, 6], "tensorspec": [], "term": [], "test": [], "test_set": [], "text": [2, 7], "text_output": [], "textmatch": 7, "textnet": [], "textnet_bas": [], "textnet_smal": [], "textnet_tini": [], "textract": [3, 5], "textstylebrush": [], "textual": [1, 2, 3], "tf": [5, 6], "tf_model": 5, "tflite": 5, "than": [4, 7], "thank": [], "thei": [], "them": [1, 4], "thi": [4, 5, 7], "thing": [], "third": [], "those": [2, 4, 5], "threaten": [], "threshold": [], "through": [1, 6], "tilman": [], "time": [1, 5, 7], "tini": [], "titl": 2, "tm": [], "tmp": [], "togeth": [2, 5], "tograi": 6, "tool": [], "top": 7, "topic": [], "torch": [], "torchvis": 6, "total": [], "toward": [], "train": [1, 5, 6], "train_it": 1, "train_load": 1, "train_pytorch": [], "train_set": 1, "train_tensorflow": [], "trainabl": 5, "tranform": 6, "transcrib": [], "transfer": [], "transfo": 6, "transform": [1, 3], "translat": [], "troll": [], "true": [1, 2, 5, 6, 7], "truth": 7, "tune": 3, "tupl": [2, 5, 6, 7], "turn": [], "two": 2, "txt": [], "type": [2, 5], "typic": [], "u": [], "ucsd": [], "udac": [], "uint8": [2, 5, 7], "ukrainian": [], "unaccept": [], "underli": 1, "underneath": 2, "understand": [1, 3], "unidecod": 7, "uniform": [5, 6], "uniformli": [], "uninterrupt": 2, "union": 7, "unit": [], "unittest": [], "unlock": [], "unoffici": [], "unprofession": [], "unsolicit": [], "unsupervis": [], "unwelcom": [], "up": 5, "updat": 7, "upgrad": [], "upper": 6, "uppercas": [], "url": [1, 2], "us": [1, 4, 7], "usabl": 5, "usag": 5, "use_polygon": [], "useabl": [], "user": [2, 3, 4], "utf": [], "util": [3, 5], "v0": 3, "v1": [], "v3": [], "valid": [], "valu": [2, 6], "valuabl": 3, "variabl": [], "varieti": [], "veri": [], "verifi": 1, "verma": [], "version": 5, "vgg": 5, "vgg16": 5, "vgg16_bn_r": [], "via": 3, "video": [], "vietnames": [], "view": [], "viewpoint": [], "violat": [], "visibl": [], "vision": [], "visiondataset": 1, "visiontransform": [], "visual": 3, "visualize_pag": 7, "vit_": [], "vit_b": [], "vitstr": [], "vitstr_bas": [], "vitstr_smal": [], "viz": [], "vocab": [3, 5], "vocabulari": [], "w": [2, 7], "w3": [], "wa": [], "wai": [1, 3, 5], "want": [], "warm": 5, "warmup": [], "wasn": [], "we": [2, 3, 5, 6], "weasyprint": [], "web": 2, "websit": [], "welcom": 3, "well": [], "were": 2, "what": [], "when": [], "whenev": [], "where": [2, 7], "whether": [1, 2, 7], "which": 5, "whichev": 4, "while": 6, "why": [], "width": 2, "wiki": [], "wildreceipt": [], "window": [4, 7], "wish": [], "within": [], "without": 5, "wonder": [], "word": [3, 5, 7], "word_1_1": [], "word_1_2": [], "word_1_3": [], "wordgener": [], "words_onli": 7, "work": [], "worker": 1, "workflow": [], "worklow": [], "world": 7, "worth": [], "wrap": [], "wrapper": [1, 6], "write": [], "written": 2, "www": 2, "x": [2, 6, 7], "x12larg": 5, "x_ascend": [], "x_descend": [], "x_i": 7, "x_size": [], "x_wconf": [], "xeon": 5, "xhtml": [], "xmax": 2, "xmin": 2, "xml": [], "xml_bytes_str": [], "xml_element": [], "xml_output": [], "xmln": [], "y": 7, "y_i": 7, "y_j": 7, "yet": [], "ymax": 2, "ymin": 2, "yolov8": [], "you": [4, 5], "your": [1, 2, 5, 7], "yoursit": 2, "yugesh": [], "zero": [5, 6], "zoo": [], "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 1, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": [], "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": [], "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": [], "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": [], "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": [], "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": [], "\u00e4\u00f6\u00e4\u00f6": [], "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": [], "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": [], "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": [], "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": [], "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": [], "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": [], "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": [], "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": [], "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": [], "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": [], "\u067e\u0686\u06a2\u06a4\u06af": [], "\u0905": [], "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": [], "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": [], "\u0950": [], "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": [], "\u09bd": [], "\u09ce": [], "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": []}, "titles": ["Changelog", "doctr.datasets", "doctr.documents", "DocTR: Document Text Recognition", "Installation", "doctr.models", "doctr.transforms", "doctr.utils"], "titleterms": {"": [], "0": 0, "01": [], "02": [], "03": 0, "04": [], "05": 0, "07": [], "08": [], "09": [], "1": 0, "10": [], "11": 0, "12": [], "18": 0, "2": 0, "2021": 0, "2022": [], "2023": [], "2024": [], "21": [], "22": [], "27": [], "28": 0, "29": [], "3": [], "31": [], "4": [], "5": [], "6": [], "7": [], "8": [], "9": [], "advanc": [], "approach": 5, "architectur": [], "arg": [], "artefact": 2, "artefactdetect": [], "attribut": [], "avail": 1, "aw": [], "ban": [], "block": 2, "bug": [], "build": 3, "changelog": 0, "choos": [], "classif": [], "code": [], "codebas": [], "commit": [], "commun": [], "compos": 6, "compress": 5, "conda": [], "conduct": [], "connect": [], "content": [], "continu": [], "contrib": [], "contribut": [], "contributor": [], "convent": [], "correct": [], "coven": [], "custom": [], "data": 1, "dataload": [], "dataset": [1, 3], "detect": [3, 5], "develop": [], "do": [], "doctr": [1, 2, 3, 5, 6, 7], "document": [2, 3], "end": 5, "enforc": [], "evalu": 7, "export": 5, "factori": [], "featur": 3, "feedback": [], "file": 2, "from": [], "gener": [], "get": 3, "git": 4, "guidelin": [], "half": [], "hub": [], "huggingfac": [], "i": [], "implement": [], "infer": [], "instal": 4, "integr": [], "io": [], "lambda": [], "let": [], "line": 2, "linux": [], "load": 1, "loader": [], "main": 3, "mode": [], "model": [3, 5], "modifi": [], "modul": [], "name": [], "note": 3, "notebook": [], "object": [], "ocr": 5, "onli": [], "onnx": [], "optim": [], "option": [], "orient": [], "our": [], "output": [], "own": [], "packag": [3, 4], "page": 2, "perman": [], "pipelin": [], "pledg": [], "post": [], "pre": 5, "precis": [], "predictor": [3, 5], "prepar": [], "prerequisit": 4, "pretrain": [], "process": 5, "push": [], "python": 4, "qualiti": [], "question": [], "read": 2, "readi": [], "recognit": [3, 5], "refer": 3, "report": [], "request": [], "resourc": [], "respons": [], "return": [], "right": [], "savedmodel": 5, "scope": [], "share": [], "should": [], "stage": 5, "standard": [], "start": 3, "structur": 2, "style": [], "support": [1, 3, 6], "synthet": [], "task": 7, "temporari": [], "test": [], "text": [3, 5], "train": 3, "transform": 6, "two": 5, "unit": [], "us": 5, "util": 7, "v0": 0, "verif": [], "via": 4, "visual": 7, "vocab": 1, "warn": [], "what": [], "word": 2, "your": 3, "zoo": [3, 5]}}) \ No newline at end of file +Search.setIndex({"alltitles": {"1. Correction": [[2, "correction"]], "2. Warning": [[2, "warning"]], "3. Temporary Ban": [[2, "temporary-ban"]], "4. Permanent Ban": [[2, "permanent-ban"]], "AWS Lambda": [[14, null]], "Advanced options": [[19, "advanced-options"]], "Args:": [[7, "args"], [7, "id4"], [7, "id7"], [7, "id10"], [7, "id13"], [7, "id16"], [7, "id19"], [7, "id22"], [7, "id25"], [7, "id29"], [7, "id32"], [7, "id37"], [7, "id40"], [7, "id46"], [7, "id49"], [7, "id50"], [7, "id51"], [7, "id54"], [7, "id57"], [7, "id60"], [7, "id61"], [8, "args"], [8, "id2"], [8, "id3"], [8, "id4"], [8, "id5"], [8, "id6"], [8, "id7"], [8, "id10"], [8, "id12"], [8, "id14"], [8, "id16"], [8, "id20"], [8, "id24"], [8, "id28"], [9, "args"], [9, "id3"], [9, "id8"], [9, "id13"], [9, "id17"], [9, "id21"], [9, "id26"], [9, "id31"], [9, "id36"], [9, "id41"], [9, "id46"], [9, "id50"], [9, "id54"], [9, "id59"], [9, "id63"], [9, "id68"], [9, "id73"], [9, "id77"], [9, "id81"], [9, "id85"], [9, "id90"], [9, "id95"], [9, "id99"], [9, "id104"], [9, "id109"], [9, "id114"], [9, "id119"], [9, "id123"], [9, "id127"], [9, "id132"], [9, "id137"], [9, "id142"], [9, "id146"], [9, "id150"], [9, "id155"], [9, "id159"], [9, "id163"], [9, "id167"], [9, "id169"], [9, "id171"], [9, "id173"], [10, "args"], [10, "id1"], [10, "id2"], [10, "id3"], [10, "id4"], [10, "id5"], [10, "id6"], [10, "id7"], [10, "id8"], [10, "id9"], [10, "id10"], [10, "id11"], [10, "id12"], [10, "id13"], [10, "id14"], [10, "id15"], [10, "id16"], [10, "id17"], [10, "id18"], [10, "id19"], [11, "args"], [11, "id3"], [11, "id4"], [11, "id5"], [11, "id6"], [11, "id7"], [11, "id8"], [11, "id9"]], "Artefact": [[8, "artefact"]], "ArtefactDetection": [[16, "artefactdetection"]], "Attribution": [[2, "attribution"]], "Available Datasets": [[17, "available-datasets"]], "Available architectures": [[19, "available-architectures"], [19, "id1"], [19, "id2"]], "Available contribution modules": [[16, "available-contribution-modules"]], "Block": [[8, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[17, null]], "Choosing the right model": [[19, null]], "Classification": [[15, "classification"]], "Code quality": [[3, "code-quality"]], "Code style verification": [[3, "code-style-verification"]], "Codebase structure": [[3, "codebase-structure"]], "Commits": [[3, "commits"]], "Community resources": [[1, null]], "Composing transformations": [[10, "composing-transformations"]], "Continuous Integration": [[3, "continuous-integration"]], "Contributing to docTR": [[3, null]], "Contributor Covenant Code of Conduct": [[2, null]], "Custom dataset loader": [[7, "custom-dataset-loader"]], "Custom orientation classification models": [[13, "custom-orientation-classification-models"]], "Data Loading": [[17, "data-loading"]], "Dataloader": [[7, "dataloader"]], "Detection": [[15, "detection"], [17, "detection"]], "Detection predictors": [[19, "detection-predictors"]], "Developer mode installation": [[3, "developer-mode-installation"]], "Developing docTR": [[3, "developing-doctr"]], "Document": [[8, "document"]], "Document structure": [[8, "document-structure"]], "End-to-End OCR": [[19, "end-to-end-ocr"]], "Enforcement": [[2, "enforcement"]], "Enforcement Guidelines": [[2, "enforcement-guidelines"]], "Enforcement Responsibilities": [[2, "enforcement-responsibilities"]], "Export to ONNX": [[18, "export-to-onnx"]], "Feature requests & bug report": [[3, "feature-requests-bug-report"]], "Feedback": [[3, "feedback"]], "File reading": [[8, "file-reading"]], "Half-precision": [[18, "half-precision"]], "Installation": [[4, null]], "Integrate contributions into your pipeline": [[16, null]], "Let\u2019s connect": [[3, "let-s-connect"]], "Line": [[8, "line"]], "Loading from Huggingface Hub": [[15, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[13, "loading-your-custom-trained-model"]], "Loading your custom trained orientation classification model": [[13, "loading-your-custom-trained-orientation-classification-model"]], "Main Features": [[5, "main-features"]], "Model optimization": [[18, "model-optimization"]], "Model zoo": [[5, "model-zoo"]], "Modifying the documentation": [[3, "modifying-the-documentation"]], "Naming conventions": [[15, "naming-conventions"]], "OCR": [[17, "ocr"]], "Object Detection": [[17, "object-detection"]], "Our Pledge": [[2, "our-pledge"]], "Our Standards": [[2, "our-standards"]], "Page": [[8, "page"]], "Preparing your model for inference": [[18, null]], "Prerequisites": [[4, "prerequisites"]], "Pretrained community models": [[15, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[15, "pushing-to-the-huggingface-hub"]], "Questions": [[3, "questions"]], "Recognition": [[15, "recognition"], [17, "recognition"]], "Recognition predictors": [[19, "recognition-predictors"]], "Returns:": [[7, "returns"], [8, "returns"], [8, "id11"], [8, "id13"], [8, "id15"], [8, "id19"], [8, "id23"], [8, "id27"], [8, "id31"], [9, "returns"], [9, "id6"], [9, "id11"], [9, "id16"], [9, "id20"], [9, "id24"], [9, "id29"], [9, "id34"], [9, "id39"], [9, "id44"], [9, "id49"], [9, "id53"], [9, "id57"], [9, "id62"], [9, "id66"], [9, "id71"], [9, "id76"], [9, "id80"], [9, "id84"], [9, "id88"], [9, "id93"], [9, "id98"], [9, "id102"], [9, "id107"], [9, "id112"], [9, "id117"], [9, "id122"], [9, "id126"], [9, "id130"], [9, "id135"], [9, "id140"], [9, "id145"], [9, "id149"], [9, "id153"], [9, "id158"], [9, "id162"], [9, "id166"], [9, "id168"], [9, "id170"], [9, "id172"], [11, "returns"]], "Scope": [[2, "scope"]], "Share your model with the community": [[15, null]], "Supported Vocabs": [[7, "supported-vocabs"]], "Supported contribution modules": [[6, "supported-contribution-modules"]], "Supported datasets": [[5, "supported-datasets"]], "Supported transformations": [[10, "supported-transformations"]], "Synthetic dataset generator": [[7, "synthetic-dataset-generator"], [17, "synthetic-dataset-generator"]], "Task evaluation": [[11, "task-evaluation"]], "Text Detection": [[19, "text-detection"]], "Text Recognition": [[19, "text-recognition"]], "Text detection models": [[5, "text-detection-models"]], "Text recognition models": [[5, "text-recognition-models"]], "Train your own model": [[13, null]], "Two-stage approaches": [[19, "two-stage-approaches"]], "Unit tests": [[3, "unit-tests"]], "Use your own datasets": [[17, "use-your-own-datasets"]], "Using your ONNX exported model": [[18, "using-your-onnx-exported-model"]], "Via Conda (Only for Linux)": [[4, "via-conda-only-for-linux"]], "Via Git": [[4, "via-git"]], "Via Python Package": [[4, "via-python-package"]], "Visualization": [[11, "visualization"]], "What should I do with the output?": [[19, "what-should-i-do-with-the-output"]], "Word": [[8, "word"]], "docTR Notebooks": [[12, null]], "docTR Vocabs": [[7, "id62"]], "docTR: Document Text Recognition": [[5, null]], "doctr.contrib": [[6, null]], "doctr.datasets": [[7, null], [7, "datasets"]], "doctr.io": [[8, null]], "doctr.models": [[9, null]], "doctr.models.classification": [[9, "doctr-models-classification"]], "doctr.models.detection": [[9, "doctr-models-detection"]], "doctr.models.factory": [[9, "doctr-models-factory"]], "doctr.models.recognition": [[9, "doctr-models-recognition"]], "doctr.models.zoo": [[9, "doctr-models-zoo"]], "doctr.transforms": [[10, null]], "doctr.utils": [[11, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.10.0 (2024-10-21)": [[0, "v0-10-0-2024-10-21"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]], "v0.7.0 (2023-09-09)": [[0, "v0-7-0-2023-09-09"]], "v0.8.0 (2024-02-28)": [[0, "v0-8-0-2024-02-28"]], "v0.8.1 (2024-03-04)": [[0, "v0-8-1-2024-03-04"]], "v0.9.0 (2024-08-08)": [[0, "v0-9-0-2024-08-08"]]}, "docnames": ["changelog", "community/resources", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/contrib", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_contrib_modules", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "community/resources.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/contrib.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_contrib_modules.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[8, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[8, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[10, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[7, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[10, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[10, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[7, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[7, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[9, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[7, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[7, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[8, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[8, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[7, "doctr.datasets.encode_sequences", false]], "fast_base() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_base", false]], "fast_small() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_small", false]], "fast_tiny() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_tiny", false]], "from_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[7, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[10, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[10, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[7, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[7, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[7, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[7, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[7, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[9, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[10, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[8, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet34() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[7, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_crop_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_crop_orientation", false]], "mobilenet_v3_small_page_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_page_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[10, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[9, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[7, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[10, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[8, "doctr.io.Page", false]], "page_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.page_orientation_predictor", false]], "parseq() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[10, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[10, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[10, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[10, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[10, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[10, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[10, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[10, "doctr.transforms.RandomJpegQuality", false]], "randomresize (class in doctr.transforms)": [[10, "doctr.transforms.RandomResize", false]], "randomrotate (class in doctr.transforms)": [[10, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[10, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[10, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[8, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[8, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[8, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[7, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[10, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[8, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[8, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[7, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[7, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[7, "doctr.datasets.SVT", false]], "synthtext (class in doctr.datasets)": [[7, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.TextMatch", false]], "textnet_base() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_base", false]], "textnet_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_small", false]], "textnet_tiny() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_tiny", false]], "togray (class in doctr.transforms)": [[10, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[11, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_small", false]], "wildreceipt (class in doctr.datasets)": [[7, "doctr.datasets.WILDRECEIPT", false]], "word (class in doctr.io)": [[8, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[7, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[7, 0, 1, "", "CORD"], [7, 0, 1, "", "CharacterGenerator"], [7, 0, 1, "", "DetectionDataset"], [7, 0, 1, "", "DocArtefacts"], [7, 0, 1, "", "FUNSD"], [7, 0, 1, "", "IC03"], [7, 0, 1, "", "IC13"], [7, 0, 1, "", "IIIT5K"], [7, 0, 1, "", "IIITHWS"], [7, 0, 1, "", "IMGUR5K"], [7, 0, 1, "", "MJSynth"], [7, 0, 1, "", "OCRDataset"], [7, 0, 1, "", "RecognitionDataset"], [7, 0, 1, "", "SROIE"], [7, 0, 1, "", "SVHN"], [7, 0, 1, "", "SVT"], [7, 0, 1, "", "SynthText"], [7, 0, 1, "", "WILDRECEIPT"], [7, 0, 1, "", "WordGenerator"], [7, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[7, 0, 1, "", "DataLoader"]], "doctr.io": [[8, 0, 1, "", "Artefact"], [8, 0, 1, "", "Block"], [8, 0, 1, "", "Document"], [8, 0, 1, "", "DocumentFile"], [8, 0, 1, "", "Line"], [8, 0, 1, "", "Page"], [8, 0, 1, "", "Word"], [8, 1, 1, "", "decode_img_as_tensor"], [8, 1, 1, "", "read_html"], [8, 1, 1, "", "read_img_as_numpy"], [8, 1, 1, "", "read_img_as_tensor"], [8, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[8, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[8, 2, 1, "", "from_images"], [8, 2, 1, "", "from_pdf"], [8, 2, 1, "", "from_url"]], "doctr.io.Page": [[8, 2, 1, "", "show"]], "doctr.models": [[9, 1, 1, "", "kie_predictor"], [9, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[9, 1, 1, "", "crop_orientation_predictor"], [9, 1, 1, "", "magc_resnet31"], [9, 1, 1, "", "mobilenet_v3_large"], [9, 1, 1, "", "mobilenet_v3_large_r"], [9, 1, 1, "", "mobilenet_v3_small"], [9, 1, 1, "", "mobilenet_v3_small_crop_orientation"], [9, 1, 1, "", "mobilenet_v3_small_page_orientation"], [9, 1, 1, "", "mobilenet_v3_small_r"], [9, 1, 1, "", "page_orientation_predictor"], [9, 1, 1, "", "resnet18"], [9, 1, 1, "", "resnet31"], [9, 1, 1, "", "resnet34"], [9, 1, 1, "", "resnet50"], [9, 1, 1, "", "textnet_base"], [9, 1, 1, "", "textnet_small"], [9, 1, 1, "", "textnet_tiny"], [9, 1, 1, "", "vgg16_bn_r"], [9, 1, 1, "", "vit_b"], [9, 1, 1, "", "vit_s"]], "doctr.models.detection": [[9, 1, 1, "", "db_mobilenet_v3_large"], [9, 1, 1, "", "db_resnet50"], [9, 1, 1, "", "detection_predictor"], [9, 1, 1, "", "fast_base"], [9, 1, 1, "", "fast_small"], [9, 1, 1, "", "fast_tiny"], [9, 1, 1, "", "linknet_resnet18"], [9, 1, 1, "", "linknet_resnet34"], [9, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[9, 1, 1, "", "from_hub"], [9, 1, 1, "", "login_to_hub"], [9, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[9, 1, 1, "", "crnn_mobilenet_v3_large"], [9, 1, 1, "", "crnn_mobilenet_v3_small"], [9, 1, 1, "", "crnn_vgg16_bn"], [9, 1, 1, "", "master"], [9, 1, 1, "", "parseq"], [9, 1, 1, "", "recognition_predictor"], [9, 1, 1, "", "sar_resnet31"], [9, 1, 1, "", "vitstr_base"], [9, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[10, 0, 1, "", "ChannelShuffle"], [10, 0, 1, "", "ColorInversion"], [10, 0, 1, "", "Compose"], [10, 0, 1, "", "GaussianBlur"], [10, 0, 1, "", "GaussianNoise"], [10, 0, 1, "", "LambdaTransformation"], [10, 0, 1, "", "Normalize"], [10, 0, 1, "", "OneOf"], [10, 0, 1, "", "RandomApply"], [10, 0, 1, "", "RandomBrightness"], [10, 0, 1, "", "RandomContrast"], [10, 0, 1, "", "RandomCrop"], [10, 0, 1, "", "RandomGamma"], [10, 0, 1, "", "RandomHorizontalFlip"], [10, 0, 1, "", "RandomHue"], [10, 0, 1, "", "RandomJpegQuality"], [10, 0, 1, "", "RandomResize"], [10, 0, 1, "", "RandomRotate"], [10, 0, 1, "", "RandomSaturation"], [10, 0, 1, "", "RandomShadow"], [10, 0, 1, "", "Resize"], [10, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[11, 0, 1, "", "DetectionMetric"], [11, 0, 1, "", "LocalizationConfusion"], [11, 0, 1, "", "OCRMetric"], [11, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.visualization": [[11, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [2, 8, 9, 11, 15, 18], "0": [2, 4, 7, 10, 11, 13, 16, 17, 19], "00": 19, "01": 19, "0123456789": 7, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "02562": 9, "03": 19, "035": 19, "0361328125": 19, "04": 19, "05": 19, "06": 19, "06640625": 19, "07": 19, "08": [10, 19], "09": 19, "0966796875": 19, "1": [7, 8, 9, 10, 11, 13, 17, 19], "10": [4, 7, 11, 19], "100": [7, 10, 11, 17, 19], "1000": 19, "101": 7, "1024": [9, 13, 19], "104": 7, "106": 7, "108": 7, "1095": 17, "11": 19, "110": 11, "1107": 17, "114": 7, "115": 7, "1156": 17, "116": 7, "118": 7, "11800h": 19, "11th": 19, "12": 19, "120": 7, "123": 7, "126": 7, "1268": 17, "128": [9, 13, 18, 19], "13": 19, "130": 7, "13068": 17, "131": 7, "1337891": 17, "1357421875": 19, "1396484375": 19, "14": 19, "1420": 19, "14470v1": 7, "149": 17, "15": 19, "150": [11, 19], "1552": 19, "16": [9, 18, 19], "1630859375": 19, "1684": 19, "16x16": 9, "17": 19, "1778": 19, "1782": 19, "18": [9, 19], "185546875": 19, "1900": 19, "1910": 9, "19342": 17, "19370": 17, "195": 7, "19598": 17, "199": 19, "1999": 19, "2": [4, 5, 7, 8, 9, 10, 16, 19], "20": 19, "200": 11, "2000": 17, "2003": [5, 7], "2012": 7, "2013": [5, 7], "2015": 7, "2019": 5, "2023": 1, "207901": 17, "21": 19, "2103": 7, "2186": 17, "21888": 17, "22": 19, "224": [9, 10], "225": 10, "22672": 17, "229": [10, 17], "23": 19, "233": 17, "236": 7, "24": 19, "246": 17, "249": 17, "25": 19, "2504": 19, "255": [8, 9, 10, 11, 19], "256": 9, "257": 17, "26": 19, "26032": 17, "264": 13, "27": 19, "2700": 17, "2710": 19, "2749": 13, "28": 19, "287": 13, "29": 19, "296": 13, "299": 13, "2d": 19, "3": [4, 5, 8, 9, 10, 11, 18, 19], "30": 19, "300": 17, "3000": 17, "301": 13, "30595": 19, "30ghz": 19, "31": 9, "32": [7, 9, 10, 13, 17, 18, 19], "3232421875": 19, "33": [10, 19], "33402": 17, "33608": 17, "34": [9, 19], "340": 19, "3456": 19, "3515625": 19, "36": 19, "360": 17, "37": [7, 19], "38": 19, "39": 19, "4": [9, 10, 11, 19], "40": 19, "406": 10, "41": 19, "42": 19, "43": 19, "44": 19, "45": 19, "456": 10, "46": 19, "47": 19, "472": 17, "48": [7, 19], "485": 10, "49": 19, "49377": 17, "5": [7, 10, 11, 16, 19], "50": [9, 17, 19], "51": 19, "51171875": 19, "512": 9, "52": [7, 19], "529": 19, "53": 19, "54": 19, "540": 19, "5478515625": 19, "55": 19, "56": 19, "57": 19, "58": [7, 19], "580": 19, "5810546875": 19, "583": 19, "59": 19, "597": 19, "5k": [5, 7], "5m": 19, "6": [10, 19], "60": 10, "600": [9, 11, 19], "61": 19, "62": 19, "626": 17, "63": 19, "64": [9, 10, 19], "641": 19, "647": 17, "65": 19, "66": 19, "67": 19, "68": 19, "69": 19, "693": 13, "694": 13, "695": 13, "6m": 19, "7": 19, "70": [7, 11, 19], "707470": 17, "71": [7, 19], "7100000": 17, "7141797": 17, "7149": 17, "72": 19, "72dpi": 8, "73": 19, "73257": 17, "74": 19, "75": [10, 19], "7581382": 17, "76": 19, "77": 19, "772": 13, "772875": 17, "78": 19, "785": 13, "79": 19, "793533": 17, "796": 17, "798": 13, "7m": 19, "8": [9, 10, 19], "80": 19, "800": [9, 11, 17, 19], "81": 19, "82": 19, "83": 19, "84": 19, "849": 17, "85": 19, "8564453125": 19, "857": 19, "85875": 17, "86": 19, "8603515625": 19, "87": 19, "8707": 17, "88": 19, "89": 19, "9": [10, 19], "90": 19, "90k": 7, "90kdict32px": 7, "91": 19, "914085328578949": 19, "92": 19, "93": 19, "94": [7, 19], "95": [11, 19], "9578408598899841": 19, "96": 19, "97": 19, "98": 19, "99": 19, "9949972033500671": 19, "A": [2, 3, 5, 7, 8, 9, 12, 18], "As": 3, "Be": 19, "Being": 2, "By": 14, "For": [2, 3, 4, 13, 19], "If": [3, 8, 9, 13, 19], "In": [3, 7, 17], "It": [10, 15, 16, 18], "Its": [5, 9], "No": [2, 19], "Of": 7, "Or": [16, 18], "The": [2, 3, 7, 8, 11, 14, 16, 17, 18, 19], "Then": 9, "To": [3, 4, 14, 15, 16, 18, 19], "_": [2, 7, 9], "__call__": 19, "_build": 3, "_i": 11, "ab": 7, "abc": 18, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "abdef": [7, 17], "abl": [17, 19], "about": [2, 17, 19], "abov": 19, "abstract": 1, "abstractdataset": 7, "abus": 2, "accept": 2, "access": [5, 8, 17, 19], "account": [2, 15], "accur": 19, "accuraci": 11, "achiev": 18, "act": 2, "action": 2, "activ": 5, "ad": [3, 9, 10], "adapt": 2, "add": [10, 11, 15, 19], "add_hook": 19, "add_label": 11, "addit": [3, 4, 8, 16, 19], "addition": [3, 19], "address": [2, 8], "adjust": 10, "advanc": 2, "advantag": 18, "advis": 3, "aesthet": [5, 7], "affect": 2, "after": [15, 19], "ag": 2, "again": 9, "aggreg": [11, 17], "aggress": 2, "align": [2, 8, 10], "all": [2, 3, 6, 7, 8, 10, 11, 16, 17, 19], "allow": [2, 18], "along": 19, "alreadi": [3, 18], "also": [2, 9, 15, 16, 17, 19], "alwai": 17, "an": [2, 3, 5, 7, 8, 9, 11, 16, 18, 19], "analysi": [8, 16], "ancient_greek": 7, "andrej": 1, "angl": [8, 10], "ani": [2, 7, 8, 9, 10, 11, 18, 19], "annot": 7, "anot": 17, "anoth": [9, 13, 17], "answer": 2, "anyascii": 11, "anyon": 5, "anyth": 16, "api": [3, 5], "apolog": 2, "apologi": 2, "app": 3, "appear": 2, "appli": [2, 7, 10], "applic": [5, 9], "appoint": 2, "appreci": 15, "appropri": [2, 3, 19], "ar": [2, 3, 4, 6, 7, 8, 10, 11, 12, 16, 17, 19], "arab": 7, "arabic_diacrit": 7, "arabic_lett": 7, "arabic_punctu": 7, "arbitrarili": [5, 9], "arch": [9, 15], "architectur": [5, 9, 15, 16], "area": 19, "argument": [7, 8, 9, 11, 13, 19], "around": 2, "arrai": [8, 10, 11], "art": [5, 16], "artefact": [11, 16, 19], "artefact_typ": 8, "articl": 1, "artifici": [5, 7], "arxiv": [7, 9], "asarrai": 11, "ascii_lett": 7, "aspect": [5, 9, 10, 19], "assess": 11, "assign": 11, "associ": 8, "assum": 9, "assume_straight_pag": [9, 13, 19], "astyp": [9, 11, 19], "attack": 2, "attend": [5, 9], "attent": [2, 9], "autom": 5, "automat": 19, "autoregress": [5, 9], "avail": [2, 5, 6, 10], "averag": [10, 19], "avoid": [2, 4], "aw": [5, 19], "awar": 19, "azur": 19, "b": [9, 11, 19], "b_j": 11, "back": 3, "backbon": 9, "backend": 19, "background": 17, "bangla": 7, "bar": 16, "bar_cod": 17, "baranovskij": 1, "base": [5, 9, 16], "baselin": [5, 9, 19], "batch": [7, 9, 10, 16, 17, 19], "batch_siz": [7, 9, 13, 16, 17, 18], "bblanchon": 4, "bbox": 19, "becaus": 14, "been": [3, 11, 17, 19], "befor": [7, 9, 10, 19], "begin": 11, "behavior": [2, 19], "being": [11, 19], "belong": 19, "benchmark": 19, "best": [1, 2], "better": [12, 19], "between": [10, 11, 19], "bgr": 8, "bilinear": 10, "bin_thresh": 19, "binar": [5, 9, 19], "binari": [8, 18, 19], "bit": 18, "block": [11, 19], "block_1_1": 19, "blur": 10, "bmvc": 7, "bn": 15, "bodi": [2, 19], "bool": [7, 8, 9, 10, 11], "boolean": [9, 19], "both": [5, 7, 10, 17, 19], "bottom": [9, 19], "bound": [7, 8, 9, 10, 11, 16, 17, 19], "box": [7, 8, 9, 10, 11, 16, 17, 19], "box_thresh": 19, "bright": 10, "browser": [3, 5], "build": [3, 4, 18], "built": 3, "byte": [8, 19], "c": [4, 8, 11], "c_j": 11, "cach": [3, 7, 14], "cache_sampl": 7, "call": 18, "callabl": [7, 10], "can": [3, 4, 13, 14, 15, 16, 17, 19], "capabl": [3, 12, 19], "case": [7, 11], "cf": 19, "cfg": 19, "challeng": 7, "challenge2_test_task12_imag": 7, "challenge2_test_task1_gt": 7, "challenge2_training_task12_imag": 7, "challenge2_training_task1_gt": 7, "chang": [14, 19], "channel": [2, 3, 8, 10], "channel_prior": 4, "channelshuffl": 10, "charact": [5, 7, 8, 11, 17, 19], "charactergener": [7, 17], "characterist": 2, "charg": 19, "charset": 19, "chart": 8, "check": [3, 15, 19], "checkpoint": 9, "chip": 4, "christian": 1, "ci": 3, "clarifi": 2, "clariti": 2, "class": [2, 7, 8, 10, 11, 19], "class_nam": 13, "classif": [17, 19], "classmethod": 8, "clear": 3, "clone": 4, "close": 3, "co": 15, "code": [5, 8, 16], "codecov": 3, "colab": 12, "collate_fn": 7, "collect": [8, 16], "color": 10, "colorinvers": 10, "column": 8, "com": [2, 4, 8, 9, 15], "combin": 19, "command": [3, 16], "comment": 2, "commit": 2, "common": [2, 10, 11, 18], "commun": 2, "compar": 5, "comparison": [11, 19], "competit": 7, "compil": [12, 19], "complaint": 2, "complementari": 11, "complet": 3, "compon": 19, "compos": [7, 19], "comprehens": 19, "comput": [7, 11, 18, 19], "conf_threshold": 16, "confid": [8, 19], "config": [4, 9], "configur": 9, "confus": 11, "consecut": [10, 19], "consequ": 2, "consid": [2, 3, 7, 8, 11, 19], "consist": 19, "consolid": [5, 7], "constant": 10, "construct": 2, "contact": 2, "contain": [1, 6, 7, 12, 17, 19], "content": [7, 8, 19], "context": 9, "contib": 4, "continu": 2, "contrast": 10, "contrast_factor": 10, "contrib": [4, 16], "contribut": 2, "contributor": 3, "convers": 8, "convert": [8, 10], "convolut": 9, "cool": 1, "coordin": [8, 19], "cord": [5, 7, 17, 19], "core": [11, 19], "corner": 19, "correct": 10, "correspond": [4, 8, 10, 19], "could": [2, 16], "counterpart": 11, "cover": 3, "coverag": 3, "cpu": [5, 13, 18], "creat": [1, 15], "crnn": [5, 9, 15], "crnn_mobilenet_v3_larg": [9, 15, 19], "crnn_mobilenet_v3_smal": [9, 18, 19], "crnn_vgg16_bn": [9, 13, 15, 19], "crop": [8, 9, 10, 13, 17, 19], "crop_orient": [8, 19], "crop_orientation_predictor": [9, 13], "crop_param": 13, "cuda": 18, "currenc": 7, "current": [3, 13, 19], "custom": [15, 16, 18, 19], "custom_crop_orientation_model": 13, "custom_page_orientation_model": 13, "customhook": 19, "cvit": 5, "czczup": 9, "czech": 7, "d": [7, 17], "danish": 7, "data": [5, 7, 8, 10, 11, 13, 15], "dataload": 17, "dataset": [9, 13, 19], "dataset_info": 7, "date": [13, 19], "db": 15, "db_mobilenet_v3_larg": [9, 15, 19], "db_resnet34": 19, "db_resnet50": [9, 13, 15, 19], "dbnet": [5, 9], "deal": [12, 19], "decis": 2, "decod": 8, "decode_img_as_tensor": 8, "dedic": 18, "deem": 2, "deep": [9, 19], "def": 19, "default": [4, 8, 13, 14, 19], "defer": 17, "defin": [11, 18], "degre": [8, 10, 19], "degress": 8, "delet": 3, "delimit": 19, "delta": 10, "demo": [3, 5], "demonstr": 2, "depend": [3, 4, 5, 19], "deploi": 3, "deploy": 5, "derogatori": 2, "describ": 9, "descript": 12, "design": 10, "desir": 8, "det_arch": [9, 13, 15, 18], "det_b": 19, "det_model": [13, 15, 18], "det_param": 13, "det_predictor": [13, 19], "detail": [13, 19], "detect": [1, 7, 8, 11, 12, 13, 16], "detect_languag": 9, "detect_orient": [9, 13, 19], "detection_predictor": [9, 19], "detection_task": [7, 17], "detectiondataset": [7, 17], "detectionmetr": 11, "detectionpredictor": [9, 13], "detector": [5, 9, 16], "deterior": 9, "determin": 2, "dev": [3, 14], "develop": 4, "deviat": 10, "devic": 18, "dict": [8, 11, 19], "dictionari": [8, 11], "differ": 2, "differenti": [5, 9], "digit": [5, 7, 17], "dimens": [8, 11, 19], "dimension": 10, "direct": 7, "directli": [15, 19], "directori": [3, 14], "disabl": [2, 14, 19], "disable_crop_orient": 19, "disable_page_orient": 19, "disclaim": 19, "discuss": 3, "disparag": 2, "displai": [8, 11], "display_artefact": 11, "distribut": 10, "div": 19, "divers": 2, "divid": 8, "do": [3, 4, 9], "doc": [3, 8, 16, 18, 19], "docartefact": [7, 17], "docstr": 3, "doctr": [1, 4, 13, 14, 15, 16, 17, 18, 19], "doctr_cache_dir": 14, "doctr_multiprocessing_dis": 14, "document": [1, 7, 9, 11, 12, 13, 16, 17, 18, 19], "documentbuild": 19, "documentfil": [8, 13, 15, 16, 18], "doesn": 18, "don": [13, 19], "done": 10, "download": [7, 17], "downsiz": 9, "draw": 10, "drop": 7, "drop_last": 7, "dtype": [8, 9, 10, 11, 18], "dual": [5, 7], "dummi": 15, "dummy_img": 19, "dummy_input": 18, "dure": 2, "dutch": 7, "dynam": [7, 16], "dynamic_seq_length": 7, "e": [2, 3, 4, 8, 9], "each": [5, 7, 8, 9, 10, 11, 17, 19], "eas": 3, "easi": [5, 11, 15, 18], "easili": [8, 11, 13, 15, 17, 19], "econom": 2, "edit": 2, "educ": 2, "effect": 19, "effici": [3, 5, 7, 9], "either": [11, 19], "element": [7, 8, 9, 19], "els": [3, 16], "email": 2, "empathi": 2, "en": 19, "enabl": [7, 8], "enclos": 8, "encod": [5, 7, 8, 9, 19], "encode_sequ": 7, "encount": 3, "encrypt": 8, "end": [5, 7, 9, 11], "english": [7, 17], "enough": [3, 19], "ensur": 3, "entri": 7, "environ": [2, 14], "eo": 7, "equiv": 19, "estim": 9, "etc": [8, 16], "ethnic": 2, "evalu": [17, 19], "event": 2, "everyon": 2, "everyth": [3, 19], "exact": [11, 19], "exampl": [2, 3, 5, 7, 9, 15, 19], "exchang": 18, "execut": 19, "exist": 15, "expand": 10, "expect": [8, 10, 11], "experi": 2, "explan": [2, 19], "explicit": 2, "exploit": [5, 9], "export": [8, 9, 11, 12, 16, 19], "export_as_straight_box": [9, 19], "export_as_xml": 19, "export_model_to_onnx": 18, "express": [2, 10], "extens": 8, "extern": [2, 17], "extract": [1, 5, 7], "extractor": 9, "f_": 11, "f_a": 11, "factor": 10, "fair": 2, "fairli": 2, "fals": [7, 8, 9, 10, 11, 13, 19], "faq": 2, "fascan": 15, "fast": [5, 7, 9], "fast_bas": [9, 19], "fast_smal": [9, 19], "fast_tini": [9, 19], "faster": [5, 9, 18], "fasterrcnn_mobilenet_v3_large_fpn": 9, "favorit": 19, "featur": [4, 9, 11, 12, 13, 16], "feedback": 2, "feel": [3, 15], "felix92": 15, "few": [18, 19], "figsiz": 11, "figur": [11, 16], "file": [3, 7], "final": 9, "find": [3, 17], "fine": 1, "finnish": 7, "first": [3, 7], "firsthand": 7, "fit": [9, 19], "flag": 19, "flip": 10, "float": [8, 10, 11, 18], "float32": [8, 9, 10, 18], "fn": 10, "focu": 15, "focus": [2, 7], "folder": 7, "follow": [2, 3, 4, 7, 10, 11, 13, 14, 15, 16, 19], "font": 7, "font_famili": 7, "foral": 11, "forc": 3, "forg": 4, "form": [5, 7, 19], "format": [8, 11, 13, 17, 18, 19], "forpost": [5, 7], "forum": 3, "found": 1, "fp16": 18, "frac": 11, "framework": [4, 15, 17, 19], "free": [2, 3, 15], "french": [7, 13, 15, 19], "friendli": 5, "from": [1, 2, 5, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19], "from_hub": [9, 15], "from_imag": [8, 15, 16, 18], "from_pdf": 8, "from_url": 8, "full": [7, 11, 19], "function": [7, 10, 11, 16], "funsd": [5, 7, 17, 19], "further": 17, "futur": 7, "g": [8, 9], "g_": 11, "g_x": 11, "gallagh": 1, "gamma": 10, "gaussian": 10, "gaussianblur": 10, "gaussiannois": 10, "gen": 19, "gender": 2, "gener": [3, 5, 8, 9], "generic_cyrillic_lett": 7, "geometri": [5, 8, 19], "geq": 11, "german": [7, 13, 15], "get": [18, 19], "git": 15, "github": [3, 4, 9, 15], "give": [2, 16], "given": [7, 8, 10, 11, 19], "global": 9, "go": 19, "good": 18, "googl": 3, "googlevis": 5, "gpu": [5, 16, 18], "gracefulli": 2, "graph": [5, 7, 8], "grayscal": 10, "ground": 11, "groung": 11, "group": [5, 19], "gt": 11, "gt_box": 11, "gt_label": 11, "guid": 3, "guidanc": 17, "gvision": 19, "h": [8, 9, 10], "h_": 11, "ha": [3, 7, 11, 17], "handl": [12, 17, 19], "handwrit": 7, "handwritten": 17, "harass": 2, "hardwar": 19, "harm": 2, "hat": 11, "have": [2, 3, 11, 13, 15, 17, 18, 19], "head": [9, 19], "healthi": 2, "hebrew": 7, "height": [8, 10], "hello": [11, 19], "help": 18, "here": [6, 10, 12, 16, 17, 19], "hf": 9, "hf_hub_download": 9, "high": 8, "higher": [4, 7, 19], "hindi": 7, "hindi_digit": 7, "hocr": 19, "hook": 19, "horizont": [8, 10, 19], "hous": 7, "how": [1, 3, 12, 13, 15, 17], "howev": 17, "hsv": 10, "html": [2, 3, 4, 8, 19], "http": [2, 4, 7, 8, 9, 15, 19], "hub": 9, "hue": 10, "huggingfac": 9, "hw": 7, "i": [2, 3, 7, 8, 9, 10, 11, 14, 15, 16, 17, 18], "i7": 19, "ibrahimov": 1, "ic03": [5, 7, 17], "ic13": [5, 7, 17], "icdar": [5, 7], "icdar2019": 7, "id": 19, "ident": 2, "identifi": 5, "iiit": [5, 7], "iiit5k": [7, 17], "iiithw": [5, 7, 17], "imag": [1, 5, 7, 8, 9, 10, 11, 15, 16, 17, 19], "imagenet": 9, "imageri": 2, "images_90k_norm": 7, "img": [7, 10, 17, 18], "img_cont": 8, "img_fold": [7, 17], "img_path": 8, "img_transform": 7, "imgur5k": [5, 7, 17], "imgur5k_annot": 7, "imlist": 7, "impact": 2, "implement": [7, 8, 9, 10, 11, 19], "import": [7, 8, 9, 10, 11, 13, 15, 16, 17, 18, 19], "improv": 9, "inappropri": 2, "incid": 2, "includ": [2, 7, 17, 18], "inclus": 2, "increas": 10, "independ": 10, "index": [3, 8], "indic": 11, "individu": 2, "infer": [5, 9, 10, 16, 19], "inform": [1, 2, 3, 5, 7, 17], "input": [3, 8, 9, 10, 18, 19], "input_crop": 9, "input_pag": [9, 11, 19], "input_shap": 18, "input_tensor": 9, "inspir": [2, 10], "instal": [15, 16, 18], "instanc": [2, 19], "instanti": [9, 19], "instead": [7, 8, 9], "insult": 2, "int": [7, 8, 9, 10], "int64": 11, "integ": 11, "integr": [1, 5, 15, 17], "intel": 19, "interact": [2, 8, 11], "interfac": [15, 18], "interoper": 18, "interpol": 10, "interpret": [7, 8], "intersect": 11, "invert": 10, "investig": 2, "invis": 2, "involv": [2, 19], "io": [13, 15, 16, 18], "iou": 11, "iou_thresh": 11, "iou_threshold": 16, "irregular": [5, 9, 17], "isn": 7, "issu": [2, 3, 15], "italian": 7, "iter": [7, 10, 17, 19], "its": [8, 9, 10, 11, 17, 19], "itself": [9, 15], "j": 11, "jame": 1, "job": 3, "join": 3, "jpeg": 10, "jpegqual": 10, "jpg": [7, 8, 15, 18], "json": [7, 17, 19], "json_output": 19, "jump": 3, "just": 2, "kei": [5, 7], "kera": [9, 18], "kernel": [5, 9, 10], "kernel_shap": 10, "keywoard": 9, "keyword": [7, 8, 9, 11], "kie": [9, 13], "kie_predictor": [9, 13], "kiepredictor": 9, "kind": 2, "know": [3, 18], "kwarg": [7, 8, 9, 11], "l": 11, "l_j": 11, "label": [7, 11, 16, 17], "label_fil": [7, 17], "label_fold": 7, "label_path": [7, 17], "labels_path": [7, 17], "ladder": 2, "lambda": 10, "lambdatransform": 10, "lang": 19, "languag": [2, 5, 7, 8, 9, 15, 19], "larg": [9, 15], "largest": 11, "last": [4, 7], "latenc": 9, "later": 3, "latest": 19, "latin": 7, "layer": 18, "layout": 19, "lead": 2, "leader": 2, "learn": [2, 5, 9, 18, 19], "least": 4, "left": [11, 19], "legacy_french": 7, "length": [7, 19], "less": [18, 19], "level": [2, 7, 11, 19], "leverag": 12, "lf": 15, "librari": [3, 4, 12, 13], "light": 5, "lightweight": 18, "like": 2, "limits_": 11, "line": [5, 9, 11, 19], "line_1_1": 19, "link": 13, "linknet": [5, 9], "linknet_resnet18": [9, 13, 18, 19], "linknet_resnet34": [9, 18, 19], "linknet_resnet50": [9, 19], "list": [7, 8, 10, 11, 15], "ll": 11, "load": [5, 7, 9, 16, 18], "load_state_dict": 13, "load_weight": 13, "loc_pr": 19, "local": [3, 5, 7, 9, 11, 17, 19], "localis": 7, "localizationconfus": 11, "locat": [3, 8, 19], "login": 9, "login_to_hub": [9, 15], "logo": [8, 16, 17], "love": 15, "lower": [10, 11, 19], "m": [3, 11, 19], "m1": 4, "macbook": 4, "machin": 18, "made": 5, "magc_resnet31": 9, "mai": [2, 3], "mail": 2, "main": 12, "maintain": 5, "mainten": 3, "make": [2, 3, 11, 13, 14, 15, 18, 19], "mani": [17, 19], "manipul": 19, "map": [7, 9], "map_loc": 13, "master": [5, 9, 19], "match": [11, 19], "mathcal": 11, "matplotlib": [8, 11], "max": [7, 10, 11], "max_angl": 10, "max_area": 10, "max_char": [7, 17], "max_delta": 10, "max_gain": 10, "max_gamma": 10, "max_qual": 10, "max_ratio": 10, "maximum": [7, 10], "maxval": [9, 10], "mbox": 11, "mean": [10, 11, 13], "meaniou": 11, "meant": [8, 18], "measur": 19, "media": 2, "median": 9, "meet": 13, "member": 2, "memori": [14, 18], "mention": 19, "merg": 7, "messag": 3, "meta": 19, "metadata": 18, "metal": 4, "method": [8, 10, 19], "metric": [11, 19], "middl": 19, "might": [18, 19], "min": 10, "min_area": 10, "min_char": [7, 17], "min_gain": 10, "min_gamma": 10, "min_qual": 10, "min_ratio": 10, "min_val": 10, "minde": [1, 2, 4, 5, 9], "minim": [3, 5], "minimalist": [5, 9], "minimum": [4, 7, 10, 11, 19], "minval": 10, "miss": 4, "mistak": 2, "mixed_float16": 18, "mixed_precis": 18, "mjsynth": [5, 7, 17], "mnt": 7, "mobilenet": [9, 15], "mobilenet_v3_larg": 9, "mobilenet_v3_large_r": 9, "mobilenet_v3_smal": [9, 13], "mobilenet_v3_small_crop_orient": [9, 13], "mobilenet_v3_small_page_orient": [9, 13], "mobilenet_v3_small_r": 9, "mobilenetv3": 9, "modal": [5, 7], "mode": 4, "model": [7, 11, 14, 16, 17], "model_nam": [9, 15, 18], "model_path": [16, 18], "moder": 2, "modif": 3, "modifi": [9, 14, 19], "modul": [4, 8, 9, 10, 11, 19], "more": [3, 17, 19], "moscardi": 1, "most": 19, "mozilla": 2, "multi": [5, 9], "multilingu": [7, 15], "multipl": [7, 8, 10, 19], "multipli": 10, "multiprocess": 14, "my": 9, "my_awesome_model": 15, "my_hook": 19, "n": [7, 11], "name": [7, 9, 18, 19], "nation": 2, "natur": [2, 5, 7], "ndarrai": [7, 8, 10, 11], "necessari": [4, 13, 14], "need": [3, 4, 7, 11, 13, 14, 15, 16, 19], "neg": 10, "nest": 19, "netraj": 1, "network": [5, 7, 9, 18], "neural": [5, 7, 9, 18], "new": [3, 11], "next": [7, 17], "nois": 10, "noisi": [5, 7], "non": [5, 7, 8, 9, 10, 11], "none": [7, 8, 9, 10, 11, 19], "normal": [9, 10], "norwegian": 7, "note": [0, 3, 7, 9, 13, 15, 16, 18], "now": 3, "np": [9, 10, 11, 19], "num_output_channel": 10, "num_sampl": [7, 17], "number": [7, 9, 10, 11, 19], "numpi": [8, 9, 11, 19], "o": 4, "obb": 16, "obj_detect": 15, "object": [7, 8, 11, 16, 19], "objectness_scor": [8, 19], "oblig": 2, "obtain": 19, "occupi": 18, "ocr": [1, 5, 7, 9, 11, 15], "ocr_carea": 19, "ocr_db_crnn": 11, "ocr_lin": 19, "ocr_pag": 19, "ocr_par": 19, "ocr_predictor": [9, 13, 15, 18, 19], "ocrdataset": [7, 17], "ocrmetr": 11, "ocrpredictor": [9, 13], "ocrx_word": 19, "offens": 2, "offici": [2, 9], "offlin": 2, "offset": 10, "onc": 19, "one": [3, 7, 9, 10, 13, 15, 19], "oneof": 10, "ones": [7, 11], "onli": [3, 9, 10, 11, 13, 15, 17, 18, 19], "onlin": 2, "onnx": 16, "onnxruntim": [16, 18], "onnxtr": 18, "opac": 10, "opacity_rang": 10, "open": [1, 2, 3, 15, 18], "opinion": 2, "optic": [5, 19], "optim": [5, 19], "option": [7, 9, 13], "order": [3, 7, 8, 10], "org": [2, 7, 9, 19], "organ": 8, "orient": [2, 8, 9, 12, 16, 19], "orientationpredictor": 9, "other": [2, 3], "otherwis": [2, 8, 11], "our": [1, 3, 9, 19], "out": [3, 9, 10, 11, 19], "outpout": 19, "output": [8, 10, 18], "output_s": [8, 10], "outsid": 14, "over": [7, 11, 19], "overal": [2, 9], "overlai": 8, "overview": 16, "overwrit": 13, "overwritten": 15, "own": 5, "p": [10, 19], "packag": [3, 5, 11, 14, 16, 17, 18], "pad": [7, 9, 10, 19], "page": [4, 7, 9, 11, 13, 19], "page1": 8, "page2": 8, "page_1": 19, "page_idx": [8, 19], "page_orientation_predictor": [9, 13], "page_param": 13, "pair": 11, "paper": 9, "par_1_1": 19, "paragraph": 19, "paragraph_break": 19, "parallel": 9, "param": [10, 19], "paramet": [5, 8, 9, 18], "pars": [5, 7], "parseq": [5, 9, 15, 18, 19], "part": [7, 10, 19], "parti": 4, "partial": 19, "particip": 2, "pass": [7, 8, 9, 13, 19], "password": 8, "patch": [9, 11], "path": [7, 8, 16, 17, 18], "path_to_checkpoint": 13, "path_to_custom_model": 18, "path_to_pt": 13, "patil": 1, "pattern": 2, "pdf": [8, 9, 12], "pdfpage": 8, "peopl": 2, "per": [10, 19], "perform": [5, 8, 9, 10, 11, 14, 18, 19], "period": 2, "permiss": 2, "permut": [5, 9], "persian_lett": 7, "person": [2, 17], "phase": 19, "photo": 17, "physic": [2, 8], "pick": 10, "pictur": 8, "pip": [3, 4, 16, 18], "pipelin": 19, "pixel": [8, 10, 19], "pleas": 3, "plot": 11, "plt": 11, "plug": 15, "plugin": 4, "png": 8, "point": 18, "polici": 14, "polish": 7, "polit": 2, "polygon": [7, 11, 19], "pool": 9, "portugues": 7, "posit": [2, 11], "possibl": [3, 11, 15, 19], "post": [2, 19], "postprocessor": 19, "potenti": 9, "power": 5, "ppageno": 19, "pre": [3, 9, 18], "precis": [11, 19], "pred": 11, "pred_box": 11, "pred_label": 11, "predefin": 17, "predict": [8, 9, 11, 19], "predictor": [5, 8, 9, 12, 13, 15, 18], "prefer": 17, "preinstal": 4, "preprocessor": [13, 19], "prerequisit": 15, "present": 12, "preserv": [9, 10, 19], "preserve_aspect_ratio": [8, 9, 10, 13, 19], "pretrain": [5, 9, 11, 13, 18, 19], "pretrained_backbon": [9, 13], "print": 19, "prior": 7, "privaci": 2, "privat": 2, "probabl": [1, 10], "problem": 3, "procedur": 10, "process": [3, 5, 8, 9, 13, 19], "processor": 19, "produc": [12, 19], "product": 18, "profession": 2, "project": [3, 17], "promptli": 2, "proper": 3, "properli": 7, "provid": [2, 3, 5, 15, 16, 17, 19], "public": [2, 5], "publicli": 19, "publish": 2, "pull": 15, "punctuat": 7, "pure": 7, "purpos": 3, "push_to_hf_hub": [9, 15], "py": 15, "pypdfium2": [4, 8], "pyplot": [8, 11], "python": [1, 3, 16], "python3": 15, "pytorch": [4, 5, 9, 10, 13, 15, 18, 19], "q": 3, "qr": [8, 16], "qr_code": 17, "qualiti": 10, "question": 2, "quickli": 5, "quicktour": 12, "r": 19, "race": 2, "ramdisk": 7, "rand": [9, 10, 11, 18, 19], "random": [9, 10, 11, 19], "randomappli": 10, "randombright": 10, "randomcontrast": 10, "randomcrop": 10, "randomgamma": 10, "randomhorizontalflip": 10, "randomhu": 10, "randomjpegqu": 10, "randomli": 10, "randomres": 10, "randomrot": 10, "randomsatur": 10, "randomshadow": 10, "rang": 10, "rassi": 15, "ratio": [9, 10, 19], "raw": [8, 11], "re": 18, "read": [5, 7, 9], "read_html": 8, "read_img_as_numpi": 8, "read_img_as_tensor": 8, "read_pdf": 8, "readi": 18, "real": [1, 5, 9, 10], "realli": 1, "reason": [2, 5, 7], "rebuild": 3, "rebuilt": 3, "recal": [11, 19], "receipt": [5, 7, 19], "reco_arch": [9, 13, 15, 18], "reco_b": 19, "reco_model": [13, 15, 18], "reco_param": 13, "reco_predictor": 13, "recogn": 19, "recognit": [7, 11, 12, 13], "recognition_predictor": [9, 19], "recognition_task": [7, 17], "recognitiondataset": [7, 17], "recognitionpredictor": [9, 13], "rectangular": 9, "reduc": [4, 10], "refer": [3, 4, 13, 15, 16, 17, 19], "regardless": 2, "region": 19, "regroup": 11, "regular": 17, "reject": 2, "rel": [8, 10, 11, 19], "relat": 8, "releas": [0, 4], "relev": 16, "religion": 2, "remov": 2, "render": [8, 19], "repo": 9, "repo_id": [9, 15], "report": 2, "repositori": [7, 9, 15], "repres": [2, 18, 19], "represent": [5, 9], "request": [2, 15], "requir": [4, 10, 18], "research": 5, "residu": 9, "resiz": [10, 19], "resnet": 9, "resnet18": [9, 15], "resnet31": 9, "resnet34": 9, "resnet50": [9, 15], "resolv": 8, "resolve_block": 19, "resolve_lin": 19, "resourc": 17, "respect": 2, "rest": [3, 10, 11], "restrict": 14, "result": [3, 7, 8, 12, 15, 18, 19], "return": 19, "reusabl": 19, "review": 2, "rgb": [8, 10], "rgb_mode": 8, "rgb_output": 8, "right": [2, 9, 11], "roboflow": 1, "robust": [5, 7], "root": 7, "rotat": [7, 8, 9, 10, 11, 12, 13, 17, 19], "run": [3, 4, 9], "same": [3, 8, 11, 17, 18, 19], "sampl": [7, 9, 17, 19], "sample_transform": 7, "sanjin": 1, "sar": [5, 9], "sar_resnet31": [9, 19], "satur": 10, "save": [9, 17], "scale": [8, 9, 10, 11], "scale_rang": 10, "scan": [5, 7], "scene": [5, 7, 9], "score": [8, 11], "script": [3, 17], "seamless": 5, "seamlessli": [5, 19], "search": [1, 9], "searchabl": 12, "sec": 19, "second": 19, "section": [1, 13, 15, 16, 18, 19], "secur": [2, 14], "see": [2, 3], "seen": 19, "segment": [5, 9, 19], "self": 19, "semant": [5, 9], "send": 19, "sens": 11, "sensit": 17, "separ": 19, "sequenc": [5, 7, 8, 9, 11, 19], "sequenti": [10, 19], "seri": 2, "seriou": 2, "set": [2, 4, 7, 9, 11, 14, 16, 19], "set_global_polici": 18, "sever": [8, 10, 19], "sex": 2, "sexual": 2, "shade": 10, "shape": [5, 8, 9, 10, 11, 19], "share": [14, 17], "shift": 10, "shm": 14, "should": [3, 7, 8, 10, 11], "show": [5, 8, 9, 11, 13, 15, 16], "showcas": [3, 12], "shuffl": [7, 10], "side": 11, "signatur": 8, "signific": 17, "simpl": [5, 9, 18], "simpler": 9, "sinc": [7, 17], "singl": [2, 3, 5, 7], "single_img_doc": 18, "size": [2, 7, 8, 10, 16, 19], "skew": 19, "slack": 3, "slightli": 9, "small": [3, 9, 19], "smallest": 8, "snapshot_download": 9, "snippet": 19, "so": [3, 4, 7, 9, 15, 17], "social": 2, "socio": 2, "some": [1, 4, 12, 15, 17], "someth": 3, "somewher": 3, "sort": 2, "sourc": [1, 7, 8, 9, 10, 11, 15], "space": [2, 19], "span": 19, "spanish": 7, "spatial": [5, 7, 8], "specif": [3, 4, 11, 13, 17, 19], "specifi": [2, 7, 8], "speed": [5, 9, 19], "sphinx": 3, "sroie": [5, 7, 17], "stabl": 4, "stackoverflow": 3, "stage": 5, "standalon": 12, "standard": 10, "start": 7, "state": [1, 5, 11, 16], "static": 11, "statist": 1, "statu": 2, "std": [10, 13], "step": 14, "still": 19, "str": [7, 8, 9, 10, 11], "straight": [7, 9, 17, 19], "straighten": 19, "straighten_pag": [9, 13, 19], "straigten_pag": 13, "stream": 8, "street": [5, 7], "strict": 4, "strictli": 11, "string": [7, 8, 11, 19], "strive": 4, "strong": [5, 9], "structur": [18, 19], "subset": [7, 19], "suggest": [3, 15], "sum": 11, "summari": 11, "support": [4, 13, 16, 18, 19], "sustain": 2, "svhn": [5, 7, 17], "svt": [7, 17], "swedish": 7, "symmetr": [9, 10, 19], "symmetric_pad": [9, 10, 19], "synthet": 5, "synthtext": [5, 7, 17], "system": 19, "t": [3, 7, 13, 18, 19], "tabl": [15, 16, 17], "take": [2, 7, 19], "target": [7, 8, 10, 11, 17], "target_s": 7, "task": [5, 7, 9, 15, 17, 19], "task2": 7, "team": 4, "techminde": 4, "templat": [3, 5], "tensor": [7, 8, 10, 19], "tensorflow": [4, 5, 8, 9, 10, 13, 15, 18, 19], "tensorspec": 18, "term": 2, "test": [7, 17], "test_set": 7, "text": [1, 7, 8, 9, 11, 17], "text_output": 19, "textmatch": 11, "textnet": 9, "textnet_bas": 9, "textnet_smal": 9, "textnet_tini": 9, "textract": [5, 19], "textstylebrush": [5, 7], "textual": [5, 7, 8, 9, 19], "tf": [4, 8, 9, 10, 15, 18], "than": [3, 11, 15], "thank": 3, "thei": [2, 11], "them": [7, 19], "thi": [1, 2, 3, 4, 6, 7, 10, 11, 13, 14, 15, 17, 18, 19], "thing": [18, 19], "third": 4, "those": [2, 8, 19], "threaten": 2, "threshold": 19, "through": [2, 10, 16, 17], "tilman": 15, "time": [1, 2, 5, 9, 11, 17], "tini": 9, "titl": [8, 19], "tm": 19, "tmp": 14, "togeth": [3, 8], "tograi": 10, "tool": [1, 17], "top": [11, 18, 19], "topic": 3, "torch": [4, 10, 13, 15, 18], "torchvis": 10, "total": 13, "toward": [2, 4], "train": [3, 7, 9, 10, 15, 16, 17, 18, 19], "train_it": [7, 17], "train_load": [7, 17], "train_pytorch": 15, "train_set": [7, 17], "train_tensorflow": 15, "trainabl": [5, 9], "tranform": 10, "transcrib": 19, "transfer": [5, 7], "transfo": 10, "transform": [5, 7, 9], "translat": 2, "troll": 2, "true": [7, 8, 9, 10, 11, 13, 14, 15, 17, 18, 19], "truth": 11, "tune": [1, 18], "tupl": [7, 8, 10, 11], "two": [8, 14], "txt": 7, "type": [8, 11, 15, 18, 19], "typic": 19, "u": [2, 3], "ucsd": 7, "udac": 3, "uint8": [8, 9, 11, 19], "ukrainian": 7, "unaccept": 2, "underli": [17, 19], "underneath": 8, "understand": [5, 7, 19], "uniform": [9, 10], "uniformli": 10, "uninterrupt": [8, 19], "union": 11, "unit": 1, "unittest": 3, "unlock": 8, "unoffici": 9, "unprofession": 2, "unsolicit": 2, "unsupervis": 5, "unwelcom": 2, "up": [9, 19], "updat": 11, "upgrad": 3, "upper": [7, 10], "uppercas": 17, "url": 8, "us": [2, 3, 4, 7, 9, 11, 12, 13, 14, 15, 16, 19], "usabl": 19, "usag": [14, 18], "use_polygon": [7, 11, 17], "useabl": 19, "user": [5, 8, 12], "utf": 19, "util": 18, "v1": 15, "v3": [9, 15, 19], "valid": 17, "valu": [3, 8, 10, 19], "valuabl": 5, "variabl": 14, "varieti": 7, "veri": 9, "verma": 1, "version": [2, 3, 4, 18, 19], "vgg": 9, "vgg16": 15, "vgg16_bn_r": 9, "via": 2, "video": 1, "vietnames": 7, "view": [5, 7], "viewpoint": 2, "violat": 2, "visibl": 2, "vision": [5, 7, 9], "visiondataset": 7, "visiontransform": 9, "visual": [4, 5, 16], "visualize_pag": 11, "vit_": 9, "vit_b": 9, "vitstr": [5, 9, 18], "vitstr_bas": [9, 19], "vitstr_smal": [9, 13, 18, 19], "viz": 4, "vocab": [13, 15, 17, 18, 19], "vocabulari": [7, 13, 15], "w": [8, 9, 10, 11], "w3": 19, "wa": 2, "wai": [2, 5, 17], "want": [3, 18, 19], "warmup": 19, "wasn": 3, "we": [1, 2, 3, 4, 5, 8, 10, 13, 15, 17, 18, 19], "weasyprint": 8, "web": [3, 8], "websit": 7, "welcom": 2, "well": [1, 2, 18], "were": [2, 8, 19], "what": [1, 2], "when": [2, 3, 9], "whenev": 3, "where": [3, 8, 10, 11], "whether": [3, 7, 8, 10, 11, 17, 19], "which": [2, 9, 14, 16, 17, 19], "whichev": 4, "while": [10, 19], "why": 2, "width": [8, 10], "wiki": 2, "wildreceipt": [5, 7, 17], "window": [9, 11], "wish": 3, "within": 2, "without": [2, 7, 9], "wonder": 3, "word": [5, 7, 9, 11, 19], "word_1_1": 19, "word_1_2": 19, "word_1_3": 19, "wordgener": [7, 17], "words_onli": 11, "work": [1, 13, 14, 19], "workflow": 3, "worklow": 3, "world": [11, 19], "worth": 9, "wrap": 19, "wrapper": [7, 10], "write": 14, "written": [2, 8], "www": [2, 8, 19], "x": [8, 10, 11], "x_ascend": 19, "x_descend": 19, "x_i": 11, "x_size": 19, "x_wconf": 19, "xhtml": 19, "xmax": 8, "xmin": 8, "xml": 19, "xml_bytes_str": 19, "xml_element": 19, "xml_output": 19, "xmln": 19, "y": 11, "y_i": 11, "y_j": 11, "yet": 16, "ymax": 8, "ymin": 8, "yolov8": 16, "you": [3, 4, 7, 8, 9, 13, 14, 15, 16, 17, 18, 19], "your": [3, 5, 8, 11, 19], "yoursit": 8, "yugesh": 1, "zero": [10, 11], "zoo": 13, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 7, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 7, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": 7, "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 7, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 7, "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 7, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 7, "\u00e4\u00f6\u00e4\u00f6": 7, "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 7, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": 7, "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": 7, "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": 7, "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": 7, "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": 7, "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0905": 7, "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": 7, "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": 7, "\u0950": 7, "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": 7, "\u09bd": 7, "\u09ce": 7, "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": 7}, "titles": ["Changelog", "Community resources", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.contrib", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Integrate contributions into your pipeline", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 3, "0": 0, "01": 0, "02": 0, "03": 0, "04": 0, "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 2], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 2], "2021": 0, "2022": 0, "2023": 0, "2024": 0, "21": 0, "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 2], "31": 0, "4": [0, 2], "5": 0, "6": 0, "7": 0, "8": 0, "9": 0, "advanc": 19, "approach": 19, "architectur": 19, "arg": [7, 8, 9, 10, 11], "artefact": 8, "artefactdetect": 16, "attribut": 2, "avail": [16, 17, 19], "aw": 14, "ban": 2, "block": 8, "bug": 3, "changelog": 0, "choos": [17, 19], "classif": [9, 13, 15], "code": [2, 3], "codebas": 3, "commit": 3, "commun": [1, 15], "compos": 10, "conda": 4, "conduct": 2, "connect": 3, "continu": 3, "contrib": 6, "contribut": [3, 6, 16], "contributor": 2, "convent": 15, "correct": 2, "coven": 2, "custom": [7, 13], "data": 17, "dataload": 7, "dataset": [5, 7, 17], "detect": [5, 9, 15, 17, 19], "develop": 3, "do": 19, "doctr": [3, 5, 6, 7, 8, 9, 10, 11, 12], "document": [3, 5, 8], "end": 19, "enforc": 2, "evalu": 11, "export": 18, "factori": 9, "featur": [3, 5], "feedback": 3, "file": 8, "from": 15, "gener": [7, 17], "git": 4, "guidelin": 2, "half": 18, "hub": 15, "huggingfac": 15, "i": 19, "infer": 18, "instal": [3, 4], "integr": [3, 16], "io": 8, "lambda": 14, "let": 3, "line": 8, "linux": 4, "load": [13, 15, 17], "loader": 7, "main": 5, "mode": 3, "model": [5, 9, 13, 15, 18, 19], "modifi": 3, "modul": [6, 16], "name": 15, "notebook": 12, "object": 17, "ocr": [17, 19], "onli": 4, "onnx": 18, "optim": 18, "option": 19, "orient": 13, "our": 2, "output": 19, "own": [13, 17], "packag": 4, "page": 8, "perman": 2, "pipelin": 16, "pledg": 2, "precis": 18, "predictor": 19, "prepar": 18, "prerequisit": 4, "pretrain": 15, "push": 15, "python": 4, "qualiti": 3, "question": 3, "read": 8, "readi": 17, "recognit": [5, 9, 15, 17, 19], "report": 3, "request": 3, "resourc": 1, "respons": 2, "return": [7, 8, 9, 11], "right": 19, "scope": 2, "share": 15, "should": 19, "stage": 19, "standard": 2, "structur": [3, 8], "style": 3, "support": [5, 6, 7, 10], "synthet": [7, 17], "task": 11, "temporari": 2, "test": 3, "text": [5, 19], "train": 13, "transform": 10, "two": 19, "unit": 3, "us": [17, 18], "util": 11, "v0": 0, "verif": 3, "via": 4, "visual": 11, "vocab": 7, "warn": 2, "what": 19, "word": 8, "your": [13, 15, 16, 17, 18], "zoo": [5, 9]}}) \ No newline at end of file diff --git a/v0.4.1/transforms.html b/v0.4.1/transforms.html deleted file mode 100644 index 85e94d8a76..0000000000 --- a/v0.4.1/transforms.html +++ /dev/null @@ -1,684 +0,0 @@ - - - - - - - - - - - - - doctr.transforms - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    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:

    -
    -
    -class doctr.transforms.Resize(output_size: Tuple[int, int], method: str = 'bilinear', preserve_aspect_ratio: bool = False, symmetric_pad: bool = False)[source]
    -

    Resizes a tensor to a target size

    -
    -
    Example::
    >>> from doctr.transforms import Resize
    ->>> import tensorflow as tf
    ->>> transfo = Resize((32, 32))
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • output_size – expected output size

    • -
    • method – interpolation method

    • -
    • preserve_aspect_ratio – if True, preserve aspect ratio and pad the rest with zeros

    • -
    • symmetric_pad – if True while preserving aspect ratio, the padding will be done symmetrically

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.Normalize(mean: Tuple[float, float, float], std: Tuple[float, float, float])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • mean – average value per channel

    • -
    • std – standard deviation per channel

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.LambdaTransformation(fn: Callable[[Tensor], Tensor])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import LambdaTransformation
    ->>> import tensorflow as tf
    ->>> transfo = LambdaTransformation(lambda x: x/ 255.)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    fn – the function to be applied to the input tensor

    -
    -
    -
    - -
    -
    -class doctr.transforms.ToGray[source]
    -

    Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ToGray()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    - -
    -
    -class doctr.transforms.ColorInversion(min_val: float = 0.5)[source]
    -

    Applies the following tranformation to a tensor (image or batch of images): -convert to grayscale, colorize (shift 0-values randomly), and then invert colors

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ColorInversion(min_val=0.6)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    min_val – range [min_val, 1] to colorize RGB pixels

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomBrightness(max_delta: float = 0.3)[source]
    -

    Randomly adjust brightness of a tensor (batch of images or image) by adding a delta -to all pixels

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Brightness()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    • -
    • p – probability to apply transformation

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomContrast(delta: float = 0.3)[source]
    -

    Randomly adjust contrast of a tensor (batch of images or image) by adjusting -each pixel: (img - mean) * contrast_factor + mean.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Contrast()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomSaturation(delta: float = 0.5)[source]
    -

    Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and -increasing saturation by a factor.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Saturation()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomHue(max_delta: float = 0.3)[source]
    -

    Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Hue()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomGamma(min_gamma: float = 0.5, max_gamma: float = 1.5, min_gain: float = 0.8, max_gain: float = 1.2)[source]
    -

    randomly performs gamma correction for a tensor (batch of images or image)

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Gamma()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_gamma – non-negative real number, lower bound for gamma param

    • -
    • max_gamma – non-negative real number, upper bound for gamma

    • -
    • min_gain – lower bound for constant multiplier

    • -
    • max_gain – upper bound for constant multiplier

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomJpegQuality(min_quality: int = 60, max_quality: int = 100)[source]
    -

    Randomly adjust jpeg quality of a 3 dimensional RGB image

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = JpegQuality()
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_quality – int between [0, 100]

    • -
    • max_quality – int between [0, 100]

    • -
    -
    -
    -
    - -
    -
    -

    Composing transformations

    -

    It is common to require several transformations to be performed consecutively.

    -
    -
    -class doctr.transforms.Compose(transforms: List[Callable[[Any], Any]])[source]
    -

    Implements a wrapper that will apply transformations sequentially

    -
    -
    Example::
    >>> from doctr.transforms import Compose, Resize
    ->>> import tensorflow as tf
    ->>> transfos = Compose([Resize((32, 32))])
    ->>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformation modules

    -
    -
    -
    - -
    -
    -class doctr.transforms.OneOf(transforms: List[Callable[[Any], Any]])[source]
    -

    Randomly apply one of the input transformations

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = OneOf([JpegQuality(), Gamma()])
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformations, one only will be picked

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomApply(transform: Callable[[Any], Any], p: float = 0.5)[source]
    -

    Apply with a probability p the input transformation

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = RandomApply(Gamma(), p=.5)
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • transform – transformation to apply

    • -
    • p – probability to apply

    • -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.4.1/using_doctr/custom_models_training.html b/v0.4.1/using_doctr/custom_models_training.html index df39d8d568..b714c1f971 100644 --- a/v0.4.1/using_doctr/custom_models_training.html +++ b/v0.4.1/using_doctr/custom_models_training.html @@ -14,7 +14,7 @@ - + Train your own model - docTR documentation @@ -619,7 +619,7 @@

    Loading your custom trained orientation classification model - + diff --git a/v0.4.1/using_doctr/running_on_aws.html b/v0.4.1/using_doctr/running_on_aws.html index 16ceaca7a1..808ea541cd 100644 --- a/v0.4.1/using_doctr/running_on_aws.html +++ b/v0.4.1/using_doctr/running_on_aws.html @@ -14,7 +14,7 @@ - + AWS Lambda - docTR documentation @@ -362,7 +362,7 @@

    AWS Lambda - + diff --git a/v0.4.1/using_doctr/sharing_models.html b/v0.4.1/using_doctr/sharing_models.html index d76b4017f4..c9e978400a 100644 --- a/v0.4.1/using_doctr/sharing_models.html +++ b/v0.4.1/using_doctr/sharing_models.html @@ -14,7 +14,7 @@ - + Share your model with the community - docTR documentation @@ -544,7 +544,7 @@

    Recognition - + diff --git a/v0.4.1/using_doctr/using_contrib_modules.html b/v0.4.1/using_doctr/using_contrib_modules.html index 50598dae5d..0c5fffdf9f 100644 --- a/v0.4.1/using_doctr/using_contrib_modules.html +++ b/v0.4.1/using_doctr/using_contrib_modules.html @@ -14,7 +14,7 @@ - + Integrate contributions into your pipeline - docTR documentation @@ -415,7 +415,7 @@

    ArtefactDetection - + diff --git a/v0.4.1/using_doctr/using_datasets.html b/v0.4.1/using_doctr/using_datasets.html index 460476dbbf..8a7d4f0a64 100644 --- a/v0.4.1/using_doctr/using_datasets.html +++ b/v0.4.1/using_doctr/using_datasets.html @@ -14,7 +14,7 @@ - + Choose a ready to use dataset - docTR documentation @@ -642,7 +642,7 @@

    Data Loading - + diff --git a/v0.4.1/using_doctr/using_model_export.html b/v0.4.1/using_doctr/using_model_export.html index 6124c00ebe..6790dd0642 100644 --- a/v0.4.1/using_doctr/using_model_export.html +++ b/v0.4.1/using_doctr/using_model_export.html @@ -14,7 +14,7 @@ - + Preparing your model for inference - docTR documentation @@ -467,7 +467,7 @@

    Using your ONNX exported model - + diff --git a/v0.4.1/using_doctr/using_models.html b/v0.4.1/using_doctr/using_models.html index 61f1f5ab7a..9ead8498e1 100644 --- a/v0.4.1/using_doctr/using_models.html +++ b/v0.4.1/using_doctr/using_models.html @@ -14,7 +14,7 @@ - + Choosing the right model - docTR documentation @@ -1253,7 +1253,7 @@

    Advanced options - + diff --git a/v0.4.1/utils.html b/v0.4.1/utils.html deleted file mode 100644 index e2f223f06a..0000000000 --- a/v0.4.1/utils.html +++ /dev/null @@ -1,574 +0,0 @@ - - - - - - - - - - - - - doctr.utils - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.utils

    -

    This module regroups non-core features that are complementary to the rest of the package.

    -
    -

    Visualization

    -

    Easy-to-use functions to make sense of your model’s predictions.

    -
    -
    -doctr.utils.visualization.visualize_page(page: Dict[str, Any], image: ndarray, words_only: bool = True, display_artefacts: bool = True, scale: float = 10, interactive: bool = True, add_labels: bool = True, **kwargs: Any) Figure[source]
    -

    Visualize a full page with predicted blocks, lines and words

    -
    -
    Example::
    >>> import numpy as np
    ->>> import matplotlib.pyplot as plt
    ->>> from doctr.utils.visualization import visualize_page
    ->>> from doctr.models import ocr_db_crnn
    ->>> model = ocr_db_crnn(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([[input_page]])
    ->>> visualize_page(out[0].pages[0].export(), input_page)
    ->>> plt.show()
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • page – the exported Page of a Document

    • -
    • image – np array of the page, needs to have the same shape than page[‘dimensions’]

    • -
    • words_only – whether only words should be displayed

    • -
    • display_artefacts – whether artefacts should be displayed

    • -
    • scale – figsize of the largest windows side

    • -
    • interactive – whether the plot should be interactive

    • -
    • add_labels – for static plot, adds text labels on top of bounding box

    • -
    -
    -
    -
    - -
    -
    -

    Task evaluation

    -

    Implementations of task-specific metrics to easily assess your model performances.

    -
    -
    -class doctr.utils.metrics.TextMatch[source]
    -

    Implements text match metric (word-level accuracy) for recognition task.

    -

    The raw aggregated metric is computed as follows:

    -
    -
    -\[\forall X, Y \in \mathcal{W}^N, -TextMatch(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N f_{Y_i}(X_i)\]
    -
    -

    with the indicator function \(f_{a}\) defined as:

    -
    -
    -\[\begin{split}\forall a, x \in \mathcal{W}, -f_a(x) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } x = a \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{W}\) is the set of all possible character sequences, -\(N\) is a strictly positive integer.

    -
    -
    Example::
    >>> from doctr.utils import TextMatch
    ->>> metric = TextMatch()
    ->>> metric.update(['Hello', 'world'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    -summary() Dict[str, float][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a dictionary with the exact match score for the raw data, its lower-case counterpart, its unidecode -counterpart and its lower-case unidecode counterpart

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.LocalizationConfusion(iou_thresh: float = 0.5, rotated_bbox: bool = False, mask_shape: Tuple[int, int] = (1024, 1024))[source]
    -

    Implements common confusion metrics and mean IoU for localization evaluation.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall Y \in \mathcal{B}^N, \forall X \in \mathcal{B}^M, \\ -Recall(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -Precision(X, Y) = \frac{1}{M} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -meanIoU(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(X_i, Y_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(g_{X}\) defined as:

    -
    -
    -\[\begin{split}\forall y \in \mathcal{B}, -g_X(y) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } y\mbox{ has been assigned to any }(X_i)_i\mbox{ with an }IoU \geq 0.5 \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import LocalizationConfusion
    ->>> metric = LocalizationConfusion(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]))
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[float | None, float | None, float | None][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall, precision and meanIoU scores

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.OCRMetric(iou_thresh: float = 0.5, rotated_bbox: bool = False, mask_shape: Tuple[int, int] = (1024, 1024))[source]
    -

    Implements end-to-end OCR metric.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall (B, L) \in \mathcal{B}^N \times \mathcal{L}^N, -\forall (\hat{B}, \hat{L}) \in \mathcal{B}^M \times \mathcal{L}^M, \\ -Recall(B, \hat{B}, L, \hat{L}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -Precision(B, \hat{B}, L, \hat{L}) = \frac{1}{M} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(h_{B, L}\) defined as:

    -
    -
    -\[\begin{split}\forall (b, l) \in \mathcal{B} \times \mathcal{L}, -h_{B,L}(b, l) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ - & IoU \geq 0.5 \mbox{ and that for this assignment, } l = L_j\\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(\mathcal{L}\) is the set of possible character sequences, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import OCRMetric
    ->>> metric = OCRMetric(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]),
    -['hello'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[Dict[str, float | None], Dict[str, float | None], float | None][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall & precision for each string comparison flexibility and the mean IoU

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.5.0/_modules/doctr/datasets/cord.html b/v0.5.0/_modules/doctr/datasets/cord.html index f98ee6901c..55b0584830 100644 --- a/v0.5.0/_modules/doctr/datasets/cord.html +++ b/v0.5.0/_modules/doctr/datasets/cord.html @@ -13,7 +13,7 @@ - + doctr.datasets.cord - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.cord

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
     from .datasets import VisionDataset
    -from doctr.utils.geometry import fit_rbbox
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['CORD']
    +__all__ = ["CORD"]
     
     
     
    -[docs] +[docs] class CORD(VisionDataset): """CORD dataset from `"CORD: A Consolidated Receipt Dataset forPost-OCR Parsing" <https://openreview.net/pdf?id=SJl3z659UH>`_. - Example:: - >>> from doctr.datasets import CORD - >>> train_set = CORD(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/cord-grid.png&src=0 + :align: center + + >>> from doctr.datasets import CORD + >>> train_set = CORD(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - TRAIN = ('https://github.com/mindee/doctr/releases/download/v0.1.1/cord_train.zip', - '45f9dc77f126490f3e52d7cb4f70ef3c57e649ea86d19d862a2757c9c455d7f8') - TEST = ('https://github.com/mindee/doctr/releases/download/v0.1.1/cord_test.zip', - '8c895e3d6f7e1161c5b7245e3723ce15c04d84be89eaa6093949b75a66fb3c58') + TRAIN = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/cord_train.zip&src=0", + "45f9dc77f126490f3e52d7cb4f70ef3c57e649ea86d19d862a2757c9c455d7f8", + "cord_train.zip", + ) + + TEST = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/cord_test.zip&src=0", + "8c895e3d6f7e1161c5b7245e3723ce15c04d84be89eaa6093949b75a66fb3c58", + "cord_test.zip", + ) def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + url, sha256, name = self.TRAIN if train else self.TEST + super().__init__( + url, + name, + sha256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - url, sha256 = self.TRAIN if train else self.TEST - super().__init__(url, None, sha256, True, **kwargs) - - # # List images - self.root = os.path.join(self._root, 'image') - self.data: List[Tuple[str, Dict[str, Any]]] = [] + # List images + tmp_root = os.path.join(self.root, "image") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] self.train = train - self.sample_transforms = sample_transforms - for img_path in os.listdir(self.root): + np_dtype = np.float32 + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking CORD", total=len(os.listdir(tmp_root))): # File existence check - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem _targets = [] - with open(os.path.join(self._root, 'json', f"{stem}.json"), 'rb') as f: + with open(os.path.join(self.root, "json", f"{stem}.json"), "rb") as f: label = json.load(f) for line in label["valid_line"]: for word in line["words"]: if len(word["text"]) > 0: x = word["quad"]["x1"], word["quad"]["x2"], word["quad"]["x3"], word["quad"]["x4"] y = word["quad"]["y1"], word["quad"]["y2"], word["quad"]["y3"], word["quad"]["y4"] - if rotated_bbox: - box = list(fit_rbbox(np.array([ - [x[0], y[0]], - [x[1], y[1]], - [x[2], y[2]], - [x[3], y[3]], - ], dtype=np.float32))) + box: Union[List[float], np.ndarray] + if use_polygons: + # (x, y) coordinates of top left, top right, bottom right, bottom left corners + box = np.array( + [ + [x[0], y[0]], + [x[1], y[1]], + [x[2], y[2]], + [x[3], y[3]], + ], + dtype=np_dtype, + ) else: - # Reduce 8 coords to 4 + # Reduce 8 coords to 4 -> xmin, ymin, xmax, ymax box = [min(x), min(y), max(x), max(y)] - _targets.append((word['text'], box)) + _targets.append((word["text"], box)) text_targets, box_targets = zip(*_targets) - self.data.append(( - img_path, - dict(boxes=np.asarray(box_targets, dtype=int).clip(min=0), labels=text_targets) - )) + if recognition_task: + crops = crop_bboxes_from_image( + img_path=os.path.join(tmp_root, img_path), geoms=np.asarray(box_targets, dtype=int).clip(min=0) + ) + for crop, label in zip(crops, list(text_targets)): + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, np.asarray(box_targets, dtype=int).clip(min=0))) + else: + self.data.append(( + img_path, + dict(boxes=np.asarray(box_targets, dtype=int).clip(min=0), labels=list(text_targets)), + )) + + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -397,8 +461,8 @@

    Source code for doctr.datasets.cord

           
         
       
    -
    - + + diff --git a/v0.5.0/_modules/doctr/datasets/core.html b/v0.5.0/_modules/doctr/datasets/core.html deleted file mode 100644 index b3dcc29ff9..0000000000 --- a/v0.5.0/_modules/doctr/datasets/core.html +++ /dev/null @@ -1,417 +0,0 @@ - - - - - - - - - - - - doctr.datasets.core - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.core

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from pathlib import Path
    -from zipfile import ZipFile
    -from typing import List, Any, Optional, Tuple
    -import tensorflow as tf
    -
    -from doctr.models.utils import download_from_url
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset:
    -
    -    data: List[Any] = []
    -
    -    def __len__(self):
    -        return len(self.data)
    -
    -    def __getitem__(
    -        self,
    -        index: int
    -    ) -> Tuple[tf.Tensor, Any]:
    -
    -        img_name, target = self.data[index]
    -        # Read image
    -        img = tf.io.read_file(os.path.join(self.root, img_name))
    -        img = tf.image.decode_jpeg(img, channels=3)
    -        if self.sample_transforms is not None:
    -            img = self.sample_transforms(img)
    -
    -        return img, target
    -
    -    def extra_repr(self) -> str:
    -        return ""
    -
    -    def __repr__(self) -> str:
    -        return f"{self.__class__.__name__}({self.extra_repr()})"
    -
    -    @staticmethod
    -    def collate_fn(samples: List[Tuple[tf.Tensor, Any]]) -> Tuple[tf.Tensor, List[Any]]:
    -
    -        images, targets = zip(*samples)
    -        images = tf.stack(images, axis=0)
    -
    -        return images, list(targets)
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset): - """Implements an abstract dataset - - Args: - url: URL of the dataset - file_name: name of the file once downloaded - file_hash: expected SHA256 of the file - extract_archive: whether the downloaded file is an archive to be extracted - download: whether the dataset should be downloaded if not present on disk - overwrite: whether the archive should be re-extracted - """ - - def __init__( - self, - url: str, - file_name: Optional[str] = None, - file_hash: Optional[str] = None, - extract_archive: bool = False, - download: bool = False, - overwrite: bool = False, - ) -> None: - - dataset_cache = os.path.join(os.path.expanduser('~'), '.cache', 'doctr', 'datasets') - - file_name = file_name if isinstance(file_name, str) else os.path.basename(url) - # Download the file if not present - archive_path = os.path.join(dataset_cache, file_name) - - if not os.path.exists(archive_path) and not download: - raise ValueError("the dataset needs to be downloaded first with download=True") - - archive_path = download_from_url(url, file_name, file_hash, cache_subdir='datasets') - - # Extract the archive - if extract_archive: - archive_path = Path(archive_path) - dataset_path = archive_path.parent.joinpath(archive_path.stem) - if not dataset_path.is_dir() or overwrite: - with ZipFile(archive_path, 'r') as f: - f.extractall(path=dataset_path) - - # List images - self._root = dataset_path if extract_archive else archive_path - self.data: List[Any] = []
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.0/_modules/doctr/datasets/datasets/tensorflow.html b/v0.5.0/_modules/doctr/datasets/datasets/tensorflow.html deleted file mode 100644 index a236abd9fe..0000000000 --- a/v0.5.0/_modules/doctr/datasets/datasets/tensorflow.html +++ /dev/null @@ -1,356 +0,0 @@ - - - - - - - - - - - - doctr.datasets.datasets.tensorflow - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.datasets.tensorflow

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from typing import List, Any, Tuple
    -import tensorflow as tf
    -
    -from .base import _AbstractDataset, _VisionDataset
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset(_AbstractDataset):
    -
    -    def _read_sample(self, index: int) -> Tuple[tf.Tensor, Any]:
    -        img_name, target = self.data[index]
    -        # Read image
    -        img = tf.io.read_file(os.path.join(self.root, img_name))
    -        img = tf.image.decode_jpeg(img, channels=3)
    -
    -        return img, target
    -
    -    @staticmethod
    -    def collate_fn(samples: List[Tuple[tf.Tensor, Any]]) -> Tuple[tf.Tensor, List[Any]]:
    -
    -        images, targets = zip(*samples)
    -        images = tf.stack(images, axis=0)
    -
    -        return images, list(targets)
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset, _VisionDataset): - pass
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.0/_modules/doctr/datasets/detection.html b/v0.5.0/_modules/doctr/datasets/detection.html index 739563e466..718001e4cf 100644 --- a/v0.5.0/_modules/doctr/datasets/detection.html +++ b/v0.5.0/_modules/doctr/datasets/detection.html @@ -13,7 +13,7 @@ - + doctr.datasets.detection - docTR documentation @@ -430,7 +430,7 @@

    Source code for doctr.datasets.detection

         
       
    - + diff --git a/v0.5.0/_modules/doctr/datasets/doc_artefacts.html b/v0.5.0/_modules/doctr/datasets/doc_artefacts.html index 3313ae4660..94c32aaa0f 100644 --- a/v0.5.0/_modules/doctr/datasets/doc_artefacts.html +++ b/v0.5.0/_modules/doctr/datasets/doc_artefacts.html @@ -13,7 +13,7 @@ - + doctr.datasets.doc_artefacts - docTR documentation @@ -414,7 +414,7 @@

    Source code for doctr.datasets.doc_artefacts

       
    - + diff --git a/v0.5.0/_modules/doctr/datasets/funsd.html b/v0.5.0/_modules/doctr/datasets/funsd.html index 35d7ad4cf5..f08612f9fa 100644 --- a/v0.5.0/_modules/doctr/datasets/funsd.html +++ b/v0.5.0/_modules/doctr/datasets/funsd.html @@ -13,7 +13,7 @@ - + doctr.datasets.funsd - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.funsd

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
     from .datasets import VisionDataset
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['FUNSD']
    +__all__ = ["FUNSD"]
     
     
     
    -[docs] +[docs] class FUNSD(VisionDataset): """FUNSD dataset from `"FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents" <https://arxiv.org/pdf/1905.13538.pdf>`_. - Example:: - >>> from doctr.datasets import FUNSD - >>> train_set = FUNSD(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/funsd-grid.png&src=0 + :align: center + + >>> from doctr.datasets import FUNSD + >>> train_set = FUNSD(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - URL = 'https://guillaumejaume.github.io/FUNSD/dataset.zip' - SHA256 = 'c31735649e4f441bcbb4fd0f379574f7520b42286e80b01d80b445649d54761f' - FILE_NAME = 'funsd.zip' + URL = "https://guillaumejaume.github.io/FUNSD/dataset.zip" + SHA256 = "c31735649e4f441bcbb4fd0f379574f7520b42286e80b01d80b445649d54761f" + FILE_NAME = "funsd.zip" def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + super().__init__( + self.URL, + self.FILE_NAME, + self.SHA256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - super().__init__(self.URL, self.FILE_NAME, self.SHA256, True, **kwargs) self.train = train - self.sample_transforms = sample_transforms + np_dtype = np.float32 # Use the subset - subfolder = os.path.join('dataset', 'training_data' if train else 'testing_data') + subfolder = os.path.join("dataset", "training_data" if train else "testing_data") # # List images - self.root = os.path.join(self._root, subfolder, 'images') - self.data: List[Tuple[str, Dict[str, Any]]] = [] - for img_path in os.listdir(self.root): + tmp_root = os.path.join(self.root, subfolder, "images") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking FUNSD", total=len(os.listdir(tmp_root))): # File existence check - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem - with open(os.path.join(self._root, subfolder, 'annotations', f"{stem}.json"), 'rb') as f: + with open(os.path.join(self.root, subfolder, "annotations", f"{stem}.json"), "rb") as f: data = json.load(f) - _targets = [(word['text'], word['box']) for block in data['form'] - for word in block['words'] if len(word['text']) > 0] + _targets = [ + (word["text"], word["box"]) + for block in data["form"] + for word in block["words"] + if len(word["text"]) > 0 + ] text_targets, box_targets = zip(*_targets) - if rotated_bbox: - # box_targets: xmin, ymin, xmax, ymax -> x, y, w, h, alpha = 0 - box_targets = [ + if use_polygons: + # xmin, ymin, xmax, ymax -> (x, y) coordinates of top left, top right, bottom right, bottom left corners + box_targets = [ # type: ignore[assignment] [ - (box[0] + box[2]) / 2, (box[1] + box[3]) / 2, box[2] - box[0], box[3] - box[1], 0 - ] for box in box_targets + [box[0], box[1]], + [box[2], box[1]], + [box[2], box[3]], + [box[0], box[3]], + ] + for box in box_targets ] - self.data.append((img_path, dict(boxes=np.asarray(box_targets, dtype=int), labels=text_targets))) + if recognition_task: + crops = crop_bboxes_from_image( + img_path=os.path.join(tmp_root, img_path), geoms=np.asarray(box_targets, dtype=np_dtype) + ) + for crop, label in zip(crops, list(text_targets)): + # filter labels with unknown characters + if not any(char in label for char in ["☑", "☐", "\uf703", "\uf702"]): + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, np.asarray(box_targets, dtype=np_dtype))) + else: + self.data.append(( + img_path, + dict(boxes=np.asarray(box_targets, dtype=np_dtype), labels=list(text_targets)), + )) + + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -386,8 +453,8 @@

    Source code for doctr.datasets.funsd

           
         
       
    -
    - + + diff --git a/v0.5.0/_modules/doctr/datasets/generator/tensorflow.html b/v0.5.0/_modules/doctr/datasets/generator/tensorflow.html index 9f562582d9..a3e619f720 100644 --- a/v0.5.0/_modules/doctr/datasets/generator/tensorflow.html +++ b/v0.5.0/_modules/doctr/datasets/generator/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.datasets.generator.tensorflow - docTR documentation @@ -395,7 +395,7 @@

    Source code for doctr.datasets.generator.tensorflow

    - + diff --git a/v0.5.0/_modules/doctr/datasets/ic03.html b/v0.5.0/_modules/doctr/datasets/ic03.html index 3d221d07de..60e54a8a4b 100644 --- a/v0.5.0/_modules/doctr/datasets/ic03.html +++ b/v0.5.0/_modules/doctr/datasets/ic03.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic03 - docTR documentation @@ -468,7 +468,7 @@

    Source code for doctr.datasets.ic03

         
       
    - + diff --git a/v0.5.0/_modules/doctr/datasets/ic13.html b/v0.5.0/_modules/doctr/datasets/ic13.html index 8137e08e9f..219c98dcd1 100644 --- a/v0.5.0/_modules/doctr/datasets/ic13.html +++ b/v0.5.0/_modules/doctr/datasets/ic13.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic13 - docTR documentation @@ -440,7 +440,7 @@

    Source code for doctr.datasets.ic13

         
       
    - + diff --git a/v0.5.0/_modules/doctr/datasets/iiit5k.html b/v0.5.0/_modules/doctr/datasets/iiit5k.html index 1fc8ecfb27..b49c80fe18 100644 --- a/v0.5.0/_modules/doctr/datasets/iiit5k.html +++ b/v0.5.0/_modules/doctr/datasets/iiit5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiit5k - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.datasets.iiit5k

         
       
    - + diff --git a/v0.5.0/_modules/doctr/datasets/iiithws.html b/v0.5.0/_modules/doctr/datasets/iiithws.html index 07f5b13685..f7220afbc7 100644 --- a/v0.5.0/_modules/doctr/datasets/iiithws.html +++ b/v0.5.0/_modules/doctr/datasets/iiithws.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiithws - docTR documentation @@ -407,7 +407,7 @@

    Source code for doctr.datasets.iiithws

         
       
    - + diff --git a/v0.5.0/_modules/doctr/datasets/imgur5k.html b/v0.5.0/_modules/doctr/datasets/imgur5k.html index 68d433ca62..51c6545db4 100644 --- a/v0.5.0/_modules/doctr/datasets/imgur5k.html +++ b/v0.5.0/_modules/doctr/datasets/imgur5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.imgur5k - docTR documentation @@ -488,7 +488,7 @@

    Source code for doctr.datasets.imgur5k

         
       
    - + diff --git a/v0.5.0/_modules/doctr/datasets/loader.html b/v0.5.0/_modules/doctr/datasets/loader.html index d32e6da298..ed80350ef0 100644 --- a/v0.5.0/_modules/doctr/datasets/loader.html +++ b/v0.5.0/_modules/doctr/datasets/loader.html @@ -13,7 +13,7 @@ - + doctr.datasets.loader - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.loader

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import math
    -import tensorflow as tf
    -import numpy as np
    -from typing import Optional
    +from typing import Callable, Optional
     
    -from .multithreading import multithread_exec
    +import numpy as np
    +import tensorflow as tf
     
     __all__ = ["DataLoader"]
     
    @@ -293,12 +314,13 @@ 

    Source code for doctr.datasets.loader

         """Collate multiple elements into batches
     
         Args:
    +    ----
             samples: list of N tuples containing M elements
     
         Returns:
    +    -------
             Tuple of M sequences contianing N elements each
         """
    -
         batch_data = zip(*samples)
     
         tf_data = tuple(tf.stack(elt, axis=0) for elt in batch_data)
    @@ -307,23 +329,23 @@ 

    Source code for doctr.datasets.loader

     
     
     
    -[docs] +[docs] class DataLoader: """Implements a dataset wrapper for fast data loading - Example:: - >>> from doctr.datasets import FUNSD, 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) + >>> 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) Args: + ---- dataset: the dataset shuffle: whether the samples should be shuffled before passing it to the iterator batch_size: number of elements in each batch drop_last: if `True`, drops the last batch if it isn't full - workers: number of workers to use for data loading + collate_fn: function to merge samples into a batch """ def __init__( @@ -332,17 +354,22 @@

    Source code for doctr.datasets.loader

             shuffle: bool = True,
             batch_size: int = 1,
             drop_last: bool = False,
    -        workers: Optional[int] = None,
    +        collate_fn: Optional[Callable] = None,
         ) -> None:
             self.dataset = dataset
             self.shuffle = shuffle
             self.batch_size = batch_size
             nb = len(self.dataset) / batch_size
             self.num_batches = math.floor(nb) if drop_last else math.ceil(nb)
    -        self.collate_fn = self.dataset.collate_fn if hasattr(self.dataset, 'collate_fn') else default_collate
    -        self.workers = workers
    +        if collate_fn is None:
    +            self.collate_fn = self.dataset.collate_fn if hasattr(self.dataset, "collate_fn") else default_collate
    +        else:
    +            self.collate_fn = collate_fn
             self.reset()
     
    +    def __len__(self) -> int:
    +        return self.num_batches
    +
         def reset(self) -> None:
             # Updates indices after each epoch
             self._num_yielded = 0
    @@ -358,9 +385,9 @@ 

    Source code for doctr.datasets.loader

             if self._num_yielded < self.num_batches:
                 # Get next indices
                 idx = self._num_yielded * self.batch_size
    -            indices = self.indices[idx: min(len(self.dataset), idx + self.batch_size)]
    +            indices = self.indices[idx : min(len(self.dataset), idx + self.batch_size)]
     
    -            samples = multithread_exec(self.dataset.__getitem__, indices, threads=self.workers)
    +            samples = list(map(self.dataset.__getitem__, indices))
     
                 batch_data = self.collate_fn(samples)
     
    @@ -401,8 +428,8 @@ 

    Source code for doctr.datasets.loader

           
         
       
    -
    - +
    + diff --git a/v0.5.0/_modules/doctr/datasets/mjsynth.html b/v0.5.0/_modules/doctr/datasets/mjsynth.html index 77bb01d523..df34e49cf9 100644 --- a/v0.5.0/_modules/doctr/datasets/mjsynth.html +++ b/v0.5.0/_modules/doctr/datasets/mjsynth.html @@ -13,7 +13,7 @@ - + doctr.datasets.mjsynth - docTR documentation @@ -438,7 +438,7 @@

    Source code for doctr.datasets.mjsynth

         
       
    - + diff --git a/v0.5.0/_modules/doctr/datasets/ocr.html b/v0.5.0/_modules/doctr/datasets/ocr.html index 11297d5952..ce1ed8b0d4 100644 --- a/v0.5.0/_modules/doctr/datasets/ocr.html +++ b/v0.5.0/_modules/doctr/datasets/ocr.html @@ -13,7 +13,7 @@ - + doctr.datasets.ocr - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.ocr

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple
     
    -from .datasets import AbstractDataset
    -from doctr.utils.geometry import fit_rbbox
    +import numpy as np
     
    +from .datasets import AbstractDataset
     
    -__all__ = ['OCRDataset']
    +__all__ = ["OCRDataset"]
     
     
     
    -[docs] +[docs] class OCRDataset(AbstractDataset): """Implements an OCR dataset + >>> from doctr.datasets import OCRDataset + >>> train_set = OCRDataset(img_folder="/path/to/images", + >>> label_file="/path/to/labels.json") + >>> img, target = train_set[0] + Args: + ---- img_folder: local path to image folder (all jpg at the root) label_file: local path to the label file - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) - **kwargs: keyword arguments from `VisionDataset`. + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + **kwargs: keyword arguments from `AbstractDataset`. """ def __init__( self, img_folder: str, label_file: str, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, **kwargs: Any, ) -> None: - - self.sample_transforms = sample_transforms - self.root = img_folder + super().__init__(img_folder, **kwargs) # List images self.data: List[Tuple[str, Dict[str, Any]]] = [] - with open(label_file, 'rb') as f: + np_dtype = np.float32 + with open(label_file, "rb") as f: data = json.load(f) - for file_dic in data: + for img_name, annotations in data.items(): # Get image path - img_name = Path(os.path.basename(file_dic["raw-archive-filepath"])).stem + '.jpg' + img_name = Path(img_name) # File existence check if not os.path.exists(os.path.join(self.root, img_name)): raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_name)}") # handle empty images - if (len(file_dic["coordinates"]) == 0 or - (len(file_dic["coordinates"]) == 1 and file_dic["coordinates"][0] == "N/A")): - self.data.append((img_name, dict(boxes=np.zeros((0, 4), dtype=np.float32), labels=[]))) + if len(annotations["typed_words"]) == 0: + self.data.append((img_name, dict(boxes=np.zeros((0, 4), dtype=np_dtype), labels=[]))) continue - is_valid: List[bool] = [] - box_targets: List[List[float]] = [] - for box in file_dic["coordinates"]: - if rotated_bbox: - x, y, w, h, alpha = fit_rbbox(np.asarray(box, dtype=np.float32)) - box = [x, y, w, h, alpha] - is_valid.append(w > 0 and h > 0) - else: - xs, ys = zip(*box) - box = [min(xs), min(ys), max(xs), max(ys)] - is_valid.append(box[0] < box[2] and box[1] < box[3]) - if is_valid[-1]: - box_targets.append(box) + # Unpack the straight boxes (xmin, ymin, xmax, ymax) + geoms = [list(map(float, obj["geometry"][:4])) for obj in annotations["typed_words"]] + if use_polygons: + # (x, y) coordinates of top left, top right, bottom right, bottom left corners + geoms = [ + [geom[:2], [geom[2], geom[1]], geom[2:], [geom[0], geom[3]]] # type: ignore[list-item] + for geom in geoms + ] + + text_targets = [obj["value"] for obj in annotations["typed_words"]] - text_targets = [word for word, _valid in zip(file_dic["string"], is_valid) if _valid] - self.data.append((img_name, dict(boxes=np.asarray(box_targets, dtype=np.float32), labels=text_targets)))
    + self.data.append((img_name, dict(boxes=np.asarray(geoms, dtype=np_dtype), labels=text_targets)))
    @@ -383,8 +402,8 @@

    Source code for doctr.datasets.ocr

           
         
       
    - - + + diff --git a/v0.5.0/_modules/doctr/datasets/recognition.html b/v0.5.0/_modules/doctr/datasets/recognition.html index 512c70c308..1754789364 100644 --- a/v0.5.0/_modules/doctr/datasets/recognition.html +++ b/v0.5.0/_modules/doctr/datasets/recognition.html @@ -13,7 +13,7 @@ - + doctr.datasets.recognition - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.datasets.recognition

         
       
    - + diff --git a/v0.5.0/_modules/doctr/datasets/sroie.html b/v0.5.0/_modules/doctr/datasets/sroie.html index 66fd4ca3e0..04cf10bda2 100644 --- a/v0.5.0/_modules/doctr/datasets/sroie.html +++ b/v0.5.0/_modules/doctr/datasets/sroie.html @@ -13,7 +13,7 @@ - + doctr.datasets.sroie - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.sroie

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import csv
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
     from .datasets import VisionDataset
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['SROIE']
    +__all__ = ["SROIE"]
     
     
     
    -[docs] +[docs] class SROIE(VisionDataset): """SROIE dataset from `"ICDAR2019 Competition on Scanned Receipt OCR and Information Extraction" <https://arxiv.org/pdf/2103.10213.pdf>`_. - Example:: - >>> from doctr.datasets import SROIE - >>> train_set = SROIE(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/sroie-grid.png&src=0 + :align: center + + >>> from doctr.datasets import SROIE + >>> train_set = SROIE(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - TRAIN = ('https://github.com/mindee/doctr/releases/download/v0.1.1/sroie2019_train_task1.zip', - 'd4fa9e60abb03500d83299c845b9c87fd9c9430d1aeac96b83c5d0bb0ab27f6f') - TEST = ('https://github.com/mindee/doctr/releases/download/v0.1.1/sroie2019_test.zip', - '41b3c746a20226fddc80d86d4b2a903d43b5be4f521dd1bbe759dbf8844745e2') + TRAIN = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/sroie2019_train_task1.zip&src=0", + "d4fa9e60abb03500d83299c845b9c87fd9c9430d1aeac96b83c5d0bb0ab27f6f", + "sroie2019_train_task1.zip", + ) + TEST = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/sroie2019_test.zip&src=0", + "41b3c746a20226fddc80d86d4b2a903d43b5be4f521dd1bbe759dbf8844745e2", + "sroie2019_test.zip", + ) def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + url, sha256, name = self.TRAIN if train else self.TEST + super().__init__( + url, + name, + sha256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - url, sha256 = self.TRAIN if train else self.TEST - super().__init__(url, None, sha256, True, **kwargs) - self.sample_transforms = sample_transforms self.train = train - if rotated_bbox: - raise NotImplementedError + tmp_root = os.path.join(self.root, "images") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] + np_dtype = np.float32 - # # List images - self.root = os.path.join(self._root, 'images') - self.data: List[Tuple[str, Dict[str, Any]]] = [] - for img_path in os.listdir(self.root): + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking SROIE", total=len(os.listdir(tmp_root))): # File existence check - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem - _targets = [] - with open(os.path.join(self._root, 'annotations', f"{stem}.txt"), encoding='latin') as f: - for row in csv.reader(f, delimiter=','): - # Safeguard for blank lines - if len(row) > 0: - # Label may contain commas - label = ",".join(row[8:]) - # Reduce 8 coords to 4 - p1_x, p1_y, p2_x, p2_y, p3_x, p3_y, p4_x, p4_y = map(int, row[:8]) - left, right = min(p1_x, p2_x, p3_x, p4_x), max(p1_x, p2_x, p3_x, p4_x) - top, bot = min(p1_y, p2_y, p3_y, p4_y), max(p1_y, p2_y, p3_y, p4_y) - if len(label) > 0: - _targets.append((label, [left, top, right, bot])) - - text_targets, box_targets = zip(*_targets) - - self.data.append((img_path, dict(boxes=np.asarray(box_targets, dtype=np.float32), labels=text_targets))) + with open(os.path.join(self.root, "annotations", f"{stem}.txt"), encoding="latin") as f: + _rows = [row for row in list(csv.reader(f, delimiter=",")) if len(row) > 0] + + labels = [",".join(row[8:]) for row in _rows] + # reorder coordinates (8 -> (4,2) -> + # (x, y) coordinates of top left, top right, bottom right, bottom left corners) and filter empty lines + coords: np.ndarray = np.stack( + [np.array(list(map(int, row[:8])), dtype=np_dtype).reshape((4, 2)) for row in _rows], axis=0 + ) + + if not use_polygons: + # xmin, ymin, xmax, ymax + coords = np.concatenate((coords.min(axis=1), coords.max(axis=1)), axis=1) + + if recognition_task: + crops = crop_bboxes_from_image(img_path=os.path.join(tmp_root, img_path), geoms=coords) + for crop, label in zip(crops, labels): + if crop.shape[0] > 0 and crop.shape[1] > 0 and len(label) > 0: + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, coords)) + else: + self.data.append((img_path, dict(boxes=coords, labels=labels))) + + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -390,8 +444,8 @@

    Source code for doctr.datasets.sroie

           
         
       
    -
    - + + diff --git a/v0.5.0/_modules/doctr/datasets/svhn.html b/v0.5.0/_modules/doctr/datasets/svhn.html index 48e4e4d210..60e02b1b3b 100644 --- a/v0.5.0/_modules/doctr/datasets/svhn.html +++ b/v0.5.0/_modules/doctr/datasets/svhn.html @@ -13,7 +13,7 @@ - + doctr.datasets.svhn - docTR documentation @@ -473,7 +473,7 @@

    Source code for doctr.datasets.svhn

         
       
    - + diff --git a/v0.5.0/_modules/doctr/datasets/svt.html b/v0.5.0/_modules/doctr/datasets/svt.html index 4144dc6b9b..a997fcbb50 100644 --- a/v0.5.0/_modules/doctr/datasets/svt.html +++ b/v0.5.0/_modules/doctr/datasets/svt.html @@ -13,7 +13,7 @@ - + doctr.datasets.svt - docTR documentation @@ -459,7 +459,7 @@

    Source code for doctr.datasets.svt

         
       
    - + diff --git a/v0.5.0/_modules/doctr/datasets/synthtext.html b/v0.5.0/_modules/doctr/datasets/synthtext.html index 3b9de506a7..c776e1d673 100644 --- a/v0.5.0/_modules/doctr/datasets/synthtext.html +++ b/v0.5.0/_modules/doctr/datasets/synthtext.html @@ -13,7 +13,7 @@ - + doctr.datasets.synthtext - docTR documentation @@ -470,7 +470,7 @@

    Source code for doctr.datasets.synthtext

         
       
    - + diff --git a/v0.5.0/_modules/doctr/datasets/utils.html b/v0.5.0/_modules/doctr/datasets/utils.html index 2259698c0f..bde9304597 100644 --- a/v0.5.0/_modules/doctr/datasets/utils.html +++ b/v0.5.0/_modules/doctr/datasets/utils.html @@ -13,7 +13,7 @@ - + doctr.datasets.utils - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.utils

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import string
     import unicodedata
    +from collections.abc import Sequence
    +from functools import partial
    +from pathlib import Path
    +from typing import Any, Dict, List, Optional, Tuple, TypeVar, Union
    +from typing import Sequence as SequenceType
    +
     import numpy as np
    -from typing import List, Optional, Any
    +from PIL import Image
    +
    +from doctr.io.image import get_img_shape
    +from doctr.utils.geometry import convert_to_relative_coords, extract_crops, extract_rcrops
     
     from .vocabs import VOCABS
     
    -__all__ = ['translate', 'encode_sequence', 'decode_sequence', 'encode_sequences']
    +__all__ = ["translate", "encode_string", "decode_sequence", "encode_sequences", "pre_transform_multiclass"]
    +
    +ImageTensor = TypeVar("ImageTensor")
     
     
     def translate(
         input_string: str,
         vocab_name: str,
    -    unknown_char: str = '■',
    +    unknown_char: str = "■",
     ) -> str:
         """Translate a string input in a given vocabulary
     
         Args:
    +    ----
             input_string: input string to translate
             vocab_name: vocabulary to use (french, latin, ...)
             unknown_char: unknown character for non-translatable characters
     
         Returns:
    -        A string translated in a given vocab"""
    -
    +    -------
    +        A string translated in a given vocab
    +    """
         if VOCABS.get(vocab_name) is None:
             raise KeyError("output vocabulary must be in vocabs dictionnary")
     
    -    translated = ''
    +    translated = ""
         for char in input_string:
             if char not in VOCABS[vocab_name]:
                 # we need to translate char into a vocab char
    @@ -315,51 +350,63 @@ 

    Source code for doctr.datasets.utils

                     # remove whitespaces
                     continue
                 # normalize character if it is not in vocab
    -            char = unicodedata.normalize('NFD', char).encode('ascii', 'ignore').decode('ascii')
    -            if char == '' or char not in VOCABS[vocab_name]:
    +            char = unicodedata.normalize("NFD", char).encode("ascii", "ignore").decode("ascii")
    +            if char == "" or char not in VOCABS[vocab_name]:
                     # if normalization fails or char still not in vocab, return unknown character)
                     char = unknown_char
             translated += char
         return translated
     
     
    -def encode_sequence(
    +def encode_string(
         input_string: str,
         vocab: str,
     ) -> List[int]:
         """Given a predefined mapping, encode the string to a sequence of numbers
     
         Args:
    +    ----
             input_string: string to encode
             vocab: vocabulary (string), the encoding is given by the indexing of the character sequence
     
         Returns:
    -        A list encoding the input_string"""
    -
    -    return list(map(vocab.index, input_string))  # type: ignore[arg-type]
    +    -------
    +        A list encoding the input_string
    +    """
    +    try:
    +        return list(map(vocab.index, input_string))
    +    except ValueError:
    +        raise ValueError(
    +            f"some characters cannot be found in 'vocab'. \
    +                         Please check the input string {input_string} and the vocabulary {vocab}"
    +        )
     
     
     def decode_sequence(
    -    input_array: np.array,
    +    input_seq: Union[np.ndarray, SequenceType[int]],
         mapping: str,
     ) -> str:
         """Given a predefined mapping, decode the sequence of numbers to a string
     
         Args:
    -        input_array: array to decode
    +    ----
    +        input_seq: array to decode
             mapping: vocabulary (string), the encoding is given by the indexing of the character sequence
     
         Returns:
    -        A string, decoded from input_array"""
    -
    -    if not input_array.dtype == np.int_ or input_array.max() >= len(mapping):
    +    -------
    +        A string, decoded from input_seq
    +    """
    +    if not isinstance(input_seq, (Sequence, np.ndarray)):
    +        raise TypeError("Invalid sequence type")
    +    if isinstance(input_seq, np.ndarray) and (input_seq.dtype != np.int_ or input_seq.max() >= len(mapping)):
             raise AssertionError("Input must be an array of int, with max less than mapping size")
    -    decoded = ''.join(mapping[idx] for idx in input_array)
    -    return decoded
    +
    +    return "".join(map(mapping.__getitem__, input_seq))
     
     
     
    -[docs] +[docs] def encode_sequences( sequences: List[str], vocab: str, @@ -367,48 +414,53 @@

    Source code for doctr.datasets.utils

         eos: int = -1,
         sos: Optional[int] = None,
         pad: Optional[int] = None,
    -    **kwargs: Any,
    +    dynamic_seq_length: bool = False,
     ) -> np.ndarray:
         """Encode character sequences using a given vocab as mapping
     
         Args:
    +    ----
             sequences: the list of character sequences of size N
             vocab: the ordered vocab to use for encoding
             target_size: maximum length of the encoded data
             eos: encoding of End Of String
             sos: optional encoding of Start Of String
             pad: optional encoding for padding. In case of padding, all sequences are followed by 1 EOS then PAD
    +        dynamic_seq_length: if `target_size` is specified, uses it as upper bound and enables dynamic sequence size
     
         Returns:
    +    -------
             the padded encoded data as a tensor
         """
    -
         if 0 <= eos < len(vocab):
             raise ValueError("argument 'eos' needs to be outside of vocab possible indices")
     
    -    if not isinstance(target_size, int):
    -        target_size = max(len(w) for w in sequences)
    -        if sos:
    -            target_size += 1
    -        if pad:
    -            target_size += 1
    +    if not isinstance(target_size, int) or dynamic_seq_length:
    +        # Maximum string length + EOS
    +        max_length = max(len(w) for w in sequences) + 1
    +        if isinstance(sos, int):
    +            max_length += 1
    +        if isinstance(pad, int):
    +            max_length += 1
    +        target_size = max_length if not isinstance(target_size, int) else min(max_length, target_size)
     
         # Pad all sequences
    -    if pad:  # pad with padding symbol
    +    if isinstance(pad, int):  # pad with padding symbol
             if 0 <= pad < len(vocab):
                 raise ValueError("argument 'pad' needs to be outside of vocab possible indices")
             # In that case, add EOS at the end of the word before padding
    -        encoded_data = np.full([len(sequences), target_size], pad, dtype=np.int32)
    +        default_symbol = pad
         else:  # pad with eos symbol
    -        encoded_data = np.full([len(sequences), target_size], eos, dtype=np.int32)
    +        default_symbol = eos
    +    encoded_data: np.ndarray = np.full([len(sequences), target_size], default_symbol, dtype=np.int32)
     
    -    for idx, seq in enumerate(sequences):
    -        encoded_seq = encode_sequence(seq, vocab)
    -        if pad:  # add eos at the end of the sequence
    -            encoded_seq.append(eos)
    -        encoded_data[idx, :min(len(encoded_seq), target_size)] = encoded_seq[:min(len(encoded_seq), target_size)]
    +    # Encode the strings
    +    for idx, seq in enumerate(map(partial(encode_string, vocab=vocab), sequences)):
    +        if isinstance(pad, int):  # add eos at the end of the sequence
    +            seq.append(eos)
    +        encoded_data[idx, : min(len(seq), target_size)] = seq[: min(len(seq), target_size)]
     
    -    if sos:  # place eos symbol at the beginning of each sequence
    +    if isinstance(sos, int):  # place sos symbol at the beginning of each sequence
             if 0 <= sos < len(vocab):
                 raise ValueError("argument 'sos' needs to be outside of vocab possible indices")
             encoded_data = np.roll(encoded_data, 1)
    @@ -416,6 +468,59 @@ 

    Source code for doctr.datasets.utils

     
         return encoded_data
    + + +def convert_target_to_relative( + img: ImageTensor, target: Union[np.ndarray, Dict[str, Any]] +) -> Tuple[ImageTensor, Union[Dict[str, Any], np.ndarray]]: + if isinstance(target, np.ndarray): + target = convert_to_relative_coords(target, get_img_shape(img)) + else: + target["boxes"] = convert_to_relative_coords(target["boxes"], get_img_shape(img)) + return img, target + + +def crop_bboxes_from_image(img_path: Union[str, Path], geoms: np.ndarray) -> List[np.ndarray]: + """Crop a set of bounding boxes from an image + + Args: + ---- + img_path: path to the image + geoms: a array of polygons of shape (N, 4, 2) or of straight boxes of shape (N, 4) + + Returns: + ------- + a list of cropped images + """ + with Image.open(img_path) as pil_img: + img: np.ndarray = np.array(pil_img.convert("RGB")) + # Polygon + if geoms.ndim == 3 and geoms.shape[1:] == (4, 2): + return extract_rcrops(img, geoms.astype(dtype=int)) + if geoms.ndim == 2 and geoms.shape[1] == 4: + return extract_crops(img, geoms.astype(dtype=int)) + raise ValueError("Invalid geometry format") + + +def pre_transform_multiclass(img, target: Tuple[np.ndarray, List]) -> Tuple[np.ndarray, Dict[str, List]]: + """Converts multiclass target to relative coordinates. + + Args: + ---- + img: Image + target: tuple of target polygons and their classes names + + Returns: + ------- + Image and dictionary of boxes, with class names as keys + """ + boxes = convert_to_relative_coords(target[0], get_img_shape(img)) + boxes_classes = target[1] + boxes_dict: Dict = {k: [] for k in sorted(set(boxes_classes))} + for k, poly in zip(boxes_classes, boxes): + boxes_dict[k].append(poly) + boxes_dict = {k: np.stack(v, axis=0) for k, v in boxes_dict.items()} + return img, boxes_dict
    @@ -448,8 +553,8 @@

    Source code for doctr.datasets.utils

           
         
       
    - - + + diff --git a/v0.5.0/_modules/doctr/datasets/wildreceipt.html b/v0.5.0/_modules/doctr/datasets/wildreceipt.html index c543ee7cac..12c6aebd14 100644 --- a/v0.5.0/_modules/doctr/datasets/wildreceipt.html +++ b/v0.5.0/_modules/doctr/datasets/wildreceipt.html @@ -13,7 +13,7 @@ - + doctr.datasets.wildreceipt - docTR documentation @@ -454,7 +454,7 @@

    Source code for doctr.datasets.wildreceipt

         
       
    - + diff --git a/v0.5.0/_modules/doctr/documents/elements.html b/v0.5.0/_modules/doctr/documents/elements.html deleted file mode 100644 index 10c1e142d2..0000000000 --- a/v0.5.0/_modules/doctr/documents/elements.html +++ /dev/null @@ -1,577 +0,0 @@ - - - - - - - - - - - - doctr.documents.elements - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.elements

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import matplotlib.pyplot as plt
    -from typing import Tuple, Dict, List, Any, Optional, Union
    -
    -from doctr.utils.geometry import resolve_enclosing_bbox, resolve_enclosing_rbbox
    -from doctr.utils.visualization import visualize_page
    -from doctr.utils.common_types import BoundingBox, RotatedBbox
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['Element', 'Word', 'Artefact', 'Line', 'Block', 'Page', 'Document']
    -
    -
    -class Element(NestedObject):
    -    """Implements an abstract document element with exporting and text rendering capabilities"""
    -
    -    _exported_keys: List[str] = []
    -
    -    def __init__(self, **kwargs: Any) -> None:
    -        self._children_names: List[str] = []
    -        for k, v in kwargs.items():
    -            setattr(self, k, v)
    -            self._children_names.append(k)
    -
    -    def export(self) -> Dict[str, Any]:
    -        """Exports the object into a nested dict format"""
    -
    -        export_dict = {k: getattr(self, k) for k in self._exported_keys}
    -        for children_name in self._children_names:
    -            export_dict[children_name] = [c.export() for c in getattr(self, children_name)]
    -
    -        return export_dict
    -
    -    def render(self) -> str:
    -        raise NotImplementedError
    -
    -
    -
    -[docs] -class Word(Element): - """Implements a word element - - Args: - value: the text string of the word - confidence: the confidence associated with the text prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size - """ - - _exported_keys: List[str] = ["value", "confidence", "geometry"] - - def __init__(self, value: str, confidence: float, geometry: Union[BoundingBox, RotatedBbox]) -> None: - super().__init__() - self.value = value - self.confidence = confidence - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return self.value - - def extra_repr(self) -> str: - return f"value='{self.value}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Artefact(Element): - """Implements a non-textual element - - Args: - artefact_type: the type of artefact - confidence: the confidence of the type prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. - """ - - _exported_keys: List[str] = ["geometry", "type", "confidence"] - - def __init__(self, artefact_type: str, confidence: float, geometry: BoundingBox) -> None: - super().__init__() - self.geometry = geometry - self.type = artefact_type - self.confidence = confidence - - def render(self) -> str: - """Renders the full text of the element""" - return f"[{self.type.upper()}]" - - def extra_repr(self) -> str: - return f"type='{self.type}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Line(Element): - """Implements a line element as a collection of words - - Args: - words: list of word elements - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all words in it. - """ - - _exported_keys: List[str] = ["geometry"] - words: List[Word] = [] - - def __init__( - self, - words: List[Word], - geometry: Optional[Union[BoundingBox, RotatedBbox]] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - # Check whether this is a rotated or straight box - box_resolution_fn = resolve_enclosing_rbbox if len(words[0].geometry) == 5 else resolve_enclosing_bbox - geometry = box_resolution_fn([w.geometry for w in words]) # type: ignore[operator, misc] - - super().__init__(words=words) - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return " ".join(w.render() for w in self.words)
    - - - -
    -[docs] -class Block(Element): - """Implements a block element as a collection of lines and artefacts - - Args: - lines: list of line elements - artefacts: list of artefacts - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all lines and artefacts in it. - """ - - _exported_keys: List[str] = ["geometry"] - lines: List[Line] = [] - artefacts: List[Artefact] = [] - - def __init__( - self, - lines: List[Line] = [], - artefacts: List[Artefact] = [], - geometry: Optional[Union[BoundingBox, RotatedBbox]] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - line_boxes = [word.geometry for line in lines for word in line.words] - artefact_boxes = [artefact.geometry for artefact in artefacts] - box_resolution_fn = resolve_enclosing_rbbox if len(lines[0].geometry) == 5 else resolve_enclosing_bbox - geometry = box_resolution_fn(line_boxes + artefact_boxes) # type: ignore[operator, arg-type] - - super().__init__(lines=lines, artefacts=artefacts) - self.geometry = geometry - - def render(self, line_break: str = '\n') -> str: - """Renders the full text of the element""" - return line_break.join(line.render() for line in self.lines)
    - - - -
    -[docs] -class Page(Element): - """Implements a page element as a collection of blocks - - Args: - blocks: list of block elements - page_idx: the index of the page in the input raw document - dimensions: the page size in pixels in format (width, height) - orientation: a dictionary with the value of the rotation angle in degress and confidence of the prediction - language: a dictionary with the language value and confidence of the prediction - """ - - _exported_keys: List[str] = ["page_idx", "dimensions", "orientation", "language"] - blocks: List[Block] = [] - - def __init__( - self, - blocks: List[Block], - page_idx: int, - dimensions: Tuple[int, int], - orientation: Optional[Dict[str, Any]] = None, - language: Optional[Dict[str, Any]] = None, - ) -> None: - super().__init__(blocks=blocks) - self.page_idx = page_idx - self.dimensions = dimensions - self.orientation = orientation if isinstance(orientation, dict) else dict(value=None, confidence=None) - self.language = language if isinstance(language, dict) else dict(value=None, confidence=None) - - def render(self, block_break: str = '\n\n') -> str: - """Renders the full text of the element""" - return block_break.join(b.render() for b in self.blocks) - - def extra_repr(self) -> str: - return f"dimensions={self.dimensions}" - -
    -[docs] - def show( - self, page: np.ndarray, interactive: bool = True, **kwargs - ) -> None: - """Overlay the result on a given image - - Args: - page: image encoded as a numpy array in uint8 - interactive: whether the display should be interactive - """ - visualize_page(self.export(), page, interactive=interactive) - plt.show(**kwargs)
    -
    - - - -
    -[docs] -class Document(Element): - """Implements a document element as a collection of pages - - Args: - pages: list of page elements - """ - - pages: List[Page] = [] - - def __init__( - self, - pages: List[Page], - ) -> None: - super().__init__(pages=pages) - - def render(self, page_break: str = '\n\n\n\n') -> str: - """Renders the full text of the element""" - return page_break.join(p.render() for p in self.pages) - -
    -[docs] - def show(self, pages: List[np.ndarray], **kwargs) -> None: - """Overlay the result on a given image - - Args: - pages: list of images encoded as numpy arrays in uint8 - """ - for img, result in zip(pages, self.pages): - result.show(img, **kwargs)
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.0/_modules/doctr/documents/reader.html b/v0.5.0/_modules/doctr/documents/reader.html deleted file mode 100644 index cdcd814b6c..0000000000 --- a/v0.5.0/_modules/doctr/documents/reader.html +++ /dev/null @@ -1,612 +0,0 @@ - - - - - - - - - - - - doctr.documents.reader - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.reader

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import cv2
    -from pathlib import Path
    -import fitz
    -from weasyprint import HTML
    -from typing import List, Tuple, Optional, Any, Union, Sequence, Dict
    -
    -__all__ = ['read_pdf', 'read_img', 'read_html', 'DocumentFile', 'PDF']
    -
    -
    -AbstractPath = Union[str, Path]
    -AbstractFile = Union[AbstractPath, bytes]
    -Bbox = Tuple[float, float, float, float]
    -
    -
    -
    -[docs] -def read_img( - file: AbstractFile, - output_size: Optional[Tuple[int, int]] = None, - rgb_output: bool = True, -) -> np.ndarray: - """Read an image file into numpy format - - Example:: - >>> from doctr.documents import read_img - >>> page = read_img("path/to/your/doc.jpg") - - Args: - file: the path to the image file - output_size: the expected output size of each page in format H x W - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - Returns: - the page decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)): - if not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - img = cv2.imread(str(file), cv2.IMREAD_COLOR) - elif isinstance(file, bytes): - file = np.frombuffer(file, np.uint8) - img = cv2.imdecode(file, cv2.IMREAD_COLOR) - else: - raise TypeError("unsupported object type for argument 'file'") - - # Validity check - if img is None: - raise ValueError("unable to read file.") - # Resizing - if isinstance(output_size, tuple): - img = cv2.resize(img, output_size[::-1], interpolation=cv2.INTER_LINEAR) - # Switch the channel order - if rgb_output: - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - return img
    - - - -
    -[docs] -def read_pdf(file: AbstractFile, **kwargs: Any) -> fitz.Document: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_pdf - >>> doc = read_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)) and not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - - fitz_args: Dict[str, AbstractFile] = {} - - if isinstance(file, (str, Path)): - fitz_args['filename'] = file - elif isinstance(file, bytes): - fitz_args['stream'] = file - else: - raise TypeError("unsupported object type for argument 'file'") - - # Read pages with fitz and convert them to numpy ndarrays - return fitz.open(**fitz_args, filetype="pdf", **kwargs)
    - - - -def convert_page_to_numpy( - page: fitz.fitz.Page, - output_size: Optional[Tuple[int, int]] = None, - bgr_output: bool = False, - default_scales: Tuple[float, float] = (2, 2), -) -> np.ndarray: - """Convert a fitz page to a numpy-formatted image - - Args: - page: the page of a file read with PyMuPDF - output_size: the expected output size of each page in format H x W. Default goes to 840 x 595 for A4 pdf, - if you want to increase the resolution while preserving the original A4 aspect ratio can pass (1024, 726) - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - default_scales: spatial scaling to be applied when output_size is not specified where (1, 1) - corresponds to 72 dpi rendering. - - Returns: - the rendered image in numpy format - """ - - # If no output size is specified, keep the origin one - if output_size is not None: - scales = (output_size[1] / page.MediaBox[2], output_size[0] / page.MediaBox[3]) - else: - # Default 72 DPI (scales of (1, 1)) is unnecessarily low - scales = default_scales - - transform_matrix = fitz.Matrix(*scales) - - # Generate the pixel map using the transformation matrix - pixmap = page.getPixmap(matrix=transform_matrix) - # Decode it into a numpy - img = np.frombuffer(pixmap.samples, dtype=np.uint8).reshape(pixmap.height, pixmap.width, 3) - - # Switch the channel order - if bgr_output: - img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) - - return img - - -
    -[docs] -def read_html(url: str, **kwargs: Any) -> bytes: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_html - >>> doc = read_html("https://www.yoursite.com") - - Args: - url: URL of the target web page - Returns: - decoded PDF file as a bytes stream - """ - - return HTML(url, **kwargs).write_pdf()
    - - - -
    -[docs] -class PDF: - """PDF document template - - Args: - doc: input PDF document - """ - def __init__(self, doc: fitz.Document) -> None: - self.doc = doc - -
    -[docs] - def as_images(self, **kwargs) -> List[np.ndarray]: - """Convert all document pages to images - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images() - - Args: - kwargs: keyword arguments of `convert_page_to_numpy` - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - return [convert_page_to_numpy(page, **kwargs) for page in self.doc]
    - - - def get_page_words(self, idx, **kwargs) -> List[Tuple[Bbox, str]]: - """Get the annotations for all words of a given page""" - - # xmin, ymin, xmax, ymax, value, block_idx, line_idx, word_idx - return [(info[:4], info[4]) for info in self.doc[idx].getTextWords(**kwargs)] - -
    -[docs] - def get_words(self, **kwargs) -> List[List[Tuple[Bbox, str]]]: - """Get the annotations for all words in the document - - Example:: - >>> from doctr.documents import DocumentFile - >>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words() - - Args: - kwargs: keyword arguments of `fitz.Page.getTextWords` - Returns: - the list of pages annotations, represented as a list of tuple (bounding box, value) - """ - return [self.get_page_words(idx, **kwargs) for idx in range(len(self.doc))]
    - - - def get_page_artefacts(self, idx) -> List[Tuple[float, float, float, float]]: - return [tuple(self.doc[idx].getImageBbox(artefact)) # type: ignore[misc] - for artefact in self.doc[idx].get_images(full=True)] - -
    -[docs] - def get_artefacts(self) -> List[List[Tuple[float, float, float, float]]]: - """Get the artefacts for the entire document - - Example:: - >>> from doctr.documents import DocumentFile - >>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts() - - Returns: - the list of pages artefacts, represented as a list of bounding boxes - """ - - return [self.get_page_artefacts(idx) for idx in range(len(self.doc))]
    -
    - - - -
    -[docs] -class DocumentFile: - """Read a document from multiple extensions""" - -
    -[docs] - @classmethod - def from_pdf(cls, file: AbstractFile, **kwargs) -> PDF: - """Read a PDF file - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file or a binary stream - Returns: - a PDF document - """ - - doc = read_pdf(file, **kwargs) - - return PDF(doc)
    - - -
    -[docs] - @classmethod - def from_url(cls, url: str, **kwargs) -> PDF: - """Interpret a web page as a PDF document - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_url("https://www.yoursite.com") - - Args: - url: the URL of the target web page - Returns: - a PDF document - """ - pdf_stream = read_html(url) - return cls.from_pdf(pdf_stream, **kwargs)
    - - -
    -[docs] - @classmethod - def from_images(cls, files: Union[Sequence[AbstractFile], AbstractFile], **kwargs) -> List[np.ndarray]: - """Read an image file (or a collection of image files) and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"]) - - Args: - files: the path to the image file or a binary stream, or a collection of those - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - if isinstance(files, (str, Path, bytes)): - files = [files] - - return [read_img(file, **kwargs) for file in files]
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.0/_modules/doctr/io/elements.html b/v0.5.0/_modules/doctr/io/elements.html index 753a47455c..e049d6ce30 100644 --- a/v0.5.0/_modules/doctr/io/elements.html +++ b/v0.5.0/_modules/doctr/io/elements.html @@ -13,7 +13,7 @@ - + doctr.io.elements - docTR documentation @@ -1008,7 +1008,7 @@

    Source code for doctr.io.elements

         
       
    - + diff --git a/v0.5.0/_modules/doctr/io/html.html b/v0.5.0/_modules/doctr/io/html.html index 7ad5b97031..be73631500 100644 --- a/v0.5.0/_modules/doctr/io/html.html +++ b/v0.5.0/_modules/doctr/io/html.html @@ -13,7 +13,7 @@ - + doctr.io.html - docTR documentation @@ -360,7 +360,7 @@

    Source code for doctr.io.html

         
       
    - + diff --git a/v0.5.0/_modules/doctr/io/image/base.html b/v0.5.0/_modules/doctr/io/image/base.html index 336b4bff0e..a50c95d595 100644 --- a/v0.5.0/_modules/doctr/io/image/base.html +++ b/v0.5.0/_modules/doctr/io/image/base.html @@ -13,7 +13,7 @@ - + doctr.io.image.base - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.io.image.base

         
       
    - + diff --git a/v0.5.0/_modules/doctr/io/image/tensorflow.html b/v0.5.0/_modules/doctr/io/image/tensorflow.html index f1846820a3..3b9e731756 100644 --- a/v0.5.0/_modules/doctr/io/image/tensorflow.html +++ b/v0.5.0/_modules/doctr/io/image/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.io.image.tensorflow - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.io.image.tensorflow

         
       
    - + diff --git a/v0.5.0/_modules/doctr/io/pdf.html b/v0.5.0/_modules/doctr/io/pdf.html index e3abf6960b..e5b94811c3 100644 --- a/v0.5.0/_modules/doctr/io/pdf.html +++ b/v0.5.0/_modules/doctr/io/pdf.html @@ -13,7 +13,7 @@ - + doctr.io.pdf - docTR documentation @@ -377,7 +377,7 @@

    Source code for doctr.io.pdf

         
       
    - + diff --git a/v0.5.0/_modules/doctr/io/reader.html b/v0.5.0/_modules/doctr/io/reader.html index c1ddc26edd..d36e5bb553 100644 --- a/v0.5.0/_modules/doctr/io/reader.html +++ b/v0.5.0/_modules/doctr/io/reader.html @@ -13,7 +13,7 @@ - + doctr.io.reader - docTR documentation @@ -426,7 +426,7 @@

    Source code for doctr.io.reader

         
       
    - + diff --git a/v0.5.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html b/v0.5.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html index 9f074805c1..61a010d548 100644 --- a/v0.5.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html +++ b/v0.5.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.magc_resnet.tensorflow - docTR documentation @@ -531,7 +531,7 @@

    Source code for doctr.models.classification.magc_resnet.tensorflow

    - + diff --git a/v0.5.0/_modules/doctr/models/classification/mobilenet/tensorflow.html b/v0.5.0/_modules/doctr/models/classification/mobilenet/tensorflow.html index 6a63851276..7c448394ad 100644 --- a/v0.5.0/_modules/doctr/models/classification/mobilenet/tensorflow.html +++ b/v0.5.0/_modules/doctr/models/classification/mobilenet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.mobilenet.tensorflow - docTR documentation @@ -793,7 +793,7 @@

    Source code for doctr.models.classification.mobilenet.tensorflow

    - + diff --git a/v0.5.0/_modules/doctr/models/classification/resnet/tensorflow.html b/v0.5.0/_modules/doctr/models/classification/resnet/tensorflow.html index 095d377f31..aed4343741 100644 --- a/v0.5.0/_modules/doctr/models/classification/resnet/tensorflow.html +++ b/v0.5.0/_modules/doctr/models/classification/resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.resnet.tensorflow - docTR documentation @@ -749,7 +749,7 @@

    Source code for doctr.models.classification.resnet.tensorflow

    - + diff --git a/v0.5.0/_modules/doctr/models/classification/textnet/tensorflow.html b/v0.5.0/_modules/doctr/models/classification/textnet/tensorflow.html index ad254ebbfb..c5567d7d67 100644 --- a/v0.5.0/_modules/doctr/models/classification/textnet/tensorflow.html +++ b/v0.5.0/_modules/doctr/models/classification/textnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.textnet.tensorflow - docTR documentation @@ -611,7 +611,7 @@

    Source code for doctr.models.classification.textnet.tensorflow

    - + diff --git a/v0.5.0/_modules/doctr/models/classification/vgg/tensorflow.html b/v0.5.0/_modules/doctr/models/classification/vgg/tensorflow.html index 01ae452624..788111ae87 100644 --- a/v0.5.0/_modules/doctr/models/classification/vgg/tensorflow.html +++ b/v0.5.0/_modules/doctr/models/classification/vgg/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vgg.tensorflow - docTR documentation @@ -451,7 +451,7 @@

    Source code for doctr.models.classification.vgg.tensorflow

    - + diff --git a/v0.5.0/_modules/doctr/models/classification/vit/tensorflow.html b/v0.5.0/_modules/doctr/models/classification/vit/tensorflow.html index 1333cf6045..971ba5abe9 100644 --- a/v0.5.0/_modules/doctr/models/classification/vit/tensorflow.html +++ b/v0.5.0/_modules/doctr/models/classification/vit/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vit.tensorflow - docTR documentation @@ -533,7 +533,7 @@

    Source code for doctr.models.classification.vit.tensorflow

    - + diff --git a/v0.5.0/_modules/doctr/models/classification/zoo.html b/v0.5.0/_modules/doctr/models/classification/zoo.html index f7796a7522..3eb2a3ec4e 100644 --- a/v0.5.0/_modules/doctr/models/classification/zoo.html +++ b/v0.5.0/_modules/doctr/models/classification/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.classification.zoo - docTR documentation @@ -447,7 +447,7 @@

    Source code for doctr.models.classification.zoo

    <
    - + diff --git a/v0.5.0/_modules/doctr/models/detection/differentiable_binarization.html b/v0.5.0/_modules/doctr/models/detection/differentiable_binarization.html deleted file mode 100644 index 38e9b36ec2..0000000000 --- a/v0.5.0/_modules/doctr/models/detection/differentiable_binarization.html +++ /dev/null @@ -1,879 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.differentiable_binarization - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.differentiable_binarization

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -import cv2
    -from copy import deepcopy
    -import numpy as np
    -from shapely.geometry import Polygon
    -import pyclipper
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers
    -from typing import Union, List, Tuple, Optional, Any, Dict
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..utils import IntermediateLayerGetter, load_pretrained_params, conv_sequence
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['DBPostProcessor', 'DBNet', 'db_resnet50']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'db_resnet50': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'backbone': 'ResNet50',
    -        'fpn_layers': ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"],
    -        'fpn_channels': 128,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'DBPostProcessor',
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.2.0/db_resnet50-adcafc63.zip',
    -    },
    -}
    -
    -
    -class DBPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for DBNet adapted from the implementation of `xuannianz
    -    <https://github.com/xuannianz/DifferentiableBinarization>`_.
    -
    -    Args:
    -        unclip ratio: ratio used to unshrink polygons
    -        min_size_box: minimal length (pix) to keep a box
    -        max_candidates: maximum boxes to consider in a single page
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        unclip_ratio: Union[float, int] = 1.5,
    -        max_candidates: int = 1000,
    -        box_thresh: float = 0.1,
    -        bin_thresh: float = 0.3,
    -    ) -> None:
    -
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -        self.unclip_ratio = unclip_ratio
    -        self.max_candidates = max_candidates
    -
    -    def polygon_to_box(
    -        self,
    -        points: np.ndarray,
    -    ) -> Optional[Tuple[int, int, int, int]]:
    -        """Expand a polygon (points) by a factor unclip_ratio, and returns a 4-points box
    -
    -        Args:
    -            points: The first parameter.
    -
    -        Returns:
    -            a box in absolute coordinates (x, y, w, h)
    -        """
    -        poly = Polygon(points)
    -        distance = poly.area * self.unclip_ratio / poly.length  # compute distance to expand polygon
    -        offset = pyclipper.PyclipperOffset()
    -        offset.AddPath(points, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        _points = offset.Execute(distance)
    -        # Take biggest stack of points
    -        idx = 0
    -        if len(_points) > 1:
    -            max_size = 0
    -            for _idx, p in enumerate(_points):
    -                if len(p) > max_size:
    -                    idx = _idx
    -                    max_size = len(p)
    -            # We ensure that _points can be correctly casted to a ndarray
    -            _points = [_points[idx]]
    -        expanded_points = np.asarray(_points)  # expand polygon
    -        if len(expanded_points) < 1:
    -            return None
    -        x, y, w, h = cv2.boundingRect(expanded_points)  # compute a 4-points box from expanded polygon
    -        return x, y, w, h
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map
    -
    -        Args:
    -            pred: Pred map from differentiable binarization output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        # get contours from connected components on the bitmap
    -        contours, _ = cv2.findContours(bitmap.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    -        for contour in contours[:self.max_candidates]:
    -            # Check whether smallest enclosing bounding box is not too small
    -            if np.any(contour[:, 0].max(axis=0) - contour[:, 0].min(axis=0) < min_size_box):
    -                continue
    -            x, y, w, h = cv2.boundingRect(contour)
    -            points = np.array([[x, y], [x, y + h], [x + w, y + h], [x + w, y]])
    -            # Compute objectness
    -            score = self.box_score(pred, points)
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            _box = self.polygon_to_box(points)
    -
    -            if _box is None or _box[2] < min_size_box or _box[3] < min_size_box:  # remove to small boxes
    -                continue
    -            x, y, w, h = _box
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -class FeaturePyramidNetwork(layers.Layer, NestedObject):
    -    """Feature Pyramid Network as described in `"Feature Pyramid Networks for Object Detection"
    -    <https://arxiv.org/pdf/1612.03144.pdf>`_.
    -
    -    Args:
    -        channels: number of channel to output
    -    """
    -
    -    def __init__(
    -        self,
    -        channels: int,
    -    ) -> None:
    -        super().__init__()
    -        self.channels = channels
    -        self.upsample = layers.UpSampling2D(size=(2, 2), interpolation='nearest')
    -        self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer='he_normal') for _ in range(4)]
    -        self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2 ** idx) for idx in range(4)]
    -
    -    @staticmethod
    -    def build_upsampling(
    -        channels: int,
    -        dilation_factor: int = 1,
    -    ) -> layers.Layer:
    -        """Module which performs a 3x3 convolution followed by up-sampling
    -
    -        Args:
    -            channels: number of output channels
    -            dilation_factor (int): dilation factor to scale the convolution output before concatenation
    -
    -        Returns:
    -            a keras.layers.Layer object, wrapping these operations in a sequential module
    -
    -        """
    -
    -        _layers = conv_sequence(channels, 'relu', True, kernel_size=3)
    -
    -        if dilation_factor > 1:
    -            _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation='nearest'))
    -
    -        module = keras.Sequential(_layers)
    -
    -        return module
    -
    -    def extra_repr(self) -> str:
    -        return f"channels={self.channels}"
    -
    -    def call(
    -        self,
    -        x: List[tf.Tensor],
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # Channel mapping
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.inner_blocks, x)]
    -        # Upsample & sum
    -        for idx in range(len(results) - 1, -1):
    -            results[idx] += self.upsample(results[idx + 1])
    -        # Conv & upsample
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.layer_blocks, results)]
    -
    -        return layers.concatenate(results)
    -
    -
    -class DBNet(DetectionModel, NestedObject):
    -    """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization"
    -    <https://arxiv.org/pdf/1911.08947.pdf>`_.
    -
    -    Args:
    -        feature extractor: the backbone serving as feature extractor
    -        fpn_channels: number of channels each extracted feature maps is mapped to
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'fpn', 'probability_head', 'threshold_head', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: IntermediateLayerGetter,
    -        fpn_channels: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(cfg=cfg)
    -
    -        self.shrink_ratio = 0.4
    -        self.thresh_min = 0.3
    -        self.thresh_max = 0.7
    -        self.min_size_box = 3
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.fpn = FeaturePyramidNetwork(channels=fpn_channels)
    -        # Initialize kernels
    -        _inputs = [layers.Input(shape=in_shape[1:]) for in_shape in self.feat_extractor.output_shape]
    -        output_shape = tuple(self.fpn(_inputs).shape)
    -
    -        self.probability_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -        self.threshold_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -
    -        self.postprocessor = DBPostProcessor()
    -
    -    @staticmethod
    -    def compute_distance(
    -        xs: np.array,
    -        ys: np.array,
    -        a: np.array,
    -        b: np.array,
    -        eps: float = 1e-7,
    -    ) -> float:
    -        """Compute the distance for each point of the map (xs, ys) to the (a, b) segment
    -
    -        Args:
    -            xs : map of x coordinates (height, width)
    -            ys : map of y coordinates (height, width)
    -            a: first point defining the [ab] segment
    -            b: second point defining the [ab] segment
    -
    -        Returns:
    -            The computed distance
    -
    -        """
    -        square_dist_1 = np.square(xs - a[0]) + np.square(ys - a[1])
    -        square_dist_2 = np.square(xs - b[0]) + np.square(ys - b[1])
    -        square_dist = np.square(a[0] - b[0]) + np.square(a[1] - b[1])
    -        cosin = (square_dist - square_dist_1 - square_dist_2) / (2 * np.sqrt(square_dist_1 * square_dist_2) + eps)
    -        square_sin = 1 - np.square(cosin)
    -        square_sin = np.nan_to_num(square_sin)
    -        result = np.sqrt(square_dist_1 * square_dist_2 * square_sin / square_dist)
    -        result[cosin < 0] = np.sqrt(np.fmin(square_dist_1, square_dist_2))[cosin < 0]
    -        return result
    -
    -    def draw_thresh_map(
    -        self,
    -        polygon: np.array,
    -        canvas: np.array,
    -        mask: np.array,
    -    ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    -        """Draw a polygon treshold map on a canvas, as described in the DB paper
    -
    -        Args:
    -            polygon : array of coord., to draw the boundary of the polygon
    -            canvas : threshold map to fill with polygons
    -            mask : mask for training on threshold polygons
    -        """
    -        if polygon.ndim != 2 or polygon.shape[1] != 2:
    -            raise AttributeError("polygon should be a 2 dimensional array of coords")
    -
    -        # Augment polygon by shrink_ratio
    -        polygon_shape = Polygon(polygon)
    -        distance = polygon_shape.area * (1 - np.power(self.shrink_ratio, 2)) / polygon_shape.length
    -        subject = [tuple(coor) for coor in polygon]  # Get coord as list of tuples
    -        padding = pyclipper.PyclipperOffset()
    -        padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        padded_polygon = np.array(padding.Execute(distance)[0])
    -
    -        # Fill the mask with 1 on the new padded polygon
    -        cv2.fillPoly(mask, [padded_polygon.astype(np.int32)], 1.0)
    -
    -        # Get min/max to recover polygon after distance computation
    -        xmin = padded_polygon[:, 0].min()
    -        xmax = padded_polygon[:, 0].max()
    -        ymin = padded_polygon[:, 1].min()
    -        ymax = padded_polygon[:, 1].max()
    -        width = xmax - xmin + 1
    -        height = ymax - ymin + 1
    -        # Get absolute polygon for distance computation
    -        polygon[:, 0] = polygon[:, 0] - xmin
    -        polygon[:, 1] = polygon[:, 1] - ymin
    -        # Get absolute padded polygon
    -        xs = np.broadcast_to(np.linspace(0, width - 1, num=width).reshape(1, width), (height, width))
    -        ys = np.broadcast_to(np.linspace(0, height - 1, num=height).reshape(height, 1), (height, width))
    -
    -        # Compute distance map to fill the padded polygon
    -        distance_map = np.zeros((polygon.shape[0], height, width), dtype=np.float32)
    -        for i in range(polygon.shape[0]):
    -            j = (i + 1) % polygon.shape[0]
    -            absolute_distance = self.compute_distance(xs, ys, polygon[i], polygon[j])
    -            distance_map[i] = np.clip(absolute_distance / distance, 0, 1)
    -        distance_map = np.min(distance_map, axis=0)
    -
    -        # Clip the padded polygon inside the canvas
    -        xmin_valid = min(max(0, xmin), canvas.shape[1] - 1)
    -        xmax_valid = min(max(0, xmax), canvas.shape[1] - 1)
    -        ymin_valid = min(max(0, ymin), canvas.shape[0] - 1)
    -        ymax_valid = min(max(0, ymax), canvas.shape[0] - 1)
    -
    -        # Fill the canvas with the distances computed inside the valid padded polygon
    -        canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1] = np.fmax(
    -            1 - distance_map[
    -                ymin_valid - ymin:ymax_valid - ymin + 1,
    -                xmin_valid - xmin:xmax_valid - xmin + 1
    -            ],
    -            canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1]
    -        )
    -
    -        return polygon, canvas, mask
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor, tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.uint8)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -        thresh_target = np.zeros(output_shape, dtype=np.uint8)
    -        thresh_mask = np.ones(output_shape, dtype=np.uint8)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            polys = np.stack([
    -                abs_boxes[:, [0, 1]],
    -                abs_boxes[:, [0, 3]],
    -                abs_boxes[:, [2, 3]],
    -                abs_boxes[:, [2, 1]],
    -            ], axis=1)
    -
    -            for box, box_size, poly, is_ambiguous in zip(abs_boxes, boxes_size, polys, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -
    -                # Negative shrink for gt, as described in paper
    -                polygon = Polygon(poly)
    -                distance = polygon.area * (1 - np.power(self.shrink_ratio, 2)) / polygon.length
    -                subject = [tuple(coor) for coor in poly]
    -                padding = pyclipper.PyclipperOffset()
    -                padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -                shrinked = padding.Execute(-distance)
    -
    -                # Draw polygon on gt if it is valid
    -                if len(shrinked) == 0:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                shrinked = np.array(shrinked[0]).reshape(-1, 2)
    -                if shrinked.shape[0] <= 2 or not Polygon(shrinked).is_valid:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                cv2.fillPoly(seg_target[idx], [shrinked.astype(np.int32)], 1)
    -
    -                # Draw on both thresh map and thresh mask
    -                poly, thresh_target[idx], thresh_mask[idx] = self.draw_thresh_map(poly, thresh_target[idx],
    -                                                                                  thresh_mask[idx])
    -
    -        thresh_target = thresh_target.astype(np.float32) * (self.thresh_max - self.thresh_min) + self.thresh_min
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -        thresh_target = tf.convert_to_tensor(thresh_target, dtype=tf.float32)
    -        thresh_mask = tf.convert_to_tensor(thresh_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask, thresh_target, thresh_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        thresh_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts, masks, thresh_gts, thresh_masks from a list of boxes
    -        and a list of masks for each image. From there it computes the loss with the model output
    -
    -        Args:
    -            out_map: output feature map of the model of shape (N, H, W, C)
    -            thresh_map: threshold map of shape (N, H, W, C)
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -
    -        prob_map = tf.math.sigmoid(tf.squeeze(out_map, axis=[-1]))
    -        thresh_map = tf.math.sigmoid(tf.squeeze(thresh_map, axis=[-1]))
    -
    -        seg_target, seg_mask, thresh_target, thresh_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute balanced BCE loss for proba_map
    -        bce_scale = 5.
    -        bce_loss = tf.keras.losses.binary_crossentropy(seg_target[..., None], out_map, from_logits=True)[seg_mask]
    -
    -        neg_target = 1 - seg_target[seg_mask]
    -        positive_count = tf.math.reduce_sum(seg_target[seg_mask])
    -        negative_count = tf.math.reduce_min([tf.math.reduce_sum(neg_target), 3. * positive_count])
    -        negative_loss = bce_loss * neg_target
    -        negative_loss, _ = tf.nn.top_k(negative_loss, tf.cast(negative_count, tf.int32))
    -        sum_losses = tf.math.reduce_sum(bce_loss * seg_target[seg_mask]) + tf.math.reduce_sum(negative_loss)
    -        balanced_bce_loss = sum_losses / (positive_count + negative_count + 1e-6)
    -
    -        # Compute dice loss for approxbin_map
    -        bin_map = 1 / (1 + tf.exp(-50. * (prob_map[seg_mask] - thresh_map[seg_mask])))
    -
    -        bce_min = tf.math.reduce_min(bce_loss)
    -        weights = (bce_loss - bce_min) / (tf.math.reduce_max(bce_loss) - bce_min) + 1.
    -        inter = tf.math.reduce_sum(bin_map * seg_target[seg_mask] * weights)
    -        union = tf.math.reduce_sum(bin_map) + tf.math.reduce_sum(seg_target[seg_mask]) + 1e-8
    -        dice_loss = 1 - 2.0 * inter / union
    -
    -        # Compute l1 loss for thresh_map
    -        l1_scale = 10.
    -        if tf.reduce_any(thresh_mask):
    -            l1_loss = tf.math.reduce_mean(tf.math.abs(thresh_map[thresh_mask] - thresh_target[thresh_mask]))
    -        else:
    -            l1_loss = tf.constant(0.)
    -
    -        return l1_scale * l1_loss + bce_scale * balanced_bce_loss + dice_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        feat_maps = self.feat_extractor(x, **kwargs)
    -        feat_concat = self.fpn(feat_maps, **kwargs)
    -        logits = self.probability_head(feat_concat, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            thresh_map = self.threshold_head(feat_concat, **kwargs)
    -            loss = self.compute_loss(logits, thresh_map, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _db_resnet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> DBNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['fpn_channels'] = kwargs.get('fpn_channels', _cfg['fpn_channels'])
    -
    -    # Feature extractor
    -    resnet = tf.keras.applications.__dict__[_cfg['backbone']](
    -        include_top=False,
    -        weights=None,
    -        input_shape=_cfg['input_shape'],
    -        pooling=None,
    -    )
    -
    -    feat_extractor = IntermediateLayerGetter(
    -        resnet,
    -        _cfg['fpn_layers'],
    -    )
    -
    -    kwargs['fpn_channels'] = _cfg['fpn_channels']
    -
    -    # Build the model
    -    model = DBNet(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def db_resnet50(pretrained: bool = False, **kwargs: Any) -> DBNet: - """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" - <https://arxiv.org/pdf/1911.08947.pdf>`_, using a ResNet-50 backbone. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _db_resnet('db_resnet50', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html b/v0.5.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html index 9145c7c3fd..66cef8663d 100644 --- a/v0.5.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html +++ b/v0.5.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.differentiable_binarization.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     # Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
     
     from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers
    -from typing import List, Tuple, Optional, Any, Dict
    +from typing import Any, Dict, List, Optional, Tuple
     
    +import numpy as np
    +import tensorflow as tf
    +from tensorflow.keras import Model, Sequential, layers, losses
    +from tensorflow.keras.applications import ResNet50
    +
    +from doctr.file_utils import CLASS_NAME
    +from doctr.models.utils import (
    +    IntermediateLayerGetter,
    +    _bf16_to_float32,
    +    _build_model,
    +    conv_sequence,
    +    load_pretrained_params,
    +)
     from doctr.utils.repr import NestedObject
    -from doctr.models.utils import IntermediateLayerGetter, load_pretrained_params, conv_sequence
    +
    +from ...classification import mobilenet_v3_large
     from .base import DBPostProcessor, _DBNet
     
    -__all__ = ['DBNet', 'db_resnet50']
    +__all__ = ["DBNet", "db_resnet50", "db_mobilenet_v3_large"]
     
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'db_resnet50': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'backbone': 'ResNet50',
    -        'fpn_layers': ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"],
    -        'fpn_channels': 128,
    -        'input_shape': (1024, 1024, 3),
    -        'rotated_bbox': False,
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.2.0/db_resnet50-adcafc63.zip',
    +    "db_resnet50": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/db_resnet50-649fa22b.weights.h5&src=0",
    +    },
    +    "db_mobilenet_v3_large": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/db_mobilenet_v3_large-ee2e1dbe.weights.h5&src=0",
         },
     }
     
    @@ -313,6 +348,7 @@ 

    Source code for doctr.models.detection.differentiable_binarization.tensorflo <https://arxiv.org/pdf/1612.03144.pdf>`_. Args: + ---- channels: number of channel to output """ @@ -322,9 +358,9 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo ) -> None: super().__init__() self.channels = channels - self.upsample = layers.UpSampling2D(size=(2, 2), interpolation='nearest') - self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer='he_normal') for _ in range(4)] - self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2 ** idx) for idx in range(4)] + self.upsample = layers.UpSampling2D(size=(2, 2), interpolation="nearest") + self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer="he_normal") for _ in range(4)] + self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2**idx) for idx in range(4)] @staticmethod def build_upsampling( @@ -334,20 +370,21 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo """Module which performs a 3x3 convolution followed by up-sampling Args: + ---- channels: number of output channels dilation_factor (int): dilation factor to scale the convolution output before concatenation Returns: + ------- a keras.layers.Layer object, wrapping these operations in a sequential module """ - - _layers = conv_sequence(channels, 'relu', True, kernel_size=3) + _layers = conv_sequence(channels, "relu", True, kernel_size=3) if dilation_factor > 1: - _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation='nearest')) + _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation="nearest")) - module = keras.Sequential(_layers) + module = Sequential(_layers) return module @@ -359,7 +396,6 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo x: List[tf.Tensor], **kwargs: Any, ) -> tf.Tensor: - # Channel mapping results = [block(fmap, **kwargs) for block, fmap in zip(self.inner_blocks, x)] # Upsample & sum @@ -371,200 +407,324 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo return layers.concatenate(results) -class DBNet(_DBNet, keras.Model, NestedObject): +class DBNet(_DBNet, Model, NestedObject): """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" <https://arxiv.org/pdf/1911.08947.pdf>`_. Args: + ---- feature extractor: the backbone serving as feature extractor fpn_channels: number of channels each extracted feature maps is mapped to + bin_thresh: threshold for binarization + box_thresh: minimal objectness score to consider a box + assume_straight_pages: if True, fit straight bounding boxes only + exportable: onnx exportable returns only logits + cfg: the configuration dict of the model + class_names: list of class names """ - _children_names: List[str] = ['feat_extractor', 'fpn', 'probability_head', 'threshold_head', 'postprocessor'] + _children_names: List[str] = ["feat_extractor", "fpn", "probability_head", "threshold_head", "postprocessor"] def __init__( self, feature_extractor: IntermediateLayerGetter, - fpn_channels: int = 128, - rotated_bbox: bool = False, + fpn_channels: int = 128, # to be set to 256 to represent the author's initial idea + bin_thresh: float = 0.3, + box_thresh: float = 0.1, + assume_straight_pages: bool = True, + exportable: bool = False, cfg: Optional[Dict[str, Any]] = None, + class_names: List[str] = [CLASS_NAME], ) -> None: - super().__init__() + self.class_names = class_names + num_classes: int = len(self.class_names) self.cfg = cfg self.feat_extractor = feature_extractor - self.rotated_bbox = rotated_bbox + self.exportable = exportable + self.assume_straight_pages = assume_straight_pages self.fpn = FeaturePyramidNetwork(channels=fpn_channels) # Initialize kernels _inputs = [layers.Input(shape=in_shape[1:]) for in_shape in self.feat_extractor.output_shape] output_shape = tuple(self.fpn(_inputs).shape) - self.probability_head = keras.Sequential( - [ - *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]), - layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'), - layers.BatchNormalization(), - layers.Activation('relu'), - layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'), - ] + self.probability_head = Sequential([ + *conv_sequence(64, "relu", True, kernel_size=3, input_shape=output_shape[1:]), + layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer="he_normal"), + layers.BatchNormalization(), + layers.Activation("relu"), + layers.Conv2DTranspose(num_classes, 2, strides=2, kernel_initializer="he_normal"), + ]) + self.threshold_head = Sequential([ + *conv_sequence(64, "relu", True, kernel_size=3, input_shape=output_shape[1:]), + layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer="he_normal"), + layers.BatchNormalization(), + layers.Activation("relu"), + layers.Conv2DTranspose(num_classes, 2, strides=2, kernel_initializer="he_normal"), + ]) + + self.postprocessor = DBPostProcessor( + assume_straight_pages=assume_straight_pages, bin_thresh=bin_thresh, box_thresh=box_thresh ) - self.threshold_head = keras.Sequential( - [ - *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]), - layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'), - layers.BatchNormalization(), - layers.Activation('relu'), - layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'), - ] - ) - - self.postprocessor = DBPostProcessor(rotated_bbox=rotated_bbox) def compute_loss( self, out_map: tf.Tensor, thresh_map: tf.Tensor, - target: List[Dict[str, Any]] + target: List[Dict[str, np.ndarray]], + gamma: float = 2.0, + alpha: float = 0.5, + eps: float = 1e-8, ) -> tf.Tensor: """Compute a batch of gts, masks, thresh_gts, thresh_masks from a list of boxes and a list of masks for each image. From there it computes the loss with the model output Args: + ---- out_map: output feature map of the model of shape (N, H, W, C) thresh_map: threshold map of shape (N, H, W, C) target: list of dictionary where each dict has a `boxes` and a `flags` entry + gamma: modulating factor in the focal loss formula + alpha: balancing factor in the focal loss formula + eps: epsilon factor in dice loss Returns: + ------- A loss tensor """ + if gamma < 0: + raise ValueError("Value of gamma should be greater than or equal to zero.") - prob_map = tf.math.sigmoid(tf.squeeze(out_map, axis=[-1])) - thresh_map = tf.math.sigmoid(tf.squeeze(thresh_map, axis=[-1])) + prob_map = tf.math.sigmoid(out_map) + thresh_map = tf.math.sigmoid(thresh_map) - seg_target, seg_mask, thresh_target, thresh_mask = self.compute_target(target, out_map.shape[:3]) - seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32) + seg_target, seg_mask, thresh_target, thresh_mask = self.build_target(target, out_map.shape[1:], True) + seg_target = tf.convert_to_tensor(seg_target, dtype=out_map.dtype) seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool) - thresh_target = tf.convert_to_tensor(thresh_target, dtype=tf.float32) + seg_mask = tf.cast(seg_mask, tf.float32) + thresh_target = tf.convert_to_tensor(thresh_target, dtype=out_map.dtype) thresh_mask = tf.convert_to_tensor(thresh_mask, dtype=tf.bool) - # Compute balanced BCE loss for proba_map - bce_scale = 5. - bce_loss = tf.keras.losses.binary_crossentropy(seg_target[..., None], out_map, from_logits=True)[seg_mask] - - neg_target = 1 - seg_target[seg_mask] - positive_count = tf.math.reduce_sum(seg_target[seg_mask]) - negative_count = tf.math.reduce_min([tf.math.reduce_sum(neg_target), 3. * positive_count]) - negative_loss = bce_loss * neg_target - negative_loss, _ = tf.nn.top_k(negative_loss, tf.cast(negative_count, tf.int32)) - sum_losses = tf.math.reduce_sum(bce_loss * seg_target[seg_mask]) + tf.math.reduce_sum(negative_loss) - balanced_bce_loss = sum_losses / (positive_count + negative_count + 1e-6) - - # Compute dice loss for approxbin_map - bin_map = 1 / (1 + tf.exp(-50. * (prob_map[seg_mask] - thresh_map[seg_mask]))) - - bce_min = tf.math.reduce_min(bce_loss) - weights = (bce_loss - bce_min) / (tf.math.reduce_max(bce_loss) - bce_min) + 1. - inter = tf.math.reduce_sum(bin_map * seg_target[seg_mask] * weights) - union = tf.math.reduce_sum(bin_map) + tf.math.reduce_sum(seg_target[seg_mask]) + 1e-8 - dice_loss = 1 - 2.0 * inter / union + # Focal loss + focal_scale = 10.0 + bce_loss = losses.binary_crossentropy(seg_target[..., None], out_map[..., None], from_logits=True) + + # Convert logits to prob, compute gamma factor + p_t = (seg_target * prob_map) + ((1 - seg_target) * (1 - prob_map)) + alpha_t = seg_target * alpha + (1 - seg_target) * (1 - alpha) + # Unreduced loss + focal_loss = alpha_t * (1 - p_t) ** gamma * bce_loss + # Class reduced + focal_loss = tf.reduce_sum(seg_mask * focal_loss, (0, 1, 2, 3)) / tf.reduce_sum(seg_mask, (0, 1, 2, 3)) + + # Compute dice loss for each class or for approx binary_map + if len(self.class_names) > 1: + dice_map = tf.nn.softmax(out_map, axis=-1) + else: + # compute binary map instead + dice_map = 1.0 / (1.0 + tf.exp(-50 * (prob_map - thresh_map))) + # Class-reduced dice loss + inter = tf.reduce_sum(seg_mask * dice_map * seg_target, axis=[0, 1, 2]) + cardinality = tf.reduce_sum(seg_mask * (dice_map + seg_target), axis=[0, 1, 2]) + dice_loss = tf.reduce_mean(1 - 2 * inter / (cardinality + eps)) # Compute l1 loss for thresh_map - l1_scale = 10. if tf.reduce_any(thresh_mask): - l1_loss = tf.math.reduce_mean(tf.math.abs(thresh_map[thresh_mask] - thresh_target[thresh_mask])) + thresh_mask = tf.cast(thresh_mask, tf.float32) + l1_loss = tf.reduce_sum(tf.abs(thresh_map - thresh_target) * thresh_mask) / ( + tf.reduce_sum(thresh_mask) + eps + ) else: - l1_loss = tf.constant(0.) + l1_loss = tf.constant(0.0) - return l1_scale * l1_loss + bce_scale * balanced_bce_loss + dice_loss + return l1_loss + focal_scale * focal_loss + dice_loss def call( self, x: tf.Tensor, - target: Optional[List[Dict[str, Any]]] = None, + target: Optional[List[Dict[str, np.ndarray]]] = None, return_model_output: bool = False, - return_boxes: bool = False, + return_preds: bool = False, **kwargs: Any, ) -> Dict[str, Any]: - feat_maps = self.feat_extractor(x, **kwargs) feat_concat = self.fpn(feat_maps, **kwargs) logits = self.probability_head(feat_concat, **kwargs) out: Dict[str, tf.Tensor] = {} - if return_model_output or target is None or return_boxes: - prob_map = tf.math.sigmoid(logits) + if self.exportable: + out["logits"] = logits + return out + + if return_model_output or target is None or return_preds: + prob_map = _bf16_to_float32(tf.math.sigmoid(logits)) if return_model_output: out["out_map"] = prob_map - if target is None or return_boxes: - # Post-process boxes - out["preds"] = self.postprocessor(tf.squeeze(prob_map, axis=-1).numpy()) + if target is None or return_preds: + # Post-process boxes (keep only text predictions) + out["preds"] = [dict(zip(self.class_names, preds)) for preds in self.postprocessor(prob_map.numpy())] if target is not None: thresh_map = self.threshold_head(feat_concat, **kwargs) loss = self.compute_loss(logits, thresh_map, target) - out['loss'] = loss + out["loss"] = loss return out -def _db_resnet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> DBNet: +def _db_resnet( + arch: str, + pretrained: bool, + backbone_fn, + fpn_layers: List[str], + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> DBNet: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['fpn_channels'] = kwargs.get('fpn_channels', _cfg['fpn_channels']) - _cfg['rotated_bbox'] = kwargs.get('rotated_bbox', _cfg['rotated_bbox']) + _cfg["input_shape"] = input_shape or _cfg["input_shape"] + if not kwargs.get("class_names", None): + kwargs["class_names"] = _cfg.get("class_names", [CLASS_NAME]) + else: + kwargs["class_names"] = sorted(kwargs["class_names"]) # Feature extractor - resnet = tf.keras.applications.__dict__[_cfg['backbone']]( - include_top=False, - weights=None, - input_shape=_cfg['input_shape'], - pooling=None, + feat_extractor = IntermediateLayerGetter( + backbone_fn( + weights="imagenet" if pretrained_backbone else None, + include_top=False, + pooling=None, + input_shape=_cfg["input_shape"], + ), + fpn_layers, ) + # Build the model + model = DBNet(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) + + # Load pretrained parameters + if pretrained: + # The given class_names differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, + _cfg["url"], + skip_mismatch=kwargs["class_names"] != default_cfgs[arch].get("class_names", [CLASS_NAME]), + ) + + return model + + +def _db_mobilenet( + arch: str, + pretrained: bool, + backbone_fn, + fpn_layers: List[str], + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> DBNet: + pretrained_backbone = pretrained_backbone and not pretrained + + # Patch the config + _cfg = deepcopy(default_cfgs[arch]) + _cfg["input_shape"] = input_shape or _cfg["input_shape"] + if not kwargs.get("class_names", None): + kwargs["class_names"] = default_cfgs[arch].get("class_names", [CLASS_NAME]) + else: + kwargs["class_names"] = sorted(kwargs["class_names"]) + + # Feature extractor feat_extractor = IntermediateLayerGetter( - resnet, - _cfg['fpn_layers'], + backbone_fn( + input_shape=_cfg["input_shape"], + include_top=False, + pretrained=pretrained_backbone, + ), + fpn_layers, ) - kwargs['fpn_channels'] = _cfg['fpn_channels'] - kwargs['rotated_bbox'] = _cfg['rotated_bbox'] - # Build the model model = DBNet(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) # Load pretrained parameters if pretrained: - load_pretrained_params(model, _cfg['url']) + # The given class_names differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, + _cfg["url"], + skip_mismatch=kwargs["class_names"] != default_cfgs[arch].get("class_names", [CLASS_NAME]), + ) return model
    -[docs] +[docs] def db_resnet50(pretrained: bool = False, **kwargs: Any) -> DBNet: """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" <https://arxiv.org/pdf/1911.08947.pdf>`_, using a ResNet-50 backbone. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import db_resnet50 + >>> model = db_resnet50(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the DBNet architecture Returns: + ------- text detection architecture """ + return _db_resnet( + "db_resnet50", + pretrained, + ResNet50, + ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"], + **kwargs, + )
    + + + +
    +[docs] +def db_mobilenet_v3_large(pretrained: bool = False, **kwargs: Any) -> DBNet: + """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" + <https://arxiv.org/pdf/1911.08947.pdf>`_, using a mobilenet v3 large backbone. + + >>> import tensorflow as tf + >>> from doctr.models import db_mobilenet_v3_large + >>> model = db_mobilenet_v3_large(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) - return _db_resnet('db_resnet50', pretrained, **kwargs)
    + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the DBNet architecture + + Returns: + ------- + text detection architecture + """ + return _db_mobilenet( + "db_mobilenet_v3_large", + pretrained, + mobilenet_v3_large, + ["inverted_2", "inverted_5", "inverted_11", "final_block"], + **kwargs, + )

    @@ -598,8 +758,8 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo - - + + diff --git a/v0.5.0/_modules/doctr/models/detection/fast/tensorflow.html b/v0.5.0/_modules/doctr/models/detection/fast/tensorflow.html index 5b84d2dea1..65e1a77af8 100644 --- a/v0.5.0/_modules/doctr/models/detection/fast/tensorflow.html +++ b/v0.5.0/_modules/doctr/models/detection/fast/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.fast.tensorflow - docTR documentation @@ -769,7 +769,7 @@

    Source code for doctr.models.detection.fast.tensorflow

    - + diff --git a/v0.5.0/_modules/doctr/models/detection/linknet.html b/v0.5.0/_modules/doctr/models/detection/linknet.html deleted file mode 100644 index 129cfdce8b..0000000000 --- a/v0.5.0/_modules/doctr/models/detection/linknet.html +++ /dev/null @@ -1,644 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.linknet - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.linknet

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -import numpy as np
    -import cv2
    -from tensorflow.keras import layers, Sequential
    -from typing import Dict, Any, Tuple, Optional, List
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..backbones import ResnetStage
    -from ..utils import conv_sequence, load_pretrained_params
    -from ...utils.repr import NestedObject
    -
    -__all__ = ['LinkNet', 'linknet', 'LinkNetPostProcessor']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'linknet': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'out_chan': 1,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'LinkNetPostProcessor',
    -        'url': None,
    -    },
    -}
    -
    -
    -class LinkNetPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for LinkNet model.
    -
    -    Args:
    -        min_size_box: minimal length (pix) to keep a box
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        min_size_box: int = 3,
    -        bin_thresh: float = 0.15,
    -        box_thresh: float = 0.1,
    -    ) -> None:
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map: find connected components then filter boxes
    -
    -        Args:
    -            pred: Pred map from differentiable linknet output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        label_num, labelimage = cv2.connectedComponents(bitmap.astype(np.uint8), connectivity=4)
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        for label in range(1, label_num + 1):
    -            points = np.array(np.where(labelimage == label)[::-1]).T
    -            if points.shape[0] < 4:  # remove polygons with 3 points or less
    -                continue
    -            score = self.box_score(pred, points.reshape(-1, 2))
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            x, y, w, h = cv2.boundingRect(points)
    -            if min(w, h) < min_size_box:  # filter too small boxes
    -                continue
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -def decoder_block(in_chan: int, out_chan: int) -> Sequential:
    -    """Creates a LinkNet decoder block"""
    -
    -    return Sequential([
    -        *conv_sequence(in_chan // 4, 'relu', True, kernel_size=1),
    -        layers.Conv2DTranspose(
    -            filters=in_chan // 4,
    -            kernel_size=3,
    -            strides=2,
    -            padding="same",
    -            use_bias=False,
    -            kernel_initializer='he_normal'
    -        ),
    -        layers.BatchNormalization(),
    -        layers.Activation('relu'),
    -        *conv_sequence(out_chan, 'relu', True, kernel_size=1),
    -    ])
    -
    -
    -class LinkNetFPN(layers.Layer, NestedObject):
    -    """LinkNet Encoder-Decoder module
    -
    -    """
    -
    -    def __init__(
    -        self,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.encoder_1 = ResnetStage(num_blocks=2, output_channels=64, downsample=True)
    -        self.encoder_2 = ResnetStage(num_blocks=2, output_channels=128, downsample=True)
    -        self.encoder_3 = ResnetStage(num_blocks=2, output_channels=256, downsample=True)
    -        self.encoder_4 = ResnetStage(num_blocks=2, output_channels=512, downsample=True)
    -        self.decoder_1 = decoder_block(in_chan=64, out_chan=64)
    -        self.decoder_2 = decoder_block(in_chan=128, out_chan=64)
    -        self.decoder_3 = decoder_block(in_chan=256, out_chan=128)
    -        self.decoder_4 = decoder_block(in_chan=512, out_chan=256)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor
    -    ) -> tf.Tensor:
    -        x_1 = self.encoder_1(x)
    -        x_2 = self.encoder_2(x_1)
    -        x_3 = self.encoder_3(x_2)
    -        x_4 = self.encoder_4(x_3)
    -        y_4 = self.decoder_4(x_4)
    -        y_3 = self.decoder_3(y_4 + x_3)
    -        y_2 = self.decoder_2(y_3 + x_2)
    -        y_1 = self.decoder_1(y_2 + x_1)
    -        return y_1
    -
    -
    -class LinkNet(DetectionModel, NestedObject):
    -    """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation"
    -    <https://arxiv.org/pdf/1707.03718.pdf>`_.
    -
    -    Args:
    -        out_chan: number of channels for the output
    -    """
    -
    -    _children_names: List[str] = ['stem', 'fpn', 'classifier', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        out_chan: int = 1,
    -        input_shape: Tuple[int, int, int] = (512, 512, 3),
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(cfg=cfg)
    -
    -        self.stem = Sequential([
    -            *conv_sequence(64, 'relu', True, strides=2, kernel_size=7, input_shape=input_shape),
    -            layers.MaxPool2D(pool_size=(3, 3), strides=2, padding='same'),
    -        ])
    -
    -        self.fpn = LinkNetFPN()
    -
    -        self.classifier = Sequential([
    -            layers.Conv2DTranspose(
    -                filters=32,
    -                kernel_size=3,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -            layers.BatchNormalization(),
    -            layers.Activation('relu'),
    -            *conv_sequence(32, 'relu', True, strides=1, kernel_size=3),
    -            layers.Conv2DTranspose(
    -                filters=out_chan,
    -                kernel_size=2,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -        ])
    -
    -        self.min_size_box = 3
    -
    -        self.postprocessor = LinkNetPostProcessor()
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.bool)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            for box, box_size, is_ambiguous in zip(abs_boxes, boxes_size, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Fill polygon with 1
    -                seg_target[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = True
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts and masks from a list of boxes and a list of masks for each image
    -        Then, it computes the loss function with proba_map, gts and masks
    -
    -        Args:
    -            out_map: output feature map of the model of shape N x H x W x 1
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -        seg_target, seg_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute BCE loss
    -        return tf.math.reduce_mean(tf.keras.losses.binary_crossentropy(
    -            seg_target[seg_mask],
    -            tf.squeeze(out_map, axis=[-1])[seg_mask],
    -            from_logits=True
    -        ))
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        logits = self.stem(x)
    -        logits = self.fpn(logits)
    -        logits = self.classifier(logits)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            loss = self.compute_loss(logits, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _linknet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> LinkNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['out_chan'] = kwargs.get('out_chan', _cfg['out_chan'])
    -
    -    kwargs['out_chan'] = _cfg['out_chan']
    -    kwargs['input_shape'] = _cfg['input_shape']
    -    # Build the model
    -    model = LinkNet(cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def linknet(pretrained: bool = False, **kwargs: Any) -> LinkNet: - """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" - <https://arxiv.org/pdf/1707.03718.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import linknet - >>> model = linknet(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _linknet('linknet', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.0/_modules/doctr/models/detection/linknet/tensorflow.html b/v0.5.0/_modules/doctr/models/detection/linknet/tensorflow.html index cd4f446673..ce995f99d4 100644 --- a/v0.5.0/_modules/doctr/models/detection/linknet/tensorflow.html +++ b/v0.5.0/_modules/doctr/models/detection/linknet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.linknet.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.detection.linknet.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     # Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
     
     from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers, Sequential
    -from typing import Dict, Any, Tuple, Optional, List
    +from typing import Any, Dict, List, Optional, Tuple
     
    +import numpy as np
    +import tensorflow as tf
    +from tensorflow.keras import Model, Sequential, layers, losses
    +
    +from doctr.file_utils import CLASS_NAME
    +from doctr.models.classification import resnet18, resnet34, resnet50
    +from doctr.models.utils import (
    +    IntermediateLayerGetter,
    +    _bf16_to_float32,
    +    _build_model,
    +    conv_sequence,
    +    load_pretrained_params,
    +)
     from doctr.utils.repr import NestedObject
    -from doctr.models.backbones import ResnetStage
    -from doctr.models.utils import conv_sequence, load_pretrained_params
    -from .base import LinkNetPostProcessor, _LinkNet
     
    -__all__ = ['LinkNet', 'linknet16']
    +from .base import LinkNetPostProcessor, _LinkNet
     
    +__all__ = ["LinkNet", "linknet_resnet18", "linknet_resnet34", "linknet_resnet50"]
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'linknet16': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'num_classes': 1,
    -        'input_shape': (1024, 1024, 3),
    -        'rotated_bbox': False,
    -        'url': None,
    +    "linknet_resnet18": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/linknet_resnet18-615a82c5.weights.h5&src=0",
    +    },
    +    "linknet_resnet34": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/linknet_resnet34-9d772be5.weights.h5&src=0",
    +    },
    +    "linknet_resnet50": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/linknet_resnet50-6bf6c8b5.weights.h5&src=0",
         },
     }
     
     
    -def decoder_block(in_chan: int, out_chan: int) -> Sequential:
    +def decoder_block(in_chan: int, out_chan: int, stride: int, **kwargs: Any) -> Sequential:
         """Creates a LinkNet decoder block"""
    -
         return Sequential([
    -        *conv_sequence(in_chan // 4, 'relu', True, kernel_size=1),
    +        *conv_sequence(in_chan // 4, "relu", True, kernel_size=1, **kwargs),
             layers.Conv2DTranspose(
                 filters=in_chan // 4,
                 kernel_size=3,
    -            strides=2,
    +            strides=stride,
                 padding="same",
                 use_bias=False,
    -            kernel_initializer='he_normal'
    +            kernel_initializer="he_normal",
             ),
             layers.BatchNormalization(),
    -        layers.Activation('relu'),
    -        *conv_sequence(out_chan, 'relu', True, kernel_size=1),
    +        layers.Activation("relu"),
    +        *conv_sequence(out_chan, "relu", True, kernel_size=1),
         ])
     
     
    -class LinkNetFPN(layers.Layer, NestedObject):
    -    """LinkNet Encoder-Decoder module"""
    +class LinkNetFPN(Model, NestedObject):
    +    """LinkNet Decoder module"""
     
         def __init__(
             self,
    +        out_chans: int,
    +        in_shapes: List[Tuple[int, ...]],
         ) -> None:
    -
             super().__init__()
    -        self.encoder_1 = ResnetStage(num_blocks=2, output_channels=64, downsample=True)
    -        self.encoder_2 = ResnetStage(num_blocks=2, output_channels=128, downsample=True)
    -        self.encoder_3 = ResnetStage(num_blocks=2, output_channels=256, downsample=True)
    -        self.encoder_4 = ResnetStage(num_blocks=2, output_channels=512, downsample=True)
    -        self.decoder_1 = decoder_block(in_chan=64, out_chan=64)
    -        self.decoder_2 = decoder_block(in_chan=128, out_chan=64)
    -        self.decoder_3 = decoder_block(in_chan=256, out_chan=128)
    -        self.decoder_4 = decoder_block(in_chan=512, out_chan=256)
    +        self.out_chans = out_chans
    +        strides = [2] * (len(in_shapes) - 1) + [1]
    +        i_chans = [s[-1] for s in in_shapes[::-1]]
    +        o_chans = i_chans[1:] + [out_chans]
    +        self.decoders = [
    +            decoder_block(in_chan, out_chan, s, input_shape=in_shape)
    +            for in_chan, out_chan, s, in_shape in zip(i_chans, o_chans, strides, in_shapes[::-1])
    +        ]
    +
    +    def call(self, x: List[tf.Tensor], **kwargs: Any) -> tf.Tensor:
    +        out = 0
    +        for decoder, fmap in zip(self.decoders, x[::-1]):
    +            out = decoder(out + fmap, **kwargs)
    +        return out
     
    -    def call(
    -        self,
    -        x: tf.Tensor
    -    ) -> tf.Tensor:
    -        x_1 = self.encoder_1(x)
    -        x_2 = self.encoder_2(x_1)
    -        x_3 = self.encoder_3(x_2)
    -        x_4 = self.encoder_4(x_3)
    -        y_4 = self.decoder_4(x_4)
    -        y_3 = self.decoder_3(y_4 + x_3)
    -        y_2 = self.decoder_2(y_3 + x_2)
    -        y_1 = self.decoder_1(y_2 + x_1)
    -        return y_1
    -
    -
    -class LinkNet(_LinkNet, keras.Model):
    +    def extra_repr(self) -> str:
    +        return f"out_chans={self.out_chans}"
    +
    +
    +class LinkNet(_LinkNet, Model):
         """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation"
         <https://arxiv.org/pdf/1707.03718.pdf>`_.
     
         Args:
    -        num_classes: number of channels for the output
    +    ----
    +        feature extractor: the backbone serving as feature extractor
    +        fpn_channels: number of channels each extracted feature maps is mapped to
    +        bin_thresh: threshold for binarization of the output feature map
    +        box_thresh: minimal objectness score to consider a box
    +        assume_straight_pages: if True, fit straight bounding boxes only
    +        exportable: onnx exportable returns only logits
    +        cfg: the configuration dict of the model
    +        class_names: list of class names
         """
     
    -    _children_names: List[str] = ['stem', 'fpn', 'classifier', 'postprocessor']
    +    _children_names: List[str] = ["feat_extractor", "fpn", "classifier", "postprocessor"]
     
         def __init__(
             self,
    -        num_classes: int = 1,
    -        input_shape: Tuple[int, int, int] = (512, 512, 3),
    -        rotated_bbox: bool = False,
    +        feat_extractor: IntermediateLayerGetter,
    +        fpn_channels: int = 64,
    +        bin_thresh: float = 0.1,
    +        box_thresh: float = 0.1,
    +        assume_straight_pages: bool = True,
    +        exportable: bool = False,
             cfg: Optional[Dict[str, Any]] = None,
    +        class_names: List[str] = [CLASS_NAME],
         ) -> None:
             super().__init__(cfg=cfg)
     
    -        self.rotated_bbox = rotated_bbox
    +        self.class_names = class_names
    +        num_classes: int = len(self.class_names)
     
    -        self.stem = Sequential([
    -            *conv_sequence(64, 'relu', True, strides=2, kernel_size=7, input_shape=input_shape),
    -            layers.MaxPool2D(pool_size=(3, 3), strides=2, padding='same'),
    -        ])
    +        self.exportable = exportable
    +        self.assume_straight_pages = assume_straight_pages
    +
    +        self.feat_extractor = feat_extractor
     
    -        self.fpn = LinkNetFPN()
    +        self.fpn = LinkNetFPN(fpn_channels, [_shape[1:] for _shape in self.feat_extractor.output_shape])
    +        self.fpn.build(self.feat_extractor.output_shape)
     
             self.classifier = Sequential([
                 layers.Conv2DTranspose(
    @@ -393,154 +442,246 @@ 

    Source code for doctr.models.detection.linknet.tensorflow

    strides=2, padding="same", use_bias=False, - kernel_initializer='he_normal' + kernel_initializer="he_normal", + input_shape=self.fpn.decoders[-1].output_shape[1:], ), layers.BatchNormalization(), - layers.Activation('relu'), - *conv_sequence(32, 'relu', True, strides=1, kernel_size=3), + layers.Activation("relu"), + *conv_sequence(32, "relu", True, kernel_size=3, strides=1), layers.Conv2DTranspose( filters=num_classes, kernel_size=2, strides=2, padding="same", - use_bias=False, - kernel_initializer='he_normal' + use_bias=True, + kernel_initializer="he_normal", ), ]) - self.postprocessor = LinkNetPostProcessor(rotated_bbox=rotated_bbox) + self.postprocessor = LinkNetPostProcessor( + assume_straight_pages=assume_straight_pages, bin_thresh=bin_thresh, box_thresh=box_thresh + ) def compute_loss( self, out_map: tf.Tensor, - target: List[Dict[str, Any]], - focal_loss: bool = False, - alpha: float = .5, - gamma: float = 2., - edge_factor: float = 2., + target: List[Dict[str, np.ndarray]], + gamma: float = 2.0, + alpha: float = 0.5, + eps: float = 1e-8, ) -> tf.Tensor: """Compute linknet loss, BCE with boosted box edges or focal loss. Focal loss implementation based on <https://github.com/tensorflow/addons/>`_. Args: + ---- out_map: output feature map of the model of shape N x H x W x 1 target: list of dictionary where each dict has a `boxes` and a `flags` entry - focal_loss: if True, use focal loss instead of BCE - edge_factor: boost factor for box edges (in case of BCE) + gamma: modulating factor in the focal loss formula alpha: balancing factor in the focal loss formula - gammma: modulating factor in the focal loss formula + eps: epsilon factor in dice loss Returns: + ------- A loss tensor """ - seg_target, seg_mask, edge_mask = self.compute_target(target, out_map.shape[:3]) - seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32) - edge_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool) + seg_target, seg_mask = self.build_target(target, out_map.shape[1:], True) + seg_target = tf.convert_to_tensor(seg_target, dtype=out_map.dtype) seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool) - - # Get the cross_entropy for each entry - bce = tf.keras.losses.binary_crossentropy( - seg_target[seg_mask], - tf.squeeze(out_map, axis=[-1])[seg_mask], - from_logits=True) - - if focal_loss: - if gamma and gamma < 0: - raise ValueError("Value of gamma should be greater than or equal to zero.") - - # Convert logits to prob, compute gamma factor - pred_prob = tf.sigmoid(tf.squeeze(out_map, axis=[-1])[seg_mask]) - p_t = (seg_target[seg_mask] * pred_prob) + ((1 - seg_target[seg_mask]) * (1 - pred_prob)) - modulating_factor = tf.pow((1.0 - p_t), gamma) - - # Compute alpha factor - alpha_factor = seg_target[seg_mask] * alpha + (1 - seg_target[seg_mask]) * (1 - alpha) - - # compute the final loss - loss = tf.reduce_mean(alpha_factor * modulating_factor * bce) - - else: - # Compute BCE loss with highlighted edges - loss = tf.math.multiply( - 1 + (edge_factor - 1) * tf.cast(edge_mask, tf.float32), - bce - ) - loss = tf.reduce_mean(loss) - - return loss + seg_mask = tf.cast(seg_mask, tf.float32) + + bce_loss = losses.binary_crossentropy(seg_target[..., None], out_map[..., None], from_logits=True) + proba_map = tf.sigmoid(out_map) + + # Focal loss + if gamma < 0: + raise ValueError("Value of gamma should be greater than or equal to zero.") + # Convert logits to prob, compute gamma factor + p_t = (seg_target * proba_map) + ((1 - seg_target) * (1 - proba_map)) + alpha_t = seg_target * alpha + (1 - seg_target) * (1 - alpha) + # Unreduced loss + focal_loss = alpha_t * (1 - p_t) ** gamma * bce_loss + # Class reduced + focal_loss = tf.reduce_sum(seg_mask * focal_loss, (0, 1, 2, 3)) / tf.reduce_sum(seg_mask, (0, 1, 2, 3)) + + # Compute dice loss for each class + dice_map = tf.nn.softmax(out_map, axis=-1) if len(self.class_names) > 1 else proba_map + # Class-reduced dice loss + inter = tf.reduce_sum(seg_mask * dice_map * seg_target, axis=[0, 1, 2]) + cardinality = tf.reduce_sum(seg_mask * (dice_map + seg_target), axis=[0, 1, 2]) + dice_loss = tf.reduce_mean(1 - 2 * inter / (cardinality + eps)) + + return focal_loss + dice_loss def call( self, x: tf.Tensor, - target: Optional[List[Dict[str, Any]]] = None, + target: Optional[List[Dict[str, np.ndarray]]] = None, return_model_output: bool = False, - return_boxes: bool = False, - focal_loss: bool = True, + return_preds: bool = False, **kwargs: Any, ) -> Dict[str, Any]: - - logits = self.stem(x) - logits = self.fpn(logits) - logits = self.classifier(logits) + feat_maps = self.feat_extractor(x, **kwargs) + logits = self.fpn(feat_maps, **kwargs) + logits = self.classifier(logits, **kwargs) out: Dict[str, tf.Tensor] = {} - if return_model_output or target is None or return_boxes: - prob_map = tf.math.sigmoid(logits) + if self.exportable: + out["logits"] = logits + return out + + if return_model_output or target is None or return_preds: + prob_map = _bf16_to_float32(tf.math.sigmoid(logits)) + if return_model_output: out["out_map"] = prob_map - if target is None or return_boxes: + if target is None or return_preds: # Post-process boxes - out["preds"] = self.postprocessor(tf.squeeze(prob_map, axis=-1).numpy()) + out["preds"] = [dict(zip(self.class_names, preds)) for preds in self.postprocessor(prob_map.numpy())] if target is not None: - loss = self.compute_loss(logits, target, focal_loss) - out['loss'] = loss + loss = self.compute_loss(logits, target) + out["loss"] = loss return out -def _linknet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> LinkNet: +def _linknet( + arch: str, + pretrained: bool, + backbone_fn, + fpn_layers: List[str], + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> LinkNet: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['num_classes'] = kwargs.get('num_classes', _cfg['num_classes']) - _cfg['rotated_bbox'] = kwargs.get('rotated_bbox', _cfg['rotated_bbox']) + _cfg["input_shape"] = input_shape or default_cfgs[arch]["input_shape"] + if not kwargs.get("class_names", None): + kwargs["class_names"] = _cfg.get("class_names", [CLASS_NAME]) + else: + kwargs["class_names"] = sorted(kwargs["class_names"]) + + # Feature extractor + feat_extractor = IntermediateLayerGetter( + backbone_fn( + pretrained=pretrained_backbone, + include_top=False, + input_shape=_cfg["input_shape"], + ), + fpn_layers, + ) - kwargs['num_classes'] = _cfg['num_classes'] - kwargs['input_shape'] = _cfg['input_shape'] - kwargs['rotated_bbox'] = _cfg['rotated_bbox'] # Build the model - model = LinkNet(cfg=_cfg, **kwargs) + model = LinkNet(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) + # Load pretrained parameters if pretrained: - load_pretrained_params(model, _cfg['url']) + # The given class_names differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, + _cfg["url"], + skip_mismatch=kwargs["class_names"] != default_cfgs[arch].get("class_names", [CLASS_NAME]), + ) return model -
    -[docs] -def linknet16(pretrained: bool = False, **kwargs: Any) -> LinkNet: +
    +[docs] +def linknet_resnet18(pretrained: bool = False, **kwargs: Any) -> LinkNet: + """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" + <https://arxiv.org/pdf/1707.03718.pdf>`_. + + >>> import tensorflow as tf + >>> from doctr.models import linknet_resnet18 + >>> model = linknet_resnet18(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the LinkNet architecture + + Returns: + ------- + text detection architecture + """ + return _linknet( + "linknet_resnet18", + pretrained, + resnet18, + ["resnet_block_1", "resnet_block_3", "resnet_block_5", "resnet_block_7"], + **kwargs, + )
    + + + +
    +[docs] +def linknet_resnet34(pretrained: bool = False, **kwargs: Any) -> LinkNet: """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" <https://arxiv.org/pdf/1707.03718.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import linknet16 - >>> model = linknet16(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import linknet_resnet34 + >>> model = linknet_resnet34(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the LinkNet architecture Returns: + ------- text detection architecture """ + return _linknet( + "linknet_resnet34", + pretrained, + resnet34, + ["resnet_block_2", "resnet_block_6", "resnet_block_12", "resnet_block_15"], + **kwargs, + )
    + + + +
    +[docs] +def linknet_resnet50(pretrained: bool = False, **kwargs: Any) -> LinkNet: + """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" + <https://arxiv.org/pdf/1707.03718.pdf>`_. + + >>> import tensorflow as tf + >>> from doctr.models import linknet_resnet50 + >>> model = linknet_resnet50(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the LinkNet architecture - return _linknet('linknet16', pretrained, **kwargs)
    + Returns: + ------- + text detection architecture + """ + return _linknet( + "linknet_resnet50", + pretrained, + resnet50, + ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"], + **kwargs, + )
    @@ -574,8 +715,8 @@

    Source code for doctr.models.detection.linknet.tensorflow

    - +
    + diff --git a/v0.5.0/_modules/doctr/models/detection/zoo.html b/v0.5.0/_modules/doctr/models/detection/zoo.html index d3128b8d14..3651c4e2d3 100644 --- a/v0.5.0/_modules/doctr/models/detection/zoo.html +++ b/v0.5.0/_modules/doctr/models/detection/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.detection.zoo - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.detection.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -from typing import Any
    +from typing import Any, List
     
     from doctr.file_utils import is_tf_available, is_torch_available
    -from .core import DetectionPredictor
    -from ..preprocessor import PreProcessor
    -from .. import detection
     
    +from .. import detection
    +from ..detection.fast import reparameterize
    +from ..preprocessor import PreProcessor
    +from .predictor import DetectionPredictor
     
     __all__ = ["detection_predictor"]
     
    +ARCHS: List[str]
    +
     
     if is_tf_available():
    -    ARCHS = ['db_resnet50', 'linknet16']
    +    ARCHS = [
    +        "db_resnet50",
    +        "db_mobilenet_v3_large",
    +        "linknet_resnet18",
    +        "linknet_resnet34",
    +        "linknet_resnet50",
    +        "fast_tiny",
    +        "fast_small",
    +        "fast_base",
    +    ]
     elif is_torch_available():
    -    ARCHS = ['db_resnet34', 'db_resnet50', 'db_mobilenet_v3', 'linknet16']
    +    ARCHS = [
    +        "db_resnet34",
    +        "db_resnet50",
    +        "db_mobilenet_v3_large",
    +        "linknet_resnet18",
    +        "linknet_resnet34",
    +        "linknet_resnet50",
    +        "fast_tiny",
    +        "fast_small",
    +        "fast_base",
    +    ]
    +
     
    +def _predictor(arch: Any, pretrained: bool, assume_straight_pages: bool = True, **kwargs: Any) -> DetectionPredictor:
    +    if isinstance(arch, str):
    +        if arch not in ARCHS:
    +            raise ValueError(f"unknown architecture '{arch}'")
     
    -def _predictor(arch: str, pretrained: bool, **kwargs: Any) -> DetectionPredictor:
    +        _model = detection.__dict__[arch](
    +            pretrained=pretrained,
    +            pretrained_backbone=kwargs.get("pretrained_backbone", True),
    +            assume_straight_pages=assume_straight_pages,
    +        )
    +        # Reparameterize FAST models by default to lower inference latency and memory usage
    +        if isinstance(_model, detection.FAST):
    +            _model = reparameterize(_model)
    +    else:
    +        if not isinstance(arch, (detection.DBNet, detection.LinkNet, detection.FAST)):
    +            raise ValueError(f"unknown architecture: {type(arch)}")
     
    -    if arch not in ARCHS:
    -        raise ValueError(f"unknown architecture '{arch}'")
    +        _model = arch
    +        _model.assume_straight_pages = assume_straight_pages
    +        _model.postprocessor.assume_straight_pages = assume_straight_pages
     
    -    # Detection
    -    _model = detection.__dict__[arch](pretrained=pretrained)
    -    kwargs['mean'] = kwargs.get('mean', _model.cfg['mean'])
    -    kwargs['std'] = kwargs.get('std', _model.cfg['std'])
    -    kwargs['batch_size'] = kwargs.get('batch_size', 1)
    +    kwargs.pop("pretrained_backbone", None)
    +
    +    kwargs["mean"] = kwargs.get("mean", _model.cfg["mean"])
    +    kwargs["std"] = kwargs.get("std", _model.cfg["std"])
    +    kwargs["batch_size"] = kwargs.get("batch_size", 2)
         predictor = DetectionPredictor(
    -        PreProcessor(_model.cfg['input_shape'][:2], **kwargs),
    -        _model
    +        PreProcessor(_model.cfg["input_shape"][:-1] if is_tf_available() else _model.cfg["input_shape"][1:], **kwargs),
    +        _model,
         )
         return predictor
     
     
     
    -[docs] -def detection_predictor(arch: str = 'db_resnet50', pretrained: bool = False, **kwargs: Any) -> DetectionPredictor: +[docs] +def detection_predictor( + arch: Any = "fast_base", + pretrained: bool = False, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + batch_size: int = 2, + **kwargs: Any, +) -> DetectionPredictor: """Text detection architecture. - Example:: - >>> import numpy as np - >>> from doctr.models import detection_predictor - >>> model = detection_predictor(pretrained=True) - >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) - >>> out = model([input_page]) + >>> import numpy as np + >>> from doctr.models import detection_predictor + >>> model = detection_predictor(arch='db_resnet50', pretrained=True) + >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) Args: - arch: name of the architecture to use ('db_resnet50') + ---- + arch: name of the architecture or model itself to use (e.g. 'db_resnet50') pretrained: If True, returns a model pre-trained on our text detection dataset + assume_straight_pages: If True, fit straight boxes to the page + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right + batch_size: number of samples the model processes in parallel + **kwargs: optional keyword arguments passed to the architecture Returns: + ------- Detection predictor """ - - return _predictor(arch, pretrained, **kwargs)
    + return _predictor( + arch=arch, + pretrained=pretrained, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + batch_size=batch_size, + **kwargs, + )
    @@ -367,8 +449,8 @@

    Source code for doctr.models.detection.zoo

           
         
       
    - - + + diff --git a/v0.5.0/_modules/doctr/models/export.html b/v0.5.0/_modules/doctr/models/export.html deleted file mode 100644 index f25a81aa21..0000000000 --- a/v0.5.0/_modules/doctr/models/export.html +++ /dev/null @@ -1,411 +0,0 @@ - - - - - - - - - - - - doctr.models.export - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.export

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import logging
    -import numpy as np
    -import tensorflow as tf
    -from tensorflow.keras import Model
    -from typing import Tuple
    -
    -logging.getLogger("tensorflow").setLevel(logging.DEBUG)
    -
    -
    -__all__ = ['convert_to_tflite', 'convert_to_fp16', 'quantize_model']
    -
    -
    -
    -[docs] -def convert_to_tflite(tf_model: Model) -> bytes: - """Converts a model to TFLite format - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_tflite, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_tflite(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - return converter.convert()
    - - - -
    -[docs] -def convert_to_fp16(tf_model: Model) -> bytes: - """Converts a model to half precision - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_fp16, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_fp16(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the serialized FP16 model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - converter.target_spec.supported_types = [tf.float16] - return converter.convert()
    - - - -
    -[docs] -def quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) -> bytes: - """Quantize a Tensorflow model - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import quantize_model, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = quantize_model(model, (224, 224, 3)) - - Args: - tf_model: a keras model - input_shape: shape of the expected input tensor (excluding batch dimension) with channel last order - - Returns: - bytes: the serialized quantized model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - - # Float fallback for operators that do not have an integer implementation - def representative_dataset(): - for _ in range(100): - data = np.random.rand(1, *input_shape) - yield [data.astype(np.float32)] - - converter.representative_dataset = representative_dataset - converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] - converter.inference_input_type = tf.int8 - converter.inference_output_type = tf.int8 - - return converter.convert()
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.0/_modules/doctr/models/factory/hub.html b/v0.5.0/_modules/doctr/models/factory/hub.html index 8274a809f5..756b2c7a17 100644 --- a/v0.5.0/_modules/doctr/models/factory/hub.html +++ b/v0.5.0/_modules/doctr/models/factory/hub.html @@ -13,7 +13,7 @@ - + doctr.models.factory.hub - docTR documentation @@ -568,7 +568,7 @@

    Source code for doctr.models.factory.hub

         
       
    - + diff --git a/v0.5.0/_modules/doctr/models/recognition/crnn.html b/v0.5.0/_modules/doctr/models/recognition/crnn.html deleted file mode 100644 index daa2393439..0000000000 --- a/v0.5.0/_modules/doctr/models/recognition/crnn.html +++ /dev/null @@ -1,565 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.crnn - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.crnn

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import layers
    -from tensorflow.keras.models import Sequential
    -from typing import Tuple, Dict, Any, Optional, List
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel, RecognitionPostProcessor
    -
    -__all__ = ['CRNN', 'crnn_vgg16_bn', 'crnn_resnet31', 'CTCPostProcessor']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'crnn_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/crnn_vgg16_bn-748c855f.zip',
    -    },
    -    'crnn_resnet31': {
    -        'mean': (0.694, 0.695, 0.693),
    -        'std': (0.299, 0.296, 0.301),
    -        'backbone': 'resnet31', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.1/crnn_resnet31-69ab71db.zip',
    -    },
    -}
    -
    -
    -class CTCPostProcessor(RecognitionPostProcessor):
    -    """
    -    Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor
    -    ) -> List[Tuple[str, float]]:
    -        """
    -        Performs decoding of raw output with CTC and decoding of CTC predictions
    -        with label_to_idx mapping dictionnary
    -
    -        Args:
    -            logits: raw output of the model, shape BATCH_SIZE X SEQ_LEN X NUM_CLASSES + 1
    -
    -        Returns:
    -            A list of decoded words of length BATCH_SIZE
    -
    -        """
    -        # Decode CTC
    -        _decoded, _log_prob = tf.nn.ctc_beam_search_decoder(
    -            tf.transpose(logits, perm=[1, 0, 2]),
    -            tf.fill(logits.shape[0], logits.shape[1]),
    -            beam_width=1, top_paths=1,
    -        )
    -        out_idxs = tf.sparse.to_dense(_decoded[0], default_value=len(self.vocab))
    -        probs = tf.math.exp(tf.squeeze(_log_prob, axis=1))
    -
    -        # Map it to characters
    -        _decoded_strings_pred = tf.strings.reduce_join(
    -            inputs=tf.nn.embedding_lookup(self._embedding, out_idxs),
    -            axis=-1
    -        )
    -        _decoded_strings_pred = tf.strings.split(_decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -class CRNN(RecognitionModel):
    -    """Implements a CRNN architecture as described in `"An End-to-End Trainable Neural Network for Image-based
    -    Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of units in the LSTM layers
    -        cfg: configuration dictionary
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: tf.keras.Model,
    -        vocab: str,
    -        rnn_units: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(vocab=vocab, cfg=cfg)
    -        self.feat_extractor = feature_extractor
    -
    -        # Initialize kernels
    -        h, w, c = self.feat_extractor.output_shape[1:]
    -        self.max_length = w
    -
    -        self.decoder = Sequential(
    -            [
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Dense(units=len(vocab) + 1)
    -            ]
    -        )
    -        self.decoder.build(input_shape=(None, w, h * c))
    -
    -        self.postprocessor = CTCPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        target: List[str],
    -    ) -> tf.Tensor:
    -        """Compute CTC loss for the model.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        gt, seq_len = self.compute_target(target)
    -        batch_len = model_output.shape[0]
    -        input_length = model_output.shape[1] * tf.ones(shape=(batch_len))
    -        ctc_loss = tf.nn.ctc_loss(
    -            gt, model_output, seq_len, input_length, logits_time_major=False, blank_index=len(self.vocab)
    -        )
    -        return ctc_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        # B x H x W x C --> B x W x H x C
    -        transposed_feat = tf.transpose(features, perm=[0, 2, 1, 3])
    -        w, h, c = transposed_feat.get_shape().as_list()[1:]
    -        # B x W x H x C --> B x W x H * C
    -        features_seq = tf.reshape(transposed_feat, shape=(-1, w, h * c))
    -        logits = self.decoder(features_seq, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = logits
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(logits)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(logits, target)
    -
    -        return out
    -
    -
    -def _crnn(arch: str, pretrained: bool, input_shape: Optional[Tuple[int, int, int]] = None, **kwargs: Any) -> CRNN:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[_cfg['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -
    -    # Build the model
    -    model = CRNN(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a VGG-16 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_vgg16_bn - >>> model = crnn_vgg16_bn(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_vgg16_bn', pretrained, **kwargs)
    - - - -def crnn_resnet31(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a resnet31 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_resnet31 - >>> model = crnn_resnet31(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_resnet31', pretrained, **kwargs) -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.0/_modules/doctr/models/recognition/crnn/tensorflow.html b/v0.5.0/_modules/doctr/models/recognition/crnn/tensorflow.html index 41cc93dd23..bc64da9a1b 100644 --- a/v0.5.0/_modules/doctr/models/recognition/crnn/tensorflow.html +++ b/v0.5.0/_modules/doctr/models/recognition/crnn/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.crnn.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple, Union
    +
     import tensorflow as tf
     from tensorflow.keras import layers
    -from tensorflow.keras.models import Sequential, Model
    -from typing import Tuple, Dict, Any, Optional, List
    +from tensorflow.keras.models import Model, Sequential
    +
    +from doctr.datasets import VOCABS
     
    -from ... import backbones
    -from ...utils import load_pretrained_params
    +from ...classification import mobilenet_v3_large_r, mobilenet_v3_small_r, vgg16_bn_r
    +from ...utils.tensorflow import _bf16_to_float32, _build_model, load_pretrained_params
     from ..core import RecognitionModel, RecognitionPostProcessor
     
    -__all__ = ['CRNN', 'crnn_vgg16_bn', 'crnn_resnet31', 'CTCPostProcessor']
    +__all__ = ["CRNN", "crnn_vgg16_bn", "crnn_mobilenet_v3_small", "crnn_mobilenet_v3_large"]
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'crnn_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/crnn_vgg16_bn-748c855f.zip',
    +    "crnn_vgg16_bn": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["legacy_french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/crnn_vgg16_bn-9c188f45.weights.h5&src=0",
         },
    -    'crnn_resnet31': {
    -        'mean': (0.694, 0.695, 0.693),
    -        'std': (0.299, 0.296, 0.301),
    -        'backbone': 'resnet31', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.1/crnn_resnet31-69ab71db.zip',
    +    "crnn_mobilenet_v3_small": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/crnn_mobilenet_v3_small-54850265.weights.h5&src=0",
    +    },
    +    "crnn_mobilenet_v3_large": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/crnn_mobilenet_v3_large-c64045e5.weights.h5&src=0",
         },
     }
     
     
     class CTCPostProcessor(RecognitionPostProcessor):
    -    """
    -    Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
    +    """Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
     
         Args:
    +    ----
             vocab: string containing the ordered sequence of supported characters
             ignore_case: if True, ignore case of letters
             ignore_accents: if True, ignore accents of letters
    @@ -325,37 +353,57 @@ 

    Source code for doctr.models.recognition.crnn.tensorflow

    def __call__( self, - logits: tf.Tensor - ) -> List[Tuple[str, float]]: - """ - Performs decoding of raw output with CTC and decoding of CTC predictions + logits: tf.Tensor, + beam_width: int = 1, + top_paths: int = 1, + ) -> Union[List[Tuple[str, float]], List[Tuple[List[str], List[float]]]]: + """Performs decoding of raw output with CTC and decoding of CTC predictions with label_to_idx mapping dictionnary Args: + ---- logits: raw output of the model, shape BATCH_SIZE X SEQ_LEN X NUM_CLASSES + 1 + beam_width: An int scalar >= 0 (beam search beam width). + top_paths: An int scalar >= 0, <= beam_width (controls output size). Returns: + ------- A list of decoded words of length BATCH_SIZE + """ # Decode CTC _decoded, _log_prob = tf.nn.ctc_beam_search_decoder( tf.transpose(logits, perm=[1, 0, 2]), - tf.fill(logits.shape[0], logits.shape[1]), - beam_width=1, top_paths=1, + tf.fill(tf.shape(logits)[:1], tf.shape(logits)[1]), + beam_width=beam_width, + top_paths=top_paths, ) - out_idxs = tf.sparse.to_dense(_decoded[0], default_value=len(self.vocab)) - probs = tf.math.exp(tf.squeeze(_log_prob, axis=1)) + + _decoded = tf.sparse.concat( + 1, + [tf.sparse.expand_dims(dec, axis=1) for dec in _decoded], + expand_nonconcat_dims=True, + ) # dim : batchsize x beamwidth x actual_max_len_predictions + out_idxs = tf.sparse.to_dense(_decoded, default_value=len(self.vocab)) # Map it to characters _decoded_strings_pred = tf.strings.reduce_join( inputs=tf.nn.embedding_lookup(tf.constant(self._embedding, dtype=tf.string), out_idxs), - axis=-1 + axis=-1, ) _decoded_strings_pred = tf.strings.split(_decoded_strings_pred, "<eos>") - decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0] - word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] - + decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value="not valid")[ + :, :, 0 + ] # dim : batch_size x beam_width + + if top_paths == 1: + probs = tf.math.exp(tf.squeeze(_log_prob, axis=1)) # dim : batchsize + decoded_strings_pred = tf.squeeze(decoded_strings_pred, axis=1) + word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] + else: + probs = tf.math.exp(_log_prob) # dim : batchsize x beamwidth + word_values = [[word.decode() for word in words] for words in decoded_strings_pred.numpy().tolist()] return list(zip(word_values, probs.numpy().tolist())) @@ -364,19 +412,26 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. Args: + ---- feature_extractor: the backbone serving as feature extractor vocab: vocabulary used for encoding rnn_units: number of units in the LSTM layers + exportable: onnx exportable returns only logits + beam_width: beam width for beam search decoding + top_paths: number of top paths for beam search decoding cfg: configuration dictionary """ - _children_names: List[str] = ['feat_extractor', 'decoder', 'postprocessor'] + _children_names: List[str] = ["feat_extractor", "decoder", "postprocessor"] def __init__( self, - feature_extractor: tf.keras.Model, + feature_extractor: Model, vocab: str, rnn_units: int = 128, + exportable: bool = False, + beam_width: int = 1, + top_paths: int = 1, cfg: Optional[Dict[str, Any]] = None, ) -> None: # Initialize kernels @@ -386,19 +441,21 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    self.vocab = vocab self.max_length = w self.cfg = cfg + self.exportable = exportable self.feat_extractor = feature_extractor - self.decoder = Sequential( - [ - layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), - layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), - layers.Dense(units=len(vocab) + 1) - ] - ) + self.decoder = Sequential([ + layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), + layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), + layers.Dense(units=len(vocab) + 1), + ]) self.decoder.build(input_shape=(None, w, h * c)) self.postprocessor = CTCPostProcessor(vocab=vocab) + self.beam_width = beam_width + self.top_paths = top_paths + def compute_loss( self, model_output: tf.Tensor, @@ -407,16 +464,17 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    """Compute CTC loss for the model. Args: - gt: the encoded tensor with gt labels + ---- model_output: predicted logits of the model - seq_len: lengths of each gt word inside the batch + target: lengths of each gt word inside the batch Returns: + ------- The loss of the model on the batch """ - gt, seq_len = self.compute_target(target) + gt, seq_len = self.build_target(target) batch_len = model_output.shape[0] - input_length = model_output.shape[1] * tf.ones(shape=(batch_len)) + input_length = tf.fill((batch_len,), model_output.shape[1]) ctc_loss = tf.nn.ctc_loss( gt, model_output, seq_len, input_length, logits_time_major=False, blank_index=len(self.vocab) ) @@ -428,8 +486,12 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    target: Optional[List[str]] = None, return_model_output: bool = False, return_preds: bool = False, + beam_width: int = 1, + top_paths: int = 1, **kwargs: Any, ) -> Dict[str, Any]: + if kwargs.get("training", False) and target is None: + raise ValueError("Need to provide labels during training") features = self.feat_extractor(x, **kwargs) # B x H x W x C --> B x W x H x C @@ -437,91 +499,132 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    w, h, c = transposed_feat.get_shape().as_list()[1:] # B x W x H x C --> B x W x H * C features_seq = tf.reshape(transposed_feat, shape=(-1, w, h * c)) - logits = self.decoder(features_seq, **kwargs) + logits = _bf16_to_float32(self.decoder(features_seq, **kwargs)) out: Dict[str, tf.Tensor] = {} + if self.exportable: + out["logits"] = logits + return out + if return_model_output: out["out_map"] = logits if target is None or return_preds: # Post-process boxes - out["preds"] = self.postprocessor(logits) + out["preds"] = self.postprocessor(logits, beam_width=beam_width, top_paths=top_paths) if target is not None: - out['loss'] = self.compute_loss(logits, target) + out["loss"] = self.compute_loss(logits, target) return out -def _crnn(arch: str, pretrained: bool, input_shape: Optional[Tuple[int, int, int]] = None, **kwargs: Any) -> CRNN: +def _crnn( + arch: str, + pretrained: bool, + backbone_fn, + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> CRNN: + pretrained_backbone = pretrained_backbone and not pretrained + + kwargs["vocab"] = kwargs.get("vocab", default_cfgs[arch]["vocab"]) - # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab']) - _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units']) + _cfg["vocab"] = kwargs["vocab"] + _cfg["input_shape"] = input_shape or default_cfgs[arch]["input_shape"] - # Feature extractor - feat_extractor = backbones.__dict__[_cfg['backbone']]( - input_shape=_cfg['input_shape'], + feat_extractor = backbone_fn( + input_shape=_cfg["input_shape"], include_top=False, + pretrained=pretrained_backbone, ) - kwargs['vocab'] = _cfg['vocab'] - kwargs['rnn_units'] = _cfg['rnn_units'] - # Build the model model = CRNN(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) # Load pretrained parameters if pretrained: - load_pretrained_params(model, _cfg['url']) + # The given vocab differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params(model, _cfg["url"], skip_mismatch=kwargs["vocab"] != default_cfgs[arch]["vocab"]) return model
    -[docs] +[docs] def crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> CRNN: """CRNN with a VGG-16 backbone as described in `"An End-to-End Trainable Neural Network for Image-based Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_vgg16_bn - >>> model = crnn_vgg16_bn(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import crnn_vgg16_bn + >>> model = crnn_vgg16_bn(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the CRNN architecture Returns: + ------- text recognition architecture """ + return _crnn("crnn_vgg16_bn", pretrained, vgg16_bn_r, **kwargs)
    + + + +
    +[docs] +def crnn_mobilenet_v3_small(pretrained: bool = False, **kwargs: Any) -> CRNN: + """CRNN with a MobileNet V3 Small backbone as described in `"An End-to-End Trainable Neural Network for Image-based + Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. + + >>> import tensorflow as tf + >>> from doctr.models import crnn_mobilenet_v3_small + >>> model = crnn_mobilenet_v3_small(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the CRNN architecture - return _crnn('crnn_vgg16_bn', pretrained, **kwargs)
    + Returns: + ------- + text recognition architecture + """ + return _crnn("crnn_mobilenet_v3_small", pretrained, mobilenet_v3_small_r, **kwargs)
    -def crnn_resnet31(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a resnet31 backbone as described in `"An End-to-End Trainable Neural Network for Image-based +
    +[docs] +def crnn_mobilenet_v3_large(pretrained: bool = False, **kwargs: Any) -> CRNN: + """CRNN with a MobileNet V3 Large backbone as described in `"An End-to-End Trainable Neural Network for Image-based Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_resnet31 - >>> model = crnn_resnet31(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import crnn_mobilenet_v3_large + >>> model = crnn_mobilenet_v3_large(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the CRNN architecture Returns: + ------- text recognition architecture """ + return _crnn("crnn_mobilenet_v3_large", pretrained, mobilenet_v3_large_r, **kwargs)
    - return _crnn('crnn_resnet31', pretrained, **kwargs)
    @@ -554,8 +657,8 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    - +
    + diff --git a/v0.5.0/_modules/doctr/models/recognition/master/tensorflow.html b/v0.5.0/_modules/doctr/models/recognition/master/tensorflow.html index 2dc5a27717..aa6aa69325 100644 --- a/v0.5.0/_modules/doctr/models/recognition/master/tensorflow.html +++ b/v0.5.0/_modules/doctr/models/recognition/master/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.master.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.master.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import tensorflow as tf
    -from tensorflow.keras import layers, Sequential, Model
    -from typing import Tuple, List, Dict, Any, Optional
     from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple
     
    -from ..core import RecognitionPostProcessor
    -from ...backbones.resnet import ResnetStage
    -from ...utils import conv_sequence, load_pretrained_params
    -from ..transformer import Decoder, positional_encoding, create_look_ahead_mask, create_padding_mask
    -from ....datasets import VOCABS
    -from .base import _MASTER, _MASTERPostProcessor
    +import tensorflow as tf
    +from tensorflow.keras import Model, layers
    +
    +from doctr.datasets import VOCABS
    +from doctr.models.classification import magc_resnet31
    +from doctr.models.modules.transformer import Decoder, PositionalEncoding
     
    +from ...utils.tensorflow import _bf16_to_float32, _build_model, load_pretrained_params
    +from .base import _MASTER, _MASTERPostProcessor
     
    -__all__ = ['MASTER', 'master', 'MASTERPostProcessor']
    +__all__ = ["MASTER", "master"]
     
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'master': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'input_shape': (48, 160, 3),
    -        'vocab': VOCABS['french'],
    -        'url': None,
    +    "master": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/master-d7fdaeff.weights.h5&src=0",
         },
     }
     
     
    -class MAGC(layers.Layer):
    -
    -    """Implements the Multi-Aspect Global Context Attention, as described in
    -    <https://arxiv.org/pdf/1910.02562.pdf>`_.
    -
    -    Args:
    -        inplanes: input channels
    -        headers: number of headers to split channels
    -        att_scale: if True, re-scale attention to counteract the variance distibutions
    -        **kwargs
    -    """
    -
    -    def __init__(
    -        self,
    -        inplanes: int,
    -        headers: int = 1,
    -        att_scale: bool = False,
    -        **kwargs
    -    ) -> None:
    -        super().__init__(**kwargs)
    -
    -        self.headers = headers  # h
    -        self.inplanes = inplanes  # C
    -        self.att_scale = att_scale
    -
    -        self.single_header_inplanes = int(inplanes / headers)  # C / h
    -
    -        self.conv_mask = tf.keras.layers.Conv2D(
    -            filters=1,
    -            kernel_size=1,
    -            kernel_initializer=tf.initializers.he_normal()
    -        )
    -
    -        self.transform = tf.keras.Sequential(
    -            [
    -                tf.keras.layers.Conv2D(
    -                    filters=self.inplanes,
    -                    kernel_size=1,
    -                    kernel_initializer=tf.initializers.he_normal()
    -                ),
    -                tf.keras.layers.LayerNormalization([1, 2, 3]),
    -                tf.keras.layers.ReLU(),
    -                tf.keras.layers.Conv2D(
    -                    filters=self.inplanes,
    -                    kernel_size=1,
    -                    kernel_initializer=tf.initializers.he_normal()
    -                ),
    -            ],
    -            name='transform'
    -        )
    -
    -    @tf.function
    -    def context_modeling(self, inputs: tf.Tensor) -> tf.Tensor:
    -        b, h, w, c = (tf.shape(inputs)[i] for i in range(4))
    -
    -        # B, H, W, C -->> B*h, H, W, C/h
    -        x = tf.reshape(inputs, shape=(b, h, w, self.headers, self.single_header_inplanes))
    -        x = tf.transpose(x, perm=(0, 3, 1, 2, 4))
    -        x = tf.reshape(x, shape=(b * self.headers, h, w, self.single_header_inplanes))
    -
    -        # Compute shorcut
    -        shortcut = x
    -        # B*h, 1, H*W, C/h
    -        shortcut = tf.reshape(shortcut, shape=(b * self.headers, 1, h * w, self.single_header_inplanes))
    -        # B*h, 1, C/h, H*W
    -        shortcut = tf.transpose(shortcut, perm=[0, 1, 3, 2])
    -
    -        # Compute context mask
    -        # B*h, H, W, 1,
    -        context_mask = self.conv_mask(x)
    -        # B*h, 1, H*W, 1
    -        context_mask = tf.reshape(context_mask, shape=(b * self.headers, 1, h * w, 1))
    -        # scale variance
    -        if self.att_scale and self.headers > 1:
    -            context_mask = context_mask / tf.sqrt(self.single_header_inplanes)
    -        # B*h, 1, H*W, 1
    -        context_mask = tf.keras.activations.softmax(context_mask, axis=2)
    -
    -        # Compute context
    -        # B*h, 1, C/h, 1
    -        context = tf.matmul(shortcut, context_mask)
    -        context = tf.reshape(context, shape=(b, 1, c, 1))
    -        # B, 1, 1, C
    -        context = tf.transpose(context, perm=(0, 1, 3, 2))
    -        # Set shape to resolve shape when calling this module in the Sequential MAGCResnet
    -        batch, chan = inputs.get_shape().as_list()[0], inputs.get_shape().as_list()[-1]
    -        context.set_shape([batch, 1, 1, chan])
    -        return context
    -
    -    def call(self, inputs: tf.Tensor, **kwargs) -> tf.Tensor:
    -        # Context modeling: B, H, W, C  ->  B, 1, 1, C
    -        context = self.context_modeling(inputs)
    -        # Transform: B, 1, 1, C  ->  B, 1, 1, C
    -        transformed = self.transform(context)
    -        return inputs + transformed
    -
    -
    -class MAGCResnet(Sequential):
    -
    -    """Implements the modified resnet with MAGC layers, as described in paper.
    -
    -    Args:
    -        headers: number of header to split channels in MAGC layers
    -        input_shape: shape of the model input (without batch dim)
    -    """
    -
    -    def __init__(
    -        self,
    -        headers: int = 1,
    -        input_shape: Tuple[int, int, int] = (48, 160, 3),
    -    ) -> None:
    -        _layers = [
    -            # conv_1x
    -            *conv_sequence(out_channels=64, activation='relu', bn=True, kernel_size=3, input_shape=input_shape),
    -            *conv_sequence(out_channels=128, activation='relu', bn=True, kernel_size=3),
    -            layers.MaxPooling2D((2, 2), (2, 2)),
    -            # conv_2x
    -            ResnetStage(num_blocks=1, output_channels=256),
    -            MAGC(inplanes=256, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=256, activation='relu', bn=True, kernel_size=3),
    -            layers.MaxPooling2D((2, 2), (2, 2)),
    -            # conv_3x
    -            ResnetStage(num_blocks=2, output_channels=512),
    -            MAGC(inplanes=512, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=512, activation='relu', bn=True, kernel_size=3),
    -            layers.MaxPooling2D((2, 1), (2, 1)),
    -            # conv_4x
    -            ResnetStage(num_blocks=5, output_channels=512),
    -            MAGC(inplanes=512, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=512, activation='relu', bn=True, kernel_size=3),
    -            # conv_5x
    -            ResnetStage(num_blocks=3, output_channels=512),
    -            MAGC(inplanes=512, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=512, activation='relu', bn=True, kernel_size=3),
    -        ]
    -        super().__init__(_layers)
    -
    -
     class MASTER(_MASTER, Model):
    -
         """Implements MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_.
         Implementation based on the official TF implementation: <https://github.com/jiangxiluning/MASTER-TF>`_.
     
         Args:
    +    ----
    +        feature_extractor: the backbone serving as feature extractor
             vocab: vocabulary, (without EOS, SOS, PAD)
             d_model: d parameter for the transformer decoder
    -        headers: headers for the MAGC module
             dff: depth of the pointwise feed-forward layer
             num_heads: number of heads for the mutli-head attention module
             num_layers: number of decoder layers to stack
             max_length: maximum length of character sequence handled by the model
    -        input_size: size of the image inputs
    +        dropout: dropout probability of the decoder
    +        input_shape: size of the image inputs
    +        exportable: onnx exportable returns only logits
    +        cfg: dictionary containing information about the model
         """
     
         def __init__(
             self,
    +        feature_extractor: Model,
             vocab: str,
             d_model: int = 512,
    -        headers: int = 1,
             dff: int = 2048,
    -        num_heads: int = 8,
    +        num_heads: int = 8,  # number of heads in the transformer decoder
             num_layers: int = 3,
             max_length: int = 50,
    -        input_shape: Tuple[int, int, int] = (48, 160, 3),
    +        dropout: float = 0.2,
    +        input_shape: Tuple[int, int, int] = (32, 128, 3),  # different from the paper
    +        exportable: bool = False,
             cfg: Optional[Dict[str, Any]] = None,
         ) -> None:
             super().__init__()
     
    -        self.vocab = vocab
    +        self.exportable = exportable
             self.max_length = max_length
    +        self.d_model = d_model
    +        self.vocab = vocab
             self.cfg = cfg
             self.vocab_size = len(vocab)
     
    -        self.feature_extractor = MAGCResnet(headers=headers, input_shape=input_shape)
    -        self.seq_embedding = layers.Embedding(self.vocab_size + 3, d_model)  # 3 more classes: EOS/PAD/SOS
    +        self.feat_extractor = feature_extractor
    +        self.positional_encoding = PositionalEncoding(self.d_model, dropout, max_len=input_shape[0] * input_shape[1])
     
             self.decoder = Decoder(
                 num_layers=num_layers,
    -            d_model=d_model,
    +            d_model=self.d_model,
                 num_heads=num_heads,
    +            vocab_size=self.vocab_size + 3,  # EOS, SOS, PAD
                 dff=dff,
    -            vocab_size=self.vocab_size,
    -            maximum_position_encoding=max_length,
    +            dropout=dropout,
    +            maximum_position_encoding=self.max_length,
             )
    -        self.feature_pe = positional_encoding(input_shape[0] * input_shape[1], d_model)
    -        self.linear = layers.Dense(self.vocab_size + 3, kernel_initializer=tf.initializers.he_uniform())
     
    +        self.linear = layers.Dense(self.vocab_size + 3, kernel_initializer=tf.initializers.he_uniform())
             self.postprocessor = MASTERPostProcessor(vocab=self.vocab)
     
         @tf.function
    -    def make_mask(self, target: tf.Tensor) -> tf.Tensor:
    -        look_ahead_mask = create_look_ahead_mask(tf.shape(target)[1])
    -        target_padding_mask = create_padding_mask(target, self.vocab_size + 2)  # Pad symbol
    -        combined_mask = tf.maximum(target_padding_mask, look_ahead_mask)
    -        return combined_mask
    +    def make_source_and_target_mask(self, source: tf.Tensor, target: tf.Tensor) -> Tuple[tf.Tensor, tf.Tensor]:
    +        # [1, 1, 1, ..., 0, 0, 0] -> 0 is masked
    +        # (N, 1, 1, max_length)
    +        target_pad_mask = tf.cast(tf.math.not_equal(target, self.vocab_size + 2), dtype=tf.uint8)
    +        target_pad_mask = target_pad_mask[:, tf.newaxis, tf.newaxis, :]
    +        target_length = target.shape[1]
    +        # sub mask filled diagonal with 1 = see 0 = masked (max_length, max_length)
    +        target_sub_mask = tf.linalg.band_part(tf.ones((target_length, target_length)), -1, 0)
    +        # source mask filled with ones (max_length, positional_encoded_seq_len)
    +        source_mask = tf.ones((target_length, source.shape[1]))
    +        # combine the two masks into one boolean mask where False is masked (N, 1, max_length, max_length)
    +        target_mask = tf.math.logical_and(
    +            tf.cast(target_sub_mask, dtype=tf.bool), tf.cast(target_pad_mask, dtype=tf.bool)
    +        )
    +        return source_mask, target_mask
     
    +    @staticmethod
         def compute_loss(
    -        self,
             model_output: tf.Tensor,
             gt: tf.Tensor,
             seq_len: List[int],
    @@ -512,11 +413,13 @@ 

    Source code for doctr.models.recognition.master.tensorflow

    Sequences are masked after the EOS character. Args: + ---- gt: the encoded tensor with gt labels model_output: predicted logits of the model seq_len: lengths of each gt word inside the batch Returns: + ------- The loss of the model on the batch """ # Input length : number of timesteps @@ -532,7 +435,7 @@

    Source code for doctr.models.recognition.master.tensorflow

    mask_values = tf.zeros_like(cce) mask_2d = tf.sequence_mask(seq_len, input_len - 1) # delete the last mask timestep as well masked_loss = tf.where(mask_2d, cce, mask_values) - ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32)) + ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, model_output.dtype)) return tf.expand_dims(ce_loss, axis=1) @@ -547,94 +450,103 @@

    Source code for doctr.models.recognition.master.tensorflow

    """Call function for training Args: + ---- x: images target: list of str labels return_model_output: if True, return logits return_preds: if True, decode logits + **kwargs: keyword arguments passed to the decoder - Return: + Returns: + ------- A dictionnary containing eventually loss, logits and predictions. """ - # Encode - feature = self.feature_extractor(x, **kwargs) - b, h, w, c = (tf.shape(feature)[i] for i in range(4)) + feature = self.feat_extractor(x, **kwargs) + b, h, w, c = feature.get_shape() + # (N, H, W, C) --> (N, H * W, C) feature = tf.reshape(feature, shape=(b, h * w, c)) - encoded = feature + self.feature_pe[:, :h * w, :] + # add positional encoding to features + encoded = self.positional_encoding(feature, **kwargs) out: Dict[str, tf.Tensor] = {} + if kwargs.get("training", False) and target is None: + raise ValueError("Need to provide labels during training") + if target is not None: # Compute target: tensor of gts and sequence lengths - gt, seq_len = self.compute_target(target) - - if kwargs.get('training', False): - if target is None: - raise AssertionError("In training mode, you need to pass a value to 'target'") - tgt_mask = self.make_mask(gt) + gt, seq_len = self.build_target(target) + # Compute decoder masks + source_mask, target_mask = self.make_source_and_target_mask(encoded, gt) # Compute logits - output = self.decoder(gt, encoded, tgt_mask, None, **kwargs) + output = self.decoder(gt, encoded, source_mask, target_mask, **kwargs) logits = self.linear(output, **kwargs) - else: - # When not training, we want to compute logits in with the decoder, although - # we have access to gts (we need gts to compute the loss, but not in the decoder) logits = self.decode(encoded, **kwargs) + logits = _bf16_to_float32(logits) + + if self.exportable: + out["logits"] = logits + return out + if target is not None: - out['loss'] = self.compute_loss(logits, gt, seq_len) + out["loss"] = self.compute_loss(logits, gt, seq_len) if return_model_output: - out['out_map'] = logits + out["out_map"] = logits if return_preds: - predictions = self.postprocessor(logits) - out['preds'] = predictions + out["preds"] = self.postprocessor(logits) return out + @tf.function def decode(self, encoded: tf.Tensor, **kwargs: Any) -> tf.Tensor: """Decode function for prediction Args: + ---- encoded: encoded features + **kwargs: keyword arguments passed to the decoder - Return: + Returns: + ------- A Tuple of tf.Tensor: predictions, logits """ - b = tf.shape(encoded)[0] - max_len = tf.constant(self.max_length, dtype=tf.int32) + b = encoded.shape[0] + start_symbol = tf.constant(self.vocab_size + 1, dtype=tf.int32) # SOS padding_symbol = tf.constant(self.vocab_size + 2, dtype=tf.int32) # PAD - ys = tf.fill(dims=(b, max_len - 1), value=padding_symbol) + ys = tf.fill(dims=(b, self.max_length - 1), value=padding_symbol) start_vector = tf.fill(dims=(b, 1), value=start_symbol) ys = tf.concat([start_vector, ys], axis=-1) - logits = tf.zeros(shape=(b, max_len - 1, self.vocab_size + 3), dtype=tf.float32) # 3 symbols - # max_len = len + 2 (sos + eos) + # Final dimension include EOS/SOS/PAD for i in range(self.max_length - 1): - ys_mask = self.make_mask(ys) - output = self.decoder(ys, encoded, ys_mask, None, **kwargs) + source_mask, target_mask = self.make_source_and_target_mask(encoded, ys) + output = self.decoder(ys, encoded, source_mask, target_mask, **kwargs) logits = self.linear(output, **kwargs) prob = tf.nn.softmax(logits, axis=-1) - next_word = tf.argmax(prob, axis=-1, output_type=ys.dtype) - # ys.shape = B, T - i_mesh, j_mesh = tf.meshgrid(tf.range(b), tf.range(max_len), indexing='ij') + next_token = tf.argmax(prob, axis=-1, output_type=ys.dtype) + # update ys with the next token and ignore the first token (SOS) + i_mesh, j_mesh = tf.meshgrid(tf.range(b), tf.range(self.max_length), indexing="ij") indices = tf.stack([i_mesh[:, i + 1], j_mesh[:, i + 1]], axis=1) - ys = tf.tensor_scatter_nd_update(ys, indices, next_word[:, i + 1]) + ys = tf.tensor_scatter_nd_update(ys, indices, next_token[:, i]) - # final_logits of shape (N, max_length - 1, vocab_size + 1) (whithout sos) + # Shape (N, max_length, vocab_size + 1) return logits class MASTERPostProcessor(_MASTERPostProcessor): """Post processor for MASTER architectures + Args: + ---- vocab: string containing the ordered sequence of supported characters - ignore_case: if True, ignore case of letters - ignore_accents: if True, ignore accents of letters """ def __call__( @@ -649,51 +561,66 @@

    Source code for doctr.models.recognition.master.tensorflow

    probs = tf.math.reduce_min(probs, axis=1) # decode raw output of the model with tf_label_to_idx - out_idxs = tf.cast(out_idxs, dtype='int32') + out_idxs = tf.cast(out_idxs, dtype="int32") embedding = tf.constant(self._embedding, dtype=tf.string) decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(embedding, out_idxs), axis=-1) decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>") - decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0] + decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value="not valid")[:, 0] word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] - return list(zip(word_values, probs.numpy().tolist())) + return list(zip(word_values, probs.numpy().clip(0, 1).tolist())) -def _master(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> MASTER: +def _master(arch: str, pretrained: bool, backbone_fn, pretrained_backbone: bool = True, **kwargs: Any) -> MASTER: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab']) + _cfg["input_shape"] = kwargs.get("input_shape", _cfg["input_shape"]) + _cfg["vocab"] = kwargs.get("vocab", _cfg["vocab"]) - kwargs['vocab'] = _cfg['vocab'] + kwargs["vocab"] = _cfg["vocab"] + kwargs["input_shape"] = _cfg["input_shape"] # Build the model - model = MASTER(cfg=_cfg, **kwargs) + model = MASTER( + backbone_fn(pretrained=pretrained_backbone, input_shape=_cfg["input_shape"], include_top=False), + cfg=_cfg, + **kwargs, + ) + _build_model(model) + # Load pretrained parameters if pretrained: - load_pretrained_params(model, default_cfgs[arch]['url']) + # The given vocab differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, default_cfgs[arch]["url"], skip_mismatch=kwargs["vocab"] != default_cfgs[arch]["vocab"] + ) return model
    -[docs] +[docs] def master(pretrained: bool = False, **kwargs: Any) -> MASTER: """MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import master - >>> model = master(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 48, 160, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + + >>> import tensorflow as tf + >>> from doctr.models import master + >>> model = master(pretrained=False) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keywoard arguments passed to the MASTER architecture + Returns: + ------- text recognition architecture """ - - return _master('master', pretrained, **kwargs)
    + return _master("master", pretrained, magc_resnet31, **kwargs)
    @@ -727,8 +654,8 @@

    Source code for doctr.models.recognition.master.tensorflow

    - +
    + diff --git a/v0.5.0/_modules/doctr/models/recognition/parseq/tensorflow.html b/v0.5.0/_modules/doctr/models/recognition/parseq/tensorflow.html index 0819737dfc..b181acef53 100644 --- a/v0.5.0/_modules/doctr/models/recognition/parseq/tensorflow.html +++ b/v0.5.0/_modules/doctr/models/recognition/parseq/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.parseq.tensorflow - docTR documentation @@ -845,7 +845,7 @@

    Source code for doctr.models.recognition.parseq.tensorflow

    - + diff --git a/v0.5.0/_modules/doctr/models/recognition/sar.html b/v0.5.0/_modules/doctr/models/recognition/sar.html deleted file mode 100644 index 2482e9f156..0000000000 --- a/v0.5.0/_modules/doctr/models/recognition/sar.html +++ /dev/null @@ -1,712 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.sar - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.sar

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import Sequential, layers
    -from typing import Tuple, Dict, List, Any, Optional
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel
    -from .core import RecognitionPostProcessor
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['SAR', 'SARPostProcessor', 'sar_vgg16_bn', 'sar_resnet31']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'sar_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1-models/sar_vgg16bn-0d7e2c26.zip',
    -    },
    -    'sar_resnet31': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'resnet31', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/sar_resnet31-ea202587.zip',
    -    },
    -}
    -
    -
    -class AttentionModule(layers.Layer, NestedObject):
    -    """Implements attention module of the SAR model
    -
    -    Args:
    -        attention_units: number of hidden attention units
    -
    -    """
    -    def __init__(
    -        self,
    -        attention_units: int
    -    ) -> None:
    -
    -        super().__init__()
    -        self.hidden_state_projector = layers.Conv2D(
    -            attention_units, 1, strides=1, use_bias=False, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.features_projector = layers.Conv2D(
    -            attention_units, 3, strides=1, use_bias=True, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.attention_projector = layers.Conv2D(
    -            1, 1, strides=1, use_bias=False, padding="same", kernel_initializer='he_normal',
    -        )
    -        self.flatten = layers.Flatten()
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        hidden_state: tf.Tensor,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        [H, W] = features.get_shape().as_list()[1:3]
    -        # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units)
    -        hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs)
    -        # shape (N, H, W, vgg_units) -> (N, H, W, attention_units)
    -        features_projection = self.features_projector(features, **kwargs)
    -        projection = tf.math.tanh(hidden_state_projection + features_projection)
    -        # shape (N, H, W, attention_units) -> (N, H, W, 1)
    -        attention = self.attention_projector(projection, **kwargs)
    -        # shape (N, H, W, 1) -> (N, H * W)
    -        attention = self.flatten(attention)
    -        attention = tf.nn.softmax(attention)
    -        # shape (N, H * W) -> (N, H, W, 1)
    -        attention_map = tf.reshape(attention, [-1, H, W, 1])
    -        glimpse = tf.math.multiply(features, attention_map)
    -        # shape (N, H * W) -> (N, 1)
    -        glimpse = tf.reduce_sum(glimpse, axis=[1, 2])
    -        return glimpse
    -
    -
    -class SARDecoder(layers.Layer, NestedObject):
    -    """Implements decoder module of the SAR model
    -
    -    Args:
    -        rnn_units: number of hidden units in recurrent cells
    -        max_length: maximum length of a sequence
    -        vocab_size: number of classes in the model alphabet
    -        embedding_units: number of hidden embedding units
    -        attention_units: number of hidden attention units
    -        num_decoder_layers: number of LSTM layers to stack
    -
    -    """
    -    def __init__(
    -        self,
    -        rnn_units: int,
    -        max_length: int,
    -        vocab_size: int,
    -        embedding_units: int,
    -        attention_units: int,
    -        num_decoder_layers: int = 2,
    -        input_shape: Optional[List[Tuple[Optional[int]]]] = None,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.vocab_size = vocab_size
    -        self.lstm_decoder = layers.StackedRNNCells(
    -            [layers.LSTMCell(rnn_units, dtype=tf.float32, implementation=1) for _ in range(num_decoder_layers)]
    -        )
    -        self.embed = layers.Dense(embedding_units, use_bias=False, input_shape=(None, self.vocab_size + 1))
    -        self.attention_module = AttentionModule(attention_units)
    -        self.output_dense = layers.Dense(vocab_size + 1, use_bias=True, input_shape=(None, 2 * rnn_units))
    -        self.max_length = max_length
    -
    -        # Initialize kernels
    -        if input_shape is not None:
    -            self.attention_module.call(layers.Input(input_shape[0][1:]), layers.Input((1, 1, rnn_units)))
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        holistic: tf.Tensor,
    -        gt: Optional[tf.Tensor] = None,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # initialize states (each of shape (N, rnn_units))
    -        states = self.lstm_decoder.get_initial_state(
    -            inputs=None, batch_size=features.shape[0], dtype=tf.float32
    -        )
    -        # run first step of lstm
    -        # holistic: shape (N, rnn_units)
    -        _, states = self.lstm_decoder(holistic, states, **kwargs)
    -        # Initialize with the index of virtual START symbol (placed after <eos>)
    -        symbol = tf.fill(features.shape[0], self.vocab_size + 1)
    -        logits_list = []
    -        if kwargs.get('training') and gt is None:
    -            raise ValueError('Need to provide labels during training for teacher forcing')
    -        for t in range(self.max_length + 1):  # keep 1 step for <eos>
    -            # one-hot symbol with depth vocab_size + 1
    -            # embeded_symbol: shape (N, embedding_units)
    -            embeded_symbol = self.embed(tf.one_hot(symbol, depth=self.vocab_size + 1), **kwargs)
    -            logits, states = self.lstm_decoder(embeded_symbol, states, **kwargs)
    -            glimpse = self.attention_module(
    -                features, tf.expand_dims(tf.expand_dims(logits, axis=1), axis=1), **kwargs,
    -            )
    -            # logits: shape (N, rnn_units), glimpse: shape (N, 1)
    -            logits = tf.concat([logits, glimpse], axis=-1)
    -            # shape (N, rnn_units + 1) -> (N, vocab_size + 1)
    -            logits = self.output_dense(logits, **kwargs)
    -            # update symbol with predicted logits for t+1 step
    -            if kwargs.get('training'):
    -                symbol = gt[:, t]
    -            else:
    -                symbol = tf.argmax(logits, axis=-1)
    -            logits_list.append(logits)
    -        outputs = tf.stack(logits_list, axis=1)  # shape (N, max_length + 1, vocab_size + 1)
    -
    -        return outputs
    -
    -
    -class SAR(RecognitionModel):
    -    """Implements a SAR architecture as described in `"Show, Attend and Read:A Simple and Strong Baseline for
    -    Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of hidden units in both encoder and decoder LSTM
    -        embedding_units: number of embedding units
    -        attention_units: number of hidden units in attention module
    -        max_length: maximum word length handled by the model
    -        num_decoders: number of LSTM to stack in decoder layer
    -
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'encoder', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor,
    -        vocab: str,
    -        rnn_units: int = 512,
    -        embedding_units: int = 512,
    -        attention_units: int = 512,
    -        max_length: int = 30,
    -        num_decoders: int = 2,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(vocab=vocab, cfg=cfg)
    -
    -        self.max_length = max_length + 1  # Add 1 timestep for EOS after the longest word
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.encoder = Sequential(
    -            [
    -                layers.LSTM(units=rnn_units, return_sequences=True),
    -                layers.LSTM(units=rnn_units, return_sequences=False)
    -            ]
    -        )
    -        # Initialize the kernels (watch out for reduce_max)
    -        self.encoder.build(input_shape=(None,) + self.feat_extractor.output_shape[2:])
    -
    -        self.decoder = SARDecoder(
    -            rnn_units, max_length, len(vocab), embedding_units, attention_units, num_decoders,
    -            input_shape=[self.feat_extractor.output_shape, self.encoder.output_shape]
    -        )
    -
    -        self.postprocessor = SARPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        gt: tf.Tensor,
    -        seq_len: tf.Tensor,
    -    ) -> tf.Tensor:
    -        """Compute categorical cross-entropy loss for the model.
    -        Sequences are masked after the EOS character.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        # Input length : number of timesteps
    -        input_len = tf.shape(model_output)[1]
    -        # Add one for additional <eos> token
    -        seq_len = seq_len + 1
    -        # One-hot gt labels
    -        oh_gt = tf.one_hot(gt, depth=model_output.shape[2])
    -        # Compute loss
    -        cce = tf.nn.softmax_cross_entropy_with_logits(oh_gt, model_output)
    -        # Compute mask
    -        mask_values = tf.zeros_like(cce)
    -        mask_2d = tf.sequence_mask(seq_len, input_len)
    -        masked_loss = tf.where(mask_2d, cce, mask_values)
    -        ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32))
    -        return tf.expand_dims(ce_loss, axis=1)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        pooled_features = tf.reduce_max(features, axis=1)  # vertical max pooling
    -        encoded = self.encoder(pooled_features, **kwargs)
    -        if target is not None:
    -            gt, seq_len = self.compute_target(target)
    -        decoded_features = self.decoder(features, encoded, gt=None if target is None else gt, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = decoded_features
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(decoded_features)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(decoded_features, gt, seq_len)
    -
    -        return out
    -
    -
    -class SARPostProcessor(RecognitionPostProcessor):
    -    """Post processor for SAR architectures
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor,
    -    ) -> List[Tuple[str, float]]:
    -        # compute pred with argmax for attention models
    -        out_idxs = tf.math.argmax(logits, axis=2)
    -        # N x L
    -        probs = tf.gather(tf.nn.softmax(logits, axis=-1), out_idxs, axis=-1, batch_dims=2)
    -        # Take the minimum confidence of the sequence
    -        probs = tf.math.reduce_min(probs, axis=1)
    -
    -        # decode raw output of the model with tf_label_to_idx
    -        out_idxs = tf.cast(out_idxs, dtype='int32')
    -        decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(self._embedding, out_idxs), axis=-1)
    -        decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -def _sar(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> SAR:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -    _cfg['embedding_units'] = kwargs.get('embedding_units', _cfg['rnn_units'])
    -    _cfg['attention_units'] = kwargs.get('attention_units', _cfg['rnn_units'])
    -    _cfg['max_length'] = kwargs.get('max_length', _cfg['max_length'])
    -    _cfg['num_decoders'] = kwargs.get('num_decoders', _cfg['num_decoders'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[default_cfgs[arch]['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -    kwargs['embedding_units'] = _cfg['embedding_units']
    -    kwargs['attention_units'] = _cfg['attention_units']
    -    kwargs['max_length'] = _cfg['max_length']
    -    kwargs['num_decoders'] = _cfg['num_decoders']
    -
    -    # Build the model
    -    model = SAR(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, default_cfgs[arch]['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a VGG16 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import sar_vgg16_bn - >>> model = sar_vgg16_bn(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_vgg16_bn', pretrained, **kwargs)
    - - - -
    -[docs] -def sar_resnet31(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a resnet-31 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example: - >>> import tensorflow as tf - >>> from doctr.models import sar_resnet31 - >>> model = sar_resnet31(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_resnet31', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.0/_modules/doctr/models/recognition/sar/tensorflow.html b/v0.5.0/_modules/doctr/models/recognition/sar/tensorflow.html index e514e4f0c4..4a591e6451 100644 --- a/v0.5.0/_modules/doctr/models/recognition/sar/tensorflow.html +++ b/v0.5.0/_modules/doctr/models/recognition/sar/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.sar.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.sar.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple
    +
     import tensorflow as tf
    -from tensorflow.keras import Sequential, layers, Model
    -from typing import Tuple, Dict, List, Any, Optional
    +from tensorflow.keras import Model, Sequential, layers
     
    -from ... import backbones
    -from ...utils import load_pretrained_params
    -from ..core import RecognitionModel, RecognitionPostProcessor
    +from doctr.datasets import VOCABS
     from doctr.utils.repr import NestedObject
     
    -__all__ = ['SAR', 'SARPostProcessor', 'sar_vgg16_bn', 'sar_resnet31']
    +from ...classification import resnet31
    +from ...utils.tensorflow import _bf16_to_float32, _build_model, load_pretrained_params
    +from ..core import RecognitionModel, RecognitionPostProcessor
    +
    +__all__ = ["SAR", "sar_resnet31"]
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'sar_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1-models/sar_vgg16bn-0d7e2c26.zip',
    -    },
    -    'sar_resnet31': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'resnet31', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/sar_resnet31-ea202587.zip',
    +    "sar_resnet31": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/sar_resnet31-5a58806c.weights.h5&src=0",
         },
     }
     
     
    +class SAREncoder(layers.Layer, NestedObject):
    +    """Implements encoder module of the SAR model
    +
    +    Args:
    +    ----
    +        rnn_units: number of hidden rnn units
    +        dropout_prob: dropout probability
    +    """
    +
    +    def __init__(self, rnn_units: int, dropout_prob: float = 0.0) -> None:
    +        super().__init__()
    +        self.rnn = Sequential([
    +            layers.LSTM(units=rnn_units, return_sequences=True, recurrent_dropout=dropout_prob),
    +            layers.LSTM(units=rnn_units, return_sequences=False, recurrent_dropout=dropout_prob),
    +        ])
    +
    +    def call(
    +        self,
    +        x: tf.Tensor,
    +        **kwargs: Any,
    +    ) -> tf.Tensor:
    +        # (N, C)
    +        return self.rnn(x, **kwargs)
    +
    +
     class AttentionModule(layers.Layer, NestedObject):
         """Implements attention module of the SAR model
     
         Args:
    +    ----
             attention_units: number of hidden attention units
     
         """
    -    def __init__(
    -        self,
    -        attention_units: int
    -    ) -> None:
     
    +    def __init__(self, attention_units: int) -> None:
             super().__init__()
             self.hidden_state_projector = layers.Conv2D(
    -            attention_units, 1, strides=1, use_bias=False, padding='same', kernel_initializer='he_normal',
    +            attention_units,
    +            1,
    +            strides=1,
    +            use_bias=False,
    +            padding="same",
    +            kernel_initializer="he_normal",
             )
             self.features_projector = layers.Conv2D(
    -            attention_units, 3, strides=1, use_bias=True, padding='same', kernel_initializer='he_normal',
    +            attention_units,
    +            3,
    +            strides=1,
    +            use_bias=True,
    +            padding="same",
    +            kernel_initializer="he_normal",
             )
             self.attention_projector = layers.Conv2D(
    -            1, 1, strides=1, use_bias=False, padding="same", kernel_initializer='he_normal',
    +            1,
    +            1,
    +            strides=1,
    +            use_bias=False,
    +            padding="same",
    +            kernel_initializer="he_normal",
             )
             self.flatten = layers.Flatten()
     
    @@ -343,12 +395,12 @@ 

    Source code for doctr.models.recognition.sar.tensorflow

    hidden_state: tf.Tensor, **kwargs: Any, ) -> tf.Tensor: - [H, W] = features.get_shape().as_list()[1:3] - # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units) - hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs) # shape (N, H, W, vgg_units) -> (N, H, W, attention_units) features_projection = self.features_projector(features, **kwargs) + # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units) + hidden_state = tf.expand_dims(tf.expand_dims(hidden_state, axis=1), axis=1) + hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs) projection = tf.math.tanh(hidden_state_projection + features_projection) # shape (N, H, W, attention_units) -> (N, H, W, 1) attention = self.attention_projector(projection, **kwargs) @@ -358,23 +410,25 @@

    Source code for doctr.models.recognition.sar.tensorflow

    # shape (N, H * W) -> (N, H, W, 1) attention_map = tf.reshape(attention, [-1, H, W, 1]) glimpse = tf.math.multiply(features, attention_map) - # shape (N, H * W) -> (N, 1) - glimpse = tf.reduce_sum(glimpse, axis=[1, 2]) - return glimpse + # shape (N, H * W) -> (N, C) + return tf.reduce_sum(glimpse, axis=[1, 2]) class SARDecoder(layers.Layer, NestedObject): """Implements decoder module of the SAR model Args: + ---- rnn_units: number of hidden units in recurrent cells max_length: maximum length of a sequence vocab_size: number of classes in the model alphabet embedding_units: number of hidden embedding units attention_units: number of hidden attention units - num_decoder_layers: number of LSTM layers to stack + num_decoder_cells: number of LSTMCell layers to stack + dropout_prob: dropout probability """ + def __init__( self, rnn_units: int, @@ -382,23 +436,22 @@

    Source code for doctr.models.recognition.sar.tensorflow

    vocab_size: int, embedding_units: int, attention_units: int, - num_decoder_layers: int = 2, - input_shape: Optional[List[Tuple[Optional[int]]]] = None, + num_decoder_cells: int = 2, + dropout_prob: float = 0.0, ) -> None: - super().__init__() self.vocab_size = vocab_size - self.lstm_decoder = layers.StackedRNNCells( - [layers.LSTMCell(rnn_units, dtype=tf.float32, implementation=1) for _ in range(num_decoder_layers)] - ) - self.embed = layers.Dense(embedding_units, use_bias=False, input_shape=(None, self.vocab_size + 1)) - self.attention_module = AttentionModule(attention_units) - self.output_dense = layers.Dense(vocab_size + 1, use_bias=True, input_shape=(None, 2 * rnn_units)) self.max_length = max_length - # Initialize kernels - if input_shape is not None: - self.attention_module.call(layers.Input(input_shape[0][1:]), layers.Input((1, 1, rnn_units))) + self.embed = layers.Dense(embedding_units, use_bias=False) + self.embed_tgt = layers.Embedding(embedding_units, self.vocab_size + 1) + + self.lstm_cells = layers.StackedRNNCells([ + layers.LSTMCell(rnn_units, implementation=1) for _ in range(num_decoder_cells) + ]) + self.attention_module = AttentionModule(attention_units) + self.output_dense = layers.Dense(self.vocab_size + 1, use_bias=True) + self.dropout = layers.Dropout(dropout_prob) def call( self, @@ -407,40 +460,47 @@

    Source code for doctr.models.recognition.sar.tensorflow

    gt: Optional[tf.Tensor] = None, **kwargs: Any, ) -> tf.Tensor: - - # initialize states (each of shape (N, rnn_units)) - states = self.lstm_decoder.get_initial_state( - inputs=None, batch_size=features.shape[0], dtype=tf.float32 - ) - # run first step of lstm - # holistic: shape (N, rnn_units) - _, states = self.lstm_decoder(holistic, states, **kwargs) - # Initialize with the index of virtual START symbol (placed after <eos>) - symbol = tf.fill(features.shape[0], self.vocab_size + 1) - logits_list = [] - if kwargs.get('training') and gt is None: - raise ValueError('Need to provide labels during training for teacher forcing') - for t in range(self.max_length + 1): # keep 1 step for <eos> - # one-hot symbol with depth vocab_size + 1 - # embeded_symbol: shape (N, embedding_units) - embeded_symbol = self.embed(tf.one_hot(symbol, depth=self.vocab_size + 1), **kwargs) - logits, states = self.lstm_decoder(embeded_symbol, states, **kwargs) - glimpse = self.attention_module( - features, tf.expand_dims(tf.expand_dims(logits, axis=1), axis=1), **kwargs, - ) - # logits: shape (N, rnn_units), glimpse: shape (N, 1) - logits = tf.concat([logits, glimpse], axis=-1) - # shape (N, rnn_units + 1) -> (N, vocab_size + 1) - logits = self.output_dense(logits, **kwargs) - # update symbol with predicted logits for t+1 step - if kwargs.get('training'): - symbol = gt[:, t] # type: ignore[index] + if gt is not None: + gt_embedding = self.embed_tgt(gt, **kwargs) + + logits_list: List[tf.Tensor] = [] + + for t in range(self.max_length + 1): # 32 + if t == 0: + # step to init the first states of the LSTMCell + states = self.lstm_cells.get_initial_state( + inputs=None, batch_size=features.shape[0], dtype=features.dtype + ) + prev_symbol = holistic + elif t == 1: + # step to init a 'blank' sequence of length vocab_size + 1 filled with zeros + # (N, vocab_size + 1) --> (N, embedding_units) + prev_symbol = tf.zeros([features.shape[0], self.vocab_size + 1], dtype=features.dtype) + prev_symbol = self.embed(prev_symbol, **kwargs) else: - symbol = tf.argmax(logits, axis=-1) - logits_list.append(logits) - outputs = tf.stack(logits_list, axis=1) # shape (N, max_length + 1, vocab_size + 1) - - return outputs + if gt is not None and kwargs.get("training", False): + # (N, embedding_units) -2 because of <bos> and <eos> (same) + prev_symbol = self.embed(gt_embedding[:, t - 2], **kwargs) + else: + # -1 to start at timestep where prev_symbol was initialized + index = tf.argmax(logits_list[t - 1], axis=-1) + # update prev_symbol with ones at the index of the previous logit vector + prev_symbol = self.embed(self.embed_tgt(index, **kwargs), **kwargs) + + # (N, C), (N, C) take the last hidden state and cell state from current timestep + _, states = self.lstm_cells(prev_symbol, states, **kwargs) + # states = (hidden_state, cell_state) + hidden_state = states[0][0] + # (N, H, W, C), (N, C) --> (N, C) + glimpse = self.attention_module(features, hidden_state, **kwargs) + # (N, C), (N, C) --> (N, 2 * C) + logits = tf.concat([hidden_state, glimpse], axis=1) + logits = self.dropout(logits, **kwargs) + # (N, vocab_size + 1) + logits_list.append(self.output_dense(logits, **kwargs)) + + # (max_length + 1, N, vocab_size + 1) --> (N, max_length + 1, vocab_size + 1) + return tf.transpose(tf.stack(logits_list[1:]), (1, 0, 2)) class SAR(Model, RecognitionModel): @@ -448,17 +508,20 @@

    Source code for doctr.models.recognition.sar.tensorflow

    Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. Args: + ---- feature_extractor: the backbone serving as feature extractor vocab: vocabulary used for encoding rnn_units: number of hidden units in both encoder and decoder LSTM embedding_units: number of embedding units attention_units: number of hidden units in attention module max_length: maximum word length handled by the model - num_decoders: number of LSTM to stack in decoder layer - + num_decoder_cells: number of LSTMCell layers to stack + dropout_prob: dropout probability for the encoder and decoder + exportable: onnx exportable returns only logits + cfg: dictionary containing information about the model """ - _children_names: List[str] = ['feat_extractor', 'encoder', 'decoder', 'postprocessor'] + _children_names: List[str] = ["feat_extractor", "encoder", "decoder", "postprocessor"] def __init__( self, @@ -468,36 +531,34 @@

    Source code for doctr.models.recognition.sar.tensorflow

    embedding_units: int = 512, attention_units: int = 512, max_length: int = 30, - num_decoders: int = 2, + num_decoder_cells: int = 2, + dropout_prob: float = 0.0, + exportable: bool = False, cfg: Optional[Dict[str, Any]] = None, ) -> None: - super().__init__() self.vocab = vocab + self.exportable = exportable self.cfg = cfg - self.max_length = max_length + 1 # Add 1 timestep for EOS after the longest word self.feat_extractor = feature_extractor - self.encoder = Sequential( - [ - layers.LSTM(units=rnn_units, return_sequences=True), - layers.LSTM(units=rnn_units, return_sequences=False) - ] - ) - # Initialize the kernels (watch out for reduce_max) - self.encoder.build(input_shape=(None,) + self.feat_extractor.output_shape[2:]) - + self.encoder = SAREncoder(rnn_units, dropout_prob) self.decoder = SARDecoder( - rnn_units, max_length, len(vocab), embedding_units, attention_units, num_decoders, - input_shape=[self.feat_extractor.output_shape, self.encoder.output_shape] + rnn_units, + self.max_length, + len(vocab), + embedding_units, + attention_units, + num_decoder_cells, + dropout_prob, ) self.postprocessor = SARPostProcessor(vocab=vocab) + @staticmethod def compute_loss( - self, model_output: tf.Tensor, gt: tf.Tensor, seq_len: tf.Tensor, @@ -506,11 +567,13 @@

    Source code for doctr.models.recognition.sar.tensorflow

    Sequences are masked after the EOS character. Args: + ---- gt: the encoded tensor with gt labels model_output: predicted logits of the model seq_len: lengths of each gt word inside the batch Returns: + ------- The loss of the model on the batch """ # Input length : number of timesteps @@ -525,7 +588,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    mask_values = tf.zeros_like(cce) mask_2d = tf.sequence_mask(seq_len, input_len) masked_loss = tf.where(mask_2d, cce, mask_values) - ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32)) + ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, model_output.dtype)) return tf.expand_dims(ce_loss, axis=1) def call( @@ -536,16 +599,28 @@

    Source code for doctr.models.recognition.sar.tensorflow

    return_preds: bool = False, **kwargs: Any, ) -> Dict[str, Any]: - features = self.feat_extractor(x, **kwargs) - pooled_features = tf.reduce_max(features, axis=1) # vertical max pooling + # vertical max pooling --> (N, C, W) + pooled_features = tf.reduce_max(features, axis=1) + # holistic (N, C) encoded = self.encoder(pooled_features, **kwargs) + if target is not None: - gt, seq_len = self.compute_target(target) + gt, seq_len = self.build_target(target) seq_len = tf.cast(seq_len, tf.int32) - decoded_features = self.decoder(features, encoded, gt=None if target is None else gt, **kwargs) + + if kwargs.get("training", False) and target is None: + raise ValueError("Need to provide labels during training for teacher forcing") + + decoded_features = _bf16_to_float32( + self.decoder(features, encoded, gt=None if target is None else gt, **kwargs) + ) out: Dict[str, tf.Tensor] = {} + if self.exportable: + out["logits"] = decoded_features + return out + if return_model_output: out["out_map"] = decoded_features @@ -554,7 +629,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    out["preds"] = self.postprocessor(decoded_features) if target is not None: - out['loss'] = self.compute_loss(decoded_features, gt, seq_len) + out["loss"] = self.compute_loss(decoded_features, gt, seq_len) return out @@ -563,9 +638,8 @@

    Source code for doctr.models.recognition.sar.tensorflow

    """Post processor for SAR architectures Args: + ---- vocab: string containing the ordered sequence of supported characters - ignore_case: if True, ignore case of letters - ignore_accents: if True, ignore accents of letters """ def __call__( @@ -580,95 +654,75 @@

    Source code for doctr.models.recognition.sar.tensorflow

    probs = tf.math.reduce_min(probs, axis=1) # decode raw output of the model with tf_label_to_idx - out_idxs = tf.cast(out_idxs, dtype='int32') + out_idxs = tf.cast(out_idxs, dtype="int32") embedding = tf.constant(self._embedding, dtype=tf.string) decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(embedding, out_idxs), axis=-1) decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>") - decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0] + decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value="not valid")[:, 0] word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] - return list(zip(word_values, probs.numpy().tolist())) + return list(zip(word_values, probs.numpy().clip(0, 1).tolist())) -def _sar(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> SAR: +def _sar( + arch: str, + pretrained: bool, + backbone_fn, + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> SAR: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab']) - _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units']) - _cfg['embedding_units'] = kwargs.get('embedding_units', _cfg['rnn_units']) - _cfg['attention_units'] = kwargs.get('attention_units', _cfg['rnn_units']) - _cfg['max_length'] = kwargs.get('max_length', _cfg['max_length']) - _cfg['num_decoders'] = kwargs.get('num_decoders', _cfg['num_decoders']) + _cfg["input_shape"] = input_shape or _cfg["input_shape"] + _cfg["vocab"] = kwargs.get("vocab", _cfg["vocab"]) # Feature extractor - feat_extractor = backbones.__dict__[default_cfgs[arch]['backbone']]( - input_shape=_cfg['input_shape'], + feat_extractor = backbone_fn( + pretrained=pretrained_backbone, + input_shape=_cfg["input_shape"], include_top=False, ) - kwargs['vocab'] = _cfg['vocab'] - kwargs['rnn_units'] = _cfg['rnn_units'] - kwargs['embedding_units'] = _cfg['embedding_units'] - kwargs['attention_units'] = _cfg['attention_units'] - kwargs['max_length'] = _cfg['max_length'] - kwargs['num_decoders'] = _cfg['num_decoders'] + kwargs["vocab"] = _cfg["vocab"] # Build the model model = SAR(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) # Load pretrained parameters if pretrained: - load_pretrained_params(model, default_cfgs[arch]['url']) + # The given vocab differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, default_cfgs[arch]["url"], skip_mismatch=kwargs["vocab"] != default_cfgs[arch]["vocab"] + ) return model -
    -[docs] -def sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a VGG16 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import sar_vgg16_bn - >>> model = sar_vgg16_bn(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_vgg16_bn', pretrained, **kwargs)
    - - -
    -[docs] +[docs] def sar_resnet31(pretrained: bool = False, **kwargs: Any) -> SAR: """SAR with a resnet-31 feature extractor as described in `"Show, Attend and Read:A Simple and Strong Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - Example: - >>> import tensorflow as tf - >>> from doctr.models import sar_resnet31 - >>> model = sar_resnet31(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import sar_resnet31 + >>> model = sar_resnet31(pretrained=False) + >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the SAR architecture Returns: + ------- text recognition architecture """ - - return _sar('sar_resnet31', pretrained, **kwargs)
    + return _sar("sar_resnet31", pretrained, resnet31, **kwargs)
    @@ -702,8 +756,8 @@

    Source code for doctr.models.recognition.sar.tensorflow

    - +
    + diff --git a/v0.5.0/_modules/doctr/models/recognition/vitstr/tensorflow.html b/v0.5.0/_modules/doctr/models/recognition/vitstr/tensorflow.html index 6e101893bf..c594d40a56 100644 --- a/v0.5.0/_modules/doctr/models/recognition/vitstr/tensorflow.html +++ b/v0.5.0/_modules/doctr/models/recognition/vitstr/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.vitstr.tensorflow - docTR documentation @@ -621,7 +621,7 @@

    Source code for doctr.models.recognition.vitstr.tensorflow

    - + diff --git a/v0.5.0/_modules/doctr/models/recognition/zoo.html b/v0.5.0/_modules/doctr/models/recognition/zoo.html index bf0ae6af6e..f664304019 100644 --- a/v0.5.0/_modules/doctr/models/recognition/zoo.html +++ b/v0.5.0/_modules/doctr/models/recognition/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.zoo - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -from typing import Any
    +from typing import Any, List
     
    -from doctr.file_utils import is_tf_available, is_torch_available
    -from .core import RecognitionPredictor
    -from ..preprocessor import PreProcessor
    -from .. import recognition
    +from doctr.file_utils import is_tf_available
    +from doctr.models.preprocessor import PreProcessor
     
    +from .. import recognition
    +from .predictor import RecognitionPredictor
     
     __all__ = ["recognition_predictor"]
     
     
    -if is_tf_available():
    -    ARCHS = ['crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31', 'master']
    -elif is_torch_available():
    -    ARCHS = ['crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31']
    +ARCHS: List[str] = [
    +    "crnn_vgg16_bn",
    +    "crnn_mobilenet_v3_small",
    +    "crnn_mobilenet_v3_large",
    +    "sar_resnet31",
    +    "master",
    +    "vitstr_small",
    +    "vitstr_base",
    +    "parseq",
    +]
    +
     
    +def _predictor(arch: Any, pretrained: bool, **kwargs: Any) -> RecognitionPredictor:
    +    if isinstance(arch, str):
    +        if arch not in ARCHS:
    +            raise ValueError(f"unknown architecture '{arch}'")
     
    -def _predictor(arch: str, pretrained: bool, **kwargs: Any) -> RecognitionPredictor:
    +        _model = recognition.__dict__[arch](
    +            pretrained=pretrained, pretrained_backbone=kwargs.get("pretrained_backbone", True)
    +        )
    +    else:
    +        if not isinstance(
    +            arch, (recognition.CRNN, recognition.SAR, recognition.MASTER, recognition.ViTSTR, recognition.PARSeq)
    +        ):
    +            raise ValueError(f"unknown architecture: {type(arch)}")
    +        _model = arch
     
    -    if arch not in ARCHS:
    -        raise ValueError(f"unknown architecture '{arch}'")
    +    kwargs.pop("pretrained_backbone", None)
     
    -    _model = recognition.__dict__[arch](pretrained=pretrained)
    -    kwargs['mean'] = kwargs.get('mean', _model.cfg['mean'])
    -    kwargs['std'] = kwargs.get('std', _model.cfg['std'])
    -    kwargs['batch_size'] = kwargs.get('batch_size', 32)
    -    predictor = RecognitionPredictor(
    -        PreProcessor(_model.cfg['input_shape'][:2], preserve_aspect_ratio=True, **kwargs),
    -        _model
    -    )
    +    kwargs["mean"] = kwargs.get("mean", _model.cfg["mean"])
    +    kwargs["std"] = kwargs.get("std", _model.cfg["std"])
    +    kwargs["batch_size"] = kwargs.get("batch_size", 128)
    +    input_shape = _model.cfg["input_shape"][:2] if is_tf_available() else _model.cfg["input_shape"][-2:]
    +    predictor = RecognitionPredictor(PreProcessor(input_shape, preserve_aspect_ratio=True, **kwargs), _model)
     
         return predictor
     
     
     
    -[docs] -def recognition_predictor(arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) -> RecognitionPredictor: +[docs] +def recognition_predictor( + arch: Any = "crnn_vgg16_bn", + pretrained: bool = False, + symmetric_pad: bool = False, + batch_size: int = 128, + **kwargs: Any, +) -> RecognitionPredictor: """Text recognition architecture. Example:: @@ -326,14 +369,18 @@

    Source code for doctr.models.recognition.zoo

            >>> out = model([input_page])
     
         Args:
    -        arch: name of the architecture to use ('crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31')
    +    ----
    +        arch: name of the architecture or model itself to use (e.g. 'crnn_vgg16_bn')
             pretrained: If True, returns a model pre-trained on our text recognition dataset
    +        symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right
    +        batch_size: number of samples the model processes in parallel
    +        **kwargs: optional parameters to be passed to the architecture
     
         Returns:
    +    -------
             Recognition predictor
         """
    -
    -    return _predictor(arch, pretrained, **kwargs)
    + return _predictor(arch=arch, pretrained=pretrained, symmetric_pad=symmetric_pad, batch_size=batch_size, **kwargs)
    @@ -367,8 +414,8 @@

    Source code for doctr.models.recognition.zoo

       
    -
    - +
    + diff --git a/v0.5.0/_modules/doctr/models/zoo.html b/v0.5.0/_modules/doctr/models/zoo.html index dec6857019..d459671648 100644 --- a/v0.5.0/_modules/doctr/models/zoo.html +++ b/v0.5.0/_modules/doctr/models/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.zoo - docTR documentation @@ -225,15 +225,42 @@

    Source code for doctr.models.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     from typing import Any
    -from .core import OCRPredictor
    +
     from .detection.zoo import detection_predictor
    +from .kie_predictor import KIEPredictor
    +from .predictor import OCRPredictor
     from .recognition.zoo import recognition_predictor
     
    +__all__ = ["ocr_predictor", "kie_predictor"]
     
    -__all__ = ["ocr_predictor"]
    -
    -
    -def _predictor(det_arch: str, reco_arch: str, pretrained: bool, det_bs=2, reco_bs=128) -> OCRPredictor:
     
    +def _predictor(
    +    det_arch: Any,
    +    reco_arch: Any,
    +    pretrained: bool,
    +    pretrained_backbone: bool = True,
    +    assume_straight_pages: bool = True,
    +    preserve_aspect_ratio: bool = True,
    +    symmetric_pad: bool = True,
    +    det_bs: int = 2,
    +    reco_bs: int = 128,
    +    detect_orientation: bool = False,
    +    straighten_pages: bool = False,
    +    detect_language: bool = False,
    +    **kwargs,
    +) -> OCRPredictor:
         # Detection
    -    det_predictor = detection_predictor(det_arch, pretrained=pretrained, batch_size=det_bs)
    +    det_predictor = detection_predictor(
    +        det_arch,
    +        pretrained=pretrained,
    +        pretrained_backbone=pretrained_backbone,
    +        batch_size=det_bs,
    +        assume_straight_pages=assume_straight_pages,
    +        preserve_aspect_ratio=preserve_aspect_ratio,
    +        symmetric_pad=symmetric_pad,
    +    )
     
         # Recognition
    -    reco_predictor = recognition_predictor(reco_arch, pretrained=pretrained, batch_size=reco_bs)
    +    reco_predictor = recognition_predictor(
    +        reco_arch,
    +        pretrained=pretrained,
    +        pretrained_backbone=pretrained_backbone,
    +        batch_size=reco_bs,
    +    )
     
    -    return OCRPredictor(det_predictor, reco_predictor)
    +    return OCRPredictor(
    +        det_predictor,
    +        reco_predictor,
    +        assume_straight_pages=assume_straight_pages,
    +        preserve_aspect_ratio=preserve_aspect_ratio,
    +        symmetric_pad=symmetric_pad,
    +        detect_orientation=detect_orientation,
    +        straighten_pages=straighten_pages,
    +        detect_language=detect_language,
    +        **kwargs,
    +    )
     
     
     
    -[docs] +[docs] def ocr_predictor( - det_arch: str = 'db_resnet50', - reco_arch: str = 'crnn_vgg16_bn', + det_arch: Any = "fast_base", + reco_arch: Any = "crnn_vgg16_bn", pretrained: bool = False, - **kwargs: Any + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + export_as_straight_boxes: bool = False, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs: Any, ) -> OCRPredictor: """End-to-end OCR architecture using one model for localization, and another for text recognition. - Example:: - >>> import numpy as np - >>> from doctr.models import ocr_predictor - >>> model = ocr_predictor(pretrained=True) - >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) - >>> out = model([input_page]) + >>> 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(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) Args: - arch: name of the architecture to use ('db_sar_vgg', 'db_sar_resnet', 'db_crnn_vgg', 'db_crnn_resnet') + ---- + det_arch: name of the detection architecture or the model itself to use + (e.g. 'db_resnet50', 'db_mobilenet_v3_large') + reco_arch: name of the recognition architecture or the model itself to use + (e.g. 'crnn_vgg16_bn', 'sar_resnet31') pretrained: If True, returns a model pre-trained on our OCR dataset + pretrained_backbone: If True, returns a model with a pretrained backbone + assume_straight_pages: if True, speeds up the inference by assuming you only pass straight pages + without rotated textual elements. + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it. + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right. + export_as_straight_boxes: when assume_straight_pages is set to False, export final predictions + (potentially rotated) as straight bounding boxes. + detect_orientation: if True, the estimated general page orientation will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + straighten_pages: if True, estimates the page general orientation + based on the segmentation map median line orientation. + Then, rotates page before passing it again to the deep learning detection module. + Doing so will improve performances for documents with page-uniform rotations. + detect_language: if True, the language prediction will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + kwargs: keyword args of `OCRPredictor` Returns: + ------- OCR predictor """ + return _predictor( + det_arch, + reco_arch, + pretrained, + pretrained_backbone=pretrained_backbone, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + export_as_straight_boxes=export_as_straight_boxes, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + )
    + + - return _predictor(det_arch, reco_arch, pretrained, **kwargs)
    +def _kie_predictor( + det_arch: Any, + reco_arch: Any, + pretrained: bool, + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + det_bs: int = 2, + reco_bs: int = 128, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs, +) -> KIEPredictor: + # Detection + det_predictor = detection_predictor( + det_arch, + pretrained=pretrained, + pretrained_backbone=pretrained_backbone, + batch_size=det_bs, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + ) + + # Recognition + reco_predictor = recognition_predictor( + reco_arch, + pretrained=pretrained, + pretrained_backbone=pretrained_backbone, + batch_size=reco_bs, + ) + + return KIEPredictor( + det_predictor, + reco_predictor, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + ) + + +
    +[docs] +def kie_predictor( + det_arch: Any = "fast_base", + reco_arch: Any = "crnn_vgg16_bn", + pretrained: bool = False, + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + export_as_straight_boxes: bool = False, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs: Any, +) -> KIEPredictor: + """End-to-end KIE architecture using one model for localization, and another for text recognition. + + >>> 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(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) + + Args: + ---- + det_arch: name of the detection architecture or the model itself to use + (e.g. 'db_resnet50', 'db_mobilenet_v3_large') + reco_arch: name of the recognition architecture or the model itself to use + (e.g. 'crnn_vgg16_bn', 'sar_resnet31') + pretrained: If True, returns a model pre-trained on our OCR dataset + pretrained_backbone: If True, returns a model with a pretrained backbone + assume_straight_pages: if True, speeds up the inference by assuming you only pass straight pages + without rotated textual elements. + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it. + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right. + export_as_straight_boxes: when assume_straight_pages is set to False, export final predictions + (potentially rotated) as straight bounding boxes. + detect_orientation: if True, the estimated general page orientation will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + straighten_pages: if True, estimates the page general orientation + based on the segmentation map median line orientation. + Then, rotates page before passing it again to the deep learning detection module. + Doing so will improve performances for documents with page-uniform rotations. + detect_language: if True, the language prediction will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + kwargs: keyword args of `OCRPredictor` + + Returns: + ------- + KIE predictor + """ + return _kie_predictor( + det_arch, + reco_arch, + pretrained, + pretrained_backbone=pretrained_backbone, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + export_as_straight_boxes=export_as_straight_boxes, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + )
    @@ -353,8 +575,8 @@

    Source code for doctr.models.zoo

           
         
       
    - - + + diff --git a/v0.5.0/_modules/doctr/transforms/modules.html b/v0.5.0/_modules/doctr/transforms/modules.html deleted file mode 100644 index ba8269e7ef..0000000000 --- a/v0.5.0/_modules/doctr/transforms/modules.html +++ /dev/null @@ -1,734 +0,0 @@ - - - - - - - - - - - - doctr.transforms.modules - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.transforms.modules

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import random
    -import tensorflow as tf
    -from typing import List, Any, Tuple, Callable
    -
    -from doctr.utils.repr import NestedObject
    -from . import functional as F
    -
    -
    -__all__ = ['Compose', 'Resize', 'Normalize', 'LambdaTransformation', 'ToGray', 'ColorInversion',
    -           'RandomBrightness', 'RandomContrast', 'RandomSaturation', 'RandomHue', 'RandomGamma', 'RandomJpegQuality',
    -           'OneOf', 'RandomApply']
    -
    -
    -
    -[docs] -class Compose(NestedObject): - """Implements a wrapper that will apply transformations sequentially - - Example:: - >>> from doctr.transforms import Compose, Resize - >>> import tensorflow as tf - >>> transfos = Compose([Resize((32, 32))]) - >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformation modules - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, x: Any) -> Any: - for t in self.transforms: - x = t(x) - - return x
    - - - -
    -[docs] -class Resize(NestedObject): - """Resizes a tensor to a target size - - Example:: - >>> from doctr.transforms import Resize - >>> import tensorflow as tf - >>> transfo = Resize((32, 32)) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - output_size: expected output size - method: interpolation method - preserve_aspect_ratio: if `True`, preserve aspect ratio and pad the rest with zeros - symmetric_pad: if `True` while preserving aspect ratio, the padding will be done symmetrically - """ - def __init__( - self, - output_size: Tuple[int, int], - method: str = 'bilinear', - preserve_aspect_ratio: bool = False, - symmetric_pad: bool = False, - ) -> None: - self.output_size = output_size - self.method = method - self.preserve_aspect_ratio = preserve_aspect_ratio - self.symmetric_pad = symmetric_pad - - def extra_repr(self) -> str: - _repr = f"output_size={self.output_size}, method='{self.method}'" - if self.preserve_aspect_ratio: - _repr += f", preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}" - return _repr - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img = tf.image.resize(img, self.output_size, self.method, self.preserve_aspect_ratio) - if self.preserve_aspect_ratio: - # pad width - if not self.symmetric_pad: - offset = (0, 0) - elif self.output_size[0] == img.shape[0]: - offset = (0, int((self.output_size[1] - img.shape[1]) / 2)) - else: - offset = (int((self.output_size[0] - img.shape[0]) / 2), 0) - img = tf.image.pad_to_bounding_box(img, *offset, *self.output_size) - return img
    - - - -
    -[docs] -class Normalize(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - mean: average value per channel - std: standard deviation per channel - """ - def __init__(self, mean: Tuple[float, float, float], std: Tuple[float, float, float]) -> None: - self.mean = tf.constant(mean, dtype=tf.float32) - self.std = tf.constant(std, dtype=tf.float32) - - def extra_repr(self) -> str: - return f"mean={self.mean.numpy().tolist()}, std={self.std.numpy().tolist()}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img -= self.mean - img /= self.std - return img
    - - - -
    -[docs] -class LambdaTransformation(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import LambdaTransformation - >>> import tensorflow as tf - >>> transfo = LambdaTransformation(lambda x: x/ 255.) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - fn: the function to be applied to the input tensor - """ - def __init__(self, fn: Callable[[tf.Tensor], tf.Tensor]) -> None: - self.fn = fn - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return self.fn(img)
    - - - -
    -[docs] -class ToGray(NestedObject): - """Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ToGray() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - """ - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.rgb_to_grayscale(img)
    - - - -
    -[docs] -class ColorInversion(NestedObject): - """Applies the following tranformation to a tensor (image or batch of images): - convert to grayscale, colorize (shift 0-values randomly), and then invert colors - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ColorInversion(min_val=0.6) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_val: range [min_val, 1] to colorize RGB pixels - """ - def __init__(self, min_val: float = 0.6) -> None: - self.min_val = min_val - - def extra_repr(self) -> str: - return f"min_val={self.min_val}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return F.invert_colors(img, self.min_val)
    - - - -
    -[docs] -class RandomBrightness(NestedObject): - """Randomly adjust brightness of a tensor (batch of images or image) by adding a delta - to all pixels - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Brightness() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - p: probability to apply transformation - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_brightness(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomContrast(NestedObject): - """Randomly adjust contrast of a tensor (batch of images or image) by adjusting - each pixel: (img - mean) * contrast_factor + mean. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Contrast() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1) - """ - def __init__(self, delta: float = .3) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_contrast(img, lower=1 - self.delta, upper=1 / (1 - self.delta))
    - - - -
    -[docs] -class RandomSaturation(NestedObject): - """Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and - increasing saturation by a factor. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Saturation() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1) - """ - def __init__(self, delta: float = .5) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_saturation(img, lower=1 - self.delta, upper=1 + self.delta)
    - - - -
    -[docs] -class RandomHue(NestedObject): - """Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Hue() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_hue(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomGamma(NestedObject): - """randomly performs gamma correction for a tensor (batch of images or image) - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Gamma() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_gamma: non-negative real number, lower bound for gamma param - max_gamma: non-negative real number, upper bound for gamma - min_gain: lower bound for constant multiplier - max_gain: upper bound for constant multiplier - """ - def __init__( - self, - min_gamma: float = 0.5, - max_gamma: float = 1.5, - min_gain: float = 0.8, - max_gain: float = 1.2, - ) -> None: - self.min_gamma = min_gamma - self.max_gamma = max_gamma - self.min_gain = min_gain - self.max_gain = max_gain - - def extra_repr(self) -> str: - return f"""gamma_range=({self.min_gamma}, {self.max_gamma}), - gain_range=({self.min_gain}, {self.max_gain})""" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - gamma = random.uniform(self.min_gamma, self.max_gamma) - gain = random.uniform(self.min_gain, self.max_gain) - return tf.image.adjust_gamma(img, gamma=gamma, gain=gain)
    - - - -
    -[docs] -class RandomJpegQuality(NestedObject): - """Randomly adjust jpeg quality of a 3 dimensional RGB image - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = JpegQuality() - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - min_quality: int between [0, 100] - max_quality: int between [0, 100] - """ - def __init__(self, min_quality: int = 60, max_quality: int = 100) -> None: - self.min_quality = min_quality - self.max_quality = max_quality - - def extra_repr(self) -> str: - return f"min_quality={self.min_quality}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_jpeg_quality( - img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality - )
    - - - -
    -[docs] -class OneOf(NestedObject): - """Randomly apply one of the input transformations - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = OneOf([JpegQuality(), Gamma()]) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformations, one only will be picked - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - # Pick transformation - transfo = self.transforms[int(random.random() * len(self.transforms))] - # Apply - return transfo(img)
    - - - -
    -[docs] -class RandomApply(NestedObject): - """Apply with a probability p the input transformation - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = RandomApply(Gamma(), p=.5) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transform: transformation to apply - p: probability to apply - """ - def __init__(self, transform: NestedObject, p: float = .5) -> None: - self.transform = transform - self.p = p - - def extra_repr(self) -> str: - return f"transform={self.transform}, p={self.p}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - if random.random() < self.p: - return self.transform(img) - return img
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.0/_modules/doctr/transforms/modules/base.html b/v0.5.0/_modules/doctr/transforms/modules/base.html index c42079a8fd..4596df3848 100644 --- a/v0.5.0/_modules/doctr/transforms/modules/base.html +++ b/v0.5.0/_modules/doctr/transforms/modules/base.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.base - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.transforms.modules.base

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    +import math
     import random
    -from typing import List, Any, Callable
    +from typing import Any, Callable, List, Optional, Tuple, Union
    +
    +import numpy as np
     
     from doctr.utils.repr import NestedObject
    +
     from .. import functional as F
     
    +__all__ = ["SampleCompose", "ImageTransform", "ColorInversion", "OneOf", "RandomApply", "RandomRotate", "RandomCrop"]
    +
    +
    +class SampleCompose(NestedObject):
    +    """Implements a wrapper that will apply transformations sequentially on both image and target
    +
    +    .. tabs::
    +
    +        .. tab:: TensorFlow
    +
    +            .. code:: python
    +
    +                >>> import numpy as np
    +                >>> import tensorflow as tf
    +                >>> from doctr.transforms import SampleCompose, ImageTransform, ColorInversion, RandomRotate
    +                >>> transfo = SampleCompose([ImageTransform(ColorInversion((32, 32))), RandomRotate(30)])
    +                >>> out, out_boxes = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1), np.zeros((2, 4)))
    +
    +        .. tab:: PyTorch
    +
    +            .. code:: python
    +
    +                >>> import numpy as np
    +                >>> import torch
    +                >>> from doctr.transforms import SampleCompose, ImageTransform, ColorInversion, RandomRotate
    +                >>> transfos = SampleCompose([ImageTransform(ColorInversion((32, 32))), RandomRotate(30)])
    +                >>> out, out_boxes = transfos(torch.rand(8, 64, 64, 3), np.zeros((2, 4)))
    +
    +    Args:
    +    ----
    +        transforms: list of transformation modules
    +    """
    +
    +    _children_names: List[str] = ["sample_transforms"]
    +
    +    def __init__(self, transforms: List[Callable[[Any, Any], Tuple[Any, Any]]]) -> None:
    +        self.sample_transforms = transforms
    +
    +    def __call__(self, x: Any, target: Any) -> Tuple[Any, Any]:
    +        for t in self.sample_transforms:
    +            x, target = t(x, target)
    +
    +        return x, target
    +
    +
    +class ImageTransform(NestedObject):
    +    """Implements a transform wrapper to turn an image-only transformation into an image+target transform
    +
    +    .. tabs::
    +
    +        .. tab:: TensorFlow
    +
    +            .. code:: python
    +
    +                >>> import tensorflow as tf
    +                >>> from doctr.transforms import ImageTransform, ColorInversion
    +                >>> transfo = ImageTransform(ColorInversion((32, 32)))
    +                >>> out, _ = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1), None)
    +
    +        .. tab:: PyTorch
    +
    +            .. code:: python
    +
    +                >>> import torch
    +                >>> from doctr.transforms import ImageTransform, ColorInversion
    +                >>> transfo = ImageTransform(ColorInversion((32, 32)))
    +                >>> out, _ = transfo(torch.rand(8, 64, 64, 3), None)
    +
    +    Args:
    +    ----
    +        transform: the image transformation module to wrap
    +    """
    +
    +    _children_names: List[str] = ["img_transform"]
    +
    +    def __init__(self, transform: Callable[[Any], Any]) -> None:
    +        self.img_transform = transform
     
    -__all__ = ['ColorInversion', 'OneOf', 'RandomApply']
    +    def __call__(self, img: Any, target: Any) -> Tuple[Any, Any]:
    +        img = self.img_transform(img)
    +        return img, target
     
     
     
    -[docs] +[docs] class ColorInversion(NestedObject): """Applies the following tranformation to a tensor (image or batch of images): convert to grayscale, colorize (shift 0-values randomly), and then invert colors - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ColorInversion(min_val=0.6) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + .. tabs:: + + .. tab:: TensorFlow + + .. code:: python + + >>> import tensorflow as tf + >>> from doctr.transforms import ColorInversion + >>> transfo = ColorInversion(min_val=0.6) + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + + .. tab:: PyTorch + + .. code:: python + + >>> import torch + >>> from doctr.transforms import ColorInversion + >>> transfo = ColorInversion(min_val=0.6) + >>> out = transfo(torch.rand(8, 64, 64, 3)) Args: + ---- min_val: range [min_val, 1] to colorize RGB pixels """ + def __init__(self, min_val: float = 0.5) -> None: self.min_val = min_val @@ -316,59 +437,178 @@

    Source code for doctr.transforms.modules.base

    -[docs] +[docs] class OneOf(NestedObject): """Randomly apply one of the input transformations - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = OneOf([JpegQuality(), Gamma()]) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + .. tabs:: + + .. tab:: TensorFlow + + .. code:: python + + >>> import tensorflow as tf + >>> from doctr.transforms import OneOf + >>> transfo = OneOf([JpegQuality(), Gamma()]) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + .. tab:: PyTorch + + .. code:: python + + >>> import torch + >>> from doctr.transforms import OneOf + >>> transfo = OneOf([JpegQuality(), Gamma()]) + >>> out = transfo(torch.rand(1, 64, 64, 3)) Args: + ---- transforms: list of transformations, one only will be picked """ - _children_names: List[str] = ['transforms'] + _children_names: List[str] = ["transforms"] def __init__(self, transforms: List[Callable[[Any], Any]]) -> None: self.transforms = transforms - def __call__(self, img: Any) -> Any: + def __call__(self, img: Any, target: Optional[np.ndarray] = None) -> Union[Any, Tuple[Any, np.ndarray]]: # Pick transformation transfo = self.transforms[int(random.random() * len(self.transforms))] # Apply - return transfo(img)
    + return transfo(img) if target is None else transfo(img, target) # type: ignore[call-arg]
    -[docs] +[docs] class RandomApply(NestedObject): """Apply with a probability p the input transformation - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = RandomApply(Gamma(), p=.5) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + .. tabs:: + + .. tab:: TensorFlow + + .. code:: python + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomApply + >>> transfo = RandomApply(Gamma(), p=.5) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + .. tab:: PyTorch + + .. code:: python + + >>> import torch + >>> from doctr.transforms import RandomApply + >>> transfo = RandomApply(Gamma(), p=.5) + >>> out = transfo(torch.rand(1, 64, 64, 3)) Args: + ---- transform: transformation to apply p: probability to apply """ - def __init__(self, transform: Callable[[Any], Any], p: float = .5) -> None: + + def __init__(self, transform: Callable[[Any], Any], p: float = 0.5) -> None: self.transform = transform self.p = p def extra_repr(self) -> str: return f"transform={self.transform}, p={self.p}" - def __call__(self, img: Any) -> Any: + def __call__(self, img: Any, target: Optional[np.ndarray] = None) -> Union[Any, Tuple[Any, np.ndarray]]: if random.random() < self.p: - return self.transform(img) - return img
    + return self.transform(img) if target is None else self.transform(img, target) # type: ignore[call-arg] + return img if target is None else (img, target)
    + + + +
    +[docs] +class RandomRotate(NestedObject): + """Randomly rotate a tensor image and its boxes + + .. image:: https://doctr-static.mindee.com/models?id=v0.4.0/rotation_illustration.png&src=0 + :align: center + + Args: + ---- + max_angle: maximum angle for rotation, in degrees. Angles will be uniformly picked in + [-max_angle, max_angle] + expand: whether the image should be padded before the rotation + """ + + def __init__(self, max_angle: float = 5.0, expand: bool = False) -> None: + self.max_angle = max_angle + self.expand = expand + + def extra_repr(self) -> str: + return f"max_angle={self.max_angle}, expand={self.expand}" + + def __call__(self, img: Any, target: np.ndarray) -> Tuple[Any, np.ndarray]: + angle = random.uniform(-self.max_angle, self.max_angle) + r_img, r_polys = F.rotate_sample(img, target, angle, self.expand) + # Removes deleted boxes + is_kept = (r_polys.max(1) > r_polys.min(1)).sum(1) == 2 + return r_img, r_polys[is_kept]
    + + + +
    +[docs] +class RandomCrop(NestedObject): + """Randomly crop a tensor image and its boxes + + Args: + ---- + scale: tuple of floats, relative (min_area, max_area) of the crop + ratio: tuple of float, relative (min_ratio, max_ratio) where ratio = h/w + """ + + def __init__(self, scale: Tuple[float, float] = (0.08, 1.0), ratio: Tuple[float, float] = (0.75, 1.33)) -> None: + self.scale = scale + self.ratio = ratio + + def extra_repr(self) -> str: + return f"scale={self.scale}, ratio={self.ratio}" + + def __call__(self, img: Any, target: np.ndarray) -> Tuple[Any, np.ndarray]: + scale = random.uniform(self.scale[0], self.scale[1]) + ratio = random.uniform(self.ratio[0], self.ratio[1]) + + height, width = img.shape[:2] + + # Calculate crop size + crop_area = scale * width * height + aspect_ratio = ratio * (width / height) + crop_width = int(round(math.sqrt(crop_area * aspect_ratio))) + crop_height = int(round(math.sqrt(crop_area / aspect_ratio))) + + # Ensure crop size does not exceed image dimensions + crop_width = min(crop_width, width) + crop_height = min(crop_height, height) + + # Randomly select crop position + x = random.randint(0, width - crop_width) + y = random.randint(0, height - crop_height) + + # relative crop box + crop_box = (x / width, y / height, (x + crop_width) / width, (y + crop_height) / height) + if target.shape[1:] == (4, 2): + min_xy = np.min(target, axis=1) + max_xy = np.max(target, axis=1) + _target = np.concatenate((min_xy, max_xy), axis=1) + else: + _target = target + + # Crop image and targets + croped_img, crop_boxes = F.crop_detection(img, _target, crop_box) + # hard fallback if no box is kept + if crop_boxes.shape[0] == 0: + return img, target + # clip boxes + return croped_img, np.clip(crop_boxes, 0, 1)
    @@ -402,8 +642,8 @@

    Source code for doctr.transforms.modules.base

    - - + + diff --git a/v0.5.0/_modules/doctr/transforms/modules/tensorflow.html b/v0.5.0/_modules/doctr/transforms/modules/tensorflow.html index 1d192a876b..acbbe96225 100644 --- a/v0.5.0/_modules/doctr/transforms/modules/tensorflow.html +++ b/v0.5.0/_modules/doctr/transforms/modules/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.transforms.modules.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import random
    +from typing import Any, Callable, Iterable, List, Optional, Tuple, Union
    +
    +import numpy as np
     import tensorflow as tf
    -from typing import List, Any, Tuple, Callable
     
     from doctr.utils.repr import NestedObject
     
    -
    -__all__ = ['Compose', 'Resize', 'Normalize', 'LambdaTransformation', 'ToGray', 'RandomBrightness',
    -           'RandomContrast', 'RandomSaturation', 'RandomHue', 'RandomGamma', 'RandomJpegQuality']
    +from ..functional.tensorflow import _gaussian_filter, random_shadow
    +
    +__all__ = [
    +    "Compose",
    +    "Resize",
    +    "Normalize",
    +    "LambdaTransformation",
    +    "ToGray",
    +    "RandomBrightness",
    +    "RandomContrast",
    +    "RandomSaturation",
    +    "RandomHue",
    +    "RandomGamma",
    +    "RandomJpegQuality",
    +    "GaussianBlur",
    +    "ChannelShuffle",
    +    "GaussianNoise",
    +    "RandomHorizontalFlip",
    +    "RandomShadow",
    +    "RandomResize",
    +]
     
     
     
    -[docs] +[docs] class Compose(NestedObject): """Implements a wrapper that will apply transformations sequentially - Example:: - >>> from doctr.transforms import Compose, Resize - >>> import tensorflow as tf - >>> transfos = Compose([Resize((32, 32))]) - >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import Compose, Resize + >>> transfos = Compose([Resize((32, 32))]) + >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) Args: + ---- transforms: list of transformation modules """ - _children_names: List[str] = ['transforms'] + _children_names: List[str] = ["transforms"] def __init__(self, transforms: List[Callable[[Any], Any]]) -> None: self.transforms = transforms @@ -319,26 +361,27 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class Resize(NestedObject): """Resizes a tensor to a target size - Example:: - >>> from doctr.transforms import Resize - >>> import tensorflow as tf - >>> transfo = Resize((32, 32)) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import Resize + >>> transfo = Resize((32, 32)) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) Args: + ---- output_size: expected output size method: interpolation method preserve_aspect_ratio: if `True`, preserve aspect ratio and pad the rest with zeros symmetric_pad: if `True` while preserving aspect ratio, the padding will be done symmetrically """ + def __init__( self, - output_size: Tuple[int, int], - method: str = 'bilinear', + output_size: Union[int, Tuple[int, int]], + method: str = "bilinear", preserve_aspect_ratio: bool = False, symmetric_pad: bool = False, ) -> None: @@ -346,6 +389,14 @@

    Source code for doctr.transforms.modules.tensorflow

    self.method = method self.preserve_aspect_ratio = preserve_aspect_ratio self.symmetric_pad = symmetric_pad + self.antialias = True + + if isinstance(self.output_size, int): + self.wanted_size = (self.output_size, self.output_size) + elif isinstance(self.output_size, (tuple, list)): + self.wanted_size = self.output_size + else: + raise AssertionError("Output size should be either a list, a tuple or an int") def extra_repr(self) -> str: _repr = f"output_size={self.output_size}, method='{self.method}'" @@ -353,64 +404,106 @@

    Source code for doctr.transforms.modules.tensorflow

    _repr += f", preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}" return _repr - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img = tf.image.resize(img, self.output_size, self.method, self.preserve_aspect_ratio) + def __call__( + self, + img: tf.Tensor, + target: Optional[np.ndarray] = None, + ) -> Union[tf.Tensor, Tuple[tf.Tensor, np.ndarray]]: + input_dtype = img.dtype + self.output_size = ( + (self.output_size, self.output_size) if isinstance(self.output_size, int) else self.output_size + ) + + img = tf.image.resize(img, self.wanted_size, self.method, self.preserve_aspect_ratio, self.antialias) + # It will produce an un-padded resized image, with a side shorter than wanted if we preserve aspect ratio + raw_shape = img.shape[:2] + if self.symmetric_pad: + half_pad = (int((self.output_size[0] - img.shape[0]) / 2), 0) if self.preserve_aspect_ratio: - # pad width - if not self.symmetric_pad: - offset = (0, 0) - elif self.output_size[0] == img.shape[0]: - offset = (0, int((self.output_size[1] - img.shape[1]) / 2)) - else: - offset = (int((self.output_size[0] - img.shape[0]) / 2), 0) - img = tf.image.pad_to_bounding_box(img, *offset, *self.output_size) - return img
    + if isinstance(self.output_size, (tuple, list)): + # In that case we need to pad because we want to enforce both width and height + if not self.symmetric_pad: + half_pad = (0, 0) + elif self.output_size[0] == img.shape[0]: + half_pad = (0, int((self.output_size[1] - img.shape[1]) / 2)) + # Pad image + img = tf.image.pad_to_bounding_box(img, *half_pad, *self.output_size) + + # In case boxes are provided, resize boxes if needed (for detection task if preserve aspect ratio) + if target is not None: + if self.symmetric_pad: + offset = half_pad[0] / img.shape[0], half_pad[1] / img.shape[1] + + if self.preserve_aspect_ratio: + # Get absolute coords + if target.shape[1:] == (4,): + if isinstance(self.output_size, (tuple, list)) and self.symmetric_pad: + target[:, [0, 2]] = offset[1] + target[:, [0, 2]] * raw_shape[1] / img.shape[1] + target[:, [1, 3]] = offset[0] + target[:, [1, 3]] * raw_shape[0] / img.shape[0] + else: + target[:, [0, 2]] *= raw_shape[1] / img.shape[1] + target[:, [1, 3]] *= raw_shape[0] / img.shape[0] + elif target.shape[1:] == (4, 2): + if isinstance(self.output_size, (tuple, list)) and self.symmetric_pad: + target[..., 0] = offset[1] + target[..., 0] * raw_shape[1] / img.shape[1] + target[..., 1] = offset[0] + target[..., 1] * raw_shape[0] / img.shape[0] + else: + target[..., 0] *= raw_shape[1] / img.shape[1] + target[..., 1] *= raw_shape[0] / img.shape[0] + else: + raise AssertionError("Boxes should be in the format (n_boxes, 4, 2) or (n_boxes, 4)") + + return tf.cast(img, dtype=input_dtype), np.clip(target, 0, 1) + + return tf.cast(img, dtype=input_dtype)
    -[docs] +[docs] class Normalize(NestedObject): """Normalize a tensor to a Gaussian distribution for each channel - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import Normalize + >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- mean: average value per channel std: standard deviation per channel """ + def __init__(self, mean: Tuple[float, float, float], std: Tuple[float, float, float]) -> None: - self.mean = tf.constant(mean, dtype=tf.float32) - self.std = tf.constant(std, dtype=tf.float32) + self.mean = tf.constant(mean) + self.std = tf.constant(std) def extra_repr(self) -> str: return f"mean={self.mean.numpy().tolist()}, std={self.std.numpy().tolist()}" def __call__(self, img: tf.Tensor) -> tf.Tensor: - img -= self.mean - img /= self.std + img -= tf.cast(self.mean, dtype=img.dtype) + img /= tf.cast(self.std, dtype=img.dtype) return img
    -[docs] +[docs] class LambdaTransformation(NestedObject): """Normalize a tensor to a Gaussian distribution for each channel - Example:: - >>> from doctr.transforms import LambdaTransformation - >>> import tensorflow as tf - >>> transfo = LambdaTransformation(lambda x: x/ 255.) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import LambdaTransformation + >>> transfo = LambdaTransformation(lambda x: x/ 255.) + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- fn: the function to be applied to the input tensor """ + def __init__(self, fn: Callable[[tf.Tensor], tf.Tensor]) -> None: self.fn = fn @@ -420,37 +513,42 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class ToGray(NestedObject): """Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ToGray() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import ToGray + >>> transfo = ToGray() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) """ + + def __init__(self, num_output_channels: int = 1): + self.num_output_channels = num_output_channels + def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.rgb_to_grayscale(img)
    + img = tf.image.rgb_to_grayscale(img) + return img if self.num_output_channels == 1 else tf.repeat(img, self.num_output_channels, axis=-1)
    -[docs] +[docs] class RandomBrightness(NestedObject): """Randomly adjust brightness of a tensor (batch of images or image) by adding a delta to all pixels - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Brightness() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomBrightness + >>> transfo = RandomBrightness() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] p: probability to apply transformation """ + def __init__(self, max_delta: float = 0.3) -> None: self.max_delta = max_delta @@ -463,21 +561,22 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomContrast(NestedObject): """Randomly adjust contrast of a tensor (batch of images or image) by adjusting each pixel: (img - mean) * contrast_factor + mean. - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Contrast() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomContrast + >>> transfo = RandomContrast() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1) """ - def __init__(self, delta: float = .3) -> None: + + def __init__(self, delta: float = 0.3) -> None: self.delta = delta def extra_repr(self) -> str: @@ -489,21 +588,22 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomSaturation(NestedObject): """Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and increasing saturation by a factor. - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Saturation() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomSaturation + >>> transfo = RandomSaturation() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1) """ - def __init__(self, delta: float = .5) -> None: + + def __init__(self, delta: float = 0.5) -> None: self.delta = delta def extra_repr(self) -> str: @@ -515,19 +615,20 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomHue(NestedObject): """Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Hue() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomHue + >>> transfo = RandomHue() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] """ + def __init__(self, max_delta: float = 0.3) -> None: self.max_delta = max_delta @@ -540,22 +641,23 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomGamma(NestedObject): """randomly performs gamma correction for a tensor (batch of images or image) - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Gamma() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomGamma + >>> transfo = RandomGamma() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- min_gamma: non-negative real number, lower bound for gamma param max_gamma: non-negative real number, upper bound for gamma min_gain: lower bound for constant multiplier max_gain: upper bound for constant multiplier """ + def __init__( self, min_gamma: float = 0.5, @@ -580,20 +682,21 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomJpegQuality(NestedObject): """Randomly adjust jpeg quality of a 3 dimensional RGB image - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = JpegQuality() - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomJpegQuality + >>> transfo = RandomJpegQuality() + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) Args: + ---- min_quality: int between [0, 100] max_quality: int between [0, 100] """ + def __init__(self, min_quality: int = 60, max_quality: int = 100) -> None: self.min_quality = min_quality self.max_quality = max_quality @@ -602,10 +705,224 @@

    Source code for doctr.transforms.modules.tensorflow

    return f"min_quality={self.min_quality}" def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_jpeg_quality( - img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality + return tf.image.random_jpeg_quality(img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality)
    + + + +
    +[docs] +class GaussianBlur(NestedObject): + """Randomly adjust jpeg quality of a 3 dimensional RGB image + + >>> import tensorflow as tf + >>> from doctr.transforms import GaussianBlur + >>> transfo = GaussianBlur(3, (.1, 5)) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + kernel_shape: size of the blurring kernel + std: min and max value of the standard deviation + """ + + def __init__(self, kernel_shape: Union[int, Iterable[int]], std: Tuple[float, float]) -> None: + self.kernel_shape = kernel_shape + self.std = std + + def extra_repr(self) -> str: + return f"kernel_shape={self.kernel_shape}, std={self.std}" + + def __call__(self, img: tf.Tensor) -> tf.Tensor: + return tf.squeeze( + _gaussian_filter( + img[tf.newaxis, ...], + kernel_size=self.kernel_shape, + sigma=random.uniform(self.std[0], self.std[1]), + mode="REFLECT", + ), + axis=0, )
    + + +
    +[docs] +class ChannelShuffle(NestedObject): + """Randomly shuffle channel order of a given image""" + + def __init__(self): + pass + + def __call__(self, img: tf.Tensor) -> tf.Tensor: + return tf.transpose(tf.random.shuffle(tf.transpose(img, perm=[2, 0, 1])), perm=[1, 2, 0])
    + + + +
    +[docs] +class GaussianNoise(NestedObject): + """Adds Gaussian Noise to the input tensor + + >>> import tensorflow as tf + >>> from doctr.transforms import GaussianNoise + >>> transfo = GaussianNoise(0., 1.) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + mean : mean of the gaussian distribution + std : std of the gaussian distribution + """ + + def __init__(self, mean: float = 0.0, std: float = 1.0) -> None: + super().__init__() + self.std = std + self.mean = mean + + def __call__(self, x: tf.Tensor) -> tf.Tensor: + # Reshape the distribution + noise = self.mean + 2 * self.std * tf.random.uniform(x.shape) - self.std + if x.dtype == tf.uint8: + return tf.cast( + tf.clip_by_value(tf.math.round(tf.cast(x, dtype=tf.float32) + 255 * noise), 0, 255), dtype=tf.uint8 + ) + else: + return tf.cast(tf.clip_by_value(x + noise, 0, 1), dtype=x.dtype) + + def extra_repr(self) -> str: + return f"mean={self.mean}, std={self.std}"
    + + + +
    +[docs] +class RandomHorizontalFlip(NestedObject): + """Adds random horizontal flip to the input tensor/np.ndarray + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomHorizontalFlip + >>> transfo = RandomHorizontalFlip(p=0.5) + >>> image = tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1) + >>> target = np.array([[0.1, 0.1, 0.4, 0.5] ], dtype= np.float32) + >>> out = transfo(image, target) + + Args: + ---- + p : probability of Horizontal Flip + """ + + def __init__(self, p: float) -> None: + super().__init__() + self.p = p + + def __call__(self, img: Union[tf.Tensor, np.ndarray], target: np.ndarray) -> Tuple[tf.Tensor, np.ndarray]: + if np.random.rand(1) <= self.p: + _img = tf.image.flip_left_right(img) + _target = target.copy() + # Changing the relative bbox coordinates + if target.shape[1:] == (4,): + _target[:, ::2] = 1 - target[:, [2, 0]] + else: + _target[..., 0] = 1 - target[..., 0] + return _img, _target + return img, target
    + + + +
    +[docs] +class RandomShadow(NestedObject): + """Adds random shade to the input image + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomShadow + >>> transfo = RandomShadow(0., 1.) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + opacity_range : minimum and maximum opacity of the shade + """ + + def __init__(self, opacity_range: Optional[Tuple[float, float]] = None) -> None: + super().__init__() + self.opacity_range = opacity_range if isinstance(opacity_range, tuple) else (0.2, 0.8) + + def __call__(self, x: tf.Tensor) -> tf.Tensor: + # Reshape the distribution + if x.dtype == tf.uint8: + return tf.cast( + tf.clip_by_value( + tf.math.round(255 * random_shadow(tf.cast(x, dtype=tf.float32) / 255, self.opacity_range)), + 0, + 255, + ), + dtype=tf.uint8, + ) + else: + return tf.clip_by_value(random_shadow(x, self.opacity_range), 0, 1) + + def extra_repr(self) -> str: + return f"opacity_range={self.opacity_range}"
    + + + +
    +[docs] +class RandomResize(NestedObject): + """Randomly resize the input image and align corresponding targets + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomResize + >>> transfo = RandomResize((0.3, 0.9), preserve_aspect_ratio=True, symmetric_pad=True, p=0.5) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + scale_range: range of the resizing factor for width and height (independently) + preserve_aspect_ratio: whether to preserve the aspect ratio of the image, + given a float value, the aspect ratio will be preserved with this probability + symmetric_pad: whether to symmetrically pad the image, + given a float value, the symmetric padding will be applied with this probability + p: probability to apply the transformation + """ + + def __init__( + self, + scale_range: Tuple[float, float] = (0.3, 0.9), + preserve_aspect_ratio: Union[bool, float] = False, + symmetric_pad: Union[bool, float] = False, + p: float = 0.5, + ): + super().__init__() + self.scale_range = scale_range + self.preserve_aspect_ratio = preserve_aspect_ratio + self.symmetric_pad = symmetric_pad + self.p = p + self._resize = Resize + + def __call__(self, img: tf.Tensor, target: np.ndarray) -> Tuple[tf.Tensor, np.ndarray]: + if np.random.rand(1) <= self.p: + scale_h = random.uniform(*self.scale_range) + scale_w = random.uniform(*self.scale_range) + new_size = (int(img.shape[-3] * scale_h), int(img.shape[-2] * scale_w)) + + _img, _target = self._resize( + new_size, + preserve_aspect_ratio=self.preserve_aspect_ratio + if isinstance(self.preserve_aspect_ratio, bool) + else bool(np.random.rand(1) <= self.symmetric_pad), + symmetric_pad=self.symmetric_pad + if isinstance(self.symmetric_pad, bool) + else bool(np.random.rand(1) <= self.symmetric_pad), + )(img, target) + + return _img, _target + return img, target + + def extra_repr(self) -> str: + return f"scale_range={self.scale_range}, preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}, p={self.p}" # noqa: E501
    +
    @@ -638,8 +955,8 @@

    Source code for doctr.transforms.modules.tensorflow

    - +
    + diff --git a/v0.5.0/_modules/doctr/utils/metrics.html b/v0.5.0/_modules/doctr/utils/metrics.html index 460c64a385..8a37d5949a 100644 --- a/v0.5.0/_modules/doctr/utils/metrics.html +++ b/v0.5.0/_modules/doctr/utils/metrics.html @@ -13,7 +13,7 @@ - + doctr.utils.metrics - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.utils.metrics

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
    +
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +from typing import Dict, List, Optional, Tuple
     
     import numpy as np
    -import cv2
    -from typing import List, Tuple, Dict, Optional
    -from unidecode import unidecode
    +from anyascii import anyascii
     from scipy.optimize import linear_sum_assignment
    -from doctr.utils.geometry import rbbox_to_polygon
    +from shapely.geometry import Polygon
     
    -__all__ = ['TextMatch', 'box_iou', 'box_ioa', 'mask_iou', 'rbox_to_mask',
    -           'nms', 'LocalizationConfusion', 'OCRMetric']
    +__all__ = [
    +    "TextMatch",
    +    "box_iou",
    +    "polygon_iou",
    +    "nms",
    +    "LocalizationConfusion",
    +    "OCRMetric",
    +    "DetectionMetric",
    +]
     
     
     def string_match(word1: str, word2: str) -> Tuple[bool, bool, bool, bool]:
    -    """Perform string comparison with multiple levels of tolerance
    +    """Performs string comparison with multiple levels of tolerance
     
         Args:
    +    ----
             word1: a string
             word2: another string
     
         Returns:
    +    -------
             a tuple with booleans specifying respectively whether the raw strings, their lower-case counterparts, their
    -            unidecode counterparts and their lower-case unidecode counterparts match
    +            anyascii counterparts and their lower-case anyascii counterparts match
         """
    -    raw_match = (word1 == word2)
    -    caseless_match = (word1.lower() == word2.lower())
    -    unidecode_match = (unidecode(word1) == unidecode(word2))
    +    raw_match = word1 == word2
    +    caseless_match = word1.lower() == word2.lower()
    +    anyascii_match = anyascii(word1) == anyascii(word2)
     
         # Warning: the order is important here otherwise the pair ("EUR", "€") cannot be matched
    -    unicase_match = (unidecode(word1).lower() == unidecode(word2).lower())
    +    unicase_match = anyascii(word1).lower() == anyascii(word2).lower()
     
    -    return raw_match, caseless_match, unidecode_match, unicase_match
    +    return raw_match, caseless_match, anyascii_match, unicase_match
     
     
     
    -[docs] +[docs] class TextMatch: - """Implements text match metric (word-level accuracy) for recognition task. + r"""Implements text match metric (word-level accuracy) for recognition task. The raw aggregated metric is computed as follows: .. math:: - \\forall X, Y \\in \\mathcal{W}^N, - TextMatch(X, Y) = \\frac{1}{N} \\sum\\limits_{i=1}^N f_{Y_i}(X_i) + \forall X, Y \in \mathcal{W}^N, + TextMatch(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N f_{Y_i}(X_i) with the indicator function :math:`f_{a}` defined as: .. math:: - \\forall a, x \\in \\mathcal{W}, - f_a(x) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } x = a \\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{W}` is the set of all possible character sequences, + \forall a, x \in \mathcal{W}, + f_a(x) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } x = a \\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{W}` is the set of all possible character sequences, :math:`N` is a strictly positive integer. - Example:: - >>> from doctr.utils import TextMatch - >>> metric = TextMatch() - >>> metric.update(['Hello', 'world'], ['hello', 'world']) - >>> metric.summary() + >>> from doctr.utils import TextMatch + >>> metric = TextMatch() + >>> metric.update(['Hello', 'world'], ['hello', 'world']) + >>> metric.summary() """ def __init__(self) -> None: self.reset() +
    +[docs] def update( self, gt: List[str], @@ -354,29 +386,32 @@

    Source code for doctr.utils.metrics

             """Update the state of the metric with new predictions
     
             Args:
    +        ----
                 gt: list of groung-truth character sequences
    -            pred: list of predicted character sequences"""
    -
    +            pred: list of predicted character sequences
    +        """
             if len(gt) != len(pred):
                 raise AssertionError("prediction size does not match with ground-truth labels size")
     
             for gt_word, pred_word in zip(gt, pred):
    -            _raw, _caseless, _unidecode, _unicase = string_match(gt_word, pred_word)
    +            _raw, _caseless, _anyascii, _unicase = string_match(gt_word, pred_word)
                 self.raw += int(_raw)
                 self.caseless += int(_caseless)
    -            self.unidecode += int(_unidecode)
    +            self.anyascii += int(_anyascii)
                 self.unicase += int(_unicase)
     
    -        self.total += len(gt)
    +        self.total += len(gt)
    +
    -[docs] +[docs] def summary(self) -> Dict[str, float]: """Computes the aggregated metrics - Returns: - a dictionary with the exact match score for the raw data, its lower-case counterpart, its unidecode - counterpart and its lower-case unidecode counterpart + Returns + ------- + a dictionary with the exact match score for the raw data, its lower-case counterpart, its anyascii + counterpart and its lower-case anyascii counterpart """ if self.total == 0: raise AssertionError("you need to update the metric before getting the summary") @@ -384,7 +419,7 @@

    Source code for doctr.utils.metrics

             return dict(
                 raw=self.raw / self.total,
                 caseless=self.caseless / self.total,
    -            unidecode=self.unidecode / self.total,
    +            anyascii=self.anyascii / self.total,
                 unicase=self.unicase / self.total,
             )
    @@ -392,23 +427,25 @@

    Source code for doctr.utils.metrics

         def reset(self) -> None:
             self.raw = 0
             self.caseless = 0
    -        self.unidecode = 0
    +        self.anyascii = 0
             self.unicase = 0
             self.total = 0
    def box_iou(boxes_1: np.ndarray, boxes_2: np.ndarray) -> np.ndarray: - """Compute the IoU between two sets of bounding boxes + """Computes the IoU between two sets of bounding boxes Args: + ---- boxes_1: bounding boxes of shape (N, 4) in format (xmin, ymin, xmax, ymax) boxes_2: bounding boxes of shape (M, 4) in format (xmin, ymin, xmax, ymax) + Returns: + ------- the IoU matrix of shape (N, M) """ - - iou_mat = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32) + iou_mat: np.ndarray = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32) if boxes_1.shape[0] > 0 and boxes_2.shape[0] > 0: l1, t1, r1, b1 = np.split(boxes_1, 4, axis=1) @@ -419,107 +456,54 @@

    Source code for doctr.utils.metrics

             right = np.minimum(r1, r2.T)
             bot = np.minimum(b1, b2.T)
     
    -        intersection = np.clip(right - left, 0, np.Inf) * np.clip(bot - top, 0, np.Inf)
    +        intersection = np.clip(right - left, 0, np.inf) * np.clip(bot - top, 0, np.inf)
             union = (r1 - l1) * (b1 - t1) + ((r2 - l2) * (b2 - t2)).T - intersection
             iou_mat = intersection / union
     
         return iou_mat
     
     
    -def box_ioa(boxes_1: np.ndarray, boxes_2: np.ndarray) -> np.ndarray:
    -    """Compute the IoA (intersection over area) between two sets of bounding boxes:
    -    ioa(i, j) = inter(i, j) / area(i)
    -
    -    Args:
    -        boxes_1: bounding boxes of shape (N, 4) in format (xmin, ymin, xmax, ymax)
    -        boxes_2: bounding boxes of shape (M, 4) in format (xmin, ymin, xmax, ymax)
    -    Returns:
    -        the IoA matrix of shape (N, M)
    -    """
    -
    -    ioa_mat = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32)
    -
    -    if boxes_1.shape[0] > 0 and boxes_2.shape[0] > 0:
    -        l1, t1, r1, b1 = np.split(boxes_1, 4, axis=1)
    -        l2, t2, r2, b2 = np.split(boxes_2, 4, axis=1)
    -
    -        left = np.maximum(l1, l2.T)
    -        top = np.maximum(t1, t2.T)
    -        right = np.minimum(r1, r2.T)
    -        bot = np.minimum(b1, b2.T)
    -
    -        intersection = np.clip(right - left, 0, np.Inf) * np.clip(bot - top, 0, np.Inf)
    -        area = (r1 - l1) * (b1 - t1)
    -        ioa_mat = intersection / area
    -
    -    return ioa_mat
    -
    -
    -def mask_iou(masks_1: np.ndarray, masks_2: np.ndarray) -> np.ndarray:
    -    """Compute the IoU between two sets of boolean masks
    +def polygon_iou(polys_1: np.ndarray, polys_2: np.ndarray) -> np.ndarray:
    +    """Computes the IoU between two sets of rotated bounding boxes
     
         Args:
    -        masks_1: boolean masks of shape (N, H, W)
    -        masks_2: boolean masks of shape (M, H, W)
    +    ----
    +        polys_1: rotated bounding boxes of shape (N, 4, 2)
    +        polys_2: rotated bounding boxes of shape (M, 4, 2)
    +        mask_shape: spatial shape of the intermediate masks
    +        use_broadcasting: if set to True, leverage broadcasting speedup by consuming more memory
     
         Returns:
    +    -------
             the IoU matrix of shape (N, M)
         """
    +    if polys_1.ndim != 3 or polys_2.ndim != 3:
    +        raise AssertionError("expects boxes to be in format (N, 4, 2)")
     
    -    if masks_1.shape[1:] != masks_2.shape[1:]:
    -        raise AssertionError("both boolean masks should have the same spatial shape")
    +    iou_mat = np.zeros((polys_1.shape[0], polys_2.shape[0]), dtype=np.float32)
     
    -    iou_mat = np.zeros((masks_1.shape[0], masks_2.shape[0]), dtype=np.float32)
    +    shapely_polys_1 = [Polygon(poly) for poly in polys_1]
    +    shapely_polys_2 = [Polygon(poly) for poly in polys_2]
     
    -    if masks_1.shape[0] > 0 and masks_2.shape[0] > 0:
    -        intersection = np.logical_and(masks_1[:, None, ...], masks_2[None, ...])
    -        union = np.logical_or(masks_1[:, None, ...], masks_2[None, ...])
    -        axes = tuple(range(2, masks_1.ndim + 1))
    -        iou_mat = intersection.sum(axis=axes) / union.sum(axis=axes)
    +    for i, poly1 in enumerate(shapely_polys_1):
    +        for j, poly2 in enumerate(shapely_polys_2):
    +            intersection_area = poly1.intersection(poly2).area
    +            union_area = poly1.area + poly2.area - intersection_area
    +            iou_mat[i, j] = intersection_area / union_area
     
         return iou_mat
     
     
    -def rbox_to_mask(boxes: np.ndarray, shape: Tuple[int, int]) -> np.ndarray:
    -    """Convert boxes to masks
    -
    -    Args:
    -        boxes: rotated bounding boxes of shape (N, 5) in format (x, y, w, h, alpha)
    -        shape: spatial shapes of the output masks
    -
    -    Returns:
    -        the boolean masks of shape (N, H, W)
    -    """
    -
    -    masks = np.zeros((boxes.shape[0], *shape), dtype=np.uint8)
    -
    -    if boxes.shape[0] > 0:
    -        # Get absolute coordinates
    -        if boxes.dtype != np.int:
    -            abs_boxes = boxes.copy()
    -            abs_boxes[:, [0, 2]] = abs_boxes[:, [0, 2]] * shape[1]
    -            abs_boxes[:, [1, 3]] = abs_boxes[:, [1, 3]] * shape[0]
    -            abs_boxes = abs_boxes.round().astype(np.int)
    -        else:
    -            abs_boxes = boxes
    -            abs_boxes[:, 2:] = abs_boxes[:, 2:] + 1
    -
    -        # TODO: optimize slicing to improve vectorization
    -        for idx, _box in enumerate(abs_boxes):
    -            box = rbbox_to_polygon(_box)
    -            cv2.fillPoly(masks[idx], [np.array(box, np.int32)], 1)
    -
    -    return masks.astype(bool)
    -
    -
    -def nms(boxes: np.ndarray, thresh: float = .5) -> List[int]:
    +def nms(boxes: np.ndarray, thresh: float = 0.5) -> List[int]:
         """Perform non-max suppression, borrowed from <https://github.com/rbgirshick/fast-rcnn>`_.
     
         Args:
    +    ----
             boxes: np array of straight boxes: (*, 5), (xmin, ymin, xmax, ymax, score)
             thresh: iou threshold to perform box suppression.
     
         Returns:
    +    -------
             A list of box indexes to keep
         """
         x1 = boxes[:, 0]
    @@ -551,66 +535,71 @@ 

    Source code for doctr.utils.metrics

     
     
     
    -[docs] +[docs] class LocalizationConfusion: - """Implements common confusion metrics and mean IoU for localization evaluation. + r"""Implements common confusion metrics and mean IoU for localization evaluation. The aggregated metrics are computed as follows: .. math:: - \\forall Y \\in \\mathcal{B}^N, \\forall X \\in \\mathcal{B}^M, \\\\ - Recall(X, Y) = \\frac{1}{N} \\sum\\limits_{i=1}^N g_{X}(Y_i) \\\\ - Precision(X, Y) = \\frac{1}{M} \\sum\\limits_{i=1}^N g_{X}(Y_i) \\\\ - meanIoU(X, Y) = \\frac{1}{M} \\sum\\limits_{i=1}^M \\max\\limits_{j \\in [1, N]} IoU(X_i, Y_j) + \forall Y \in \mathcal{B}^N, \forall X \in \mathcal{B}^M, \\ + Recall(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N g_{X}(Y_i) \\ + Precision(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M g_{X}(Y_i) \\ + meanIoU(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(X_i, Y_j) with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and :math:`y`, and the function :math:`g_{X}` defined as: .. math:: - \\forall y \\in \\mathcal{B}, - g_X(y) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } y\\mbox{ has been assigned to any }(X_i)_i\\mbox{ with an }IoU \\geq 0.5 \\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{B}` is the set of possible bounding boxes, + \forall y \in \mathcal{B}, + g_X(y) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } y\mbox{ has been assigned to any }(X_i)_i\mbox{ with an }IoU \geq 0.5 \\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. - Example:: - >>> import numpy as np - >>> from doctr.utils import LocalizationConfusion - >>> metric = LocalizationConfusion(iou_thresh=0.5) - >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]])) - >>> metric.summary() + >>> import numpy as np + >>> from doctr.utils import LocalizationConfusion + >>> metric = LocalizationConfusion(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]])) + >>> metric.summary() Args: + ---- iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format """ def __init__( self, iou_thresh: float = 0.5, - rotated_bbox: bool = False, - mask_shape: Tuple[int, int] = (1024, 1024), + use_polygons: bool = False, ) -> None: self.iou_thresh = iou_thresh - self.rotated_bbox = rotated_bbox - self.mask_shape = mask_shape + self.use_polygons = use_polygons self.reset() +
    +[docs] def update(self, gts: np.ndarray, preds: np.ndarray) -> None: + """Updates the metric + Args: + ---- + gts: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones + preds: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones + """ if preds.shape[0] > 0: # Compute IoU - if self.rotated_bbox: - mask_gts = rbox_to_mask(gts, shape=self.mask_shape) - mask_preds = rbox_to_mask(preds, shape=self.mask_shape) - iou_mat = mask_iou(mask_gts, mask_preds) + if self.use_polygons: + iou_mat = polygon_iou(gts, preds) else: iou_mat = box_iou(gts, preds) - self.tot_iou += float(iou_mat.max(axis=1).sum()) + self.tot_iou += float(iou_mat.max(axis=0).sum()) # Assign pairs gt_indices, pred_indices = linear_sum_assignment(-iou_mat) @@ -618,17 +607,18 @@

    Source code for doctr.utils.metrics

     
             # Update counts
             self.num_gts += gts.shape[0]
    -        self.num_preds += preds.shape[0]
    +        self.num_preds += preds.shape[0]
    +
    -[docs] +[docs] def summary(self) -> Tuple[Optional[float], Optional[float], Optional[float]]: """Computes the aggregated metrics - Returns: + Returns + ------- a tuple with the recall, precision and meanIoU scores """ - # Recall recall = self.matches / self.num_gts if self.num_gts > 0 else None @@ -636,7 +626,7 @@

    Source code for doctr.utils.metrics

             precision = self.matches / self.num_preds if self.num_preds > 0 else None
     
             # mean IoU
    -        mean_iou = self.tot_iou / self.num_preds if self.num_preds > 0 else None
    +        mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None
     
             return recall, precision, mean_iou
    @@ -645,64 +635,65 @@

    Source code for doctr.utils.metrics

             self.num_gts = 0
             self.num_preds = 0
             self.matches = 0
    -        self.tot_iou = 0.
    + self.tot_iou = 0.0
    -[docs] +[docs] class OCRMetric: - """Implements end-to-end OCR metric. + r"""Implements an end-to-end OCR metric. The aggregated metrics are computed as follows: .. math:: - \\forall (B, L) \\in \\mathcal{B}^N \\times \\mathcal{L}^N, - \\forall (\\hat{B}, \\hat{L}) \\in \\mathcal{B}^M \\times \\mathcal{L}^M, \\\\ - Recall(B, \\hat{B}, L, \\hat{L}) = \\frac{1}{N} \\sum\\limits_{i=1}^N h_{B,L}(\\hat{B}_i, \\hat{L}_i) \\\\ - Precision(B, \\hat{B}, L, \\hat{L}) = \\frac{1}{M} \\sum\\limits_{i=1}^N h_{B,L}(\\hat{B}_i, \\hat{L}_i) \\\\ - meanIoU(B, \\hat{B}) = \\frac{1}{M} \\sum\\limits_{i=1}^M \\max\\limits_{j \\in [1, N]} IoU(\\hat{B}_i, B_j) + \forall (B, L) \in \mathcal{B}^N \times \mathcal{L}^N, + \forall (\hat{B}, \hat{L}) \in \mathcal{B}^M \times \mathcal{L}^M, \\ + Recall(B, \hat{B}, L, \hat{L}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ + Precision(B, \hat{B}, L, \hat{L}) = \frac{1}{M} \sum\limits_{i=1}^M h_{B,L}(\hat{B}_i, \hat{L}_i) \\ + meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j) with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and :math:`y`, and the function :math:`h_{B, L}` defined as: .. math:: - \\forall (b, l) \\in \\mathcal{B} \\times \\mathcal{L}, - h_{B,L}(b, l) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } b\\mbox{ has been assigned to a given }B_j\\mbox{ with an } \\\\ - & IoU \\geq 0.5 \\mbox{ and that for this assignment, } l = L_j\\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{B}` is the set of possible bounding boxes, - :math:`\\mathcal{L}` is the set of possible character sequences, + \forall (b, l) \in \mathcal{B} \times \mathcal{L}, + h_{B,L}(b, l) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ + & IoU \geq 0.5 \mbox{ and that for this assignment, } l = L_j\\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, + :math:`\mathcal{L}` is the set of possible character sequences, :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. - Example:: - >>> import numpy as np - >>> from doctr.utils import OCRMetric - >>> metric = OCRMetric(iou_thresh=0.5) - >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), - ['hello'], ['hello', 'world']) - >>> metric.summary() + >>> import numpy as np + >>> from doctr.utils import OCRMetric + >>> metric = OCRMetric(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), + >>> ['hello'], ['hello', 'world']) + >>> metric.summary() Args: + ---- iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format """ def __init__( self, iou_thresh: float = 0.5, - rotated_bbox: bool = False, - mask_shape: Tuple[int, int] = (1024, 1024), + use_polygons: bool = False, ) -> None: self.iou_thresh = iou_thresh - self.rotated_bbox = rotated_bbox - self.mask_shape = mask_shape + self.use_polygons = use_polygons self.reset() +
    +[docs] def update( self, gt_boxes: np.ndarray, @@ -710,50 +701,58 @@

    Source code for doctr.utils.metrics

             gt_labels: List[str],
             pred_labels: List[str],
         ) -> None:
    +        """Updates the metric
     
    +        Args:
    +        ----
    +            gt_boxes: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones
    +            pred_boxes: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones
    +            gt_labels: a list of N string labels
    +            pred_labels: a list of M string labels
    +        """
             if gt_boxes.shape[0] != len(gt_labels) or pred_boxes.shape[0] != len(pred_labels):
    -            raise AssertionError("there should be the same number of boxes and string both for the ground truth "
    -                                 "and the predictions")
    +            raise AssertionError(
    +                "there should be the same number of boxes and string both for the ground truth and the predictions"
    +            )
     
             # Compute IoU
             if pred_boxes.shape[0] > 0:
    -            if self.rotated_bbox:
    -                mask_gts = rbox_to_mask(gt_boxes, shape=self.mask_shape)
    -                mask_preds = rbox_to_mask(pred_boxes, shape=self.mask_shape)
    -                iou_mat = mask_iou(mask_gts, mask_preds)
    +            if self.use_polygons:
    +                iou_mat = polygon_iou(gt_boxes, pred_boxes)
                 else:
                     iou_mat = box_iou(gt_boxes, pred_boxes)
     
    -            self.tot_iou += float(iou_mat.max(axis=1).sum())
    +            self.tot_iou += float(iou_mat.max(axis=0).sum())
     
                 # Assign pairs
                 gt_indices, pred_indices = linear_sum_assignment(-iou_mat)
                 is_kept = iou_mat[gt_indices, pred_indices] >= self.iou_thresh
                 # String comparison
                 for gt_idx, pred_idx in zip(gt_indices[is_kept], pred_indices[is_kept]):
    -                _raw, _caseless, _unidecode, _unicase = string_match(gt_labels[gt_idx], pred_labels[pred_idx])
    +                _raw, _caseless, _anyascii, _unicase = string_match(gt_labels[gt_idx], pred_labels[pred_idx])
                     self.raw_matches += int(_raw)
                     self.caseless_matches += int(_caseless)
    -                self.unidecode_matches += int(_unidecode)
    +                self.anyascii_matches += int(_anyascii)
                     self.unicase_matches += int(_unicase)
     
             self.num_gts += gt_boxes.shape[0]
    -        self.num_preds += pred_boxes.shape[0]
    +        self.num_preds += pred_boxes.shape[0]
    +
    -[docs] +[docs] def summary(self) -> Tuple[Dict[str, Optional[float]], Dict[str, Optional[float]], Optional[float]]: """Computes the aggregated metrics - Returns: - a tuple with the recall & precision for each string comparison flexibility and the mean IoU + Returns + ------- + a tuple with the recall & precision for each string comparison and the mean IoU """ - # Recall recall = dict( raw=self.raw_matches / self.num_gts if self.num_gts > 0 else None, caseless=self.caseless_matches / self.num_gts if self.num_gts > 0 else None, - unidecode=self.unidecode_matches / self.num_gts if self.num_gts > 0 else None, + anyascii=self.anyascii_matches / self.num_gts if self.num_gts > 0 else None, unicase=self.unicase_matches / self.num_gts if self.num_gts > 0 else None, ) @@ -761,12 +760,12 @@

    Source code for doctr.utils.metrics

             precision = dict(
                 raw=self.raw_matches / self.num_preds if self.num_preds > 0 else None,
                 caseless=self.caseless_matches / self.num_preds if self.num_preds > 0 else None,
    -            unidecode=self.unidecode_matches / self.num_preds if self.num_preds > 0 else None,
    +            anyascii=self.anyascii_matches / self.num_preds if self.num_preds > 0 else None,
                 unicase=self.unicase_matches / self.num_preds if self.num_preds > 0 else None,
             )
     
             # mean IoU (overall detected boxes)
    -        mean_iou = self.tot_iou / self.num_preds if self.num_preds > 0 else None
    +        mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None
     
             return recall, precision, mean_iou
    @@ -774,12 +773,136 @@

    Source code for doctr.utils.metrics

         def reset(self) -> None:
             self.num_gts = 0
             self.num_preds = 0
    -        self.tot_iou = 0.
    +        self.tot_iou = 0.0
             self.raw_matches = 0
             self.caseless_matches = 0
    -        self.unidecode_matches = 0
    +        self.anyascii_matches = 0
             self.unicase_matches = 0
    + + +
    +[docs] +class DetectionMetric: + r"""Implements an object detection metric. + + The aggregated metrics are computed as follows: + + .. math:: + \forall (B, C) \in \mathcal{B}^N \times \mathcal{C}^N, + \forall (\hat{B}, \hat{C}) \in \mathcal{B}^M \times \mathcal{C}^M, \\ + Recall(B, \hat{B}, C, \hat{C}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,C}(\hat{B}_i, \hat{C}_i) \\ + Precision(B, \hat{B}, C, \hat{C}) = \frac{1}{M} \sum\limits_{i=1}^M h_{B,C}(\hat{B}_i, \hat{C}_i) \\ + meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j) + + with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and + :math:`y`, and the function :math:`h_{B, C}` defined as: + + .. math:: + \forall (b, c) \in \mathcal{B} \times \mathcal{C}, + h_{B,C}(b, c) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ + & IoU \geq 0.5 \mbox{ and that for this assignment, } c = C_j\\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, + :math:`\mathcal{C}` is the set of possible class indices, + :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. + + >>> import numpy as np + >>> from doctr.utils import DetectionMetric + >>> metric = DetectionMetric(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), + >>> np.zeros(1, dtype=np.int64), np.array([0, 1], dtype=np.int64)) + >>> metric.summary() + + Args: + ---- + iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format + """ + + def __init__( + self, + iou_thresh: float = 0.5, + use_polygons: bool = False, + ) -> None: + self.iou_thresh = iou_thresh + self.use_polygons = use_polygons + self.reset() + +
    +[docs] + def update( + self, + gt_boxes: np.ndarray, + pred_boxes: np.ndarray, + gt_labels: np.ndarray, + pred_labels: np.ndarray, + ) -> None: + """Updates the metric + + Args: + ---- + gt_boxes: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones + pred_boxes: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones + gt_labels: an array of class indices of shape (N,) + pred_labels: an array of class indices of shape (M,) + """ + if gt_boxes.shape[0] != gt_labels.shape[0] or pred_boxes.shape[0] != pred_labels.shape[0]: + raise AssertionError( + "there should be the same number of boxes and string both for the ground truth and the predictions" + ) + + # Compute IoU + if pred_boxes.shape[0] > 0: + if self.use_polygons: + iou_mat = polygon_iou(gt_boxes, pred_boxes) + else: + iou_mat = box_iou(gt_boxes, pred_boxes) + + self.tot_iou += float(iou_mat.max(axis=0).sum()) + + # Assign pairs + gt_indices, pred_indices = linear_sum_assignment(-iou_mat) + is_kept = iou_mat[gt_indices, pred_indices] >= self.iou_thresh + # Category comparison + self.num_matches += int((gt_labels[gt_indices[is_kept]] == pred_labels[pred_indices[is_kept]]).sum()) + + self.num_gts += gt_boxes.shape[0] + self.num_preds += pred_boxes.shape[0]
    + + +
    +[docs] + def summary(self) -> Tuple[Optional[float], Optional[float], Optional[float]]: + """Computes the aggregated metrics + + Returns + ------- + a tuple with the recall & precision for each class prediction and the mean IoU + """ + # Recall + recall = self.num_matches / self.num_gts if self.num_gts > 0 else None + + # Precision + precision = self.num_matches / self.num_preds if self.num_preds > 0 else None + + # mean IoU (overall detected boxes) + mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None + + return recall, precision, mean_iou
    + + + def reset(self) -> None: + self.num_gts = 0 + self.num_preds = 0 + self.tot_iou = 0.0 + self.num_matches = 0
    +
    @@ -812,8 +935,8 @@

    Source code for doctr.utils.metrics

           
         
       
    - - + + diff --git a/v0.5.0/_modules/doctr/utils/visualization.html b/v0.5.0/_modules/doctr/utils/visualization.html index 8e7dcca811..c818be6d7b 100644 --- a/v0.5.0/_modules/doctr/utils/visualization.html +++ b/v0.5.0/_modules/doctr/utils/visualization.html @@ -13,7 +13,7 @@ - + doctr.utils.visualization - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.utils.visualization

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
    +import colorsys
    +from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple, Union
     
    -import matplotlib.pyplot as plt
    -from matplotlib.figure import Figure
    +import cv2
     import matplotlib.patches as patches
    -import mplcursors
    -from PIL import ImageFont, ImageDraw, Image
    +import matplotlib.pyplot as plt
     import numpy as np
    -import cv2
    -from typing import Tuple, List, Dict, Any, Union
    +from matplotlib.figure import Figure
     
    -from .common_types import BoundingBox, RotatedBbox
    +from .common_types import BoundingBox, Polygon4P
     
    -__all__ = ['visualize_page', 'synthetize_page']
    +__all__ = ["visualize_page", "visualize_kie_page", "draw_boxes"]
     
     
    -def create_rect_patch(
    -    geometry: Union[BoundingBox, RotatedBbox],
    -    label: str,
    +def rect_patch(
    +    geometry: BoundingBox,
         page_dimensions: Tuple[int, int],
    -    color: Tuple[int, int, int],
    +    label: Optional[str] = None,
    +    color: Tuple[float, float, float] = (0, 0, 0),
         alpha: float = 0.3,
         linewidth: int = 2,
         fill: bool = True,
    -) -> patches.Patch:
    -    """Create a matplotlib patch (rectangle) bounding the element
    +    preserve_aspect_ratio: bool = False,
    +) -> patches.Rectangle:
    +    """Create a matplotlib rectangular patch for the element
     
         Args:
    +    ----
             geometry: bounding box of the element
    +        page_dimensions: dimensions of the Page in format (height, width)
             label: label to display when hovered
    -        page_dimensions: dimensions of the Page
             color: color to draw box
             alpha: opacity parameter to fill the boxes, 0 = transparent
             linewidth: line width
    +        fill: whether the patch should be filled
    +        preserve_aspect_ratio: pass True if you passed True to the predictor
     
         Returns:
    +    -------
             a rectangular Patch
         """
    +    if len(geometry) != 2 or any(not isinstance(elt, tuple) or len(elt) != 2 for elt in geometry):
    +        raise ValueError("invalid geometry format")
    +
    +    # Unpack
         height, width = page_dimensions
    -    if len(geometry) == 5:
    -        x, y, w, h, a = geometry  # type: ignore[misc]
    -        x, w = x * width, w * width
    -        y, h = y * height, h * height
    -        points = cv2.boxPoints(((x, y), (w, h), a))
    -        return patches.Polygon(
    -            points,
    -            fill=fill,
    -            linewidth=linewidth,
    -            edgecolor=(*color, alpha),
    -            facecolor=(*color, alpha),
    -            label=label
    -        )
    -    else:
    -        (xmin, ymin), (xmax, ymax) = geometry  # type: ignore[misc]
    -        xmin, xmax = xmin * width, xmax * width
    -        ymin, ymax = ymin * height, ymax * height
    -        return patches.Rectangle(
    -            (xmin, ymin),
    -            xmax - xmin,
    -            ymax - ymin,
    -            fill=fill,
    -            linewidth=linewidth,
    -            edgecolor=(*color, alpha),
    -            facecolor=(*color, alpha),
    -            label=label
    -        )
    +    (xmin, ymin), (xmax, ymax) = geometry
    +    # Switch to absolute coords
    +    if preserve_aspect_ratio:
    +        width = height = max(height, width)
    +    xmin, w = xmin * width, (xmax - xmin) * width
    +    ymin, h = ymin * height, (ymax - ymin) * height
    +
    +    return patches.Rectangle(
    +        (xmin, ymin),
    +        w,
    +        h,
    +        fill=fill,
    +        linewidth=linewidth,
    +        edgecolor=(*color, alpha),
    +        facecolor=(*color, alpha),
    +        label=label,
    +    )
    +
    +
    +def polygon_patch(
    +    geometry: np.ndarray,
    +    page_dimensions: Tuple[int, int],
    +    label: Optional[str] = None,
    +    color: Tuple[float, float, float] = (0, 0, 0),
    +    alpha: float = 0.3,
    +    linewidth: int = 2,
    +    fill: bool = True,
    +    preserve_aspect_ratio: bool = False,
    +) -> patches.Polygon:
    +    """Create a matplotlib polygon patch for the element
    +
    +    Args:
    +    ----
    +        geometry: bounding box of the element
    +        page_dimensions: dimensions of the Page in format (height, width)
    +        label: label to display when hovered
    +        color: color to draw box
    +        alpha: opacity parameter to fill the boxes, 0 = transparent
    +        linewidth: line width
    +        fill: whether the patch should be filled
    +        preserve_aspect_ratio: pass True if you passed True to the predictor
    +
    +    Returns:
    +    -------
    +        a polygon Patch
    +    """
    +    if not geometry.shape == (4, 2):
    +        raise ValueError("invalid geometry format")
    +
    +    # Unpack
    +    height, width = page_dimensions
    +    geometry[:, 0] = geometry[:, 0] * (max(width, height) if preserve_aspect_ratio else width)
    +    geometry[:, 1] = geometry[:, 1] * (max(width, height) if preserve_aspect_ratio else height)
    +
    +    return patches.Polygon(
    +        geometry,
    +        fill=fill,
    +        linewidth=linewidth,
    +        edgecolor=(*color, alpha),
    +        facecolor=(*color, alpha),
    +        label=label,
    +    )
    +
    +
    +def create_obj_patch(
    +    geometry: Union[BoundingBox, Polygon4P, np.ndarray],
    +    page_dimensions: Tuple[int, int],
    +    **kwargs: Any,
    +) -> patches.Patch:
    +    """Create a matplotlib patch for the element
    +
    +    Args:
    +    ----
    +        geometry: bounding box (straight or rotated) of the element
    +        page_dimensions: dimensions of the page in format (height, width)
    +        **kwargs: keyword arguments for the patch
    +
    +    Returns:
    +    -------
    +        a matplotlib Patch
    +    """
    +    if isinstance(geometry, tuple):
    +        if len(geometry) == 2:  # straight word BB (2 pts)
    +            return rect_patch(geometry, page_dimensions, **kwargs)
    +        elif len(geometry) == 4:  # rotated word BB (4 pts)
    +            return polygon_patch(np.asarray(geometry), page_dimensions, **kwargs)
    +    elif isinstance(geometry, np.ndarray) and geometry.shape == (4, 2):  # rotated line
    +        return polygon_patch(geometry, page_dimensions, **kwargs)
    +    raise ValueError("invalid geometry format")
    +
    +
    +def get_colors(num_colors: int) -> List[Tuple[float, float, float]]:
    +    """Generate num_colors color for matplotlib
    +
    +    Args:
    +    ----
    +        num_colors: number of colors to generate
    +
    +    Returns:
    +    -------
    +        colors: list of generated colors
    +    """
    +    colors = []
    +    for i in np.arange(0.0, 360.0, 360.0 / num_colors):
    +        hue = i / 360.0
    +        lightness = (50 + np.random.rand() * 10) / 100.0
    +        saturation = (90 + np.random.rand() * 10) / 100.0
    +        colors.append(colorsys.hls_to_rgb(hue, lightness, saturation))
    +    return colors
     
     
     
    -[docs] +[docs] def visualize_page( page: Dict[str, Any], image: np.ndarray, @@ -359,18 +472,18 @@

    Source code for doctr.utils.visualization

     ) -> Figure:
         """Visualize a full page with predicted blocks, lines and words
     
    -    Example::
    -        >>> import numpy as np
    -        >>> import matplotlib.pyplot as plt
    -        >>> from doctr.utils.visualization import visualize_page
    -        >>> from doctr.models import ocr_db_crnn
    -        >>> model = ocr_db_crnn(pretrained=True)
    -        >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    -        >>> out = model([[input_page]])
    -        >>> visualize_page(out[0].pages[0].export(), input_page)
    -        >>> plt.show()
    +    >>> import numpy as np
    +    >>> import matplotlib.pyplot as plt
    +    >>> from doctr.utils.visualization import visualize_page
    +    >>> from doctr.models import ocr_db_crnn
    +    >>> model = ocr_db_crnn(pretrained=True)
    +    >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    +    >>> out = model([[input_page]])
    +    >>> visualize_page(out[0].pages[0].export(), input_page)
    +    >>> plt.show()
     
         Args:
    +    ----
             page: the exported Page of a Document
             image: np array of the page, needs to have the same shape than page['dimensions']
             words_only: whether only words should be displayed
    @@ -378,6 +491,11 @@ 

    Source code for doctr.utils.visualization

             scale: figsize of the largest windows side
             interactive: whether the plot should be interactive
             add_labels: for static plot, adds text labels on top of bounding box
    +        **kwargs: keyword arguments for the polygon patch
    +
    +    Returns:
    +    -------
    +        the matplotlib figure
         """
         # Get proper scale and aspect ratio
         h, w = image.shape[:2]
    @@ -386,128 +504,189 @@ 

    Source code for doctr.utils.visualization

         # Display the image
         ax.imshow(image)
         # hide both axis
    -    ax.axis('off')
    +    ax.axis("off")
     
         if interactive:
             artists: List[patches.Patch] = []  # instantiate an empty list of patches (to be drawn on the page)
     
    -    for block in page['blocks']:
    +    for block in page["blocks"]:
             if not words_only:
    -            rect = create_rect_patch(block['geometry'], 'block', page['dimensions'], (0, 1, 0), linewidth=1, **kwargs)
    +            rect = create_obj_patch(
    +                block["geometry"], page["dimensions"], label="block", color=(0, 1, 0), linewidth=1, **kwargs
    +            )
                 # add patch on figure
                 ax.add_patch(rect)
                 if interactive:
                     # add patch to cursor's artists
                     artists.append(rect)
     
    -        for line in block['lines']:
    +        for line in block["lines"]:
                 if not words_only:
    -                rect = create_rect_patch(line['geometry'], 'line', page['dimensions'], (1, 0, 0), linewidth=1, **kwargs)
    +                rect = create_obj_patch(
    +                    line["geometry"], page["dimensions"], label="line", color=(1, 0, 0), linewidth=1, **kwargs
    +                )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
     
    -            for word in line['words']:
    -                rect = create_rect_patch(word['geometry'], f"{word['value']} (confidence: {word['confidence']:.2%})",
    -                                         page['dimensions'], (0, 0, 1), **kwargs)
    +            for word in line["words"]:
    +                rect = create_obj_patch(
    +                    word["geometry"],
    +                    page["dimensions"],
    +                    label=f"{word['value']} (confidence: {word['confidence']:.2%})",
    +                    color=(0, 0, 1),
    +                    **kwargs,
    +                )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
                     elif add_labels:
    -                    if len(word['geometry']) == 5:
    +                    if len(word["geometry"]) == 5:
                             text_loc = (
    -                            int(page['dimensions'][1] * (word['geometry'][0] - word['geometry'][2] / 2)),
    -                            int(page['dimensions'][0] * (word['geometry'][1] - word['geometry'][3] / 2))
    +                            int(page["dimensions"][1] * (word["geometry"][0] - word["geometry"][2] / 2)),
    +                            int(page["dimensions"][0] * (word["geometry"][1] - word["geometry"][3] / 2)),
                             )
                         else:
                             text_loc = (
    -                            int(page['dimensions'][1] * word['geometry'][0][0]),
    -                            int(page['dimensions'][0] * word['geometry'][0][1])
    +                            int(page["dimensions"][1] * word["geometry"][0][0]),
    +                            int(page["dimensions"][0] * word["geometry"][0][1]),
    +                        )
    +
    +                    if len(word["geometry"]) == 2:
    +                        # We draw only if boxes are in straight format
    +                        ax.text(
    +                            *text_loc,
    +                            word["value"],
    +                            size=10,
    +                            alpha=0.5,
    +                            color=(0, 0, 1),
                             )
    -                    ax.text(
    -                        *text_loc,
    -                        word['value'],
    -                        size=10,
    -                        alpha=0.5,
    -                        color=(0, 0, 1),
    -                    )
     
             if display_artefacts:
    -            for artefact in block['artefacts']:
    -                rect = create_rect_patch(
    -                    artefact['geometry'],
    -                    'artefact',
    -                    page['dimensions'],
    -                    (0.5, 0.5, 0.5),  # type: ignore[arg-type]
    +            for artefact in block["artefacts"]:
    +                rect = create_obj_patch(
    +                    artefact["geometry"],
    +                    page["dimensions"],
    +                    label="artefact",
    +                    color=(0.5, 0.5, 0.5),
                         linewidth=1,
    -                    **kwargs
    +                    **kwargs,
                     )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
     
         if interactive:
    +        import mplcursors
    +
             # Create mlp Cursor to hover patches in artists
             mplcursors.Cursor(artists, hover=2).connect("add", lambda sel: sel.annotation.set_text(sel.artist.get_label()))
    -    fig.tight_layout(pad=0.)
    +    fig.tight_layout(pad=0.0)
     
         return fig
    -def synthetize_page( +def visualize_kie_page( page: Dict[str, Any], - draw_proba: bool = False, - font_size: int = 13, -) -> np.ndarray: - """Draw a the content of the element page (OCR response) on a blank page. + image: np.ndarray, + words_only: bool = False, + display_artefacts: bool = True, + scale: float = 10, + interactive: bool = True, + add_labels: bool = True, + **kwargs: Any, +) -> Figure: + """Visualize a full page with predicted blocks, lines and words + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from doctr.utils.visualization import visualize_page + >>> from doctr.models import ocr_db_crnn + >>> model = ocr_db_crnn(pretrained=True) + >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) + >>> out = model([[input_page]]) + >>> visualize_kie_page(out[0].pages[0].export(), input_page) + >>> plt.show() Args: - page: exported Page object to represent - draw_proba: if True, draw words in colors to represent confidence. Blue: p=1, red: p=0 - font_size: size of the font, default font = 13 + ---- + page: the exported Page of a Document + image: np array of the page, needs to have the same shape than page['dimensions'] + words_only: whether only words should be displayed + display_artefacts: whether artefacts should be displayed + scale: figsize of the largest windows side + interactive: whether the plot should be interactive + add_labels: for static plot, adds text labels on top of bounding box + **kwargs: keyword arguments for the polygon patch - Return: - A np array (drawn page) + Returns: + ------- + the matplotlib figure """ - # Draw template - h, w = page["dimensions"] - response = 255 * np.ones((h, w, 3), dtype=np.int32) + # Get proper scale and aspect ratio + h, w = image.shape[:2] + size = (scale * w / h, scale) if h > w else (scale, h / w * scale) + fig, ax = plt.subplots(figsize=size) + # Display the image + ax.imshow(image) + # hide both axis + ax.axis("off") - # Draw each word - for block in page["blocks"]: - for line in block["lines"]: - for word in line["words"]: - # Get aboslute word geometry - (xmin, ymin), (xmax, ymax) = word["geometry"] - xmin, xmax = int(w * xmin), int(w * xmax) - ymin, ymax = int(h * ymin), int(h * ymax) - - # White drawing context adapted to font size, 0.75 factor to convert pts --> pix - h_box, w_box = ymax - ymin, xmax - xmin - h_font, w_font = font_size, int(font_size * w_box / (h_box * 0.75)) - img = Image.new('RGB', (w_font, h_font), color=(255, 255, 255)) - d = ImageDraw.Draw(img) - - # Draw in black the value of the word - d.text((0, 0), word["value"], font=ImageFont.load_default(), fill=(0, 0, 0)) - - # Resize back to box size - img = img.resize((w_box, h_box), Image.NEAREST) - - # Colorize if draw_proba - if draw_proba: - p = int(255 * word["confidence"]) - mask = np.where(np.array(img) == 0, 1, 0) - proba = np.array([255 - p, 0, p]) - color = mask * proba[np.newaxis, np.newaxis, :] - white_mask = 255 * (1 - mask) - img = color + white_mask - - # Write to response page - response[ymin:ymax, xmin:xmax, :] = np.array(img) - - return response + if interactive: + artists: List[patches.Patch] = [] # instantiate an empty list of patches (to be drawn on the page) + + colors = {k: color for color, k in zip(get_colors(len(page["predictions"])), page["predictions"])} + for key, value in page["predictions"].items(): + for prediction in value: + if not words_only: + rect = create_obj_patch( + prediction["geometry"], + page["dimensions"], + label=f"{key} \n {prediction['value']} (confidence: {prediction['confidence']:.2%}", + color=colors[key], + linewidth=1, + **kwargs, + ) + # add patch on figure + ax.add_patch(rect) + if interactive: + # add patch to cursor's artists + artists.append(rect) + + if interactive: + import mplcursors + + # Create mlp Cursor to hover patches in artists + mplcursors.Cursor(artists, hover=2).connect("add", lambda sel: sel.annotation.set_text(sel.artist.get_label())) + fig.tight_layout(pad=0.0) + + return fig + + +def draw_boxes(boxes: np.ndarray, image: np.ndarray, color: Optional[Tuple[int, int, int]] = None, **kwargs) -> None: + """Draw an array of relative straight boxes on an image + + Args: + ---- + boxes: array of relative boxes, of shape (*, 4) + image: np array, float32 or uint8 + color: color to use for bounding box edges + **kwargs: keyword arguments from `matplotlib.pyplot.plot` + """ + h, w = image.shape[:2] + # Convert boxes to absolute coords + _boxes = deepcopy(boxes) + _boxes[:, [0, 2]] *= w + _boxes[:, [1, 3]] *= h + _boxes = _boxes.astype(np.int32) + for box in _boxes.tolist(): + xmin, ymin, xmax, ymax = box + image = cv2.rectangle( + image, (xmin, ymin), (xmax, ymax), color=color if isinstance(color, tuple) else (0, 0, 255), thickness=2 + ) + plt.imshow(image) + plt.plot(**kwargs)
    @@ -540,8 +719,8 @@

    Source code for doctr.utils.visualization

           
         
       
    - - + + diff --git a/v0.5.0/_modules/index.html b/v0.5.0/_modules/index.html index e86abcd4d4..5793c44f20 100644 --- a/v0.5.0/_modules/index.html +++ b/v0.5.0/_modules/index.html @@ -13,7 +13,7 @@ - + Overview: module code - docTR documentation @@ -225,20 +225,42 @@ - - + + diff --git a/v0.5.0/_sources/changelog.rst.txt b/v0.5.0/_sources/changelog.rst.txt index 430097d6c8..35befe7b96 100644 --- a/v0.5.0/_sources/changelog.rst.txt +++ b/v0.5.0/_sources/changelog.rst.txt @@ -1,6 +1,54 @@ Changelog ========= +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 `_ diff --git a/v0.5.0/_sources/datasets.rst.txt b/v0.5.0/_sources/datasets.rst.txt deleted file mode 100644 index 354122f1e5..0000000000 --- a/v0.5.0/_sources/datasets.rst.txt +++ /dev/null @@ -1,68 +0,0 @@ -doctr.datasets -============== - -.. currentmodule:: doctr.datasets - -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. - - -.. _datasets: - -Available Datasets ------------------- -The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL. - -.. autoclass:: doctr.datasets.datasets.VisionDataset - - -Here are all datasets that are available through DocTR: - -.. autoclass:: FUNSD -.. autoclass:: SROIE -.. autoclass:: CORD -.. autoclass:: OCRDataset - - -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. - -.. autoclass:: doctr.datasets.loader.DataLoader - - -.. _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 - * - digits - - 10 - - 0123456789 - * - ascii_letters - - 52 - - abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ - * - punctuation - - 32 - - !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ - * - currency - - 5 - - £€¥¢฿ - * - latin - - 96 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~° - * - french - - 154 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿ - -.. autofunction:: encode_sequences diff --git a/v0.5.0/_sources/documents.rst.txt b/v0.5.0/_sources/documents.rst.txt deleted file mode 100644 index 655730073e..0000000000 --- a/v0.5.0/_sources/documents.rst.txt +++ /dev/null @@ -1,87 +0,0 @@ -doctr.documents -=============== - - -.. currentmodule:: doctr.documents - -The documents module enables users to easily access content from documents and export analysis -results to structured formats. - - -Document structure ------------------- - -Structural organization of the documents. - -Word -^^^^ -A Word is an uninterrupted sequence of characters. - -.. autoclass:: Word - -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 - -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 - - -Document -^^^^^^^^ - -A Document is a collection of Pages. - -.. autoclass:: Document - - .. automethod:: show - - -File reading ------------- - -High-performance file reading and conversion to processable structured data. - -.. autofunction:: read_pdf - -.. autofunction:: read_img - -.. autofunction:: read_html - - -.. autoclass:: DocumentFile - - .. automethod:: from_pdf - - .. automethod:: from_url - - .. automethod:: from_images - -.. autoclass:: PDF - - .. automethod:: as_images - - .. automethod:: get_words - - .. automethod:: get_artefacts diff --git a/v0.5.0/_sources/getting_started/installing.rst.txt b/v0.5.0/_sources/getting_started/installing.rst.txt index e764e734a7..39e79aa3dd 100644 --- a/v0.5.0/_sources/getting_started/installing.rst.txt +++ b/v0.5.0/_sources/getting_started/installing.rst.txt @@ -3,7 +3,7 @@ Installation ************ -This library requires `Python `_ 3.9 or higher. +This library requires `Python `_ 3.10 or higher. Prerequisites diff --git a/v0.5.0/_sources/index.rst.txt b/v0.5.0/_sources/index.rst.txt index fc3ff89fdf..53251db142 100644 --- a/v0.5.0/_sources/index.rst.txt +++ b/v0.5.0/_sources/index.rst.txt @@ -1,7 +1,8 @@ -DocTR: Document Text Recognition -================================ +******************************** +docTR: Document Text Recognition +******************************** -State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 (PyTorch now in beta) +State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 & PyTorch .. image:: https://github.com/mindee/doctr/releases/download/v0.2.0/ocr.png :align: center @@ -9,38 +10,29 @@ State-of-the-art Optical Character Recognition made seamless & accessible to any DocTR provides an easy and powerful way to extract valuable information from your documents: -* |:receipt:| **for automation**: seemlessly process documents for Natural Language Understanding tasks: we provide OCR predictors to parse textual information (localize and identify each word) 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. -Welcome to the documentation of `DocTR `_! - - Main Features ------------- * |:robot:| Robust 2-stage (detection + recognition) OCR predictors with pretrained parameters * |:zap:| User-friendly, 3 lines of code to load a document and extract text with a predictor -* |:rocket:| State-of-the-art performances on public document datasets, comparable with GoogleVision/AWS Textract +* |: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, small dependencies -* |:tools:| Daily maintained -* |:factory:| Easy integration - +* |:bird:| Light package, minimal dependencies +* |:tools:| Actively maintained by Mindee +* |:factory:| Easy integration (available templates for browser demo & API deployment) -Getting Started ---------------- .. toctree:: :maxdepth: 2 + :caption: Getting started + :hidden: - installing - - -Build & train your predictor -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* Compose your own end-to-end OCR predictor: mix and match detection & recognition predictors (all-pretrained) -* Fine-tune or train from scratch any detection or recognition model to specialize on your data + getting_started/installing + notebooks Model zoo @@ -48,36 +40,83 @@ Model zoo Text detection models """"""""""""""""""""" - * `DBNet `_ (Differentiable Binarization) - * `LinkNet `_ +* 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 `_ (Show, Attend and Read) - * `CRNN `_ (Convolutional Recurrent Neural Network) - * `MASTER `_ (Multi-Aspect Non-local Network for Scene Text Recognition) +* 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" `_ 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 `_. +* 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" `_. .. toctree:: :maxdepth: 2 - :caption: Notes + :caption: Using docTR + :hidden: - changelog + 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 + + +.. toctree:: + :maxdepth: 2 + :caption: Community + :hidden: + + community/resources .. toctree:: :maxdepth: 2 :caption: Package Reference + :hidden: - datasets - documents - models - transforms - utils + 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/v0.5.0/_sources/installing.rst.txt b/v0.5.0/_sources/installing.rst.txt deleted file mode 100644 index 5c8779dc1c..0000000000 --- a/v0.5.0/_sources/installing.rst.txt +++ /dev/null @@ -1,46 +0,0 @@ - -************ -Installation -************ - -This library requires Python 3.6 or higher. - - -Prerequisites -============= - -Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so: - -* TensorFlow: `installation page `_. -* PyTorch: `installation page `_. - -If you are running another OS than Linux, you will need a few extra dependencies. - -For MacOS users, you can install them as follows: - -.. code:: shell - - brew install cairo pango gdk-pixbuf libffi - -For Windows users, those dependencies are included in GTK. You can find the latest installer over `here `_. - - -Via Python Package -================== - -Install the last stable release of the package using pip: - -.. code:: bash - - pip install python-doctr - - -Via Git -======= - -Install the library in developper mode: - -.. code:: bash - - git clone https://github.com/mindee/doctr.git - pip install -e doctr/. diff --git a/v0.5.0/_sources/models.rst.txt b/v0.5.0/_sources/models.rst.txt deleted file mode 100644 index 9830c6c153..0000000000 --- a/v0.5.0/_sources/models.rst.txt +++ /dev/null @@ -1,215 +0,0 @@ -doctr.models -============ - -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. - -.. currentmodule:: doctr.models - -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 TensorFlow model. -* Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable. - - -Text Detection --------------- -Localizing text elements in images - -+---------------------------------------------------+----------------------------+----------------------------+---------+ -| | FUNSD | CORD | | -+==================+=================+==============+============+===============+============+===============+=========+ -| **Architecture** | **Input shape** | **# params** | **Recall** | **Precision** | **Recall** | **Precision** | **FPS** | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ -| db_resnet50 | (1024, 1024, 3) | 25.2 M | 82.14 | 87.64 | 92.49 | 89.66 | 2.1 | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ - -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 combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for detection -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for detection is the following: - -1. resize each input image to the target size (bilinear interpolation by default) with potential deformation. -2. batch images together -3. normalize the batch using the training data statistics - - -Detection models -^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - -.. autofunction:: doctr.models.detection.db_resnet50 -.. autofunction:: doctr.models.detection.linknet16 - -Detection predictors -^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information. - -.. autofunction:: doctr.models.detection.detection_predictor - - -Text Recognition ----------------- -Identifying strings in images - -.. list-table:: Text recognition model zoo - :widths: 20 20 15 10 10 10 - :header-rows: 1 - - * - Architecture - - Input shape - - # params - - FUNSD - - CORD - - FPS - * - crnn_vgg16_bn - - (32, 128, 3) - - 15.8M - - 86.02 - - 91.3 - - 12.8 - * - sar_vgg16_bn - - (32, 128, 3) - - 21.5M - - 86.2 - - 91.7 - - 3.3 - * - sar_resnet31 - - (32, 128, 3) - - 53.1M - - **86.3** - - **92.1** - - 2.7 - -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 metrics being used are available in :ref:`metrics`. - -All these recognition models are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for recognition -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for recognition is the following: - -1. resize each input image to the target size (bilinear interpolation by default) without deformation. -2. pad the image to the target size (with zeros by default) -3. batch images together -4. normalize the batch using the training data statistics - -Recognition models -^^^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - - -.. autofunction:: doctr.models.recognition.crnn_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_resnet31 -.. autofunction:: doctr.models.recognition.master - - -Recognition predictors -^^^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage. - -.. autofunction:: doctr.models.recognition.recognition_predictor - - -End-to-End OCR --------------- -Predictors that localize and identify text elements in images - -+-----------------------------+--------------------------------------+--------------------------------------+ -| | FUNSD | CORD | -+=============================+============+===============+=========+============+===============+=========+ -| **Architecture** | **Recall** | **Precision** | **FPS** | **Recall** | **Precision** | **FPS** | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + crnn_vgg16_bn | 70.08 | 74.77 | 0.85 | 82.19 | **79.67** | 1.6 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_vgg16_bn | N/A | N/A | 0.49 | N/A | N/A | 1.0 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_resnet31 | N/A | N/A | 0.27 | N/A | N/A | 0.83 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision text detection | 59.50 | 62.50 | | 75.30 | 70.00 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision doc. text detection | 64.00 | 53.30 | | 68.90 | 61.10 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| AWS textract | **78.10** | **83.00** | | **87.50** | 66.00 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ - -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`. - -All recognition models of predictors are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Results on private ocr datasets - -+------------------------------------+----------------------------+----------------------------+----------------------------+ -| | Receipts | Invoices | IDs | -+====================================+============+===============+============+===============+============+===============+ -| **Architecture** | **Recall** | **Precision** | **Recall** | **Precision** | **Recall** | **Precision** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| db_resnet50 + crnn_vgg16_bn (ours) | **78.90** | **81.01** | 65.68 | **69.86** | **49.48** | **50.46** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| Gvision doc. text detection | 68.91 | 59.89 | 63.20 | 52.85 | 43.70 | 29.21 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| AWS textract | 75.77 | 77.70 | **70.47** | 69.13 | 46.39 | 43.32 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ - - -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. - -.. autofunction:: doctr.models.zoo.ocr_predictor - - -Model export ------------- -Utility functions to make the most of document analysis models. - -.. currentmodule:: doctr.models.export - -Model compression -^^^^^^^^^^^^^^^^^ - -.. autofunction:: convert_to_tflite - -.. autofunction:: convert_to_fp16 - -.. autofunction:: quantize_model - -Using SavedModel -^^^^^^^^^^^^^^^^ - -Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -`SavedModel `_ format as follows: - - - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> _ = model(input_t, training=False) - >>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/') - -And loaded just as easily: - - - >>> import tensorflow as tf - >>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/') diff --git a/v0.5.0/_sources/transforms.rst.txt b/v0.5.0/_sources/transforms.rst.txt deleted file mode 100644 index 0230fe75f5..0000000000 --- a/v0.5.0/_sources/transforms.rst.txt +++ /dev/null @@ -1,32 +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: - -.. autoclass:: Resize -.. autoclass:: Normalize -.. autoclass:: LambdaTransformation -.. autoclass:: ToGray -.. autoclass:: ColorInversion -.. autoclass:: RandomBrightness -.. autoclass:: RandomContrast -.. autoclass:: RandomSaturation -.. autoclass:: RandomHue -.. autoclass:: RandomGamma -.. autoclass:: RandomJpegQuality - - -Composing transformations ---------------------------------------------- -It is common to require several transformations to be performed consecutively. - -.. autoclass:: Compose -.. autoclass:: OneOf -.. autoclass:: RandomApply diff --git a/v0.5.0/_sources/utils.rst.txt b/v0.5.0/_sources/utils.rst.txt deleted file mode 100644 index 69c1abe0eb..0000000000 --- a/v0.5.0/_sources/utils.rst.txt +++ /dev/null @@ -1,36 +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 - - -.. _metrics: - -Task evaluation ---------------- -Implementations of task-specific metrics to easily assess your model performances. - -.. currentmodule:: doctr.utils.metrics - -.. autoclass:: TextMatch - - .. automethod:: summary - -.. autoclass:: LocalizationConfusion - - .. automethod:: summary - -.. autoclass:: OCRMetric - - .. automethod:: summary diff --git a/v0.5.0/_static/basic.css b/v0.5.0/_static/basic.css index f316efcb47..7ebbd6d07b 100644 --- a/v0.5.0/_static/basic.css +++ b/v0.5.0/_static/basic.css @@ -1,12 +1,5 @@ /* - * basic.css - * ~~~~~~~~~ - * * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ /* -- main layout ----------------------------------------------------------- */ @@ -115,15 +108,11 @@ img { /* -- search page ----------------------------------------------------------- */ ul.search { - margin: 10px 0 0 20px; - padding: 0; + margin-top: 10px; } ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; + padding: 5px 0; } ul.search li a { diff --git a/v0.5.0/_static/doctools.js b/v0.5.0/_static/doctools.js index 4d67807d17..0398ebb9f0 100644 --- a/v0.5.0/_static/doctools.js +++ b/v0.5.0/_static/doctools.js @@ -1,12 +1,5 @@ /* - * doctools.js - * ~~~~~~~~~~~ - * * Base JavaScript utilities for all Sphinx HTML documentation. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; diff --git a/v0.5.0/_static/documentation_options.js b/v0.5.0/_static/documentation_options.js index a7b5cbe04a..4f656fdbea 100644 --- a/v0.5.0/_static/documentation_options.js +++ b/v0.5.0/_static/documentation_options.js @@ -1,5 +1,5 @@ const DOCUMENTATION_OPTIONS = { - VERSION: '0.3.0a0-git', + VERSION: '0.10.1a0-git', LANGUAGE: 'en', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/v0.5.0/_static/language_data.js b/v0.5.0/_static/language_data.js index 367b8ed81b..c7fe6c6faf 100644 --- a/v0.5.0/_static/language_data.js +++ b/v0.5.0/_static/language_data.js @@ -1,13 +1,6 @@ /* - * language_data.js - * ~~~~~~~~~~~~~~~~ - * * This script contains the language-specific data used by searchtools.js, * namely the list of stopwords, stemmer, scorer and splitter. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; diff --git a/v0.5.0/_static/searchtools.js b/v0.5.0/_static/searchtools.js index b08d58c9b9..2c774d17af 100644 --- a/v0.5.0/_static/searchtools.js +++ b/v0.5.0/_static/searchtools.js @@ -1,12 +1,5 @@ /* - * searchtools.js - * ~~~~~~~~~~~~~~~~ - * * Sphinx JavaScript utilities for the full-text search. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; @@ -20,7 +13,7 @@ if (typeof Scorer === "undefined") { // and returns the new score. /* score: result => { - const [docname, title, anchor, descr, score, filename] = result + const [docname, title, anchor, descr, score, filename, kind] = result return score }, */ @@ -47,6 +40,14 @@ if (typeof Scorer === "undefined") { }; } +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + const _removeChildren = (element) => { while (element && element.lastChild) element.removeChild(element.lastChild); }; @@ -64,9 +65,13 @@ const _displayItem = (item, searchTerms, highlightTerms) => { const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; const contentRoot = document.documentElement.dataset.content_root; - const [docName, title, anchor, descr, score, _filename] = item; + const [docName, title, anchor, descr, score, _filename, kind] = item; let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); let requestUrl; let linkUrl; if (docBuilder === "dirhtml") { @@ -115,8 +120,10 @@ const _finishSearch = (resultCount) => { "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." ); else - Search.status.innerText = _( - "Search finished, found ${resultCount} page(s) matching the search query." + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, ).replace('${resultCount}', resultCount); }; const _displayNextItem = ( @@ -138,7 +145,7 @@ const _displayNextItem = ( else _finishSearch(resultCount); }; // Helper function used by query() to order search results. -// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. // Order the results by score (in opposite order of appearance, since the // `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. const _orderResultsByScoreThenName = (a, b) => { @@ -248,6 +255,7 @@ const Search = { searchSummary.classList.add("search-summary"); searchSummary.innerText = ""; const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); searchList.classList.add("search"); const out = document.getElementById("search-results"); @@ -318,7 +326,7 @@ const Search = { const indexEntries = Search._index.indexentries; // Collect multiple result groups to be sorted separately and then ordered. - // Each is an array of [docname, title, anchor, descr, score, filename]. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. const normalResults = []; const nonMainIndexResults = []; @@ -337,6 +345,7 @@ const Search = { null, score + boost, filenames[file], + SearchResultKind.title, ]); } } @@ -354,6 +363,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.index, ]; if (isMain) { normalResults.push(result); @@ -475,6 +485,7 @@ const Search = { descr, score, filenames[match[0]], + SearchResultKind.object, ]); }; Object.keys(objects).forEach((prefix) => @@ -585,6 +596,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.text, ]); } return results; diff --git a/v0.5.0/changelog.html b/v0.5.0/changelog.html index eafac3a877..fc45a50384 100644 --- a/v0.5.0/changelog.html +++ b/v0.5.0/changelog.html @@ -12,9 +12,9 @@ gtag('js', new Date()); gtag('config', 'G-40DVRMX8T4'); - + - + Changelog - docTR documentation @@ -226,20 +226,42 @@ + diff --git a/v0.5.0/community/resources.html b/v0.5.0/community/resources.html index 2564037893..9a1988258c 100644 --- a/v0.5.0/community/resources.html +++ b/v0.5.0/community/resources.html @@ -14,7 +14,7 @@ - + Community resources - docTR documentation @@ -389,7 +389,7 @@

    Community resources - + diff --git a/v0.5.0/contributing/code_of_conduct.html b/v0.5.0/contributing/code_of_conduct.html index 5ea4a1f99d..03422dbb4d 100644 --- a/v0.5.0/contributing/code_of_conduct.html +++ b/v0.5.0/contributing/code_of_conduct.html @@ -14,7 +14,7 @@ - + Contributor Covenant Code of Conduct - docTR documentation @@ -504,7 +504,7 @@

    Attribution - + diff --git a/v0.5.0/contributing/contributing.html b/v0.5.0/contributing/contributing.html index e5a85682c6..05e2b3641b 100644 --- a/v0.5.0/contributing/contributing.html +++ b/v0.5.0/contributing/contributing.html @@ -14,7 +14,7 @@ - + Contributing to docTR - docTR documentation @@ -481,7 +481,7 @@

    Let’s connect - + diff --git a/v0.5.0/datasets.html b/v0.5.0/datasets.html deleted file mode 100644 index 193e576c57..0000000000 --- a/v0.5.0/datasets.html +++ /dev/null @@ -1,578 +0,0 @@ - - - - - - - - - - - - - doctr.datasets - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.datasets

    -

    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.

    -
    -

    Available Datasets

    -

    The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL.

    -
    -
    -class doctr.datasets.datasets.VisionDataset(url: str, file_name: str | None = None, file_hash: str | None = None, extract_archive: bool = False, download: bool = False, overwrite: bool = False)[source]
    -
    - -

    Here are all datasets that are available through DocTR:

    -
    -
    -class doctr.datasets.FUNSD(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    FUNSD dataset from “FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents”.

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD
    ->>> train_set = FUNSD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.SROIE(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    SROIE dataset from “ICDAR2019 Competition on Scanned Receipt OCR and Information Extraction”.

    -
    -
    Example::
    >>> from doctr.datasets import SROIE
    ->>> train_set = SROIE(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.CORD(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    CORD dataset from “CORD: A Consolidated Receipt Dataset forPost-OCR Parsing”.

    -
    -
    Example::
    >>> from doctr.datasets import CORD
    ->>> train_set = CORD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.OCRDataset(img_folder: str, label_file: str, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    Implements an OCR dataset

    -
    -
    Parameters:
    -
      -
    • img_folder – local path to image folder (all jpg at the root)

    • -
    • label_file – local path to the label file

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -

    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.

    -
    -
    -class doctr.datasets.loader.DataLoader(dataset, shuffle: bool = True, batch_size: int = 1, drop_last: bool = False, workers: int | None = None)[source]
    -

    Implements a dataset wrapper for fast data loading

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD, 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)
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • dataset – the dataset

    • -
    • shuffle – whether the samples should be shuffled before passing it to the iterator

    • -
    • batch_size – number of elements in each batch

    • -
    • drop_last – if True, drops the last batch if it isn’t full

    • -
    • workers – number of workers to use for data loading

    • -
    -
    -
    -
    - -
    -
    -

    Supported Vocabs

    -

    Since textual content has to be encoded properly for models to interpret them efficiently, DocTR supports multiple sets -of vocabs.

    -
    - - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    DocTR Vocabs

    Name

    size

    characters

    digits

    10

    0123456789

    ascii_letters

    52

    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

    punctuation

    32

    !”#$%&'()*+,-./:;<=>?@[\]^_`{|}~

    currency

    5

    £€¥¢฿

    latin

    96

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°

    french

    154

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿

    -
    -
    -
    -doctr.datasets.encode_sequences(sequences: List[str], vocab: str, target_size: int | None = None, eos: int = -1, sos: int | None = None, pad: int | None = None, **kwargs: Any) ndarray[source]
    -

    Encode character sequences using a given vocab as mapping

    -
    -
    Parameters:
    -
      -
    • sequences – the list of character sequences of size N

    • -
    • vocab – the ordered vocab to use for encoding

    • -
    • target_size – maximum length of the encoded data

    • -
    • eos – encoding of End Of String

    • -
    • sos – optional encoding of Start Of String

    • -
    • pad – optional encoding for padding. In case of padding, all sequences are followed by 1 EOS then PAD

    • -
    -
    -
    Returns:
    -

    the padded encoded data as a tensor

    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.0/documents.html b/v0.5.0/documents.html deleted file mode 100644 index 98cbb2c5ef..0000000000 --- a/v0.5.0/documents.html +++ /dev/null @@ -1,772 +0,0 @@ - - - - - - - - - - - - - doctr.documents - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.documents

    -

    The documents module enables users to easily access content from documents and export analysis -results to structured formats.

    -
    -

    Document structure

    -

    Structural organization of the documents.

    -
    -

    Word

    -

    A Word is an uninterrupted sequence of characters.

    -
    -
    -class doctr.documents.Word(value: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float])[source]
    -

    Implements a word element

    -
    -
    Parameters:
    -
      -
    • value – the text string of the word

    • -
    • confidence – the confidence associated with the text prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to

    • -
    • size (the page's)

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Line(words: List[Word], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float] | None = None)[source]
    -

    Implements a line element as a collection of words

    -
    -
    Parameters:
    -
      -
    • words – list of word elements

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all words in it.

    • -
    -
    -
    -
    - -
    -
    -

    Artefact

    -

    An Artefact is a non-textual element (e.g. QR code, picture, chart, signature, logo, etc.).

    -
    -
    -class doctr.documents.Artefact(artefact_type: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]])[source]
    -

    Implements a non-textual element

    -
    -
    Parameters:
    -
      -
    • artefact_type – the type of artefact

    • -
    • confidence – the confidence of the type prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size.

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Block(lines: List[Line] = [], artefacts: List[Artefact] = [], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float] | None = None)[source]
    -

    Implements a block element as a collection of lines and artefacts

    -
    -
    Parameters:
    -
      -
    • lines – list of line elements

    • -
    • artefacts – list of artefacts

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all lines and artefacts in it.

    • -
    -
    -
    -
    - -
    -
    -

    Page

    -

    A Page is a collection of Blocks that were on the same physical page.

    -
    -
    -class doctr.documents.Page(blocks: List[Block], page_idx: int, dimensions: Tuple[int, int], orientation: Dict[str, Any] | None = None, language: Dict[str, Any] | None = None)[source]
    -

    Implements a page element as a collection of blocks

    -
    -
    Parameters:
    -
      -
    • blocks – list of block elements

    • -
    • page_idx – the index of the page in the input raw document

    • -
    • dimensions – the page size in pixels in format (width, height)

    • -
    • orientation – a dictionary with the value of the rotation angle in degress and confidence of the prediction

    • -
    • language – a dictionary with the language value and confidence of the prediction

    • -
    -
    -
    -
    -
    -show(page: ndarray, interactive: bool = True, **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -
      -
    • page – image encoded as a numpy array in uint8

    • -
    • interactive – whether the display should be interactive

    • -
    -
    -
    -
    - -
    - -
    -
    -

    Document

    -

    A Document is a collection of Pages.

    -
    -
    -class doctr.documents.Document(pages: List[Page])[source]
    -

    Implements a document element as a collection of pages

    -
    -
    Parameters:
    -

    pages – list of page elements

    -
    -
    -
    -
    -show(pages: List[ndarray], **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -

    pages – list of images encoded as numpy arrays in uint8

    -
    -
    -
    - -
    - -
    -
    -
    -

    File reading

    -

    High-performance file reading and conversion to processable structured data.

    -
    -
    -doctr.documents.read_pdf(file: str | Path | bytes, **kwargs: Any) Document[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_pdf
    ->>> doc = read_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_img(file: str | Path | bytes, output_size: Tuple[int, int] | None = None, rgb_output: bool = True) ndarray[source]
    -

    Read an image file into numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_img
    ->>> page = read_img("path/to/your/doc.jpg")
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • file – the path to the image file

    • -
    • output_size – the expected output size of each page in format H x W

    • -
    • rgb_output – whether the output ndarray channel order should be RGB instead of BGR.

    • -
    -
    -
    Returns:
    -

    the page decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_html(url: str, **kwargs: Any) bytes[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_html
    ->>> doc = read_html("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – URL of the target web page

    -
    -
    Returns:
    -

    decoded PDF file as a bytes stream

    -
    -
    -
    - -
    -
    -class doctr.documents.DocumentFile[source]
    -

    Read a document from multiple extensions

    -
    -
    -classmethod from_pdf(file: str | Path | bytes, **kwargs) PDF[source]
    -

    Read a PDF file

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file or a binary stream

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_url(url: str, **kwargs) PDF[source]
    -

    Interpret a web page as a PDF document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_url("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – the URL of the target web page

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_images(files: Sequence[str | Path | bytes] | str | Path | bytes, **kwargs) List[ndarray][source]
    -

    Read an image file (or a collection of image files) and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"])
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    files – the path to the image file or a binary stream, or a collection of those

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    - -
    -
    -class doctr.documents.PDF(doc: Document)[source]
    -

    PDF document template

    -
    -
    Parameters:
    -

    doc – input PDF document

    -
    -
    -
    -
    -as_images(**kwargs) List[ndarray][source]
    -

    Convert all document pages to images

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of convert_page_to_numpy

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -get_words(**kwargs) List[List[Tuple[Tuple[float, float, float, float], str]]][source]
    -

    Get the annotations for all words in the document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of fitz.Page.getTextWords

    -
    -
    Returns:
    -

    the list of pages annotations, represented as a list of tuple (bounding box, value)

    -
    -
    -
    - -
    -
    -get_artefacts() List[List[Tuple[float, float, float, float]]][source]
    -

    Get the artefacts for the entire document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts()
    -
    -
    -
    -
    -
    -
    Returns:
    -

    the list of pages artefacts, represented as a list of bounding boxes

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.0/genindex.html b/v0.5.0/genindex.html index a19b433943..21520455b4 100644 --- a/v0.5.0/genindex.html +++ b/v0.5.0/genindex.html @@ -13,7 +13,7 @@ - Index - docTR documentation + Index - docTR documentation @@ -224,20 +224,42 @@

    +
    +

    U

    + + +
    +
    +

    V

    @@ -561,7 +711,13 @@

    V

    W

    +
    @@ -599,8 +755,8 @@

    W

    - - + + diff --git a/v0.5.0/getting_started/installing.html b/v0.5.0/getting_started/installing.html index a488e9a030..af3b58193e 100644 --- a/v0.5.0/getting_started/installing.html +++ b/v0.5.0/getting_started/installing.html @@ -14,7 +14,7 @@ - + Installation - docTR documentation @@ -305,7 +305,7 @@

    Installation

    -

    This library requires Python 3.9 or higher.

    +

    This library requires Python 3.10 or higher.

    Prerequisites

    Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so:

    @@ -435,7 +435,7 @@

    Via Git - + diff --git a/v0.5.0/index.html b/v0.5.0/index.html index 4c6a28c66a..3a06afc6d9 100644 --- a/v0.5.0/index.html +++ b/v0.5.0/index.html @@ -12,9 +12,9 @@ gtag('js', new Date()); gtag('config', 'G-40DVRMX8T4'); - + - + docTR documentation @@ -226,20 +226,42 @@
    -

    DocTR: Document Text Recognition

    -

    State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 (PyTorch now in beta)

    +

    docTR: Document Text Recognition

    +

    State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 & PyTorch

    https://github.com/mindee/doctr/releases/download/v0.2.0/ocr.png

    DocTR provides an easy and powerful way to extract valuable information from your documents:

      -
    • 🧾 for automation: seemlessly process documents for Natural Language Understanding tasks: we provide OCR predictors to parse textual information (localize and identify each word) from your documents.

    • +
    • 🧾 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.

    • 👩‍🔬 for research: quickly compare your own architectures speed & performances with state-of-art models on public datasets.

    -

    Welcome to the documentation of DocTR!

    Main Features

    • 🤖 Robust 2-stage (detection + recognition) OCR predictors with pretrained parameters

    • ⚡ User-friendly, 3 lines of code to load a document and extract text with a predictor

    • -
    • 🚀 State-of-the-art performances on public document datasets, comparable with GoogleVision/AWS Textract

    • +
    • 🚀 State-of-the-art performance on public document datasets, comparable with GoogleVision/AWS Textract

    • ⚡ Optimized for inference speed on both CPU & GPU

    • -
    • 🐦 Light package, small dependencies

    • -
    • 🛠️ Daily maintained

    • -
    • 🏭 Easy integration

    • +
    • 🐦 Light package, minimal dependencies

    • +
    • 🛠️ Actively maintained by Mindee

    • +
    • 🏭 Easy integration (available templates for browser demo & API deployment)

    -
    -
    -

    Getting Started

    -
    -

    Build & train your predictor

    -
      -
    • Compose your own end-to-end OCR predictor: mix and match detection & recognition predictors (all-pretrained)

    • -
    • Fine-tune or train from scratch any detection or recognition model to specialize on your data

    • -
    -

    Model zoo

    Text detection models

    -
    -

    Text recognition models

    -
    -

    Supported datasets

    -
    -
    +
    +
    +
    +
    +
    @@ -406,7 +381,7 @@

    Supported datasets - +
    Next @@ -446,10 +421,8 @@

    Supported datasets + diff --git a/v0.5.0/installing.html b/v0.5.0/installing.html deleted file mode 100644 index b61c60134b..0000000000 --- a/v0.5.0/installing.html +++ /dev/null @@ -1,395 +0,0 @@ - - - - - - - - - - - - - Installation - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    Installation

    -

    This library requires Python 3.6 or higher.

    -
    -

    Prerequisites

    -

    Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so:

    - -

    If you are running another OS than Linux, you will need a few extra dependencies.

    -

    For MacOS users, you can install them as follows:

    -
    brew install cairo pango gdk-pixbuf libffi
    -
    -
    -

    For Windows users, those dependencies are included in GTK. You can find the latest installer over here.

    -
    -
    -

    Via Python Package

    -

    Install the last stable release of the package using pip:

    -
    pip install python-doctr
    -
    -
    -
    -
    -

    Via Git

    -

    Install the library in developper mode:

    -
    git clone https://github.com/mindee/doctr.git
    -pip install -e doctr/.
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.0/models.html b/v0.5.0/models.html deleted file mode 100644 index b5cd44c9fa..0000000000 --- a/v0.5.0/models.html +++ /dev/null @@ -1,1002 +0,0 @@ - - - - - - - - - - - - - doctr.models - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.models

    -

    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 TensorFlow model.

    • -
    • Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable.

    • -
    -
    -

    Text Detection

    -

    Localizing text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Input shape

    # params

    Recall

    Precision

    Recall

    Precision

    FPS

    db_resnet50

    (1024, 1024, 3)

    25.2 M

    82.14

    87.64

    92.49

    89.66

    2.1

    -
    -

    All text detection models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for detection

    -

    In DocTR, the pre-processing scheme for detection is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) with potential deformation.

    2. -
    3. batch images together

    4. -
    5. normalize the batch using the training data statistics

    6. -
    -
    -
    -

    Detection models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.detection.db_resnet50(pretrained: bool = False, **kwargs: Any) DBNet[source]
    -

    DBNet as described in “Real-time Scene Text Detection with Differentiable Binarization”, using a ResNet-50 backbone.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -doctr.models.detection.linknet16(pretrained: bool = False, **kwargs: Any) LinkNet[source]
    -

    LinkNet as described in “LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import linknet16
    ->>> model = linknet16(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -

    Detection predictors

    -

    Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information.

    -
    -
    -doctr.models.detection.detection_predictor(arch: str = 'db_resnet50', pretrained: bool = False, **kwargs: Any) DetectionPredictor[source]
    -

    Text detection architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import detection_predictor
    ->>> model = detection_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_resnet50’)

    • -
    • pretrained – If True, returns a model pre-trained on our text detection dataset

    • -
    -
    -
    Returns:
    -

    Detection predictor

    -
    -
    -
    - -
    -
    -
    -

    Text Recognition

    -

    Identifying strings in images

    -
    - - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Text recognition model zoo

    Architecture

    Input shape

    # params

    FUNSD

    CORD

    FPS

    crnn_vgg16_bn

    (32, 128, 3)

    15.8M

    86.02

    91.3

    12.8

    sar_vgg16_bn

    (32, 128, 3)

    21.5M

    86.2

    91.7

    3.3

    sar_resnet31

    (32, 128, 3)

    53.1M

    86.3

    92.1

    2.7

    -
    -

    All text recognition models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All these recognition models are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for recognition

    -

    In DocTR, the pre-processing scheme for recognition is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) without deformation.

    2. -
    3. pad the image to the target size (with zeros by default)

    4. -
    5. batch images together

    6. -
    7. normalize the batch using the training data statistics

    8. -
    -
    -
    -

    Recognition models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.recognition.crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) CRNN[source]
    -

    CRNN with a VGG-16 backbone as described in “An End-to-End Trainable Neural Network for Image-based -Sequence Recognition and Its Application to Scene Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import crnn_vgg16_bn
    ->>> model = crnn_vgg16_bn(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a VGG16 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_vgg16_bn
    ->>> model = sar_vgg16_bn(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_resnet31(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a resnet-31 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -

    Example

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_resnet31
    ->>> model = sar_resnet31(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.master(pretrained: bool = False, **kwargs: Any) MASTER[source]
    -

    MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_. -Example:

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import master
    ->>> model = master(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 48, 160, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -

    Recognition predictors

    -

    Combining the right components around a given architecture for easier usage.

    -
    -
    -doctr.models.recognition.recognition_predictor(arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) RecognitionPredictor[source]
    -

    Text recognition architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import recognition_predictor
    ->>> model = recognition_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(32, 128, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘crnn_vgg16_bn’, ‘crnn_resnet31’, ‘sar_vgg16_bn’, ‘sar_resnet31’)

    • -
    • pretrained – If True, returns a model pre-trained on our text recognition dataset

    • -
    -
    -
    Returns:
    -

    Recognition predictor

    -
    -
    -
    - -
    -
    -
    -

    End-to-End OCR

    -

    Predictors that localize and identify text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Recall

    Precision

    FPS

    Recall

    Precision

    FPS

    db_resnet50 + crnn_vgg16_bn

    70.08

    74.77

    0.85

    82.19

    79.67

    1.6

    db_resnet50 + sar_vgg16_bn

    N/A

    N/A

    0.49

    N/A

    N/A

    1.0

    db_resnet50 + sar_resnet31

    N/A

    N/A

    0.27

    N/A

    N/A

    0.83

    Gvision text detection

    59.50

    62.50

    75.30

    70.00

    Gvision doc. text detection

    64.00

    53.30

    68.90

    61.10

    AWS textract

    78.10

    83.00

    87.50

    66.00

    -
    -

    All OCR models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All recognition models of predictors are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -

    Results on private ocr datasets

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Receipts

    Invoices

    IDs

    Architecture

    Recall

    Precision

    Recall

    Precision

    Recall

    Precision

    db_resnet50 + crnn_vgg16_bn (ours)

    78.90

    81.01

    65.68

    69.86

    49.48

    50.46

    Gvision doc. text detection

    68.91

    59.89

    63.20

    52.85

    43.70

    29.21

    AWS textract

    75.77

    77.70

    70.47

    69.13

    46.39

    43.32

    -
    -
    -

    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.

    -
    -
    -doctr.models.zoo.ocr_predictor(det_arch: str = 'db_resnet50', reco_arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) OCRPredictor[source]
    -

    End-to-end OCR architecture using one model for localization, and another for text recognition.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import ocr_predictor
    ->>> model = ocr_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_sar_vgg’, ‘db_sar_resnet’, ‘db_crnn_vgg’, ‘db_crnn_resnet’)

    • -
    • pretrained – If True, returns a model pre-trained on our OCR dataset

    • -
    -
    -
    Returns:
    -

    OCR predictor

    -
    -
    -
    - -
    -
    -
    -

    Model export

    -

    Utility functions to make the most of document analysis models.

    -
    -

    Model compression

    -
    -
    -doctr.models.export.convert_to_tflite(tf_model: Model) bytes[source]
    -

    Converts a model to TFLite format

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_tflite, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_tflite(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.convert_to_fp16(tf_model: Model) bytes[source]
    -

    Converts a model to half precision

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_fp16, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_fp16(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the serialized FP16 model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) bytes[source]
    -

    Quantize a Tensorflow model

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import quantize_model, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = quantize_model(model, (224, 224, 3))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • tf_model – a keras model

    • -
    • input_shape – shape of the expected input tensor (excluding batch dimension) with channel last order

    • -
    -
    -
    Returns:
    -

    the serialized quantized model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -

    Using SavedModel

    -

    Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -SavedModel format as follows:

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> _ = model(input_t, training=False)
    ->>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/')
    -
    -
    -

    And loaded just as easily:

    -
    >>> import tensorflow as tf
    ->>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/')
    -
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.0/modules/contrib.html b/v0.5.0/modules/contrib.html index e99f6b3f74..7fb86b8b38 100644 --- a/v0.5.0/modules/contrib.html +++ b/v0.5.0/modules/contrib.html @@ -14,7 +14,7 @@ - + doctr.contrib - docTR documentation @@ -380,7 +380,7 @@

    Supported contribution modules - + diff --git a/v0.5.0/modules/datasets.html b/v0.5.0/modules/datasets.html index 456e10b172..380a986793 100644 --- a/v0.5.0/modules/datasets.html +++ b/v0.5.0/modules/datasets.html @@ -14,7 +14,7 @@ - + doctr.datasets - docTR documentation @@ -1081,7 +1081,7 @@

    Returns:

    - + diff --git a/v0.5.0/modules/io.html b/v0.5.0/modules/io.html index 01eadaa4b8..24c41954be 100644 --- a/v0.5.0/modules/io.html +++ b/v0.5.0/modules/io.html @@ -14,7 +14,7 @@ - + doctr.io - docTR documentation @@ -760,7 +760,7 @@

    Returns: - + diff --git a/v0.5.0/modules/models.html b/v0.5.0/modules/models.html index c465cc0586..91b8810a6a 100644 --- a/v0.5.0/modules/models.html +++ b/v0.5.0/modules/models.html @@ -14,7 +14,7 @@ - + doctr.models - docTR documentation @@ -1612,7 +1612,7 @@

    Args: - + diff --git a/v0.5.0/modules/transforms.html b/v0.5.0/modules/transforms.html index 30f7a2631a..c5ead3f3ce 100644 --- a/v0.5.0/modules/transforms.html +++ b/v0.5.0/modules/transforms.html @@ -14,7 +14,7 @@ - + doctr.transforms - docTR documentation @@ -835,7 +835,7 @@

    Args:< - + diff --git a/v0.5.0/modules/utils.html b/v0.5.0/modules/utils.html index 888a32c321..b7f6fc570b 100644 --- a/v0.5.0/modules/utils.html +++ b/v0.5.0/modules/utils.html @@ -14,7 +14,7 @@ - + doctr.utils - docTR documentation @@ -715,7 +715,7 @@

    Args: - + diff --git a/v0.5.0/notebooks.html b/v0.5.0/notebooks.html index f97771aebb..d36539f59e 100644 --- a/v0.5.0/notebooks.html +++ b/v0.5.0/notebooks.html @@ -14,7 +14,7 @@ - + docTR Notebooks - docTR documentation @@ -391,7 +391,7 @@

    docTR Notebooks - + diff --git a/v0.5.0/objects.inv b/v0.5.0/objects.inv index a22d2ce821026792e7b2f75cbac9aecf06cc0d89..c1700f291b7b6bc7252a625eb1acde3a63b67eca 100644 GIT binary patch delta 4090 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 delta 1868 zcmV-S2ebIcAgT|LI|DN=Fp)nzf2CSYZ`(K!z4KQH*kG}Hpb@8NQRLRfO*V_9$=YuA zXwVYv2$4jUq@1R|zWk6VlKNIpp``cTkeuOgNGBp8r;7-2#4u-ztB3U}$lor*1ThvY z1M}gTr^z2@0R)jtxc>HaGmY_ZwO;A=B-&>EaQkHvBP2BP1_XUt)H3{re~@WB#VjV- zoZg!T#~CB^kdW6dwV^(C^rm4FXCaC3j^XcxXksQU9EvRDf;6Vf0?Q)bzeAnV@P<}G zP=x71_VrFRCrus+X=~IBb;jZ}G#Mo^_Je9jP{WND35yhG;{7Me@d1TyNSLqwu*`?g z2?<{&a#m$)CT+o<$*bh1f0#d$Xd3xCPVh{-lDarhlJ4RZ9d$6y?Sj_Hqr>lu6f~JC zau;V)C*g5*J)N;YZ01}^@)7eLDx-3?z^h20)5)UCQ%5T(vjX#f(ZPwfaDuzmENlpL zuJayUZ&446YC?};d}I4?+)uJe|%GqVifHp!QTLf zgGEu}^f;4Qrl{#mxmC92-0_ZAyiRo|BaQHsfLB_nC@Ki`fZNzT;$f$bVOXACj2k85-XU zl=1i{-l^A91Sk&4e=&>IJcKmMcBkWU`C+%u=8B|x+F}gRGQ{33e@A1^ zl>8(_)}Ipx!70kvfzVVODM&)-tqcuWt=qem1?r=xIbnD*?+&H=2yLquh|e+pRWcJ1 zkCT}X8GblSX01^ck@QoZCvP*kpJ{x2<{4&eroa`+#5=}kf6{MInxdK9e+oR4`EQPo z{}sYheD1t$5HIMwAX#HJCqlO5hN9`+73-}?Fk9@!iL7&N!KT>Ix*Rg)1@ssTnldEZ z8uMf1ZDNlR>%yUtOUBKUCXF-EpLWLBYgHI$yd_ikkw(3wJj#^jj5FY;?=c2S zPGz9rv8bHHd7s9iSob89i%<c7_oe3s#6wq;JmU!U(a4tM$EVPV9u? z%{9EmKlnZ3;qyephBwvlDQ1P4H}iydd3m6>%3V*Tf4X7{7>_O!v=?U<*mBCP?o9wX zp;*Ag(X_f#^_b4>t3gA9{$vEon_UL>>i(p^1L}?i?29;wf$tPIhqf5m4zZj>hZeY3^uXK( zlqWOSqJ3I3NLV6Q9@Ww^ZXKm(p;s*u-cJ#|Gb)jF^f&Hvdc(dX8?1Y>?HP#Tm8j-! z&>vr#Y@wZ0<8rNJqG=jaGA_+{0xmLJV4dbWe;2DeWGH#i=B-G$U0(*~c2uU!U|j=% zG1;F_Dghf$i%@LRc*rHXXEl7)PynubtEhLB0xu8%&P0+TQ2Yro~2f=5J$*@C6wV@5Y_sON4zSSI3 zDpB_qrR4u4q^D-rh<7Fe0B^oM0Ff3UNkgtmlf{syZLnscuz-!foTJ=E_%`I`n1 zvmo{W63V#{;UV&1P;Vujli{+UV8NR8*dy6yuQOQ=ShRVMy|U6O#ovpV#k?7%{?d80 z!N8j4f%$)Y6Xk2>y20+_&|YR?s~v6KpgW3Vwt8FD!mnFp0K*8d_ue*|J2o z?K;3K0?Qoh#ZKTCjDWuqxp|A^e~EIDP*C`cdlr{LLkqoakpP9tOAMWGr1y(XO@)LO z@|&V=r!b_=gb9mIRfOCz}Q~EsFI53>6&YH - - - - - - - - - - Python Module Index - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    - -
    -

    Python Module Index

    - -
    - - - - - - - - - - - -
     
    d
    - doctr -
    - -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.5.0/search.html b/v0.5.0/search.html index 73772822d2..d050f5eac7 100644 --- a/v0.5.0/search.html +++ b/v0.5.0/search.html @@ -14,7 +14,7 @@ - + Search - docTR documentation @@ -226,20 +226,42 @@ - - + + diff --git a/v0.5.0/searchindex.js b/v0.5.0/searchindex.js index 803f4f4bcf..6f154115ab 100644 --- a/v0.5.0/searchindex.js +++ b/v0.5.0/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles": {"Artefact": [[2, "artefact"]], "Available Datasets": [[1, "available-datasets"]], "Block": [[2, "block"]], "Build & train your predictor": [[3, "build-train-your-predictor"]], "Changelog": [[0, null]], "Composing transformations": [[6, "composing-transformations"]], "Data Loading": [[1, "data-loading"]], "Detection models": [[5, "detection-models"]], "Detection predictors": [[5, "detection-predictors"]], "DocTR Vocabs": [[1, "id1"]], "DocTR: Document Text Recognition": [[3, null]], "Document": [[2, "document"]], "Document structure": [[2, "document-structure"]], "End-to-End OCR": [[5, "end-to-end-ocr"]], "File reading": [[2, "file-reading"]], "Getting Started": [[3, "getting-started"]], "Installation": [[4, null]], "Line": [[2, "line"]], "Main Features": [[3, "main-features"]], "Model compression": [[5, "model-compression"]], "Model export": [[5, "model-export"]], "Model zoo": [[3, "model-zoo"]], "Notes": [[3, null]], "Package Reference": [[3, null]], "Page": [[2, "page"]], "Pre-processing for detection": [[5, "pre-processing-for-detection"]], "Pre-processing for recognition": [[5, "pre-processing-for-recognition"]], "Prerequisites": [[4, "prerequisites"]], "Recognition models": [[5, "recognition-models"]], "Recognition predictors": [[5, "recognition-predictors"]], "Supported Vocabs": [[1, "supported-vocabs"]], "Supported datasets": [[3, "supported-datasets"]], "Supported transformations": [[6, "supported-transformations"]], "Task evaluation": [[7, "task-evaluation"]], "Text Detection": [[5, "text-detection"]], "Text Recognition": [[5, "text-recognition"]], "Text detection models": [[3, "text-detection-models"]], "Text recognition model zoo": [[5, "id2"]], "Text recognition models": [[3, "text-recognition-models"]], "Two-stage approaches": [[5, "two-stage-approaches"]], "Using SavedModel": [[5, "using-savedmodel"]], "Via Git": [[4, "via-git"]], "Via Python Package": [[4, "via-python-package"]], "Visualization": [[7, "visualization"]], "Word": [[2, "word"]], "doctr.datasets": [[1, null]], "doctr.documents": [[2, null]], "doctr.models": [[5, null]], "doctr.transforms": [[6, null]], "doctr.utils": [[7, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]]}, "docnames": ["changelog", "datasets", "documents", "index", "installing", "models", "transforms", "utils"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "datasets.rst", "documents.rst", "index.rst", "installing.rst", "models.rst", "transforms.rst", "utils.rst"], "indexentries": {"artefact (class in doctr.documents)": [[2, "doctr.documents.Artefact", false]], "as_images() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.as_images", false]], "block (class in doctr.documents)": [[2, "doctr.documents.Block", false]], "colorinversion (class in doctr.transforms)": [[6, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[6, "doctr.transforms.Compose", false]], "convert_to_fp16() (in module doctr.models.export)": [[5, "doctr.models.export.convert_to_fp16", false]], "convert_to_tflite() (in module doctr.models.export)": [[5, "doctr.models.export.convert_to_tflite", false]], "cord (class in doctr.datasets)": [[1, "doctr.datasets.CORD", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.crnn_vgg16_bn", false]], "dataloader (class in doctr.datasets.loader)": [[1, "doctr.datasets.loader.DataLoader", false]], "db_resnet50() (in module doctr.models.detection)": [[5, "doctr.models.detection.db_resnet50", false]], "detection_predictor() (in module doctr.models.detection)": [[5, "doctr.models.detection.detection_predictor", false]], "document (class in doctr.documents)": [[2, "doctr.documents.Document", false]], "documentfile (class in doctr.documents)": [[2, "doctr.documents.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[1, "doctr.datasets.encode_sequences", false]], "from_images() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_images", false]], "from_pdf() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_pdf", false]], "from_url() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[1, "doctr.datasets.FUNSD", false]], "get_artefacts() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.get_artefacts", false]], "get_words() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.get_words", false]], "lambdatransformation (class in doctr.transforms)": [[6, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.documents)": [[2, "doctr.documents.Line", false]], "linknet16() (in module doctr.models.detection)": [[5, "doctr.models.detection.linknet16", false]], "localizationconfusion (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.LocalizationConfusion", false]], "master() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.master", false]], "normalize (class in doctr.transforms)": [[6, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models.zoo)": [[5, "doctr.models.zoo.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[1, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[6, "doctr.transforms.OneOf", false]], "page (class in doctr.documents)": [[2, "doctr.documents.Page", false]], "pdf (class in doctr.documents)": [[2, "doctr.documents.PDF", false]], "quantize_model() (in module doctr.models.export)": [[5, "doctr.models.export.quantize_model", false]], "randomapply (class in doctr.transforms)": [[6, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[6, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[6, "doctr.transforms.RandomContrast", false]], "randomgamma (class in doctr.transforms)": [[6, "doctr.transforms.RandomGamma", false]], "randomhue (class in doctr.transforms)": [[6, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[6, "doctr.transforms.RandomJpegQuality", false]], "randomsaturation (class in doctr.transforms)": [[6, "doctr.transforms.RandomSaturation", false]], "read_html() (in module doctr.documents)": [[2, "doctr.documents.read_html", false]], "read_img() (in module doctr.documents)": [[2, "doctr.documents.read_img", false]], "read_pdf() (in module doctr.documents)": [[2, "doctr.documents.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.recognition_predictor", false]], "resize (class in doctr.transforms)": [[6, "doctr.transforms.Resize", false]], "sar_resnet31() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.sar_resnet31", false]], "sar_vgg16_bn() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.sar_vgg16_bn", false]], "show() (doctr.documents.document method)": [[2, "doctr.documents.Document.show", false]], "show() (doctr.documents.page method)": [[2, "doctr.documents.Page.show", false]], "sroie (class in doctr.datasets)": [[1, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[7, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[7, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[7, "doctr.utils.metrics.TextMatch.summary", false]], "textmatch (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.TextMatch", false]], "togray (class in doctr.transforms)": [[6, "doctr.transforms.ToGray", false]], "visiondataset (class in doctr.datasets.datasets)": [[1, "doctr.datasets.datasets.VisionDataset", false]], "visualize_page() (in module doctr.utils.visualization)": [[7, "doctr.utils.visualization.visualize_page", false]], "word (class in doctr.documents)": [[2, "doctr.documents.Word", false]]}, "objects": {"doctr.datasets": [[1, 0, 1, "", "CORD"], [1, 0, 1, "", "FUNSD"], [1, 0, 1, "", "OCRDataset"], [1, 0, 1, "", "SROIE"], [1, 1, 1, "", "encode_sequences"]], "doctr.datasets.datasets": [[1, 0, 1, "", "VisionDataset"]], "doctr.datasets.loader": [[1, 0, 1, "", "DataLoader"]], "doctr.documents": [[2, 0, 1, "", "Artefact"], [2, 0, 1, "", "Block"], [2, 0, 1, "", "Document"], [2, 0, 1, "", "DocumentFile"], [2, 0, 1, "", "Line"], [2, 0, 1, "", "PDF"], [2, 0, 1, "", "Page"], [2, 0, 1, "", "Word"], [2, 1, 1, "", "read_html"], [2, 1, 1, "", "read_img"], [2, 1, 1, "", "read_pdf"]], "doctr.documents.Document": [[2, 2, 1, "", "show"]], "doctr.documents.DocumentFile": [[2, 2, 1, "", "from_images"], [2, 2, 1, "", "from_pdf"], [2, 2, 1, "", "from_url"]], "doctr.documents.PDF": [[2, 2, 1, "", "as_images"], [2, 2, 1, "", "get_artefacts"], [2, 2, 1, "", "get_words"]], "doctr.documents.Page": [[2, 2, 1, "", "show"]], "doctr.models.detection": [[5, 1, 1, "", "db_resnet50"], [5, 1, 1, "", "detection_predictor"], [5, 1, 1, "", "linknet16"]], "doctr.models.export": [[5, 1, 1, "", "convert_to_fp16"], [5, 1, 1, "", "convert_to_tflite"], [5, 1, 1, "", "quantize_model"]], "doctr.models.recognition": [[5, 1, 1, "", "crnn_vgg16_bn"], [5, 1, 1, "", "master"], [5, 1, 1, "", "recognition_predictor"], [5, 1, 1, "", "sar_resnet31"], [5, 1, 1, "", "sar_vgg16_bn"]], "doctr.models.zoo": [[5, 1, 1, "", "ocr_predictor"]], "doctr.transforms": [[6, 0, 1, "", "ColorInversion"], [6, 0, 1, "", "Compose"], [6, 0, 1, "", "LambdaTransformation"], [6, 0, 1, "", "Normalize"], [6, 0, 1, "", "OneOf"], [6, 0, 1, "", "RandomApply"], [6, 0, 1, "", "RandomBrightness"], [6, 0, 1, "", "RandomContrast"], [6, 0, 1, "", "RandomGamma"], [6, 0, 1, "", "RandomHue"], [6, 0, 1, "", "RandomJpegQuality"], [6, 0, 1, "", "RandomSaturation"], [6, 0, 1, "", "Resize"], [6, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[7, 0, 1, "", "LocalizationConfusion"], [7, 0, 1, "", "OCRMetric"], [7, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.LocalizationConfusion": [[7, 2, 1, "", "summary"]], "doctr.utils.metrics.OCRMetric": [[7, 2, 1, "", "summary"]], "doctr.utils.metrics.TextMatch": [[7, 2, 1, "", "summary"]], "doctr.utils.visualization": [[7, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [2, 7], "0": [1, 3, 5, 6, 7], "00": 5, "01": 5, "0123456789": 1, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 1, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": [], "02": 5, "02562": 5, "03": 3, "035": [], "0361328125": [], "04": [], "05": 3, "06": [], "06640625": [], "07": [], "08": 5, "09": [], "0966796875": [], "1": [1, 3, 5, 6, 7], "10": [1, 5, 7], "100": [5, 6, 7], "1000": 5, "101": [], "1024": [5, 7], "104": [], "106": [], "108": [], "1095": [], "11": 3, "110": 7, "1107": [], "114": [], "115": [], "1156": [], "116": [], "118": [], "11800h": [], "11th": [], "12": 5, "120": [], "123": [], "126": [], "1268": [], "128": 5, "13": 5, "130": [], "13068": [], "131": [], "1337891": [], "1357421875": [], "1396484375": [], "14": 5, "1420": [], "14470v1": [], "149": [], "15": 5, "150": 7, "154": 1, "1552": [], "16": 5, "160": 5, "1630859375": [], "1684": [], "16x16": [], "17": [], "1778": [], "1782": [], "18": 3, "185546875": [], "19": 5, "1900": [], "1910": 5, "19342": [], "19370": [], "195": [], "19598": [], "199": 5, "1999": [], "1m": 5, "2": [3, 5, 6], "20": 5, "200": 7, "2000": [], "2003": [], "2012": [], "2013": [], "2015": [], "2019": 3, "2021": 3, "2023": [], "207901": [], "21": 5, "2103": [], "2186": [], "21888": [], "22": [], "224": [5, 6], "225": 6, "22672": [], "229": 6, "23": [], "233": [], "236": [], "24": [], "246": [], "249": [], "25": 5, "2504": [], "255": [5, 6, 7], "256": 5, "257": [], "26": [], "26032": [], "264": [], "27": 5, "2700": [], "2710": [], "2749": [], "28": 3, "287": [], "29": 5, "296": [], "299": [], "2d": [], "3": [2, 3, 4, 5, 6, 7], "30": 5, "300": [], "3000": [], "301": [], "30595": 5, "30ghz": [], "31": 5, "32": [1, 5, 6], "3232421875": [], "33": [], "33402": [], "33608": [], "34": [], "340": [], "3456": [], "3515625": [], "36": [], "360": [], "37": [], "38": [], "39": 5, "4": [], "40": [], "406": 6, "41": [], "42": [], "43": 5, "44": [], "45": [], "456": 6, "46": 5, "47": 5, "472": [], "48": 5, "485": 6, "49": 5, "49377": [], "5": [1, 6, 7], "50": 5, "51": [], "51171875": [], "512": [], "52": [1, 5], "529": [], "53": 5, "533": [], "54": [], "540": [], "5478515625": [], "55": [], "56": [], "57": [], "58": [], "580": [], "5810546875": [], "583": [], "59": 5, "595": [], "597": [], "5k": [], "5m": 5, "6": [4, 5, 6], "60": 6, "600": [5, 7], "61": 5, "611": [], "62": 5, "625": [], "626": [], "629": [], "63": 5, "630": [], "64": [5, 6], "640": [], "641": [], "647": [], "65": 5, "66": 5, "660": [], "664": [], "666": [], "67": 5, "672": [], "68": 5, "689": [], "69": 5, "693": [], "694": [], "695": [], "6m": [], "7": 5, "70": [5, 7], "700": [], "701": [], "702": [], "707470": [], "71": [], "7100000": [], "713": [], "7141797": [], "7149": [], "72": [], "72dpi": [], "73": [], "73257": [], "733": [], "74": 5, "745": [], "75": 5, "753": [], "7581382": [], "76": [], "77": 5, "772": [], "772875": [], "78": 5, "780": [], "781": [], "783": [], "785": [], "789": [], "79": 5, "793533": [], "796": [], "798": [], "7m": [], "8": [5, 6], "80": [], "800": [5, 7], "81": 5, "817": [], "82": 5, "8275l": 5, "83": 5, "830": [], "84": [], "849": [], "85": 5, "8564453125": [], "857": [], "85875": [], "86": 5, "860": [], "8603515625": [], "862": [], "863": [], "87": 5, "8707": [], "875": [], "88": [], "89": 5, "8m": 5, "9": [], "90": 5, "90k": [], "90kdict32px": [], "91": 5, "913": [], "914085328578949": [], "917": [], "92": 5, "921": [], "93": [], "94": [], "95": 7, "9578408598899841": [], "96": 1, "97": [], "98": [], "99": [], "9949972033500671": [], "A": [1, 2, 3, 5], "And": 5, "As": [], "Be": [], "Being": [], "By": [], "For": [4, 5], "If": [2, 4, 5], "In": [1, 5], "It": 6, "Its": 5, "No": [], "Of": 1, "Or": [], "The": [1, 2, 5, 7], "Then": 5, "To": [], "_": [1, 5], "__call__": [], "_build": [], "_i": 7, "ab": [], "abc": [], "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 1, "abdef": [], "abl": [], "about": 5, "abov": 5, "abstract": 1, "abstractdataset": [], "abus": [], "accent": [], "accept": [], "access": [1, 2, 3], "account": [], "accur": [], "accuraci": 7, "achiev": [], "act": [], "action": [], "activ": [], "ad": 6, "adapt": [], "add": [6, 7], "add_hook": [], "add_label": 7, "addit": [], "addition": 5, "address": 2, "adjust": 6, "advanc": [], "advantag": [], "advis": [], "aesthet": [], "affect": [], "after": [], "ag": [], "again": [], "aggreg": [1, 7], "aggress": [], "align": 2, "all": [1, 2, 3, 5, 6, 7], "allow": [], "along": 5, "alreadi": [], "also": [], "alwai": [], "an": [1, 2, 3, 5, 7], "analysi": [2, 5], "ancient_greek": [], "andrej": [], "angl": 2, "ani": [1, 2, 3, 5, 6, 7], "annot": 2, "anot": [], "anoth": [1, 4, 5], "answer": [], "anyascii": [], "anyon": 3, "anyth": [], "api": [], "apolog": [], "apologi": [], "app": [], "appear": [], "appli": [1, 6], "applic": 5, "appoint": [], "appreci": [], "appropri": [], "ar": [1, 2, 4, 5, 6, 7], "arab": [], "arabic_diacrit": [], "arabic_lett": [], "arabic_punctu": [], "arbitrarili": [], "arch": 5, "architectur": [3, 5], "archiv": [], "area": [], "argument": [1, 2], "around": 5, "arrai": [2, 7], "art": 3, "artefact": 7, "artefact_typ": 2, "articl": [], "artifici": [], "arxiv": 5, "as_imag": 2, "asarrai": 7, "ascii_lett": 1, "aspect": [3, 6], "assess": 7, "assign": 7, "associ": 2, "assum": [], "assume_straight_pag": [], "astyp": [5, 7], "attack": [], "attend": [3, 5], "attent": [], "autoclass": [], "autom": 3, "automat": [], "autoregress": [], "avail": [3, 5, 6], "averag": [5, 6], "avoid": [], "aw": [3, 5], "awar": [], "azur": [], "b": 7, "b_j": 7, "back": [], "backbon": 5, "backend": 5, "background": [], "bangla": [], "bar": [], "bar_cod": [], "baranovskij": [], "base": 5, "baselin": 5, "batch": [1, 5, 6], "batch_siz": 1, "bblanchon": [], "bbox": [], "becaus": [], "been": [5, 7], "befor": 1, "begin": 7, "behavior": [], "being": [5, 7], "belong": [], "benchmark": [], "best": [], "beta": 3, "better": [], "between": [6, 7], "bgr": 2, "bilinear": [5, 6], "bin_thresh": [], "binar": [3, 5], "binari": 2, "bit": [], "block": [5, 7], "block_1_1": [], "blur": [], "bmvc": [], "bn": [], "bodi": [], "bool": [1, 2, 5, 6, 7], "boolean": [], "both": [3, 5, 6], "bottom": [], "bound": [1, 2, 6, 7], "box": [1, 2, 7], "box_thresh": [], "brew": 4, "bright": 6, "browser": [], "build": [], "built": [], "byte": [2, 5], "c": [], "c5": 5, "c_j": [], "cach": [], "cache_sampl": [], "cairo": 4, "call": [], "callabl": [1, 6], "can": [1, 4, 5], "capabl": 5, "case": [1, 7], "cf": 5, "cfg": [], "challeng": [], "challenge2_test_task12_imag": [], "challenge2_test_task1_gt": [], "challenge2_training_task12_imag": [], "challenge2_training_task1_gt": [], "chang": [], "changelog": 3, "channel": [2, 5, 6], "channel_prior": [], "channelshuffl": [], "charact": [1, 2, 3, 5, 7], "charactergener": [], "characterist": [], "charg": 5, "charset": [], "chart": 2, "check": [], "checkpoint": [], "chip": [], "christian": [], "ci": [], "clarifi": [], "clariti": [], "class": [1, 2, 6, 7], "class_nam": [], "classif": [], "classmethod": 2, "clear": [], "clone": 4, "close": [], "co": [], "code": [2, 3], "codecov": [], "colab": [], "collate_fn": [], "collect": 2, "color": 6, "colorinvers": 6, "column": 2, "com": [2, 4], "combin": 5, "command": [], "comment": [], "commit": [], "common": [6, 7], "commun": [], "compar": 3, "comparison": 7, "competit": 1, "compil": [], "complaint": [], "complementari": 7, "complet": [], "compon": 5, "compos": [1, 3, 5], "comprehens": [], "comput": [5, 7], "conf_threshold": [], "confid": 2, "config": [], "configur": [], "confus": 7, "consecut": [5, 6], "consequ": [], "consid": [1, 2, 7], "consist": [], "consolid": [1, 3], "constant": 6, "construct": [], "contact": [], "contain": [], "content": [1, 2], "context": [], "contib": [], "continu": [], "contrast": 6, "contrast_factor": 6, "contrib": [], "contribut": [], "contributor": [], "conv_sequ": 5, "convers": 2, "convert": [2, 5, 6], "convert_page_to_numpi": 2, "convert_to_fp16": 5, "convert_to_tflit": 5, "convolut": 3, "cool": [], "coordin": 2, "cord": [1, 3, 5], "core": 7, "corner": [], "correct": 6, "correspond": [4, 5], "could": [], "counterpart": 7, "cover": [], "coverag": [], "cpu": [3, 5], "creat": [], "crnn": [3, 5], "crnn_mobilenet_v3_larg": [], "crnn_mobilenet_v3_smal": [], "crnn_resnet31": 5, "crnn_vgg16_bn": 5, "crop": 5, "crop_orient": [], "crop_orientation_predictor": [], "crop_param": [], "cuda": [], "currenc": 1, "current": [], "custom": [], "custom_crop_orientation_model": [], "custom_page_orientation_model": [], "customhook": [], "cvit": [], "czczup": [], "czech": [], "d": [], "daili": 3, "danish": [], "data": [2, 3, 5, 6, 7], "dataload": 1, "dataset": 5, "dataset_info": [], "date": [], "db": [], "db_crnn_resnet": 5, "db_crnn_vgg": 5, "db_mobilenet_v3_larg": [], "db_resnet34": [], "db_resnet50": 5, "db_sar_resnet": 5, "db_sar_vgg": 5, "dbnet": [3, 5], "deal": [], "decis": [], "decod": 2, "decode_img_as_tensor": [], "dedic": [], "deem": [], "deep": 5, "def": [], "default": [2, 5], "defer": 1, "defin": 7, "deform": 5, "degre": [], "degress": 2, "delet": [], "delimit": [], "delta": 6, "demo": [], "demonstr": [], "depend": [3, 4], "deploi": [], "deploy": [], "derogatori": [], "describ": 5, "descript": [], "design": 6, "desir": [], "det_arch": 5, "det_b": [], "det_model": [], "det_param": [], "det_predictor": [], "detail": [], "detect": [], "detect_languag": [], "detect_orient": [], "detection_predictor": 5, "detection_task": [], "detectiondataset": [], "detectionmetr": [], "detectionpredictor": 5, "detector": [], "deterior": [], "determin": [], "dev": [], "develop": [], "developp": 4, "deviat": 6, "devic": [], "dict": [2, 7], "dictionari": [2, 7], "differ": [], "differenti": [3, 5], "digit": 1, "dimens": [2, 5, 7], "dimension": 6, "direct": [], "directli": 5, "directori": [], "disabl": [], "disable_crop_orient": [], "disable_page_orient": [], "disclaim": 5, "discuss": [], "disk": [], "disparag": [], "displai": [2, 7], "display_artefact": 7, "distanc": [], "distribut": 6, "div": [], "divers": [], "divid": [], "do": 4, "doc": [2, 5], "docartefact": [], "docstr": [], "doctr": 4, "doctr_cache_dir": [], "doctr_multiprocessing_dis": [], "document": [1, 5, 7], "documentbuild": [], "documentfil": 2, "doesn": [], "don": [], "done": 6, "download": 1, "downsiz": [], "draw": 6, "drop": 1, "drop_last": 1, "dtype": 5, "dual": [], "dummi": [], "dummy_img": [], "dummy_input": [], "dure": [], "dutch": [], "dynam": [], "dynamic_seq_length": [], "e": [2, 4], "each": [1, 2, 3, 5, 6, 7], "eas": [], "easi": [3, 7], "easier": 5, "easili": [2, 5, 7], "econom": [], "edit": [], "educ": [], "effect": [], "effici": [1, 5], "either": 5, "element": [1, 2, 5], "els": [], "email": [], "empathi": [], "en": [], "enabl": 2, "enclos": 2, "encod": [1, 2, 5], "encode_sequ": 1, "encount": [], "encrypt": [], "end": [1, 3, 7], "english": [], "enough": 5, "ensur": [], "entir": 2, "entri": [], "environ": [], "eo": 1, "equiv": [], "error": [], "estim": [], "etc": 2, "ethnic": [], "evalu": [1, 3, 5], "event": [], "everyon": [], "everyth": [], "exact": 7, "exactmatch": [], "exampl": [1, 2, 5, 6, 7], "exchang": [], "exclud": 5, "execut": [], "exist": [], "expand": [], "expect": [2, 5, 6], "experi": 5, "explan": 5, "explicit": [], "exploit": 5, "export": [2, 3, 7], "export_as_straight_box": [], "export_as_xml": [], "export_model_to_onnx": [], "express": 6, "extens": 2, "extern": [], "extra": 4, "extract": [1, 3], "extract_arch": 1, "extractor": 5, "f_": 7, "f_a": 7, "factor": 6, "fair": [], "fairli": [], "fals": [1, 5, 6, 7], "faq": [], "fascan": [], "fast": 1, "fast_bas": [], "fast_smal": [], "fast_tini": [], "faster": [], "fasterrcnn_mobilenet_v3_large_fpn": [], "favorit": [], "featur": [5, 7], "feed": 5, "feedback": [], "feel": [], "felix92": [], "few": 4, "figsiz": 7, "figur": 7, "file": [1, 3], "file_hash": 1, "file_nam": 1, "final": [], "find": 4, "fine": 3, "finnish": [], "first": [], "firsthand": [], "fit": [], "fitz": 2, "flag": [], "flexibl": 7, "flip": [], "float": [2, 6, 7], "float32": 5, "fn": 6, "focu": [], "focus": [], "folder": [1, 5], "follow": [1, 4, 5, 6, 7], "font": [], "font_famili": [], "foral": 7, "forc": [], "forg": [], "form": [1, 3], "format": [2, 5], "forpost": [1, 3], "forum": [], "found": [], "fp": 5, "fp16": 5, "frac": 7, "frame": 5, "framework": 1, "free": [], "french": [1, 5], "friendli": 3, "from": [1, 2, 3, 5, 6, 7], "from_hub": [], "from_imag": 2, "from_pdf": 2, "from_url": 2, "full": [1, 5, 7], "fulli": [], "function": [5, 6, 7], "funsd": [1, 3, 5], "further": [], "futur": [], "g": 2, "g_": 7, "g_x": 7, "gallagh": [], "gamma": 6, "gaussian": 6, "gaussianblur": [], "gaussiannois": [], "gdk": 4, "gen": [], "gender": [], "gener": [], "generic_cyrillic_lett": [], "geometri": 2, "geq": 7, "german": [], "get": 2, "get_artefact": 2, "get_word": 2, "gettextword": 2, "git": 3, "github": 4, "give": [], "given": [1, 2, 5, 7], "global": [], "go": [], "good": [], "googl": [], "googlevis": 3, "gpu": 3, "gracefulli": [], "graph": 2, "grayscal": 6, "ground": 7, "groung": [], "group": [], "gt": [], "gt_box": [], "gt_label": [], "gtk": 4, "guid": [], "guidanc": [], "gvision": 5, "h": 2, "h_": 7, "ha": [1, 7], "half": 5, "handl": 1, "handwrit": [], "handwritten": [], "harass": [], "hardwar": [], "harm": [], "hat": 7, "have": [1, 5, 7], "head": [], "healthi": [], "hebrew": [], "height": 2, "hello": 7, "help": [], "here": [1, 4, 6], "hf": [], "hf_hub_download": [], "high": 2, "higher": 4, "hindi": [], "hindi_digit": [], "hocr": [], "hook": [], "horizont": 2, "hous": [], "how": [], "howev": [], "hsv": 6, "html": [], "http": [2, 4, 5], "hub": [], "hue": 6, "huggingfac": [], "hw": [], "i": [1, 2, 5, 6, 7], "i7": [], "ibrahimov": [], "ic03": [], "ic13": [], "icdar": 3, "icdar2019": 1, "id": 5, "ident": [], "identifi": [3, 5], "ignor": [], "ignore_acc": [], "ignore_cas": [], "iiit": [], "iiit5k": [], "iiithw": [], "imag": [1, 2, 5, 6, 7], "imagenet": [], "imageri": [], "images_90k_norm": [], "img": [1, 6], "img_cont": [], "img_fold": 1, "img_path": [], "img_transform": [], "imgur5k": [], "imgur5k_annot": [], "imlist": [], "impact": [], "implement": [1, 2, 5, 6, 7], "import": [1, 2, 5, 6, 7], "improv": [], "inappropri": [], "incid": [], "includ": [4, 5], "inclus": [], "increas": 6, "independ": [], "index": 2, "indic": 7, "individu": [], "infer": [3, 6], "inform": [1, 3, 5], "inherit": [1, 5], "input": [2, 5, 6], "input_crop": [], "input_pag": [5, 7], "input_shap": 5, "input_t": 5, "input_tensor": 5, "inspir": 6, "instal": 3, "instanc": 5, "instanti": 5, "instead": [1, 2], "insult": [], "int": [1, 2, 5, 6, 7], "int64": [], "integ": 7, "integr": 3, "intel": [], "interact": [2, 7], "interfac": [], "interoper": [], "interpol": [5, 6], "interpret": [1, 2], "intersect": 7, "invert": 6, "investig": [], "invis": [], "invoic": 5, "involv": 5, "io": [], "iou": 7, "iou_thresh": 7, "iou_threshold": [], "irregular": 5, "isn": 1, "issu": [], "italian": [], "iter": 1, "its": [1, 2, 5, 7], "itself": [], "j": 7, "jame": [], "job": [], "join": [], "jpeg": 6, "jpegqual": 6, "jpg": [1, 2], "json": [], "json_output": [], "jump": [], "just": 5, "kei": [], "kera": 5, "kernel": [], "kernel_s": 5, "kernel_shap": [], "keywoard": [], "keyword": [1, 2], "kie": [], "kie_predictor": [], "kiepredictor": [], "kind": [], "know": [], "kwarg": [1, 2, 5, 7], "l": 7, "l_j": 7, "label": [1, 7], "label_fil": 1, "label_fold": [], "label_path": [], "labels_path": [], "ladder": [], "lambda": 6, "lambdatransform": 6, "lang": [], "languag": [2, 3], "larg": [], "largest": 7, "last": [1, 4, 5], "latenc": [], "later": [], "latest": 4, "latin": 1, "layer": [], "layout": [], "lead": [], "leader": [], "learn": 5, "least": 4, "left": 7, "legacy_french": [], "length": 1, "less": [], "let": 5, "letter": [], "level": [5, 7], "levenshtein": [], "leverag": [], "lf": [], "libffi": 4, "librari": 4, "light": 3, "lightweight": [], "like": [], "limits_": 7, "line": [3, 7], "line_1_1": [], "link": [], "linknet": [3, 5], "linknet16": 5, "linknet_resnet18": [], "linknet_resnet34": [], "linknet_resnet50": [], "linux": 4, "list": [1, 2, 6], "ll": 7, "load": [3, 5], "load_state_dict": [], "load_weight": [], "loader": 1, "loc_pr": [], "local": [1, 3, 5, 7], "localis": [], "localizationconfus": 7, "locat": [], "login": [], "login_to_hub": [], "logo": 2, "love": [], "lower": [6, 7], "m": [5, 7], "m1": [], "macbook": [], "machin": [], "maco": 4, "made": 3, "magc_resnet31": [], "mai": [], "mail": [], "main": [], "maintain": 3, "mainten": [], "make": [5, 7], "mani": [], "manipul": [], "map": 1, "map_loc": [], "mask_shap": 7, "master": [3, 5], "match": [3, 7], "mathcal": 7, "matplotlib": 7, "max": 7, "max_angl": [], "max_area": [], "max_char": [], "max_delta": 6, "max_dist": [], "max_gain": 6, "max_gamma": 6, "max_qual": 6, "max_ratio": [], "maximum": 1, "maxval": [5, 6], "mbox": 7, "mean": [6, 7], "meaniou": 7, "meant": 2, "measur": 5, "media": [], "median": [], "meet": [], "member": [], "memori": [], "mention": [], "merg": [], "messag": [], "meta": [], "metadata": [], "metal": [], "method": 6, "metric": [5, 7], "middl": [], "might": 5, "min": [], "min_area": [], "min_char": [], "min_gain": 6, "min_gamma": 6, "min_qual": 6, "min_ratio": [], "min_val": 6, "minde": 4, "minim": [], "minimalist": [], "minimum": 7, "minval": 6, "miss": [], "mistak": [], "mix": 3, "mixed_float16": [], "mixed_precis": [], "mjsynth": [], "mnt": [], "mobilenet": [], "mobilenet_v3_larg": [], "mobilenet_v3_large_r": [], "mobilenet_v3_smal": [], "mobilenet_v3_small_crop_orient": [], "mobilenet_v3_small_page_orient": [], "mobilenet_v3_small_r": [], "mobilenetv3": [], "modal": [], "mode": 4, "model": [1, 7], "model_nam": [], "model_path": [], "moder": [], "modif": [], "modifi": [], "modul": [2, 5, 6, 7], "more": [], "moscardi": [], "most": 5, "mozilla": [], "multi": 3, "multilingu": [], "multipl": [1, 2, 6], "multipli": 6, "multiprocess": [], "my": [], "my_awesome_model": [], "my_hook": [], "n": [1, 5, 7], "na": [], "name": [1, 5], "nation": [], "natur": 3, "ndarrai": [1, 2, 7], "necessari": [], "need": [4, 7], "neg": 6, "nest": [], "nestedobject": [], "netraj": [], "network": [3, 5], "neural": [3, 5], "new": [], "newer": [], "next": 1, "nois": [], "noisi": [1, 3], "non": [2, 3, 6, 7], "none": [1, 2, 7], "normal": [5, 6], "norwegian": [], "note": 0, "now": 3, "np": [5, 7], "num_output_channel": [], "num_sampl": [], "number": [1, 6, 7], "numpi": [2, 5, 7], "o": 4, "obb": [], "obj_detect": [], "object": 1, "objectness_scor": [], "oblig": [], "obtain": [], "occupi": [], "ocr": [1, 3, 7], "ocr_carea": [], "ocr_db_crnn": 7, "ocr_lin": [], "ocr_pag": [], "ocr_par": [], "ocr_predictor": 5, "ocrdataset": 1, "ocrmetr": 7, "ocrpredictor": 5, "ocrx_word": [], "offens": [], "offici": [], "offlin": [], "offset": 6, "onc": 5, "one": [1, 5, 6], "oneof": 6, "ones": 1, "onli": [6, 7], "onlin": [], "onnx": [], "onnxruntim": [], "onnxtr": [], "opac": [], "opacity_rang": [], "open": [], "opinion": [], "optic": [3, 5], "optim": 3, "option": 1, "order": [1, 2, 5], "org": 5, "organ": 2, "orient": 2, "orientationpredictor": [], "other": [], "otherwis": 7, "our": 5, "out": [5, 6, 7], "outpout": [], "output": [2, 5, 6], "output_s": [2, 6], "outsid": [], "over": [4, 7], "overal": [], "overlai": 2, "overview": [], "overwrit": 1, "overwritten": [], "own": 3, "p": 6, "packag": 7, "pad": [1, 5, 6], "page": [4, 5, 7], "page1": 2, "page2": 2, "page_1": [], "page_idx": 2, "page_orientation_predictor": [], "page_param": [], "pair": 7, "pango": 4, "paper": 5, "par_1_1": [], "paragraph": [], "paragraph_break": [], "parallel": [], "param": [5, 6], "paramet": [1, 2, 3, 5, 6, 7], "pars": [1, 3], "parseq": [], "part": 6, "parti": [], "partial": [], "particip": [], "pass": [1, 5], "password": [], "patch": [], "path": [1, 2, 5], "path_to_checkpoint": [], "path_to_custom_model": [], "path_to_pt": [], "patil": [], "pattern": [], "pdf": [2, 5], "pdfpage": [], "peopl": [], "per": [5, 6], "perform": [2, 3, 5, 6, 7], "period": [], "permiss": [], "permut": [], "persian_lett": [], "person": [], "phase": [], "photo": [], "physic": 2, "pick": 6, "pictur": 2, "pip": 4, "pipelin": [], "pixbuf": 4, "pixel": [2, 6], "platinum": 5, "pleas": [], "plot": 7, "plt": 7, "plug": [], "plugin": [], "png": 2, "point": [], "polici": [], "polish": [], "polit": [], "polygon": 1, "pool": [], "portugues": [], "posit": 7, "possibl": 7, "post": 5, "postprocessor": [], "potenti": 5, "power": 3, "ppageno": [], "pre": [], "precis": [5, 7], "pred": [], "pred_box": [], "pred_label": [], "predefin": 1, "predict": [2, 7], "predictor": [], "prefer": 1, "preinstal": [], "preprocessor": 5, "prerequisit": 3, "present": [], "preserv": 6, "preserve_aspect_ratio": 6, "pretrain": [3, 5, 7], "pretrained_backbon": [], "print": [], "prior": [], "privaci": [], "privat": 5, "probabl": 6, "problem": [], "procedur": 6, "process": [2, 3], "processor": 5, "produc": 5, "product": [], "profession": [], "project": [], "promptli": [], "proper": [], "properli": 1, "properti": 5, "provid": [3, 5], "public": 3, "publicli": [], "publish": [], "pull": [], "punctuat": 1, "pure": [], "purpos": [], "push_to_hf_hub": [], "py": [], "pypdfium2": [], "pyplot": 7, "python": 3, "python3": [], "pytorch": [3, 4], "q": [], "qr": 2, "qr_code": [], "qualiti": 6, "quantiz": 5, "quantize_model": 5, "question": [], "quickli": 3, "quicktour": [], "r": [], "race": [], "ramdisk": [], "rand": [5, 7], "random": [5, 6, 7], "randomappli": 6, "randombright": 6, "randomcontrast": 6, "randomcrop": [], "randomgamma": 6, "randomhorizontalflip": [], "randomhu": 6, "randomjpegqu": 6, "randomli": 6, "randomres": [], "randomrot": [], "randomsatur": 6, "randomshadow": [], "rang": 6, "rassi": [], "ratio": 6, "raw": [2, 7], "re": [], "read": [3, 5], "read_html": 2, "read_img": 2, "read_img_as_numpi": [], "read_img_as_tensor": [], "read_pdf": 2, "readi": [], "real": [5, 6], "realli": [], "reason": [], "rebuild": [], "rebuilt": [], "recal": [5, 7], "receipt": [1, 3, 5], "reco_arch": 5, "reco_b": [], "reco_model": [], "reco_param": [], "reco_predictor": [], "recogn": [], "recognit": 7, "recognition_predictor": 5, "recognition_task": [], "recognitiondataset": [], "recognitionpredictor": 5, "rectangular": [], "recurr": 3, "reduc": 6, "refer": 4, "regardless": [], "region": [], "regroup": 7, "regular": [], "reject": [], "rel": 2, "relat": [], "releas": [0, 4], "relev": [], "religion": [], "relu": 5, "remov": [], "render": [], "repo": [], "repo_id": [], "report": [], "repositori": [], "repres": [2, 5], "represent": 5, "request": [], "requir": [4, 6], "research": 3, "residu": [], "resiz": [5, 6], "resnet": 5, "resnet18": [], "resnet31": [], "resnet34": [], "resnet50": [], "resolv": 2, "resolve_block": [], "resolve_lin": [], "resourc": [], "respect": [], "rest": [6, 7], "restrict": [], "result": [2, 5], "return": [1, 2, 5, 7], "reusabl": 5, "review": [], "rgb": [2, 6], "rgb_mode": [], "rgb_output": 2, "right": [5, 7], "roboflow": [], "robust": 3, "root": 1, "rotat": [1, 2], "rotated_bbox": [1, 7], "run": 4, "same": [2, 7], "sampl": 1, "sample_transform": 1, "sanjin": [], "sar": [3, 5], "sar_resnet31": 5, "sar_vgg16_bn": 5, "satur": 6, "save": [1, 5], "saved_model": 5, "scale": 7, "scale_rang": [], "scan": [1, 3], "scene": [3, 5], "scheme": 5, "score": 7, "scratch": 3, "script": [], "seamless": 3, "seamlessli": [], "search": [], "searchabl": [], "sec": [], "second": 5, "section": [], "secur": [], "see": [], "seemlessli": 3, "seen": 5, "segment": 5, "self": [], "semant": 5, "send": [], "sens": 7, "sensit": [], "separ": 5, "sequenc": [1, 2, 5, 7], "sequenti": [5, 6], "seri": [], "serial": 5, "serialized_model": 5, "seriou": [], "set": [1, 5, 7], "set_global_polici": [], "sever": [2, 6], "sex": [], "sexual": [], "sha256": [], "shade": [], "shape": [2, 5, 6, 7], "share": [], "shift": 6, "shm": [], "should": [1, 2, 7], "show": [2, 3, 5, 7], "showcas": [], "shuffl": 1, "side": 7, "signatur": 2, "signific": 1, "simpl": 5, "simpler": [], "sinc": 1, "singl": [], "single_img_doc": [], "size": [1, 2, 5, 6], "skew": [], "slack": [], "slightli": [], "small": 3, "smallest": 2, "snapshot_download": [], "snippet": [], "so": [1, 4], "social": [], "socio": [], "some": [], "someth": [], "somewher": [], "sort": [], "sourc": [1, 2, 5, 6, 7], "space": [], "span": [], "spanish": [], "spatial": 2, "special": 3, "specif": [1, 5, 7], "specifi": 2, "speed": [3, 5], "sphinx": [], "sroie": [1, 3], "stabl": 4, "stackoverflow": [], "stage": 3, "standalon": [], "standard": 6, "start": 1, "state": 3, "static": 7, "statist": 5, "statu": [], "std": 6, "step": [], "still": [], "str": [1, 2, 5, 6, 7], "straight": 1, "straighten": [], "straighten_pag": [], "straigten_pag": [], "stream": 2, "street": [], "strict": [], "strictli": 7, "string": [1, 2, 5, 7], "strive": [], "strong": 5, "structur": [3, 5], "subset": [1, 5], "suggest": [], "sum": 7, "summari": 7, "support": 5, "sustain": [], "svhn": [], "svt": [], "swedish": [], "symbol": [], "symmetr": 6, "symmetric_pad": 6, "synthet": [], "synthtext": [], "system": [], "t": 1, "tabl": [], "take": [], "target": [1, 2, 5, 6], "target_s": 1, "task": [1, 3, 5], "task2": [], "team": [], "techminde": [], "templat": 2, "tensor": [1, 5, 6], "tensorflow": [3, 4, 5, 6], "tensorspec": [], "term": [], "test": [], "test_set": [], "text": [2, 7], "text_output": [], "textmatch": 7, "textnet": [], "textnet_bas": [], "textnet_smal": [], "textnet_tini": [], "textract": [3, 5], "textstylebrush": [], "textual": [1, 2, 3], "tf": [5, 6], "tf_model": 5, "tflite": 5, "than": [4, 7], "thank": [], "thei": [], "them": [1, 4], "thi": [4, 5, 7], "thing": [], "third": [], "those": [2, 4, 5], "threaten": [], "threshold": [], "through": [1, 6], "tilman": [], "time": [1, 5, 7], "tini": [], "titl": 2, "tm": [], "tmp": [], "togeth": [2, 5], "tograi": 6, "tool": [], "top": 7, "topic": [], "torch": [], "torchvis": 6, "total": [], "toward": [], "train": [1, 5, 6], "train_it": 1, "train_load": 1, "train_pytorch": [], "train_set": 1, "train_tensorflow": [], "trainabl": 5, "tranform": 6, "transcrib": [], "transfer": [], "transfo": 6, "transform": [1, 3], "translat": [], "troll": [], "true": [1, 2, 5, 6, 7], "truth": 7, "tune": 3, "tupl": [2, 5, 6, 7], "turn": [], "two": 2, "txt": [], "type": [2, 5], "typic": [], "u": [], "ucsd": [], "udac": [], "uint8": [2, 5, 7], "ukrainian": [], "unaccept": [], "underli": 1, "underneath": 2, "understand": [1, 3], "unidecod": 7, "uniform": [5, 6], "uniformli": [], "uninterrupt": 2, "union": 7, "unit": [], "unittest": [], "unlock": [], "unoffici": [], "unprofession": [], "unsolicit": [], "unsupervis": [], "unwelcom": [], "up": 5, "updat": 7, "upgrad": [], "upper": 6, "uppercas": [], "url": [1, 2], "us": [1, 4, 7], "usabl": 5, "usag": 5, "use_polygon": [], "useabl": [], "user": [2, 3, 4], "utf": [], "util": [3, 5], "v0": 3, "v1": [], "v3": [], "valid": [], "valu": [2, 6], "valuabl": 3, "variabl": [], "varieti": [], "veri": [], "verifi": 1, "verma": [], "version": 5, "vgg": 5, "vgg16": 5, "vgg16_bn_r": [], "via": 3, "video": [], "vietnames": [], "view": [], "viewpoint": [], "violat": [], "visibl": [], "vision": [], "visiondataset": 1, "visiontransform": [], "visual": 3, "visualize_pag": 7, "vit_": [], "vit_b": [], "vitstr": [], "vitstr_bas": [], "vitstr_smal": [], "viz": [], "vocab": [3, 5], "vocabulari": [], "w": [2, 7], "w3": [], "wa": [], "wai": [1, 3, 5], "want": [], "warm": 5, "warmup": [], "wasn": [], "we": [2, 3, 5, 6], "weasyprint": [], "web": 2, "websit": [], "welcom": 3, "well": [], "were": 2, "what": [], "when": [], "whenev": [], "where": [2, 7], "whether": [1, 2, 7], "which": 5, "whichev": 4, "while": 6, "why": [], "width": 2, "wiki": [], "wildreceipt": [], "window": [4, 7], "wish": [], "within": [], "without": 5, "wonder": [], "word": [3, 5, 7], "word_1_1": [], "word_1_2": [], "word_1_3": [], "wordgener": [], "words_onli": 7, "work": [], "worker": 1, "workflow": [], "worklow": [], "world": 7, "worth": [], "wrap": [], "wrapper": [1, 6], "write": [], "written": 2, "www": 2, "x": [2, 6, 7], "x12larg": 5, "x_ascend": [], "x_descend": [], "x_i": 7, "x_size": [], "x_wconf": [], "xeon": 5, "xhtml": [], "xmax": 2, "xmin": 2, "xml": [], "xml_bytes_str": [], "xml_element": [], "xml_output": [], "xmln": [], "y": 7, "y_i": 7, "y_j": 7, "yet": [], "ymax": 2, "ymin": 2, "yolov8": [], "you": [4, 5], "your": [1, 2, 5, 7], "yoursit": 2, "yugesh": [], "zero": [5, 6], "zoo": [], "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 1, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": [], "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": [], "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": [], "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": [], "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": [], "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": [], "\u00e4\u00f6\u00e4\u00f6": [], "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": [], "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": [], "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": [], "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": [], "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": [], "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": [], "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": [], "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": [], "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": [], "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": [], "\u067e\u0686\u06a2\u06a4\u06af": [], "\u0905": [], "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": [], "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": [], "\u0950": [], "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": [], "\u09bd": [], "\u09ce": [], "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": []}, "titles": ["Changelog", "doctr.datasets", "doctr.documents", "DocTR: Document Text Recognition", "Installation", "doctr.models", "doctr.transforms", "doctr.utils"], "titleterms": {"": [], "0": 0, "01": [], "02": [], "03": 0, "04": [], "05": 0, "07": [], "08": [], "09": [], "1": 0, "10": [], "11": 0, "12": [], "18": 0, "2": 0, "2021": 0, "2022": [], "2023": [], "2024": [], "21": [], "22": [], "27": [], "28": 0, "29": [], "3": [], "31": [], "4": [], "5": [], "6": [], "7": [], "8": [], "9": [], "advanc": [], "approach": 5, "architectur": [], "arg": [], "artefact": 2, "artefactdetect": [], "attribut": [], "avail": 1, "aw": [], "ban": [], "block": 2, "bug": [], "build": 3, "changelog": 0, "choos": [], "classif": [], "code": [], "codebas": [], "commit": [], "commun": [], "compos": 6, "compress": 5, "conda": [], "conduct": [], "connect": [], "content": [], "continu": [], "contrib": [], "contribut": [], "contributor": [], "convent": [], "correct": [], "coven": [], "custom": [], "data": 1, "dataload": [], "dataset": [1, 3], "detect": [3, 5], "develop": [], "do": [], "doctr": [1, 2, 3, 5, 6, 7], "document": [2, 3], "end": 5, "enforc": [], "evalu": 7, "export": 5, "factori": [], "featur": 3, "feedback": [], "file": 2, "from": [], "gener": [], "get": 3, "git": 4, "guidelin": [], "half": [], "hub": [], "huggingfac": [], "i": [], "implement": [], "infer": [], "instal": 4, "integr": [], "io": [], "lambda": [], "let": [], "line": 2, "linux": [], "load": 1, "loader": [], "main": 3, "mode": [], "model": [3, 5], "modifi": [], "modul": [], "name": [], "note": 3, "notebook": [], "object": [], "ocr": 5, "onli": [], "onnx": [], "optim": [], "option": [], "orient": [], "our": [], "output": [], "own": [], "packag": [3, 4], "page": 2, "perman": [], "pipelin": [], "pledg": [], "post": [], "pre": 5, "precis": [], "predictor": [3, 5], "prepar": [], "prerequisit": 4, "pretrain": [], "process": 5, "push": [], "python": 4, "qualiti": [], "question": [], "read": 2, "readi": [], "recognit": [3, 5], "refer": 3, "report": [], "request": [], "resourc": [], "respons": [], "return": [], "right": [], "savedmodel": 5, "scope": [], "share": [], "should": [], "stage": 5, "standard": [], "start": 3, "structur": 2, "style": [], "support": [1, 3, 6], "synthet": [], "task": 7, "temporari": [], "test": [], "text": [3, 5], "train": 3, "transform": 6, "two": 5, "unit": [], "us": 5, "util": 7, "v0": 0, "verif": [], "via": 4, "visual": 7, "vocab": 1, "warn": [], "what": [], "word": 2, "your": 3, "zoo": [3, 5]}}) \ No newline at end of file +Search.setIndex({"alltitles": {"1. Correction": [[2, "correction"]], "2. Warning": [[2, "warning"]], "3. Temporary Ban": [[2, "temporary-ban"]], "4. Permanent Ban": [[2, "permanent-ban"]], "AWS Lambda": [[14, null]], "Advanced options": [[19, "advanced-options"]], "Args:": [[7, "args"], [7, "id4"], [7, "id7"], [7, "id10"], [7, "id13"], [7, "id16"], [7, "id19"], [7, "id22"], [7, "id25"], [7, "id29"], [7, "id32"], [7, "id37"], [7, "id40"], [7, "id46"], [7, "id49"], [7, "id50"], [7, "id51"], [7, "id54"], [7, "id57"], [7, "id60"], [7, "id61"], [8, "args"], [8, "id2"], [8, "id3"], [8, "id4"], [8, "id5"], [8, "id6"], [8, "id7"], [8, "id10"], [8, "id12"], [8, "id14"], [8, "id16"], [8, "id20"], [8, "id24"], [8, "id28"], [9, "args"], [9, "id3"], [9, "id8"], [9, "id13"], [9, "id17"], [9, "id21"], [9, "id26"], [9, "id31"], [9, "id36"], [9, "id41"], [9, "id46"], [9, "id50"], [9, "id54"], [9, "id59"], [9, "id63"], [9, "id68"], [9, "id73"], [9, "id77"], [9, "id81"], [9, "id85"], [9, "id90"], [9, "id95"], [9, "id99"], [9, "id104"], [9, "id109"], [9, "id114"], [9, "id119"], [9, "id123"], [9, "id127"], [9, "id132"], [9, "id137"], [9, "id142"], [9, "id146"], [9, "id150"], [9, "id155"], [9, "id159"], [9, "id163"], [9, "id167"], [9, "id169"], [9, "id171"], [9, "id173"], [10, "args"], [10, "id1"], [10, "id2"], [10, "id3"], [10, "id4"], [10, "id5"], [10, "id6"], [10, "id7"], [10, "id8"], [10, "id9"], [10, "id10"], [10, "id11"], [10, "id12"], [10, "id13"], [10, "id14"], [10, "id15"], [10, "id16"], [10, "id17"], [10, "id18"], [10, "id19"], [11, "args"], [11, "id3"], [11, "id4"], [11, "id5"], [11, "id6"], [11, "id7"], [11, "id8"], [11, "id9"]], "Artefact": [[8, "artefact"]], "ArtefactDetection": [[16, "artefactdetection"]], "Attribution": [[2, "attribution"]], "Available Datasets": [[17, "available-datasets"]], "Available architectures": [[19, "available-architectures"], [19, "id1"], [19, "id2"]], "Available contribution modules": [[16, "available-contribution-modules"]], "Block": [[8, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[17, null]], "Choosing the right model": [[19, null]], "Classification": [[15, "classification"]], "Code quality": [[3, "code-quality"]], "Code style verification": [[3, "code-style-verification"]], "Codebase structure": [[3, "codebase-structure"]], "Commits": [[3, "commits"]], "Community resources": [[1, null]], "Composing transformations": [[10, "composing-transformations"]], "Continuous Integration": [[3, "continuous-integration"]], "Contributing to docTR": [[3, null]], "Contributor Covenant Code of Conduct": [[2, null]], "Custom dataset loader": [[7, "custom-dataset-loader"]], "Custom orientation classification models": [[13, "custom-orientation-classification-models"]], "Data Loading": [[17, "data-loading"]], "Dataloader": [[7, "dataloader"]], "Detection": [[15, "detection"], [17, "detection"]], "Detection predictors": [[19, "detection-predictors"]], "Developer mode installation": [[3, "developer-mode-installation"]], "Developing docTR": [[3, "developing-doctr"]], "Document": [[8, "document"]], "Document structure": [[8, "document-structure"]], "End-to-End OCR": [[19, "end-to-end-ocr"]], "Enforcement": [[2, "enforcement"]], "Enforcement Guidelines": [[2, "enforcement-guidelines"]], "Enforcement Responsibilities": [[2, "enforcement-responsibilities"]], "Export to ONNX": [[18, "export-to-onnx"]], "Feature requests & bug report": [[3, "feature-requests-bug-report"]], "Feedback": [[3, "feedback"]], "File reading": [[8, "file-reading"]], "Half-precision": [[18, "half-precision"]], "Installation": [[4, null]], "Integrate contributions into your pipeline": [[16, null]], "Let\u2019s connect": [[3, "let-s-connect"]], "Line": [[8, "line"]], "Loading from Huggingface Hub": [[15, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[13, "loading-your-custom-trained-model"]], "Loading your custom trained orientation classification model": [[13, "loading-your-custom-trained-orientation-classification-model"]], "Main Features": [[5, "main-features"]], "Model optimization": [[18, "model-optimization"]], "Model zoo": [[5, "model-zoo"]], "Modifying the documentation": [[3, "modifying-the-documentation"]], "Naming conventions": [[15, "naming-conventions"]], "OCR": [[17, "ocr"]], "Object Detection": [[17, "object-detection"]], "Our Pledge": [[2, "our-pledge"]], "Our Standards": [[2, "our-standards"]], "Page": [[8, "page"]], "Preparing your model for inference": [[18, null]], "Prerequisites": [[4, "prerequisites"]], "Pretrained community models": [[15, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[15, "pushing-to-the-huggingface-hub"]], "Questions": [[3, "questions"]], "Recognition": [[15, "recognition"], [17, "recognition"]], "Recognition predictors": [[19, "recognition-predictors"]], "Returns:": [[7, "returns"], [8, "returns"], [8, "id11"], [8, "id13"], [8, "id15"], [8, "id19"], [8, "id23"], [8, "id27"], [8, "id31"], [9, "returns"], [9, "id6"], [9, "id11"], [9, "id16"], [9, "id20"], [9, "id24"], [9, "id29"], [9, "id34"], [9, "id39"], [9, "id44"], [9, "id49"], [9, "id53"], [9, "id57"], [9, "id62"], [9, "id66"], [9, "id71"], [9, "id76"], [9, "id80"], [9, "id84"], [9, "id88"], [9, "id93"], [9, "id98"], [9, "id102"], [9, "id107"], [9, "id112"], [9, "id117"], [9, "id122"], [9, "id126"], [9, "id130"], [9, "id135"], [9, "id140"], [9, "id145"], [9, "id149"], [9, "id153"], [9, "id158"], [9, "id162"], [9, "id166"], [9, "id168"], [9, "id170"], [9, "id172"], [11, "returns"]], "Scope": [[2, "scope"]], "Share your model with the community": [[15, null]], "Supported Vocabs": [[7, "supported-vocabs"]], "Supported contribution modules": [[6, "supported-contribution-modules"]], "Supported datasets": [[5, "supported-datasets"]], "Supported transformations": [[10, "supported-transformations"]], "Synthetic dataset generator": [[7, "synthetic-dataset-generator"], [17, "synthetic-dataset-generator"]], "Task evaluation": [[11, "task-evaluation"]], "Text Detection": [[19, "text-detection"]], "Text Recognition": [[19, "text-recognition"]], "Text detection models": [[5, "text-detection-models"]], "Text recognition models": [[5, "text-recognition-models"]], "Train your own model": [[13, null]], "Two-stage approaches": [[19, "two-stage-approaches"]], "Unit tests": [[3, "unit-tests"]], "Use your own datasets": [[17, "use-your-own-datasets"]], "Using your ONNX exported model": [[18, "using-your-onnx-exported-model"]], "Via Conda (Only for Linux)": [[4, "via-conda-only-for-linux"]], "Via Git": [[4, "via-git"]], "Via Python Package": [[4, "via-python-package"]], "Visualization": [[11, "visualization"]], "What should I do with the output?": [[19, "what-should-i-do-with-the-output"]], "Word": [[8, "word"]], "docTR Notebooks": [[12, null]], "docTR Vocabs": [[7, "id62"]], "docTR: Document Text Recognition": [[5, null]], "doctr.contrib": [[6, null]], "doctr.datasets": [[7, null], [7, "datasets"]], "doctr.io": [[8, null]], "doctr.models": [[9, null]], "doctr.models.classification": [[9, "doctr-models-classification"]], "doctr.models.detection": [[9, "doctr-models-detection"]], "doctr.models.factory": [[9, "doctr-models-factory"]], "doctr.models.recognition": [[9, "doctr-models-recognition"]], "doctr.models.zoo": [[9, "doctr-models-zoo"]], "doctr.transforms": [[10, null]], "doctr.utils": [[11, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.10.0 (2024-10-21)": [[0, "v0-10-0-2024-10-21"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]], "v0.7.0 (2023-09-09)": [[0, "v0-7-0-2023-09-09"]], "v0.8.0 (2024-02-28)": [[0, "v0-8-0-2024-02-28"]], "v0.8.1 (2024-03-04)": [[0, "v0-8-1-2024-03-04"]], "v0.9.0 (2024-08-08)": [[0, "v0-9-0-2024-08-08"]]}, "docnames": ["changelog", "community/resources", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/contrib", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_contrib_modules", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "community/resources.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/contrib.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_contrib_modules.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[8, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[8, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[10, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[7, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[10, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[10, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[7, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[7, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[9, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[7, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[7, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[8, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[8, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[7, "doctr.datasets.encode_sequences", false]], "fast_base() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_base", false]], "fast_small() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_small", false]], "fast_tiny() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_tiny", false]], "from_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[7, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[10, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[10, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[7, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[7, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[7, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[7, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[7, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[9, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[10, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[8, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet34() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[7, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_crop_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_crop_orientation", false]], "mobilenet_v3_small_page_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_page_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[10, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[9, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[7, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[10, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[8, "doctr.io.Page", false]], "page_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.page_orientation_predictor", false]], "parseq() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[10, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[10, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[10, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[10, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[10, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[10, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[10, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[10, "doctr.transforms.RandomJpegQuality", false]], "randomresize (class in doctr.transforms)": [[10, "doctr.transforms.RandomResize", false]], "randomrotate (class in doctr.transforms)": [[10, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[10, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[10, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[8, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[8, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[8, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[7, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[10, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[8, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[8, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[7, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[7, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[7, "doctr.datasets.SVT", false]], "synthtext (class in doctr.datasets)": [[7, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.TextMatch", false]], "textnet_base() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_base", false]], "textnet_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_small", false]], "textnet_tiny() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_tiny", false]], "togray (class in doctr.transforms)": [[10, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[11, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_small", false]], "wildreceipt (class in doctr.datasets)": [[7, "doctr.datasets.WILDRECEIPT", false]], "word (class in doctr.io)": [[8, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[7, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[7, 0, 1, "", "CORD"], [7, 0, 1, "", "CharacterGenerator"], [7, 0, 1, "", "DetectionDataset"], [7, 0, 1, "", "DocArtefacts"], [7, 0, 1, "", "FUNSD"], [7, 0, 1, "", "IC03"], [7, 0, 1, "", "IC13"], [7, 0, 1, "", "IIIT5K"], [7, 0, 1, "", "IIITHWS"], [7, 0, 1, "", "IMGUR5K"], [7, 0, 1, "", "MJSynth"], [7, 0, 1, "", "OCRDataset"], [7, 0, 1, "", "RecognitionDataset"], [7, 0, 1, "", "SROIE"], [7, 0, 1, "", "SVHN"], [7, 0, 1, "", "SVT"], [7, 0, 1, "", "SynthText"], [7, 0, 1, "", "WILDRECEIPT"], [7, 0, 1, "", "WordGenerator"], [7, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[7, 0, 1, "", "DataLoader"]], "doctr.io": [[8, 0, 1, "", "Artefact"], [8, 0, 1, "", "Block"], [8, 0, 1, "", "Document"], [8, 0, 1, "", "DocumentFile"], [8, 0, 1, "", "Line"], [8, 0, 1, "", "Page"], [8, 0, 1, "", "Word"], [8, 1, 1, "", "decode_img_as_tensor"], [8, 1, 1, "", "read_html"], [8, 1, 1, "", "read_img_as_numpy"], [8, 1, 1, "", "read_img_as_tensor"], [8, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[8, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[8, 2, 1, "", "from_images"], [8, 2, 1, "", "from_pdf"], [8, 2, 1, "", "from_url"]], "doctr.io.Page": [[8, 2, 1, "", "show"]], "doctr.models": [[9, 1, 1, "", "kie_predictor"], [9, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[9, 1, 1, "", "crop_orientation_predictor"], [9, 1, 1, "", "magc_resnet31"], [9, 1, 1, "", "mobilenet_v3_large"], [9, 1, 1, "", "mobilenet_v3_large_r"], [9, 1, 1, "", "mobilenet_v3_small"], [9, 1, 1, "", "mobilenet_v3_small_crop_orientation"], [9, 1, 1, "", "mobilenet_v3_small_page_orientation"], [9, 1, 1, "", "mobilenet_v3_small_r"], [9, 1, 1, "", "page_orientation_predictor"], [9, 1, 1, "", "resnet18"], [9, 1, 1, "", "resnet31"], [9, 1, 1, "", "resnet34"], [9, 1, 1, "", "resnet50"], [9, 1, 1, "", "textnet_base"], [9, 1, 1, "", "textnet_small"], [9, 1, 1, "", "textnet_tiny"], [9, 1, 1, "", "vgg16_bn_r"], [9, 1, 1, "", "vit_b"], [9, 1, 1, "", "vit_s"]], "doctr.models.detection": [[9, 1, 1, "", "db_mobilenet_v3_large"], [9, 1, 1, "", "db_resnet50"], [9, 1, 1, "", "detection_predictor"], [9, 1, 1, "", "fast_base"], [9, 1, 1, "", "fast_small"], [9, 1, 1, "", "fast_tiny"], [9, 1, 1, "", "linknet_resnet18"], [9, 1, 1, "", "linknet_resnet34"], [9, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[9, 1, 1, "", "from_hub"], [9, 1, 1, "", "login_to_hub"], [9, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[9, 1, 1, "", "crnn_mobilenet_v3_large"], [9, 1, 1, "", "crnn_mobilenet_v3_small"], [9, 1, 1, "", "crnn_vgg16_bn"], [9, 1, 1, "", "master"], [9, 1, 1, "", "parseq"], [9, 1, 1, "", "recognition_predictor"], [9, 1, 1, "", "sar_resnet31"], [9, 1, 1, "", "vitstr_base"], [9, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[10, 0, 1, "", "ChannelShuffle"], [10, 0, 1, "", "ColorInversion"], [10, 0, 1, "", "Compose"], [10, 0, 1, "", "GaussianBlur"], [10, 0, 1, "", "GaussianNoise"], [10, 0, 1, "", "LambdaTransformation"], [10, 0, 1, "", "Normalize"], [10, 0, 1, "", "OneOf"], [10, 0, 1, "", "RandomApply"], [10, 0, 1, "", "RandomBrightness"], [10, 0, 1, "", "RandomContrast"], [10, 0, 1, "", "RandomCrop"], [10, 0, 1, "", "RandomGamma"], [10, 0, 1, "", "RandomHorizontalFlip"], [10, 0, 1, "", "RandomHue"], [10, 0, 1, "", "RandomJpegQuality"], [10, 0, 1, "", "RandomResize"], [10, 0, 1, "", "RandomRotate"], [10, 0, 1, "", "RandomSaturation"], [10, 0, 1, "", "RandomShadow"], [10, 0, 1, "", "Resize"], [10, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[11, 0, 1, "", "DetectionMetric"], [11, 0, 1, "", "LocalizationConfusion"], [11, 0, 1, "", "OCRMetric"], [11, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.visualization": [[11, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [2, 8, 9, 11, 15, 18], "0": [2, 4, 7, 10, 11, 13, 16, 17, 19], "00": 19, "01": 19, "0123456789": 7, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "02562": 9, "03": 19, "035": 19, "0361328125": 19, "04": 19, "05": 19, "06": 19, "06640625": 19, "07": 19, "08": [10, 19], "09": 19, "0966796875": 19, "1": [7, 8, 9, 10, 11, 13, 17, 19], "10": [4, 7, 11, 19], "100": [7, 10, 11, 17, 19], "1000": 19, "101": 7, "1024": [9, 13, 19], "104": 7, "106": 7, "108": 7, "1095": 17, "11": 19, "110": 11, "1107": 17, "114": 7, "115": 7, "1156": 17, "116": 7, "118": 7, "11800h": 19, "11th": 19, "12": 19, "120": 7, "123": 7, "126": 7, "1268": 17, "128": [9, 13, 18, 19], "13": 19, "130": 7, "13068": 17, "131": 7, "1337891": 17, "1357421875": 19, "1396484375": 19, "14": 19, "1420": 19, "14470v1": 7, "149": 17, "15": 19, "150": [11, 19], "1552": 19, "16": [9, 18, 19], "1630859375": 19, "1684": 19, "16x16": 9, "17": 19, "1778": 19, "1782": 19, "18": [9, 19], "185546875": 19, "1900": 19, "1910": 9, "19342": 17, "19370": 17, "195": 7, "19598": 17, "199": 19, "1999": 19, "2": [4, 5, 7, 8, 9, 10, 16, 19], "20": 19, "200": 11, "2000": 17, "2003": [5, 7], "2012": 7, "2013": [5, 7], "2015": 7, "2019": 5, "2023": 1, "207901": 17, "21": 19, "2103": 7, "2186": 17, "21888": 17, "22": 19, "224": [9, 10], "225": 10, "22672": 17, "229": [10, 17], "23": 19, "233": 17, "236": 7, "24": 19, "246": 17, "249": 17, "25": 19, "2504": 19, "255": [8, 9, 10, 11, 19], "256": 9, "257": 17, "26": 19, "26032": 17, "264": 13, "27": 19, "2700": 17, "2710": 19, "2749": 13, "28": 19, "287": 13, "29": 19, "296": 13, "299": 13, "2d": 19, "3": [4, 5, 8, 9, 10, 11, 18, 19], "30": 19, "300": 17, "3000": 17, "301": 13, "30595": 19, "30ghz": 19, "31": 9, "32": [7, 9, 10, 13, 17, 18, 19], "3232421875": 19, "33": [10, 19], "33402": 17, "33608": 17, "34": [9, 19], "340": 19, "3456": 19, "3515625": 19, "36": 19, "360": 17, "37": [7, 19], "38": 19, "39": 19, "4": [9, 10, 11, 19], "40": 19, "406": 10, "41": 19, "42": 19, "43": 19, "44": 19, "45": 19, "456": 10, "46": 19, "47": 19, "472": 17, "48": [7, 19], "485": 10, "49": 19, "49377": 17, "5": [7, 10, 11, 16, 19], "50": [9, 17, 19], "51": 19, "51171875": 19, "512": 9, "52": [7, 19], "529": 19, "53": 19, "54": 19, "540": 19, "5478515625": 19, "55": 19, "56": 19, "57": 19, "58": [7, 19], "580": 19, "5810546875": 19, "583": 19, "59": 19, "597": 19, "5k": [5, 7], "5m": 19, "6": [10, 19], "60": 10, "600": [9, 11, 19], "61": 19, "62": 19, "626": 17, "63": 19, "64": [9, 10, 19], "641": 19, "647": 17, "65": 19, "66": 19, "67": 19, "68": 19, "69": 19, "693": 13, "694": 13, "695": 13, "6m": 19, "7": 19, "70": [7, 11, 19], "707470": 17, "71": [7, 19], "7100000": 17, "7141797": 17, "7149": 17, "72": 19, "72dpi": 8, "73": 19, "73257": 17, "74": 19, "75": [10, 19], "7581382": 17, "76": 19, "77": 19, "772": 13, "772875": 17, "78": 19, "785": 13, "79": 19, "793533": 17, "796": 17, "798": 13, "7m": 19, "8": [9, 10, 19], "80": 19, "800": [9, 11, 17, 19], "81": 19, "82": 19, "83": 19, "84": 19, "849": 17, "85": 19, "8564453125": 19, "857": 19, "85875": 17, "86": 19, "8603515625": 19, "87": 19, "8707": 17, "88": 19, "89": 19, "9": [10, 19], "90": 19, "90k": 7, "90kdict32px": 7, "91": 19, "914085328578949": 19, "92": 19, "93": 19, "94": [7, 19], "95": [11, 19], "9578408598899841": 19, "96": 19, "97": 19, "98": 19, "99": 19, "9949972033500671": 19, "A": [2, 3, 5, 7, 8, 9, 12, 18], "As": 3, "Be": 19, "Being": 2, "By": 14, "For": [2, 3, 4, 13, 19], "If": [3, 8, 9, 13, 19], "In": [3, 7, 17], "It": [10, 15, 16, 18], "Its": [5, 9], "No": [2, 19], "Of": 7, "Or": [16, 18], "The": [2, 3, 7, 8, 11, 14, 16, 17, 18, 19], "Then": 9, "To": [3, 4, 14, 15, 16, 18, 19], "_": [2, 7, 9], "__call__": 19, "_build": 3, "_i": 11, "ab": 7, "abc": 18, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "abdef": [7, 17], "abl": [17, 19], "about": [2, 17, 19], "abov": 19, "abstract": 1, "abstractdataset": 7, "abus": 2, "accept": 2, "access": [5, 8, 17, 19], "account": [2, 15], "accur": 19, "accuraci": 11, "achiev": 18, "act": 2, "action": 2, "activ": 5, "ad": [3, 9, 10], "adapt": 2, "add": [10, 11, 15, 19], "add_hook": 19, "add_label": 11, "addit": [3, 4, 8, 16, 19], "addition": [3, 19], "address": [2, 8], "adjust": 10, "advanc": 2, "advantag": 18, "advis": 3, "aesthet": [5, 7], "affect": 2, "after": [15, 19], "ag": 2, "again": 9, "aggreg": [11, 17], "aggress": 2, "align": [2, 8, 10], "all": [2, 3, 6, 7, 8, 10, 11, 16, 17, 19], "allow": [2, 18], "along": 19, "alreadi": [3, 18], "also": [2, 9, 15, 16, 17, 19], "alwai": 17, "an": [2, 3, 5, 7, 8, 9, 11, 16, 18, 19], "analysi": [8, 16], "ancient_greek": 7, "andrej": 1, "angl": [8, 10], "ani": [2, 7, 8, 9, 10, 11, 18, 19], "annot": 7, "anot": 17, "anoth": [9, 13, 17], "answer": 2, "anyascii": 11, "anyon": 5, "anyth": 16, "api": [3, 5], "apolog": 2, "apologi": 2, "app": 3, "appear": 2, "appli": [2, 7, 10], "applic": [5, 9], "appoint": 2, "appreci": 15, "appropri": [2, 3, 19], "ar": [2, 3, 4, 6, 7, 8, 10, 11, 12, 16, 17, 19], "arab": 7, "arabic_diacrit": 7, "arabic_lett": 7, "arabic_punctu": 7, "arbitrarili": [5, 9], "arch": [9, 15], "architectur": [5, 9, 15, 16], "area": 19, "argument": [7, 8, 9, 11, 13, 19], "around": 2, "arrai": [8, 10, 11], "art": [5, 16], "artefact": [11, 16, 19], "artefact_typ": 8, "articl": 1, "artifici": [5, 7], "arxiv": [7, 9], "asarrai": 11, "ascii_lett": 7, "aspect": [5, 9, 10, 19], "assess": 11, "assign": 11, "associ": 8, "assum": 9, "assume_straight_pag": [9, 13, 19], "astyp": [9, 11, 19], "attack": 2, "attend": [5, 9], "attent": [2, 9], "autom": 5, "automat": 19, "autoregress": [5, 9], "avail": [2, 5, 6, 10], "averag": [10, 19], "avoid": [2, 4], "aw": [5, 19], "awar": 19, "azur": 19, "b": [9, 11, 19], "b_j": 11, "back": 3, "backbon": 9, "backend": 19, "background": 17, "bangla": 7, "bar": 16, "bar_cod": 17, "baranovskij": 1, "base": [5, 9, 16], "baselin": [5, 9, 19], "batch": [7, 9, 10, 16, 17, 19], "batch_siz": [7, 9, 13, 16, 17, 18], "bblanchon": 4, "bbox": 19, "becaus": 14, "been": [3, 11, 17, 19], "befor": [7, 9, 10, 19], "begin": 11, "behavior": [2, 19], "being": [11, 19], "belong": 19, "benchmark": 19, "best": [1, 2], "better": [12, 19], "between": [10, 11, 19], "bgr": 8, "bilinear": 10, "bin_thresh": 19, "binar": [5, 9, 19], "binari": [8, 18, 19], "bit": 18, "block": [11, 19], "block_1_1": 19, "blur": 10, "bmvc": 7, "bn": 15, "bodi": [2, 19], "bool": [7, 8, 9, 10, 11], "boolean": [9, 19], "both": [5, 7, 10, 17, 19], "bottom": [9, 19], "bound": [7, 8, 9, 10, 11, 16, 17, 19], "box": [7, 8, 9, 10, 11, 16, 17, 19], "box_thresh": 19, "bright": 10, "browser": [3, 5], "build": [3, 4, 18], "built": 3, "byte": [8, 19], "c": [4, 8, 11], "c_j": 11, "cach": [3, 7, 14], "cache_sampl": 7, "call": 18, "callabl": [7, 10], "can": [3, 4, 13, 14, 15, 16, 17, 19], "capabl": [3, 12, 19], "case": [7, 11], "cf": 19, "cfg": 19, "challeng": 7, "challenge2_test_task12_imag": 7, "challenge2_test_task1_gt": 7, "challenge2_training_task12_imag": 7, "challenge2_training_task1_gt": 7, "chang": [14, 19], "channel": [2, 3, 8, 10], "channel_prior": 4, "channelshuffl": 10, "charact": [5, 7, 8, 11, 17, 19], "charactergener": [7, 17], "characterist": 2, "charg": 19, "charset": 19, "chart": 8, "check": [3, 15, 19], "checkpoint": 9, "chip": 4, "christian": 1, "ci": 3, "clarifi": 2, "clariti": 2, "class": [2, 7, 8, 10, 11, 19], "class_nam": 13, "classif": [17, 19], "classmethod": 8, "clear": 3, "clone": 4, "close": 3, "co": 15, "code": [5, 8, 16], "codecov": 3, "colab": 12, "collate_fn": 7, "collect": [8, 16], "color": 10, "colorinvers": 10, "column": 8, "com": [2, 4, 8, 9, 15], "combin": 19, "command": [3, 16], "comment": 2, "commit": 2, "common": [2, 10, 11, 18], "commun": 2, "compar": 5, "comparison": [11, 19], "competit": 7, "compil": [12, 19], "complaint": 2, "complementari": 11, "complet": 3, "compon": 19, "compos": [7, 19], "comprehens": 19, "comput": [7, 11, 18, 19], "conf_threshold": 16, "confid": [8, 19], "config": [4, 9], "configur": 9, "confus": 11, "consecut": [10, 19], "consequ": 2, "consid": [2, 3, 7, 8, 11, 19], "consist": 19, "consolid": [5, 7], "constant": 10, "construct": 2, "contact": 2, "contain": [1, 6, 7, 12, 17, 19], "content": [7, 8, 19], "context": 9, "contib": 4, "continu": 2, "contrast": 10, "contrast_factor": 10, "contrib": [4, 16], "contribut": 2, "contributor": 3, "convers": 8, "convert": [8, 10], "convolut": 9, "cool": 1, "coordin": [8, 19], "cord": [5, 7, 17, 19], "core": [11, 19], "corner": 19, "correct": 10, "correspond": [4, 8, 10, 19], "could": [2, 16], "counterpart": 11, "cover": 3, "coverag": 3, "cpu": [5, 13, 18], "creat": [1, 15], "crnn": [5, 9, 15], "crnn_mobilenet_v3_larg": [9, 15, 19], "crnn_mobilenet_v3_smal": [9, 18, 19], "crnn_vgg16_bn": [9, 13, 15, 19], "crop": [8, 9, 10, 13, 17, 19], "crop_orient": [8, 19], "crop_orientation_predictor": [9, 13], "crop_param": 13, "cuda": 18, "currenc": 7, "current": [3, 13, 19], "custom": [15, 16, 18, 19], "custom_crop_orientation_model": 13, "custom_page_orientation_model": 13, "customhook": 19, "cvit": 5, "czczup": 9, "czech": 7, "d": [7, 17], "danish": 7, "data": [5, 7, 8, 10, 11, 13, 15], "dataload": 17, "dataset": [9, 13, 19], "dataset_info": 7, "date": [13, 19], "db": 15, "db_mobilenet_v3_larg": [9, 15, 19], "db_resnet34": 19, "db_resnet50": [9, 13, 15, 19], "dbnet": [5, 9], "deal": [12, 19], "decis": 2, "decod": 8, "decode_img_as_tensor": 8, "dedic": 18, "deem": 2, "deep": [9, 19], "def": 19, "default": [4, 8, 13, 14, 19], "defer": 17, "defin": [11, 18], "degre": [8, 10, 19], "degress": 8, "delet": 3, "delimit": 19, "delta": 10, "demo": [3, 5], "demonstr": 2, "depend": [3, 4, 5, 19], "deploi": 3, "deploy": 5, "derogatori": 2, "describ": 9, "descript": 12, "design": 10, "desir": 8, "det_arch": [9, 13, 15, 18], "det_b": 19, "det_model": [13, 15, 18], "det_param": 13, "det_predictor": [13, 19], "detail": [13, 19], "detect": [1, 7, 8, 11, 12, 13, 16], "detect_languag": 9, "detect_orient": [9, 13, 19], "detection_predictor": [9, 19], "detection_task": [7, 17], "detectiondataset": [7, 17], "detectionmetr": 11, "detectionpredictor": [9, 13], "detector": [5, 9, 16], "deterior": 9, "determin": 2, "dev": [3, 14], "develop": 4, "deviat": 10, "devic": 18, "dict": [8, 11, 19], "dictionari": [8, 11], "differ": 2, "differenti": [5, 9], "digit": [5, 7, 17], "dimens": [8, 11, 19], "dimension": 10, "direct": 7, "directli": [15, 19], "directori": [3, 14], "disabl": [2, 14, 19], "disable_crop_orient": 19, "disable_page_orient": 19, "disclaim": 19, "discuss": 3, "disparag": 2, "displai": [8, 11], "display_artefact": 11, "distribut": 10, "div": 19, "divers": 2, "divid": 8, "do": [3, 4, 9], "doc": [3, 8, 16, 18, 19], "docartefact": [7, 17], "docstr": 3, "doctr": [1, 4, 13, 14, 15, 16, 17, 18, 19], "doctr_cache_dir": 14, "doctr_multiprocessing_dis": 14, "document": [1, 7, 9, 11, 12, 13, 16, 17, 18, 19], "documentbuild": 19, "documentfil": [8, 13, 15, 16, 18], "doesn": 18, "don": [13, 19], "done": 10, "download": [7, 17], "downsiz": 9, "draw": 10, "drop": 7, "drop_last": 7, "dtype": [8, 9, 10, 11, 18], "dual": [5, 7], "dummi": 15, "dummy_img": 19, "dummy_input": 18, "dure": 2, "dutch": 7, "dynam": [7, 16], "dynamic_seq_length": 7, "e": [2, 3, 4, 8, 9], "each": [5, 7, 8, 9, 10, 11, 17, 19], "eas": 3, "easi": [5, 11, 15, 18], "easili": [8, 11, 13, 15, 17, 19], "econom": 2, "edit": 2, "educ": 2, "effect": 19, "effici": [3, 5, 7, 9], "either": [11, 19], "element": [7, 8, 9, 19], "els": [3, 16], "email": 2, "empathi": 2, "en": 19, "enabl": [7, 8], "enclos": 8, "encod": [5, 7, 8, 9, 19], "encode_sequ": 7, "encount": 3, "encrypt": 8, "end": [5, 7, 9, 11], "english": [7, 17], "enough": [3, 19], "ensur": 3, "entri": 7, "environ": [2, 14], "eo": 7, "equiv": 19, "estim": 9, "etc": [8, 16], "ethnic": 2, "evalu": [17, 19], "event": 2, "everyon": 2, "everyth": [3, 19], "exact": [11, 19], "exampl": [2, 3, 5, 7, 9, 15, 19], "exchang": 18, "execut": 19, "exist": 15, "expand": 10, "expect": [8, 10, 11], "experi": 2, "explan": [2, 19], "explicit": 2, "exploit": [5, 9], "export": [8, 9, 11, 12, 16, 19], "export_as_straight_box": [9, 19], "export_as_xml": 19, "export_model_to_onnx": 18, "express": [2, 10], "extens": 8, "extern": [2, 17], "extract": [1, 5, 7], "extractor": 9, "f_": 11, "f_a": 11, "factor": 10, "fair": 2, "fairli": 2, "fals": [7, 8, 9, 10, 11, 13, 19], "faq": 2, "fascan": 15, "fast": [5, 7, 9], "fast_bas": [9, 19], "fast_smal": [9, 19], "fast_tini": [9, 19], "faster": [5, 9, 18], "fasterrcnn_mobilenet_v3_large_fpn": 9, "favorit": 19, "featur": [4, 9, 11, 12, 13, 16], "feedback": 2, "feel": [3, 15], "felix92": 15, "few": [18, 19], "figsiz": 11, "figur": [11, 16], "file": [3, 7], "final": 9, "find": [3, 17], "fine": 1, "finnish": 7, "first": [3, 7], "firsthand": 7, "fit": [9, 19], "flag": 19, "flip": 10, "float": [8, 10, 11, 18], "float32": [8, 9, 10, 18], "fn": 10, "focu": 15, "focus": [2, 7], "folder": 7, "follow": [2, 3, 4, 7, 10, 11, 13, 14, 15, 16, 19], "font": 7, "font_famili": 7, "foral": 11, "forc": 3, "forg": 4, "form": [5, 7, 19], "format": [8, 11, 13, 17, 18, 19], "forpost": [5, 7], "forum": 3, "found": 1, "fp16": 18, "frac": 11, "framework": [4, 15, 17, 19], "free": [2, 3, 15], "french": [7, 13, 15, 19], "friendli": 5, "from": [1, 2, 5, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19], "from_hub": [9, 15], "from_imag": [8, 15, 16, 18], "from_pdf": 8, "from_url": 8, "full": [7, 11, 19], "function": [7, 10, 11, 16], "funsd": [5, 7, 17, 19], "further": 17, "futur": 7, "g": [8, 9], "g_": 11, "g_x": 11, "gallagh": 1, "gamma": 10, "gaussian": 10, "gaussianblur": 10, "gaussiannois": 10, "gen": 19, "gender": 2, "gener": [3, 5, 8, 9], "generic_cyrillic_lett": 7, "geometri": [5, 8, 19], "geq": 11, "german": [7, 13, 15], "get": [18, 19], "git": 15, "github": [3, 4, 9, 15], "give": [2, 16], "given": [7, 8, 10, 11, 19], "global": 9, "go": 19, "good": 18, "googl": 3, "googlevis": 5, "gpu": [5, 16, 18], "gracefulli": 2, "graph": [5, 7, 8], "grayscal": 10, "ground": 11, "groung": 11, "group": [5, 19], "gt": 11, "gt_box": 11, "gt_label": 11, "guid": 3, "guidanc": 17, "gvision": 19, "h": [8, 9, 10], "h_": 11, "ha": [3, 7, 11, 17], "handl": [12, 17, 19], "handwrit": 7, "handwritten": 17, "harass": 2, "hardwar": 19, "harm": 2, "hat": 11, "have": [2, 3, 11, 13, 15, 17, 18, 19], "head": [9, 19], "healthi": 2, "hebrew": 7, "height": [8, 10], "hello": [11, 19], "help": 18, "here": [6, 10, 12, 16, 17, 19], "hf": 9, "hf_hub_download": 9, "high": 8, "higher": [4, 7, 19], "hindi": 7, "hindi_digit": 7, "hocr": 19, "hook": 19, "horizont": [8, 10, 19], "hous": 7, "how": [1, 3, 12, 13, 15, 17], "howev": 17, "hsv": 10, "html": [2, 3, 4, 8, 19], "http": [2, 4, 7, 8, 9, 15, 19], "hub": 9, "hue": 10, "huggingfac": 9, "hw": 7, "i": [2, 3, 7, 8, 9, 10, 11, 14, 15, 16, 17, 18], "i7": 19, "ibrahimov": 1, "ic03": [5, 7, 17], "ic13": [5, 7, 17], "icdar": [5, 7], "icdar2019": 7, "id": 19, "ident": 2, "identifi": 5, "iiit": [5, 7], "iiit5k": [7, 17], "iiithw": [5, 7, 17], "imag": [1, 5, 7, 8, 9, 10, 11, 15, 16, 17, 19], "imagenet": 9, "imageri": 2, "images_90k_norm": 7, "img": [7, 10, 17, 18], "img_cont": 8, "img_fold": [7, 17], "img_path": 8, "img_transform": 7, "imgur5k": [5, 7, 17], "imgur5k_annot": 7, "imlist": 7, "impact": 2, "implement": [7, 8, 9, 10, 11, 19], "import": [7, 8, 9, 10, 11, 13, 15, 16, 17, 18, 19], "improv": 9, "inappropri": 2, "incid": 2, "includ": [2, 7, 17, 18], "inclus": 2, "increas": 10, "independ": 10, "index": [3, 8], "indic": 11, "individu": 2, "infer": [5, 9, 10, 16, 19], "inform": [1, 2, 3, 5, 7, 17], "input": [3, 8, 9, 10, 18, 19], "input_crop": 9, "input_pag": [9, 11, 19], "input_shap": 18, "input_tensor": 9, "inspir": [2, 10], "instal": [15, 16, 18], "instanc": [2, 19], "instanti": [9, 19], "instead": [7, 8, 9], "insult": 2, "int": [7, 8, 9, 10], "int64": 11, "integ": 11, "integr": [1, 5, 15, 17], "intel": 19, "interact": [2, 8, 11], "interfac": [15, 18], "interoper": 18, "interpol": 10, "interpret": [7, 8], "intersect": 11, "invert": 10, "investig": 2, "invis": 2, "involv": [2, 19], "io": [13, 15, 16, 18], "iou": 11, "iou_thresh": 11, "iou_threshold": 16, "irregular": [5, 9, 17], "isn": 7, "issu": [2, 3, 15], "italian": 7, "iter": [7, 10, 17, 19], "its": [8, 9, 10, 11, 17, 19], "itself": [9, 15], "j": 11, "jame": 1, "job": 3, "join": 3, "jpeg": 10, "jpegqual": 10, "jpg": [7, 8, 15, 18], "json": [7, 17, 19], "json_output": 19, "jump": 3, "just": 2, "kei": [5, 7], "kera": [9, 18], "kernel": [5, 9, 10], "kernel_shap": 10, "keywoard": 9, "keyword": [7, 8, 9, 11], "kie": [9, 13], "kie_predictor": [9, 13], "kiepredictor": 9, "kind": 2, "know": [3, 18], "kwarg": [7, 8, 9, 11], "l": 11, "l_j": 11, "label": [7, 11, 16, 17], "label_fil": [7, 17], "label_fold": 7, "label_path": [7, 17], "labels_path": [7, 17], "ladder": 2, "lambda": 10, "lambdatransform": 10, "lang": 19, "languag": [2, 5, 7, 8, 9, 15, 19], "larg": [9, 15], "largest": 11, "last": [4, 7], "latenc": 9, "later": 3, "latest": 19, "latin": 7, "layer": 18, "layout": 19, "lead": 2, "leader": 2, "learn": [2, 5, 9, 18, 19], "least": 4, "left": [11, 19], "legacy_french": 7, "length": [7, 19], "less": [18, 19], "level": [2, 7, 11, 19], "leverag": 12, "lf": 15, "librari": [3, 4, 12, 13], "light": 5, "lightweight": 18, "like": 2, "limits_": 11, "line": [5, 9, 11, 19], "line_1_1": 19, "link": 13, "linknet": [5, 9], "linknet_resnet18": [9, 13, 18, 19], "linknet_resnet34": [9, 18, 19], "linknet_resnet50": [9, 19], "list": [7, 8, 10, 11, 15], "ll": 11, "load": [5, 7, 9, 16, 18], "load_state_dict": 13, "load_weight": 13, "loc_pr": 19, "local": [3, 5, 7, 9, 11, 17, 19], "localis": 7, "localizationconfus": 11, "locat": [3, 8, 19], "login": 9, "login_to_hub": [9, 15], "logo": [8, 16, 17], "love": 15, "lower": [10, 11, 19], "m": [3, 11, 19], "m1": 4, "macbook": 4, "machin": 18, "made": 5, "magc_resnet31": 9, "mai": [2, 3], "mail": 2, "main": 12, "maintain": 5, "mainten": 3, "make": [2, 3, 11, 13, 14, 15, 18, 19], "mani": [17, 19], "manipul": 19, "map": [7, 9], "map_loc": 13, "master": [5, 9, 19], "match": [11, 19], "mathcal": 11, "matplotlib": [8, 11], "max": [7, 10, 11], "max_angl": 10, "max_area": 10, "max_char": [7, 17], "max_delta": 10, "max_gain": 10, "max_gamma": 10, "max_qual": 10, "max_ratio": 10, "maximum": [7, 10], "maxval": [9, 10], "mbox": 11, "mean": [10, 11, 13], "meaniou": 11, "meant": [8, 18], "measur": 19, "media": 2, "median": 9, "meet": 13, "member": 2, "memori": [14, 18], "mention": 19, "merg": 7, "messag": 3, "meta": 19, "metadata": 18, "metal": 4, "method": [8, 10, 19], "metric": [11, 19], "middl": 19, "might": [18, 19], "min": 10, "min_area": 10, "min_char": [7, 17], "min_gain": 10, "min_gamma": 10, "min_qual": 10, "min_ratio": 10, "min_val": 10, "minde": [1, 2, 4, 5, 9], "minim": [3, 5], "minimalist": [5, 9], "minimum": [4, 7, 10, 11, 19], "minval": 10, "miss": 4, "mistak": 2, "mixed_float16": 18, "mixed_precis": 18, "mjsynth": [5, 7, 17], "mnt": 7, "mobilenet": [9, 15], "mobilenet_v3_larg": 9, "mobilenet_v3_large_r": 9, "mobilenet_v3_smal": [9, 13], "mobilenet_v3_small_crop_orient": [9, 13], "mobilenet_v3_small_page_orient": [9, 13], "mobilenet_v3_small_r": 9, "mobilenetv3": 9, "modal": [5, 7], "mode": 4, "model": [7, 11, 14, 16, 17], "model_nam": [9, 15, 18], "model_path": [16, 18], "moder": 2, "modif": 3, "modifi": [9, 14, 19], "modul": [4, 8, 9, 10, 11, 19], "more": [3, 17, 19], "moscardi": 1, "most": 19, "mozilla": 2, "multi": [5, 9], "multilingu": [7, 15], "multipl": [7, 8, 10, 19], "multipli": 10, "multiprocess": 14, "my": 9, "my_awesome_model": 15, "my_hook": 19, "n": [7, 11], "name": [7, 9, 18, 19], "nation": 2, "natur": [2, 5, 7], "ndarrai": [7, 8, 10, 11], "necessari": [4, 13, 14], "need": [3, 4, 7, 11, 13, 14, 15, 16, 19], "neg": 10, "nest": 19, "netraj": 1, "network": [5, 7, 9, 18], "neural": [5, 7, 9, 18], "new": [3, 11], "next": [7, 17], "nois": 10, "noisi": [5, 7], "non": [5, 7, 8, 9, 10, 11], "none": [7, 8, 9, 10, 11, 19], "normal": [9, 10], "norwegian": 7, "note": [0, 3, 7, 9, 13, 15, 16, 18], "now": 3, "np": [9, 10, 11, 19], "num_output_channel": 10, "num_sampl": [7, 17], "number": [7, 9, 10, 11, 19], "numpi": [8, 9, 11, 19], "o": 4, "obb": 16, "obj_detect": 15, "object": [7, 8, 11, 16, 19], "objectness_scor": [8, 19], "oblig": 2, "obtain": 19, "occupi": 18, "ocr": [1, 5, 7, 9, 11, 15], "ocr_carea": 19, "ocr_db_crnn": 11, "ocr_lin": 19, "ocr_pag": 19, "ocr_par": 19, "ocr_predictor": [9, 13, 15, 18, 19], "ocrdataset": [7, 17], "ocrmetr": 11, "ocrpredictor": [9, 13], "ocrx_word": 19, "offens": 2, "offici": [2, 9], "offlin": 2, "offset": 10, "onc": 19, "one": [3, 7, 9, 10, 13, 15, 19], "oneof": 10, "ones": [7, 11], "onli": [3, 9, 10, 11, 13, 15, 17, 18, 19], "onlin": 2, "onnx": 16, "onnxruntim": [16, 18], "onnxtr": 18, "opac": 10, "opacity_rang": 10, "open": [1, 2, 3, 15, 18], "opinion": 2, "optic": [5, 19], "optim": [5, 19], "option": [7, 9, 13], "order": [3, 7, 8, 10], "org": [2, 7, 9, 19], "organ": 8, "orient": [2, 8, 9, 12, 16, 19], "orientationpredictor": 9, "other": [2, 3], "otherwis": [2, 8, 11], "our": [1, 3, 9, 19], "out": [3, 9, 10, 11, 19], "outpout": 19, "output": [8, 10, 18], "output_s": [8, 10], "outsid": 14, "over": [7, 11, 19], "overal": [2, 9], "overlai": 8, "overview": 16, "overwrit": 13, "overwritten": 15, "own": 5, "p": [10, 19], "packag": [3, 5, 11, 14, 16, 17, 18], "pad": [7, 9, 10, 19], "page": [4, 7, 9, 11, 13, 19], "page1": 8, "page2": 8, "page_1": 19, "page_idx": [8, 19], "page_orientation_predictor": [9, 13], "page_param": 13, "pair": 11, "paper": 9, "par_1_1": 19, "paragraph": 19, "paragraph_break": 19, "parallel": 9, "param": [10, 19], "paramet": [5, 8, 9, 18], "pars": [5, 7], "parseq": [5, 9, 15, 18, 19], "part": [7, 10, 19], "parti": 4, "partial": 19, "particip": 2, "pass": [7, 8, 9, 13, 19], "password": 8, "patch": [9, 11], "path": [7, 8, 16, 17, 18], "path_to_checkpoint": 13, "path_to_custom_model": 18, "path_to_pt": 13, "patil": 1, "pattern": 2, "pdf": [8, 9, 12], "pdfpage": 8, "peopl": 2, "per": [10, 19], "perform": [5, 8, 9, 10, 11, 14, 18, 19], "period": 2, "permiss": 2, "permut": [5, 9], "persian_lett": 7, "person": [2, 17], "phase": 19, "photo": 17, "physic": [2, 8], "pick": 10, "pictur": 8, "pip": [3, 4, 16, 18], "pipelin": 19, "pixel": [8, 10, 19], "pleas": 3, "plot": 11, "plt": 11, "plug": 15, "plugin": 4, "png": 8, "point": 18, "polici": 14, "polish": 7, "polit": 2, "polygon": [7, 11, 19], "pool": 9, "portugues": 7, "posit": [2, 11], "possibl": [3, 11, 15, 19], "post": [2, 19], "postprocessor": 19, "potenti": 9, "power": 5, "ppageno": 19, "pre": [3, 9, 18], "precis": [11, 19], "pred": 11, "pred_box": 11, "pred_label": 11, "predefin": 17, "predict": [8, 9, 11, 19], "predictor": [5, 8, 9, 12, 13, 15, 18], "prefer": 17, "preinstal": 4, "preprocessor": [13, 19], "prerequisit": 15, "present": 12, "preserv": [9, 10, 19], "preserve_aspect_ratio": [8, 9, 10, 13, 19], "pretrain": [5, 9, 11, 13, 18, 19], "pretrained_backbon": [9, 13], "print": 19, "prior": 7, "privaci": 2, "privat": 2, "probabl": [1, 10], "problem": 3, "procedur": 10, "process": [3, 5, 8, 9, 13, 19], "processor": 19, "produc": [12, 19], "product": 18, "profession": 2, "project": [3, 17], "promptli": 2, "proper": 3, "properli": 7, "provid": [2, 3, 5, 15, 16, 17, 19], "public": [2, 5], "publicli": 19, "publish": 2, "pull": 15, "punctuat": 7, "pure": 7, "purpos": 3, "push_to_hf_hub": [9, 15], "py": 15, "pypdfium2": [4, 8], "pyplot": [8, 11], "python": [1, 3, 16], "python3": 15, "pytorch": [4, 5, 9, 10, 13, 15, 18, 19], "q": 3, "qr": [8, 16], "qr_code": 17, "qualiti": 10, "question": 2, "quickli": 5, "quicktour": 12, "r": 19, "race": 2, "ramdisk": 7, "rand": [9, 10, 11, 18, 19], "random": [9, 10, 11, 19], "randomappli": 10, "randombright": 10, "randomcontrast": 10, "randomcrop": 10, "randomgamma": 10, "randomhorizontalflip": 10, "randomhu": 10, "randomjpegqu": 10, "randomli": 10, "randomres": 10, "randomrot": 10, "randomsatur": 10, "randomshadow": 10, "rang": 10, "rassi": 15, "ratio": [9, 10, 19], "raw": [8, 11], "re": 18, "read": [5, 7, 9], "read_html": 8, "read_img_as_numpi": 8, "read_img_as_tensor": 8, "read_pdf": 8, "readi": 18, "real": [1, 5, 9, 10], "realli": 1, "reason": [2, 5, 7], "rebuild": 3, "rebuilt": 3, "recal": [11, 19], "receipt": [5, 7, 19], "reco_arch": [9, 13, 15, 18], "reco_b": 19, "reco_model": [13, 15, 18], "reco_param": 13, "reco_predictor": 13, "recogn": 19, "recognit": [7, 11, 12, 13], "recognition_predictor": [9, 19], "recognition_task": [7, 17], "recognitiondataset": [7, 17], "recognitionpredictor": [9, 13], "rectangular": 9, "reduc": [4, 10], "refer": [3, 4, 13, 15, 16, 17, 19], "regardless": 2, "region": 19, "regroup": 11, "regular": 17, "reject": 2, "rel": [8, 10, 11, 19], "relat": 8, "releas": [0, 4], "relev": 16, "religion": 2, "remov": 2, "render": [8, 19], "repo": 9, "repo_id": [9, 15], "report": 2, "repositori": [7, 9, 15], "repres": [2, 18, 19], "represent": [5, 9], "request": [2, 15], "requir": [4, 10, 18], "research": 5, "residu": 9, "resiz": [10, 19], "resnet": 9, "resnet18": [9, 15], "resnet31": 9, "resnet34": 9, "resnet50": [9, 15], "resolv": 8, "resolve_block": 19, "resolve_lin": 19, "resourc": 17, "respect": 2, "rest": [3, 10, 11], "restrict": 14, "result": [3, 7, 8, 12, 15, 18, 19], "return": 19, "reusabl": 19, "review": 2, "rgb": [8, 10], "rgb_mode": 8, "rgb_output": 8, "right": [2, 9, 11], "roboflow": 1, "robust": [5, 7], "root": 7, "rotat": [7, 8, 9, 10, 11, 12, 13, 17, 19], "run": [3, 4, 9], "same": [3, 8, 11, 17, 18, 19], "sampl": [7, 9, 17, 19], "sample_transform": 7, "sanjin": 1, "sar": [5, 9], "sar_resnet31": [9, 19], "satur": 10, "save": [9, 17], "scale": [8, 9, 10, 11], "scale_rang": 10, "scan": [5, 7], "scene": [5, 7, 9], "score": [8, 11], "script": [3, 17], "seamless": 5, "seamlessli": [5, 19], "search": [1, 9], "searchabl": 12, "sec": 19, "second": 19, "section": [1, 13, 15, 16, 18, 19], "secur": [2, 14], "see": [2, 3], "seen": 19, "segment": [5, 9, 19], "self": 19, "semant": [5, 9], "send": 19, "sens": 11, "sensit": 17, "separ": 19, "sequenc": [5, 7, 8, 9, 11, 19], "sequenti": [10, 19], "seri": 2, "seriou": 2, "set": [2, 4, 7, 9, 11, 14, 16, 19], "set_global_polici": 18, "sever": [8, 10, 19], "sex": 2, "sexual": 2, "shade": 10, "shape": [5, 8, 9, 10, 11, 19], "share": [14, 17], "shift": 10, "shm": 14, "should": [3, 7, 8, 10, 11], "show": [5, 8, 9, 11, 13, 15, 16], "showcas": [3, 12], "shuffl": [7, 10], "side": 11, "signatur": 8, "signific": 17, "simpl": [5, 9, 18], "simpler": 9, "sinc": [7, 17], "singl": [2, 3, 5, 7], "single_img_doc": 18, "size": [2, 7, 8, 10, 16, 19], "skew": 19, "slack": 3, "slightli": 9, "small": [3, 9, 19], "smallest": 8, "snapshot_download": 9, "snippet": 19, "so": [3, 4, 7, 9, 15, 17], "social": 2, "socio": 2, "some": [1, 4, 12, 15, 17], "someth": 3, "somewher": 3, "sort": 2, "sourc": [1, 7, 8, 9, 10, 11, 15], "space": [2, 19], "span": 19, "spanish": 7, "spatial": [5, 7, 8], "specif": [3, 4, 11, 13, 17, 19], "specifi": [2, 7, 8], "speed": [5, 9, 19], "sphinx": 3, "sroie": [5, 7, 17], "stabl": 4, "stackoverflow": 3, "stage": 5, "standalon": 12, "standard": 10, "start": 7, "state": [1, 5, 11, 16], "static": 11, "statist": 1, "statu": 2, "std": [10, 13], "step": 14, "still": 19, "str": [7, 8, 9, 10, 11], "straight": [7, 9, 17, 19], "straighten": 19, "straighten_pag": [9, 13, 19], "straigten_pag": 13, "stream": 8, "street": [5, 7], "strict": 4, "strictli": 11, "string": [7, 8, 11, 19], "strive": 4, "strong": [5, 9], "structur": [18, 19], "subset": [7, 19], "suggest": [3, 15], "sum": 11, "summari": 11, "support": [4, 13, 16, 18, 19], "sustain": 2, "svhn": [5, 7, 17], "svt": [7, 17], "swedish": 7, "symmetr": [9, 10, 19], "symmetric_pad": [9, 10, 19], "synthet": 5, "synthtext": [5, 7, 17], "system": 19, "t": [3, 7, 13, 18, 19], "tabl": [15, 16, 17], "take": [2, 7, 19], "target": [7, 8, 10, 11, 17], "target_s": 7, "task": [5, 7, 9, 15, 17, 19], "task2": 7, "team": 4, "techminde": 4, "templat": [3, 5], "tensor": [7, 8, 10, 19], "tensorflow": [4, 5, 8, 9, 10, 13, 15, 18, 19], "tensorspec": 18, "term": 2, "test": [7, 17], "test_set": 7, "text": [1, 7, 8, 9, 11, 17], "text_output": 19, "textmatch": 11, "textnet": 9, "textnet_bas": 9, "textnet_smal": 9, "textnet_tini": 9, "textract": [5, 19], "textstylebrush": [5, 7], "textual": [5, 7, 8, 9, 19], "tf": [4, 8, 9, 10, 15, 18], "than": [3, 11, 15], "thank": 3, "thei": [2, 11], "them": [7, 19], "thi": [1, 2, 3, 4, 6, 7, 10, 11, 13, 14, 15, 17, 18, 19], "thing": [18, 19], "third": 4, "those": [2, 8, 19], "threaten": 2, "threshold": 19, "through": [2, 10, 16, 17], "tilman": 15, "time": [1, 2, 5, 9, 11, 17], "tini": 9, "titl": [8, 19], "tm": 19, "tmp": 14, "togeth": [3, 8], "tograi": 10, "tool": [1, 17], "top": [11, 18, 19], "topic": 3, "torch": [4, 10, 13, 15, 18], "torchvis": 10, "total": 13, "toward": [2, 4], "train": [3, 7, 9, 10, 15, 16, 17, 18, 19], "train_it": [7, 17], "train_load": [7, 17], "train_pytorch": 15, "train_set": [7, 17], "train_tensorflow": 15, "trainabl": [5, 9], "tranform": 10, "transcrib": 19, "transfer": [5, 7], "transfo": 10, "transform": [5, 7, 9], "translat": 2, "troll": 2, "true": [7, 8, 9, 10, 11, 13, 14, 15, 17, 18, 19], "truth": 11, "tune": [1, 18], "tupl": [7, 8, 10, 11], "two": [8, 14], "txt": 7, "type": [8, 11, 15, 18, 19], "typic": 19, "u": [2, 3], "ucsd": 7, "udac": 3, "uint8": [8, 9, 11, 19], "ukrainian": 7, "unaccept": 2, "underli": [17, 19], "underneath": 8, "understand": [5, 7, 19], "uniform": [9, 10], "uniformli": 10, "uninterrupt": [8, 19], "union": 11, "unit": 1, "unittest": 3, "unlock": 8, "unoffici": 9, "unprofession": 2, "unsolicit": 2, "unsupervis": 5, "unwelcom": 2, "up": [9, 19], "updat": 11, "upgrad": 3, "upper": [7, 10], "uppercas": 17, "url": 8, "us": [2, 3, 4, 7, 9, 11, 12, 13, 14, 15, 16, 19], "usabl": 19, "usag": [14, 18], "use_polygon": [7, 11, 17], "useabl": 19, "user": [5, 8, 12], "utf": 19, "util": 18, "v1": 15, "v3": [9, 15, 19], "valid": 17, "valu": [3, 8, 10, 19], "valuabl": 5, "variabl": 14, "varieti": 7, "veri": 9, "verma": 1, "version": [2, 3, 4, 18, 19], "vgg": 9, "vgg16": 15, "vgg16_bn_r": 9, "via": 2, "video": 1, "vietnames": 7, "view": [5, 7], "viewpoint": 2, "violat": 2, "visibl": 2, "vision": [5, 7, 9], "visiondataset": 7, "visiontransform": 9, "visual": [4, 5, 16], "visualize_pag": 11, "vit_": 9, "vit_b": 9, "vitstr": [5, 9, 18], "vitstr_bas": [9, 19], "vitstr_smal": [9, 13, 18, 19], "viz": 4, "vocab": [13, 15, 17, 18, 19], "vocabulari": [7, 13, 15], "w": [8, 9, 10, 11], "w3": 19, "wa": 2, "wai": [2, 5, 17], "want": [3, 18, 19], "warmup": 19, "wasn": 3, "we": [1, 2, 3, 4, 5, 8, 10, 13, 15, 17, 18, 19], "weasyprint": 8, "web": [3, 8], "websit": 7, "welcom": 2, "well": [1, 2, 18], "were": [2, 8, 19], "what": [1, 2], "when": [2, 3, 9], "whenev": 3, "where": [3, 8, 10, 11], "whether": [3, 7, 8, 10, 11, 17, 19], "which": [2, 9, 14, 16, 17, 19], "whichev": 4, "while": [10, 19], "why": 2, "width": [8, 10], "wiki": 2, "wildreceipt": [5, 7, 17], "window": [9, 11], "wish": 3, "within": 2, "without": [2, 7, 9], "wonder": 3, "word": [5, 7, 9, 11, 19], "word_1_1": 19, "word_1_2": 19, "word_1_3": 19, "wordgener": [7, 17], "words_onli": 11, "work": [1, 13, 14, 19], "workflow": 3, "worklow": 3, "world": [11, 19], "worth": 9, "wrap": 19, "wrapper": [7, 10], "write": 14, "written": [2, 8], "www": [2, 8, 19], "x": [8, 10, 11], "x_ascend": 19, "x_descend": 19, "x_i": 11, "x_size": 19, "x_wconf": 19, "xhtml": 19, "xmax": 8, "xmin": 8, "xml": 19, "xml_bytes_str": 19, "xml_element": 19, "xml_output": 19, "xmln": 19, "y": 11, "y_i": 11, "y_j": 11, "yet": 16, "ymax": 8, "ymin": 8, "yolov8": 16, "you": [3, 4, 7, 8, 9, 13, 14, 15, 16, 17, 18, 19], "your": [3, 5, 8, 11, 19], "yoursit": 8, "yugesh": 1, "zero": [10, 11], "zoo": 13, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 7, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 7, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": 7, "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 7, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 7, "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 7, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 7, "\u00e4\u00f6\u00e4\u00f6": 7, "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 7, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": 7, "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": 7, "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": 7, "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": 7, "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": 7, "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0905": 7, "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": 7, "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": 7, "\u0950": 7, "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": 7, "\u09bd": 7, "\u09ce": 7, "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": 7}, "titles": ["Changelog", "Community resources", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.contrib", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Integrate contributions into your pipeline", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 3, "0": 0, "01": 0, "02": 0, "03": 0, "04": 0, "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 2], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 2], "2021": 0, "2022": 0, "2023": 0, "2024": 0, "21": 0, "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 2], "31": 0, "4": [0, 2], "5": 0, "6": 0, "7": 0, "8": 0, "9": 0, "advanc": 19, "approach": 19, "architectur": 19, "arg": [7, 8, 9, 10, 11], "artefact": 8, "artefactdetect": 16, "attribut": 2, "avail": [16, 17, 19], "aw": 14, "ban": 2, "block": 8, "bug": 3, "changelog": 0, "choos": [17, 19], "classif": [9, 13, 15], "code": [2, 3], "codebas": 3, "commit": 3, "commun": [1, 15], "compos": 10, "conda": 4, "conduct": 2, "connect": 3, "continu": 3, "contrib": 6, "contribut": [3, 6, 16], "contributor": 2, "convent": 15, "correct": 2, "coven": 2, "custom": [7, 13], "data": 17, "dataload": 7, "dataset": [5, 7, 17], "detect": [5, 9, 15, 17, 19], "develop": 3, "do": 19, "doctr": [3, 5, 6, 7, 8, 9, 10, 11, 12], "document": [3, 5, 8], "end": 19, "enforc": 2, "evalu": 11, "export": 18, "factori": 9, "featur": [3, 5], "feedback": 3, "file": 8, "from": 15, "gener": [7, 17], "git": 4, "guidelin": 2, "half": 18, "hub": 15, "huggingfac": 15, "i": 19, "infer": 18, "instal": [3, 4], "integr": [3, 16], "io": 8, "lambda": 14, "let": 3, "line": 8, "linux": 4, "load": [13, 15, 17], "loader": 7, "main": 5, "mode": 3, "model": [5, 9, 13, 15, 18, 19], "modifi": 3, "modul": [6, 16], "name": 15, "notebook": 12, "object": 17, "ocr": [17, 19], "onli": 4, "onnx": 18, "optim": 18, "option": 19, "orient": 13, "our": 2, "output": 19, "own": [13, 17], "packag": 4, "page": 8, "perman": 2, "pipelin": 16, "pledg": 2, "precis": 18, "predictor": 19, "prepar": 18, "prerequisit": 4, "pretrain": 15, "push": 15, "python": 4, "qualiti": 3, "question": 3, "read": 8, "readi": 17, "recognit": [5, 9, 15, 17, 19], "report": 3, "request": 3, "resourc": 1, "respons": 2, "return": [7, 8, 9, 11], "right": 19, "scope": 2, "share": 15, "should": 19, "stage": 19, "standard": 2, "structur": [3, 8], "style": 3, "support": [5, 6, 7, 10], "synthet": [7, 17], "task": 11, "temporari": 2, "test": 3, "text": [5, 19], "train": 13, "transform": 10, "two": 19, "unit": 3, "us": [17, 18], "util": 11, "v0": 0, "verif": 3, "via": 4, "visual": 11, "vocab": 7, "warn": 2, "what": 19, "word": 8, "your": [13, 15, 16, 17, 18], "zoo": [5, 9]}}) \ No newline at end of file diff --git a/v0.5.0/transforms.html b/v0.5.0/transforms.html deleted file mode 100644 index 85e94d8a76..0000000000 --- a/v0.5.0/transforms.html +++ /dev/null @@ -1,684 +0,0 @@ - - - - - - - - - - - - - doctr.transforms - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    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:

    -
    -
    -class doctr.transforms.Resize(output_size: Tuple[int, int], method: str = 'bilinear', preserve_aspect_ratio: bool = False, symmetric_pad: bool = False)[source]
    -

    Resizes a tensor to a target size

    -
    -
    Example::
    >>> from doctr.transforms import Resize
    ->>> import tensorflow as tf
    ->>> transfo = Resize((32, 32))
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • output_size – expected output size

    • -
    • method – interpolation method

    • -
    • preserve_aspect_ratio – if True, preserve aspect ratio and pad the rest with zeros

    • -
    • symmetric_pad – if True while preserving aspect ratio, the padding will be done symmetrically

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.Normalize(mean: Tuple[float, float, float], std: Tuple[float, float, float])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • mean – average value per channel

    • -
    • std – standard deviation per channel

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.LambdaTransformation(fn: Callable[[Tensor], Tensor])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import LambdaTransformation
    ->>> import tensorflow as tf
    ->>> transfo = LambdaTransformation(lambda x: x/ 255.)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    fn – the function to be applied to the input tensor

    -
    -
    -
    - -
    -
    -class doctr.transforms.ToGray[source]
    -

    Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ToGray()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    - -
    -
    -class doctr.transforms.ColorInversion(min_val: float = 0.5)[source]
    -

    Applies the following tranformation to a tensor (image or batch of images): -convert to grayscale, colorize (shift 0-values randomly), and then invert colors

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ColorInversion(min_val=0.6)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    min_val – range [min_val, 1] to colorize RGB pixels

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomBrightness(max_delta: float = 0.3)[source]
    -

    Randomly adjust brightness of a tensor (batch of images or image) by adding a delta -to all pixels

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Brightness()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    • -
    • p – probability to apply transformation

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomContrast(delta: float = 0.3)[source]
    -

    Randomly adjust contrast of a tensor (batch of images or image) by adjusting -each pixel: (img - mean) * contrast_factor + mean.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Contrast()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomSaturation(delta: float = 0.5)[source]
    -

    Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and -increasing saturation by a factor.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Saturation()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomHue(max_delta: float = 0.3)[source]
    -

    Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Hue()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomGamma(min_gamma: float = 0.5, max_gamma: float = 1.5, min_gain: float = 0.8, max_gain: float = 1.2)[source]
    -

    randomly performs gamma correction for a tensor (batch of images or image)

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Gamma()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_gamma – non-negative real number, lower bound for gamma param

    • -
    • max_gamma – non-negative real number, upper bound for gamma

    • -
    • min_gain – lower bound for constant multiplier

    • -
    • max_gain – upper bound for constant multiplier

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomJpegQuality(min_quality: int = 60, max_quality: int = 100)[source]
    -

    Randomly adjust jpeg quality of a 3 dimensional RGB image

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = JpegQuality()
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_quality – int between [0, 100]

    • -
    • max_quality – int between [0, 100]

    • -
    -
    -
    -
    - -
    -
    -

    Composing transformations

    -

    It is common to require several transformations to be performed consecutively.

    -
    -
    -class doctr.transforms.Compose(transforms: List[Callable[[Any], Any]])[source]
    -

    Implements a wrapper that will apply transformations sequentially

    -
    -
    Example::
    >>> from doctr.transforms import Compose, Resize
    ->>> import tensorflow as tf
    ->>> transfos = Compose([Resize((32, 32))])
    ->>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformation modules

    -
    -
    -
    - -
    -
    -class doctr.transforms.OneOf(transforms: List[Callable[[Any], Any]])[source]
    -

    Randomly apply one of the input transformations

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = OneOf([JpegQuality(), Gamma()])
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformations, one only will be picked

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomApply(transform: Callable[[Any], Any], p: float = 0.5)[source]
    -

    Apply with a probability p the input transformation

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = RandomApply(Gamma(), p=.5)
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • transform – transformation to apply

    • -
    • p – probability to apply

    • -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.0/using_doctr/custom_models_training.html b/v0.5.0/using_doctr/custom_models_training.html index df39d8d568..b714c1f971 100644 --- a/v0.5.0/using_doctr/custom_models_training.html +++ b/v0.5.0/using_doctr/custom_models_training.html @@ -14,7 +14,7 @@ - + Train your own model - docTR documentation @@ -619,7 +619,7 @@

    Loading your custom trained orientation classification model - + diff --git a/v0.5.0/using_doctr/running_on_aws.html b/v0.5.0/using_doctr/running_on_aws.html index 16ceaca7a1..808ea541cd 100644 --- a/v0.5.0/using_doctr/running_on_aws.html +++ b/v0.5.0/using_doctr/running_on_aws.html @@ -14,7 +14,7 @@ - + AWS Lambda - docTR documentation @@ -362,7 +362,7 @@

    AWS Lambda - + diff --git a/v0.5.0/using_doctr/sharing_models.html b/v0.5.0/using_doctr/sharing_models.html index d76b4017f4..c9e978400a 100644 --- a/v0.5.0/using_doctr/sharing_models.html +++ b/v0.5.0/using_doctr/sharing_models.html @@ -14,7 +14,7 @@ - + Share your model with the community - docTR documentation @@ -544,7 +544,7 @@

    Recognition - + diff --git a/v0.5.0/using_doctr/using_contrib_modules.html b/v0.5.0/using_doctr/using_contrib_modules.html index 50598dae5d..0c5fffdf9f 100644 --- a/v0.5.0/using_doctr/using_contrib_modules.html +++ b/v0.5.0/using_doctr/using_contrib_modules.html @@ -14,7 +14,7 @@ - + Integrate contributions into your pipeline - docTR documentation @@ -415,7 +415,7 @@

    ArtefactDetection - + diff --git a/v0.5.0/using_doctr/using_datasets.html b/v0.5.0/using_doctr/using_datasets.html index 460476dbbf..8a7d4f0a64 100644 --- a/v0.5.0/using_doctr/using_datasets.html +++ b/v0.5.0/using_doctr/using_datasets.html @@ -14,7 +14,7 @@ - + Choose a ready to use dataset - docTR documentation @@ -642,7 +642,7 @@

    Data Loading - + diff --git a/v0.5.0/using_doctr/using_model_export.html b/v0.5.0/using_doctr/using_model_export.html index 6124c00ebe..6790dd0642 100644 --- a/v0.5.0/using_doctr/using_model_export.html +++ b/v0.5.0/using_doctr/using_model_export.html @@ -14,7 +14,7 @@ - + Preparing your model for inference - docTR documentation @@ -467,7 +467,7 @@

    Using your ONNX exported model - + diff --git a/v0.5.0/using_doctr/using_models.html b/v0.5.0/using_doctr/using_models.html index 61f1f5ab7a..9ead8498e1 100644 --- a/v0.5.0/using_doctr/using_models.html +++ b/v0.5.0/using_doctr/using_models.html @@ -14,7 +14,7 @@ - + Choosing the right model - docTR documentation @@ -1253,7 +1253,7 @@

    Advanced options - + diff --git a/v0.5.0/utils.html b/v0.5.0/utils.html deleted file mode 100644 index e2f223f06a..0000000000 --- a/v0.5.0/utils.html +++ /dev/null @@ -1,574 +0,0 @@ - - - - - - - - - - - - - doctr.utils - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.utils

    -

    This module regroups non-core features that are complementary to the rest of the package.

    -
    -

    Visualization

    -

    Easy-to-use functions to make sense of your model’s predictions.

    -
    -
    -doctr.utils.visualization.visualize_page(page: Dict[str, Any], image: ndarray, words_only: bool = True, display_artefacts: bool = True, scale: float = 10, interactive: bool = True, add_labels: bool = True, **kwargs: Any) Figure[source]
    -

    Visualize a full page with predicted blocks, lines and words

    -
    -
    Example::
    >>> import numpy as np
    ->>> import matplotlib.pyplot as plt
    ->>> from doctr.utils.visualization import visualize_page
    ->>> from doctr.models import ocr_db_crnn
    ->>> model = ocr_db_crnn(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([[input_page]])
    ->>> visualize_page(out[0].pages[0].export(), input_page)
    ->>> plt.show()
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • page – the exported Page of a Document

    • -
    • image – np array of the page, needs to have the same shape than page[‘dimensions’]

    • -
    • words_only – whether only words should be displayed

    • -
    • display_artefacts – whether artefacts should be displayed

    • -
    • scale – figsize of the largest windows side

    • -
    • interactive – whether the plot should be interactive

    • -
    • add_labels – for static plot, adds text labels on top of bounding box

    • -
    -
    -
    -
    - -
    -
    -

    Task evaluation

    -

    Implementations of task-specific metrics to easily assess your model performances.

    -
    -
    -class doctr.utils.metrics.TextMatch[source]
    -

    Implements text match metric (word-level accuracy) for recognition task.

    -

    The raw aggregated metric is computed as follows:

    -
    -
    -\[\forall X, Y \in \mathcal{W}^N, -TextMatch(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N f_{Y_i}(X_i)\]
    -
    -

    with the indicator function \(f_{a}\) defined as:

    -
    -
    -\[\begin{split}\forall a, x \in \mathcal{W}, -f_a(x) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } x = a \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{W}\) is the set of all possible character sequences, -\(N\) is a strictly positive integer.

    -
    -
    Example::
    >>> from doctr.utils import TextMatch
    ->>> metric = TextMatch()
    ->>> metric.update(['Hello', 'world'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    -summary() Dict[str, float][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a dictionary with the exact match score for the raw data, its lower-case counterpart, its unidecode -counterpart and its lower-case unidecode counterpart

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.LocalizationConfusion(iou_thresh: float = 0.5, rotated_bbox: bool = False, mask_shape: Tuple[int, int] = (1024, 1024))[source]
    -

    Implements common confusion metrics and mean IoU for localization evaluation.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall Y \in \mathcal{B}^N, \forall X \in \mathcal{B}^M, \\ -Recall(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -Precision(X, Y) = \frac{1}{M} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -meanIoU(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(X_i, Y_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(g_{X}\) defined as:

    -
    -
    -\[\begin{split}\forall y \in \mathcal{B}, -g_X(y) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } y\mbox{ has been assigned to any }(X_i)_i\mbox{ with an }IoU \geq 0.5 \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import LocalizationConfusion
    ->>> metric = LocalizationConfusion(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]))
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[float | None, float | None, float | None][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall, precision and meanIoU scores

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.OCRMetric(iou_thresh: float = 0.5, rotated_bbox: bool = False, mask_shape: Tuple[int, int] = (1024, 1024))[source]
    -

    Implements end-to-end OCR metric.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall (B, L) \in \mathcal{B}^N \times \mathcal{L}^N, -\forall (\hat{B}, \hat{L}) \in \mathcal{B}^M \times \mathcal{L}^M, \\ -Recall(B, \hat{B}, L, \hat{L}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -Precision(B, \hat{B}, L, \hat{L}) = \frac{1}{M} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(h_{B, L}\) defined as:

    -
    -
    -\[\begin{split}\forall (b, l) \in \mathcal{B} \times \mathcal{L}, -h_{B,L}(b, l) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ - & IoU \geq 0.5 \mbox{ and that for this assignment, } l = L_j\\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(\mathcal{L}\) is the set of possible character sequences, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import OCRMetric
    ->>> metric = OCRMetric(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]),
    -['hello'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[Dict[str, float | None], Dict[str, float | None], float | None][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall & precision for each string comparison flexibility and the mean IoU

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.5.1/_modules/doctr/datasets/cord.html b/v0.5.1/_modules/doctr/datasets/cord.html index f98ee6901c..55b0584830 100644 --- a/v0.5.1/_modules/doctr/datasets/cord.html +++ b/v0.5.1/_modules/doctr/datasets/cord.html @@ -13,7 +13,7 @@ - + doctr.datasets.cord - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.cord

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
     from .datasets import VisionDataset
    -from doctr.utils.geometry import fit_rbbox
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['CORD']
    +__all__ = ["CORD"]
     
     
     
    -[docs] +[docs] class CORD(VisionDataset): """CORD dataset from `"CORD: A Consolidated Receipt Dataset forPost-OCR Parsing" <https://openreview.net/pdf?id=SJl3z659UH>`_. - Example:: - >>> from doctr.datasets import CORD - >>> train_set = CORD(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/cord-grid.png&src=0 + :align: center + + >>> from doctr.datasets import CORD + >>> train_set = CORD(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - TRAIN = ('https://github.com/mindee/doctr/releases/download/v0.1.1/cord_train.zip', - '45f9dc77f126490f3e52d7cb4f70ef3c57e649ea86d19d862a2757c9c455d7f8') - TEST = ('https://github.com/mindee/doctr/releases/download/v0.1.1/cord_test.zip', - '8c895e3d6f7e1161c5b7245e3723ce15c04d84be89eaa6093949b75a66fb3c58') + TRAIN = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/cord_train.zip&src=0", + "45f9dc77f126490f3e52d7cb4f70ef3c57e649ea86d19d862a2757c9c455d7f8", + "cord_train.zip", + ) + + TEST = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/cord_test.zip&src=0", + "8c895e3d6f7e1161c5b7245e3723ce15c04d84be89eaa6093949b75a66fb3c58", + "cord_test.zip", + ) def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + url, sha256, name = self.TRAIN if train else self.TEST + super().__init__( + url, + name, + sha256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - url, sha256 = self.TRAIN if train else self.TEST - super().__init__(url, None, sha256, True, **kwargs) - - # # List images - self.root = os.path.join(self._root, 'image') - self.data: List[Tuple[str, Dict[str, Any]]] = [] + # List images + tmp_root = os.path.join(self.root, "image") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] self.train = train - self.sample_transforms = sample_transforms - for img_path in os.listdir(self.root): + np_dtype = np.float32 + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking CORD", total=len(os.listdir(tmp_root))): # File existence check - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem _targets = [] - with open(os.path.join(self._root, 'json', f"{stem}.json"), 'rb') as f: + with open(os.path.join(self.root, "json", f"{stem}.json"), "rb") as f: label = json.load(f) for line in label["valid_line"]: for word in line["words"]: if len(word["text"]) > 0: x = word["quad"]["x1"], word["quad"]["x2"], word["quad"]["x3"], word["quad"]["x4"] y = word["quad"]["y1"], word["quad"]["y2"], word["quad"]["y3"], word["quad"]["y4"] - if rotated_bbox: - box = list(fit_rbbox(np.array([ - [x[0], y[0]], - [x[1], y[1]], - [x[2], y[2]], - [x[3], y[3]], - ], dtype=np.float32))) + box: Union[List[float], np.ndarray] + if use_polygons: + # (x, y) coordinates of top left, top right, bottom right, bottom left corners + box = np.array( + [ + [x[0], y[0]], + [x[1], y[1]], + [x[2], y[2]], + [x[3], y[3]], + ], + dtype=np_dtype, + ) else: - # Reduce 8 coords to 4 + # Reduce 8 coords to 4 -> xmin, ymin, xmax, ymax box = [min(x), min(y), max(x), max(y)] - _targets.append((word['text'], box)) + _targets.append((word["text"], box)) text_targets, box_targets = zip(*_targets) - self.data.append(( - img_path, - dict(boxes=np.asarray(box_targets, dtype=int).clip(min=0), labels=text_targets) - )) + if recognition_task: + crops = crop_bboxes_from_image( + img_path=os.path.join(tmp_root, img_path), geoms=np.asarray(box_targets, dtype=int).clip(min=0) + ) + for crop, label in zip(crops, list(text_targets)): + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, np.asarray(box_targets, dtype=int).clip(min=0))) + else: + self.data.append(( + img_path, + dict(boxes=np.asarray(box_targets, dtype=int).clip(min=0), labels=list(text_targets)), + )) + + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -397,8 +461,8 @@

    Source code for doctr.datasets.cord

           
         
       
    -
    - + + diff --git a/v0.5.1/_modules/doctr/datasets/core.html b/v0.5.1/_modules/doctr/datasets/core.html deleted file mode 100644 index b3dcc29ff9..0000000000 --- a/v0.5.1/_modules/doctr/datasets/core.html +++ /dev/null @@ -1,417 +0,0 @@ - - - - - - - - - - - - doctr.datasets.core - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.core

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from pathlib import Path
    -from zipfile import ZipFile
    -from typing import List, Any, Optional, Tuple
    -import tensorflow as tf
    -
    -from doctr.models.utils import download_from_url
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset:
    -
    -    data: List[Any] = []
    -
    -    def __len__(self):
    -        return len(self.data)
    -
    -    def __getitem__(
    -        self,
    -        index: int
    -    ) -> Tuple[tf.Tensor, Any]:
    -
    -        img_name, target = self.data[index]
    -        # Read image
    -        img = tf.io.read_file(os.path.join(self.root, img_name))
    -        img = tf.image.decode_jpeg(img, channels=3)
    -        if self.sample_transforms is not None:
    -            img = self.sample_transforms(img)
    -
    -        return img, target
    -
    -    def extra_repr(self) -> str:
    -        return ""
    -
    -    def __repr__(self) -> str:
    -        return f"{self.__class__.__name__}({self.extra_repr()})"
    -
    -    @staticmethod
    -    def collate_fn(samples: List[Tuple[tf.Tensor, Any]]) -> Tuple[tf.Tensor, List[Any]]:
    -
    -        images, targets = zip(*samples)
    -        images = tf.stack(images, axis=0)
    -
    -        return images, list(targets)
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset): - """Implements an abstract dataset - - Args: - url: URL of the dataset - file_name: name of the file once downloaded - file_hash: expected SHA256 of the file - extract_archive: whether the downloaded file is an archive to be extracted - download: whether the dataset should be downloaded if not present on disk - overwrite: whether the archive should be re-extracted - """ - - def __init__( - self, - url: str, - file_name: Optional[str] = None, - file_hash: Optional[str] = None, - extract_archive: bool = False, - download: bool = False, - overwrite: bool = False, - ) -> None: - - dataset_cache = os.path.join(os.path.expanduser('~'), '.cache', 'doctr', 'datasets') - - file_name = file_name if isinstance(file_name, str) else os.path.basename(url) - # Download the file if not present - archive_path = os.path.join(dataset_cache, file_name) - - if not os.path.exists(archive_path) and not download: - raise ValueError("the dataset needs to be downloaded first with download=True") - - archive_path = download_from_url(url, file_name, file_hash, cache_subdir='datasets') - - # Extract the archive - if extract_archive: - archive_path = Path(archive_path) - dataset_path = archive_path.parent.joinpath(archive_path.stem) - if not dataset_path.is_dir() or overwrite: - with ZipFile(archive_path, 'r') as f: - f.extractall(path=dataset_path) - - # List images - self._root = dataset_path if extract_archive else archive_path - self.data: List[Any] = []
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.1/_modules/doctr/datasets/datasets/tensorflow.html b/v0.5.1/_modules/doctr/datasets/datasets/tensorflow.html deleted file mode 100644 index a236abd9fe..0000000000 --- a/v0.5.1/_modules/doctr/datasets/datasets/tensorflow.html +++ /dev/null @@ -1,356 +0,0 @@ - - - - - - - - - - - - doctr.datasets.datasets.tensorflow - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.datasets.tensorflow

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from typing import List, Any, Tuple
    -import tensorflow as tf
    -
    -from .base import _AbstractDataset, _VisionDataset
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset(_AbstractDataset):
    -
    -    def _read_sample(self, index: int) -> Tuple[tf.Tensor, Any]:
    -        img_name, target = self.data[index]
    -        # Read image
    -        img = tf.io.read_file(os.path.join(self.root, img_name))
    -        img = tf.image.decode_jpeg(img, channels=3)
    -
    -        return img, target
    -
    -    @staticmethod
    -    def collate_fn(samples: List[Tuple[tf.Tensor, Any]]) -> Tuple[tf.Tensor, List[Any]]:
    -
    -        images, targets = zip(*samples)
    -        images = tf.stack(images, axis=0)
    -
    -        return images, list(targets)
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset, _VisionDataset): - pass
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.1/_modules/doctr/datasets/detection.html b/v0.5.1/_modules/doctr/datasets/detection.html index 739563e466..718001e4cf 100644 --- a/v0.5.1/_modules/doctr/datasets/detection.html +++ b/v0.5.1/_modules/doctr/datasets/detection.html @@ -13,7 +13,7 @@ - + doctr.datasets.detection - docTR documentation @@ -430,7 +430,7 @@

    Source code for doctr.datasets.detection

         
       
    - + diff --git a/v0.5.1/_modules/doctr/datasets/doc_artefacts.html b/v0.5.1/_modules/doctr/datasets/doc_artefacts.html index 3313ae4660..94c32aaa0f 100644 --- a/v0.5.1/_modules/doctr/datasets/doc_artefacts.html +++ b/v0.5.1/_modules/doctr/datasets/doc_artefacts.html @@ -13,7 +13,7 @@ - + doctr.datasets.doc_artefacts - docTR documentation @@ -414,7 +414,7 @@

    Source code for doctr.datasets.doc_artefacts

       
    - + diff --git a/v0.5.1/_modules/doctr/datasets/funsd.html b/v0.5.1/_modules/doctr/datasets/funsd.html index 35d7ad4cf5..f08612f9fa 100644 --- a/v0.5.1/_modules/doctr/datasets/funsd.html +++ b/v0.5.1/_modules/doctr/datasets/funsd.html @@ -13,7 +13,7 @@ - + doctr.datasets.funsd - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.funsd

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
     from .datasets import VisionDataset
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['FUNSD']
    +__all__ = ["FUNSD"]
     
     
     
    -[docs] +[docs] class FUNSD(VisionDataset): """FUNSD dataset from `"FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents" <https://arxiv.org/pdf/1905.13538.pdf>`_. - Example:: - >>> from doctr.datasets import FUNSD - >>> train_set = FUNSD(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/funsd-grid.png&src=0 + :align: center + + >>> from doctr.datasets import FUNSD + >>> train_set = FUNSD(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - URL = 'https://guillaumejaume.github.io/FUNSD/dataset.zip' - SHA256 = 'c31735649e4f441bcbb4fd0f379574f7520b42286e80b01d80b445649d54761f' - FILE_NAME = 'funsd.zip' + URL = "https://guillaumejaume.github.io/FUNSD/dataset.zip" + SHA256 = "c31735649e4f441bcbb4fd0f379574f7520b42286e80b01d80b445649d54761f" + FILE_NAME = "funsd.zip" def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + super().__init__( + self.URL, + self.FILE_NAME, + self.SHA256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - super().__init__(self.URL, self.FILE_NAME, self.SHA256, True, **kwargs) self.train = train - self.sample_transforms = sample_transforms + np_dtype = np.float32 # Use the subset - subfolder = os.path.join('dataset', 'training_data' if train else 'testing_data') + subfolder = os.path.join("dataset", "training_data" if train else "testing_data") # # List images - self.root = os.path.join(self._root, subfolder, 'images') - self.data: List[Tuple[str, Dict[str, Any]]] = [] - for img_path in os.listdir(self.root): + tmp_root = os.path.join(self.root, subfolder, "images") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking FUNSD", total=len(os.listdir(tmp_root))): # File existence check - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem - with open(os.path.join(self._root, subfolder, 'annotations', f"{stem}.json"), 'rb') as f: + with open(os.path.join(self.root, subfolder, "annotations", f"{stem}.json"), "rb") as f: data = json.load(f) - _targets = [(word['text'], word['box']) for block in data['form'] - for word in block['words'] if len(word['text']) > 0] + _targets = [ + (word["text"], word["box"]) + for block in data["form"] + for word in block["words"] + if len(word["text"]) > 0 + ] text_targets, box_targets = zip(*_targets) - if rotated_bbox: - # box_targets: xmin, ymin, xmax, ymax -> x, y, w, h, alpha = 0 - box_targets = [ + if use_polygons: + # xmin, ymin, xmax, ymax -> (x, y) coordinates of top left, top right, bottom right, bottom left corners + box_targets = [ # type: ignore[assignment] [ - (box[0] + box[2]) / 2, (box[1] + box[3]) / 2, box[2] - box[0], box[3] - box[1], 0 - ] for box in box_targets + [box[0], box[1]], + [box[2], box[1]], + [box[2], box[3]], + [box[0], box[3]], + ] + for box in box_targets ] - self.data.append((img_path, dict(boxes=np.asarray(box_targets, dtype=int), labels=text_targets))) + if recognition_task: + crops = crop_bboxes_from_image( + img_path=os.path.join(tmp_root, img_path), geoms=np.asarray(box_targets, dtype=np_dtype) + ) + for crop, label in zip(crops, list(text_targets)): + # filter labels with unknown characters + if not any(char in label for char in ["☑", "☐", "\uf703", "\uf702"]): + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, np.asarray(box_targets, dtype=np_dtype))) + else: + self.data.append(( + img_path, + dict(boxes=np.asarray(box_targets, dtype=np_dtype), labels=list(text_targets)), + )) + + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -386,8 +453,8 @@

    Source code for doctr.datasets.funsd

           
         
       
    -
    - + + diff --git a/v0.5.1/_modules/doctr/datasets/generator/tensorflow.html b/v0.5.1/_modules/doctr/datasets/generator/tensorflow.html index 9f562582d9..a3e619f720 100644 --- a/v0.5.1/_modules/doctr/datasets/generator/tensorflow.html +++ b/v0.5.1/_modules/doctr/datasets/generator/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.datasets.generator.tensorflow - docTR documentation @@ -395,7 +395,7 @@

    Source code for doctr.datasets.generator.tensorflow

    - + diff --git a/v0.5.1/_modules/doctr/datasets/ic03.html b/v0.5.1/_modules/doctr/datasets/ic03.html index 3d221d07de..60e54a8a4b 100644 --- a/v0.5.1/_modules/doctr/datasets/ic03.html +++ b/v0.5.1/_modules/doctr/datasets/ic03.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic03 - docTR documentation @@ -468,7 +468,7 @@

    Source code for doctr.datasets.ic03

         
       
    - + diff --git a/v0.5.1/_modules/doctr/datasets/ic13.html b/v0.5.1/_modules/doctr/datasets/ic13.html index 8137e08e9f..219c98dcd1 100644 --- a/v0.5.1/_modules/doctr/datasets/ic13.html +++ b/v0.5.1/_modules/doctr/datasets/ic13.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic13 - docTR documentation @@ -440,7 +440,7 @@

    Source code for doctr.datasets.ic13

         
       
    - + diff --git a/v0.5.1/_modules/doctr/datasets/iiit5k.html b/v0.5.1/_modules/doctr/datasets/iiit5k.html index 1fc8ecfb27..b49c80fe18 100644 --- a/v0.5.1/_modules/doctr/datasets/iiit5k.html +++ b/v0.5.1/_modules/doctr/datasets/iiit5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiit5k - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.datasets.iiit5k

         
       
    - + diff --git a/v0.5.1/_modules/doctr/datasets/iiithws.html b/v0.5.1/_modules/doctr/datasets/iiithws.html index 07f5b13685..f7220afbc7 100644 --- a/v0.5.1/_modules/doctr/datasets/iiithws.html +++ b/v0.5.1/_modules/doctr/datasets/iiithws.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiithws - docTR documentation @@ -407,7 +407,7 @@

    Source code for doctr.datasets.iiithws

         
       
    - + diff --git a/v0.5.1/_modules/doctr/datasets/imgur5k.html b/v0.5.1/_modules/doctr/datasets/imgur5k.html index 68d433ca62..51c6545db4 100644 --- a/v0.5.1/_modules/doctr/datasets/imgur5k.html +++ b/v0.5.1/_modules/doctr/datasets/imgur5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.imgur5k - docTR documentation @@ -488,7 +488,7 @@

    Source code for doctr.datasets.imgur5k

         
       
    - + diff --git a/v0.5.1/_modules/doctr/datasets/loader.html b/v0.5.1/_modules/doctr/datasets/loader.html index d32e6da298..ed80350ef0 100644 --- a/v0.5.1/_modules/doctr/datasets/loader.html +++ b/v0.5.1/_modules/doctr/datasets/loader.html @@ -13,7 +13,7 @@ - + doctr.datasets.loader - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.loader

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import math
    -import tensorflow as tf
    -import numpy as np
    -from typing import Optional
    +from typing import Callable, Optional
     
    -from .multithreading import multithread_exec
    +import numpy as np
    +import tensorflow as tf
     
     __all__ = ["DataLoader"]
     
    @@ -293,12 +314,13 @@ 

    Source code for doctr.datasets.loader

         """Collate multiple elements into batches
     
         Args:
    +    ----
             samples: list of N tuples containing M elements
     
         Returns:
    +    -------
             Tuple of M sequences contianing N elements each
         """
    -
         batch_data = zip(*samples)
     
         tf_data = tuple(tf.stack(elt, axis=0) for elt in batch_data)
    @@ -307,23 +329,23 @@ 

    Source code for doctr.datasets.loader

     
     
     
    -[docs] +[docs] class DataLoader: """Implements a dataset wrapper for fast data loading - Example:: - >>> from doctr.datasets import FUNSD, 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) + >>> 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) Args: + ---- dataset: the dataset shuffle: whether the samples should be shuffled before passing it to the iterator batch_size: number of elements in each batch drop_last: if `True`, drops the last batch if it isn't full - workers: number of workers to use for data loading + collate_fn: function to merge samples into a batch """ def __init__( @@ -332,17 +354,22 @@

    Source code for doctr.datasets.loader

             shuffle: bool = True,
             batch_size: int = 1,
             drop_last: bool = False,
    -        workers: Optional[int] = None,
    +        collate_fn: Optional[Callable] = None,
         ) -> None:
             self.dataset = dataset
             self.shuffle = shuffle
             self.batch_size = batch_size
             nb = len(self.dataset) / batch_size
             self.num_batches = math.floor(nb) if drop_last else math.ceil(nb)
    -        self.collate_fn = self.dataset.collate_fn if hasattr(self.dataset, 'collate_fn') else default_collate
    -        self.workers = workers
    +        if collate_fn is None:
    +            self.collate_fn = self.dataset.collate_fn if hasattr(self.dataset, "collate_fn") else default_collate
    +        else:
    +            self.collate_fn = collate_fn
             self.reset()
     
    +    def __len__(self) -> int:
    +        return self.num_batches
    +
         def reset(self) -> None:
             # Updates indices after each epoch
             self._num_yielded = 0
    @@ -358,9 +385,9 @@ 

    Source code for doctr.datasets.loader

             if self._num_yielded < self.num_batches:
                 # Get next indices
                 idx = self._num_yielded * self.batch_size
    -            indices = self.indices[idx: min(len(self.dataset), idx + self.batch_size)]
    +            indices = self.indices[idx : min(len(self.dataset), idx + self.batch_size)]
     
    -            samples = multithread_exec(self.dataset.__getitem__, indices, threads=self.workers)
    +            samples = list(map(self.dataset.__getitem__, indices))
     
                 batch_data = self.collate_fn(samples)
     
    @@ -401,8 +428,8 @@ 

    Source code for doctr.datasets.loader

           
         
       
    -
    - +
    + diff --git a/v0.5.1/_modules/doctr/datasets/mjsynth.html b/v0.5.1/_modules/doctr/datasets/mjsynth.html index 77bb01d523..df34e49cf9 100644 --- a/v0.5.1/_modules/doctr/datasets/mjsynth.html +++ b/v0.5.1/_modules/doctr/datasets/mjsynth.html @@ -13,7 +13,7 @@ - + doctr.datasets.mjsynth - docTR documentation @@ -438,7 +438,7 @@

    Source code for doctr.datasets.mjsynth

         
       
    - + diff --git a/v0.5.1/_modules/doctr/datasets/ocr.html b/v0.5.1/_modules/doctr/datasets/ocr.html index 11297d5952..ce1ed8b0d4 100644 --- a/v0.5.1/_modules/doctr/datasets/ocr.html +++ b/v0.5.1/_modules/doctr/datasets/ocr.html @@ -13,7 +13,7 @@ - + doctr.datasets.ocr - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.ocr

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple
     
    -from .datasets import AbstractDataset
    -from doctr.utils.geometry import fit_rbbox
    +import numpy as np
     
    +from .datasets import AbstractDataset
     
    -__all__ = ['OCRDataset']
    +__all__ = ["OCRDataset"]
     
     
     
    -[docs] +[docs] class OCRDataset(AbstractDataset): """Implements an OCR dataset + >>> from doctr.datasets import OCRDataset + >>> train_set = OCRDataset(img_folder="/path/to/images", + >>> label_file="/path/to/labels.json") + >>> img, target = train_set[0] + Args: + ---- img_folder: local path to image folder (all jpg at the root) label_file: local path to the label file - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) - **kwargs: keyword arguments from `VisionDataset`. + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + **kwargs: keyword arguments from `AbstractDataset`. """ def __init__( self, img_folder: str, label_file: str, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, **kwargs: Any, ) -> None: - - self.sample_transforms = sample_transforms - self.root = img_folder + super().__init__(img_folder, **kwargs) # List images self.data: List[Tuple[str, Dict[str, Any]]] = [] - with open(label_file, 'rb') as f: + np_dtype = np.float32 + with open(label_file, "rb") as f: data = json.load(f) - for file_dic in data: + for img_name, annotations in data.items(): # Get image path - img_name = Path(os.path.basename(file_dic["raw-archive-filepath"])).stem + '.jpg' + img_name = Path(img_name) # File existence check if not os.path.exists(os.path.join(self.root, img_name)): raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_name)}") # handle empty images - if (len(file_dic["coordinates"]) == 0 or - (len(file_dic["coordinates"]) == 1 and file_dic["coordinates"][0] == "N/A")): - self.data.append((img_name, dict(boxes=np.zeros((0, 4), dtype=np.float32), labels=[]))) + if len(annotations["typed_words"]) == 0: + self.data.append((img_name, dict(boxes=np.zeros((0, 4), dtype=np_dtype), labels=[]))) continue - is_valid: List[bool] = [] - box_targets: List[List[float]] = [] - for box in file_dic["coordinates"]: - if rotated_bbox: - x, y, w, h, alpha = fit_rbbox(np.asarray(box, dtype=np.float32)) - box = [x, y, w, h, alpha] - is_valid.append(w > 0 and h > 0) - else: - xs, ys = zip(*box) - box = [min(xs), min(ys), max(xs), max(ys)] - is_valid.append(box[0] < box[2] and box[1] < box[3]) - if is_valid[-1]: - box_targets.append(box) + # Unpack the straight boxes (xmin, ymin, xmax, ymax) + geoms = [list(map(float, obj["geometry"][:4])) for obj in annotations["typed_words"]] + if use_polygons: + # (x, y) coordinates of top left, top right, bottom right, bottom left corners + geoms = [ + [geom[:2], [geom[2], geom[1]], geom[2:], [geom[0], geom[3]]] # type: ignore[list-item] + for geom in geoms + ] + + text_targets = [obj["value"] for obj in annotations["typed_words"]] - text_targets = [word for word, _valid in zip(file_dic["string"], is_valid) if _valid] - self.data.append((img_name, dict(boxes=np.asarray(box_targets, dtype=np.float32), labels=text_targets)))
    + self.data.append((img_name, dict(boxes=np.asarray(geoms, dtype=np_dtype), labels=text_targets)))
    @@ -383,8 +402,8 @@

    Source code for doctr.datasets.ocr

           
         
       
    - - + + diff --git a/v0.5.1/_modules/doctr/datasets/recognition.html b/v0.5.1/_modules/doctr/datasets/recognition.html index 512c70c308..1754789364 100644 --- a/v0.5.1/_modules/doctr/datasets/recognition.html +++ b/v0.5.1/_modules/doctr/datasets/recognition.html @@ -13,7 +13,7 @@ - + doctr.datasets.recognition - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.datasets.recognition

         
       
    - + diff --git a/v0.5.1/_modules/doctr/datasets/sroie.html b/v0.5.1/_modules/doctr/datasets/sroie.html index 66fd4ca3e0..04cf10bda2 100644 --- a/v0.5.1/_modules/doctr/datasets/sroie.html +++ b/v0.5.1/_modules/doctr/datasets/sroie.html @@ -13,7 +13,7 @@ - + doctr.datasets.sroie - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.sroie

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import csv
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
     from .datasets import VisionDataset
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['SROIE']
    +__all__ = ["SROIE"]
     
     
     
    -[docs] +[docs] class SROIE(VisionDataset): """SROIE dataset from `"ICDAR2019 Competition on Scanned Receipt OCR and Information Extraction" <https://arxiv.org/pdf/2103.10213.pdf>`_. - Example:: - >>> from doctr.datasets import SROIE - >>> train_set = SROIE(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/sroie-grid.png&src=0 + :align: center + + >>> from doctr.datasets import SROIE + >>> train_set = SROIE(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - TRAIN = ('https://github.com/mindee/doctr/releases/download/v0.1.1/sroie2019_train_task1.zip', - 'd4fa9e60abb03500d83299c845b9c87fd9c9430d1aeac96b83c5d0bb0ab27f6f') - TEST = ('https://github.com/mindee/doctr/releases/download/v0.1.1/sroie2019_test.zip', - '41b3c746a20226fddc80d86d4b2a903d43b5be4f521dd1bbe759dbf8844745e2') + TRAIN = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/sroie2019_train_task1.zip&src=0", + "d4fa9e60abb03500d83299c845b9c87fd9c9430d1aeac96b83c5d0bb0ab27f6f", + "sroie2019_train_task1.zip", + ) + TEST = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/sroie2019_test.zip&src=0", + "41b3c746a20226fddc80d86d4b2a903d43b5be4f521dd1bbe759dbf8844745e2", + "sroie2019_test.zip", + ) def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + url, sha256, name = self.TRAIN if train else self.TEST + super().__init__( + url, + name, + sha256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - url, sha256 = self.TRAIN if train else self.TEST - super().__init__(url, None, sha256, True, **kwargs) - self.sample_transforms = sample_transforms self.train = train - if rotated_bbox: - raise NotImplementedError + tmp_root = os.path.join(self.root, "images") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] + np_dtype = np.float32 - # # List images - self.root = os.path.join(self._root, 'images') - self.data: List[Tuple[str, Dict[str, Any]]] = [] - for img_path in os.listdir(self.root): + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking SROIE", total=len(os.listdir(tmp_root))): # File existence check - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem - _targets = [] - with open(os.path.join(self._root, 'annotations', f"{stem}.txt"), encoding='latin') as f: - for row in csv.reader(f, delimiter=','): - # Safeguard for blank lines - if len(row) > 0: - # Label may contain commas - label = ",".join(row[8:]) - # Reduce 8 coords to 4 - p1_x, p1_y, p2_x, p2_y, p3_x, p3_y, p4_x, p4_y = map(int, row[:8]) - left, right = min(p1_x, p2_x, p3_x, p4_x), max(p1_x, p2_x, p3_x, p4_x) - top, bot = min(p1_y, p2_y, p3_y, p4_y), max(p1_y, p2_y, p3_y, p4_y) - if len(label) > 0: - _targets.append((label, [left, top, right, bot])) - - text_targets, box_targets = zip(*_targets) - - self.data.append((img_path, dict(boxes=np.asarray(box_targets, dtype=np.float32), labels=text_targets))) + with open(os.path.join(self.root, "annotations", f"{stem}.txt"), encoding="latin") as f: + _rows = [row for row in list(csv.reader(f, delimiter=",")) if len(row) > 0] + + labels = [",".join(row[8:]) for row in _rows] + # reorder coordinates (8 -> (4,2) -> + # (x, y) coordinates of top left, top right, bottom right, bottom left corners) and filter empty lines + coords: np.ndarray = np.stack( + [np.array(list(map(int, row[:8])), dtype=np_dtype).reshape((4, 2)) for row in _rows], axis=0 + ) + + if not use_polygons: + # xmin, ymin, xmax, ymax + coords = np.concatenate((coords.min(axis=1), coords.max(axis=1)), axis=1) + + if recognition_task: + crops = crop_bboxes_from_image(img_path=os.path.join(tmp_root, img_path), geoms=coords) + for crop, label in zip(crops, labels): + if crop.shape[0] > 0 and crop.shape[1] > 0 and len(label) > 0: + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, coords)) + else: + self.data.append((img_path, dict(boxes=coords, labels=labels))) + + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -390,8 +444,8 @@

    Source code for doctr.datasets.sroie

           
         
       
    -
    - + + diff --git a/v0.5.1/_modules/doctr/datasets/svhn.html b/v0.5.1/_modules/doctr/datasets/svhn.html index 48e4e4d210..60e02b1b3b 100644 --- a/v0.5.1/_modules/doctr/datasets/svhn.html +++ b/v0.5.1/_modules/doctr/datasets/svhn.html @@ -13,7 +13,7 @@ - + doctr.datasets.svhn - docTR documentation @@ -473,7 +473,7 @@

    Source code for doctr.datasets.svhn

         
       
    - + diff --git a/v0.5.1/_modules/doctr/datasets/svt.html b/v0.5.1/_modules/doctr/datasets/svt.html index 4144dc6b9b..a997fcbb50 100644 --- a/v0.5.1/_modules/doctr/datasets/svt.html +++ b/v0.5.1/_modules/doctr/datasets/svt.html @@ -13,7 +13,7 @@ - + doctr.datasets.svt - docTR documentation @@ -459,7 +459,7 @@

    Source code for doctr.datasets.svt

         
       
    - + diff --git a/v0.5.1/_modules/doctr/datasets/synthtext.html b/v0.5.1/_modules/doctr/datasets/synthtext.html index 3b9de506a7..c776e1d673 100644 --- a/v0.5.1/_modules/doctr/datasets/synthtext.html +++ b/v0.5.1/_modules/doctr/datasets/synthtext.html @@ -13,7 +13,7 @@ - + doctr.datasets.synthtext - docTR documentation @@ -470,7 +470,7 @@

    Source code for doctr.datasets.synthtext

         
       
    - + diff --git a/v0.5.1/_modules/doctr/datasets/utils.html b/v0.5.1/_modules/doctr/datasets/utils.html index 2259698c0f..bde9304597 100644 --- a/v0.5.1/_modules/doctr/datasets/utils.html +++ b/v0.5.1/_modules/doctr/datasets/utils.html @@ -13,7 +13,7 @@ - + doctr.datasets.utils - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.utils

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import string
     import unicodedata
    +from collections.abc import Sequence
    +from functools import partial
    +from pathlib import Path
    +from typing import Any, Dict, List, Optional, Tuple, TypeVar, Union
    +from typing import Sequence as SequenceType
    +
     import numpy as np
    -from typing import List, Optional, Any
    +from PIL import Image
    +
    +from doctr.io.image import get_img_shape
    +from doctr.utils.geometry import convert_to_relative_coords, extract_crops, extract_rcrops
     
     from .vocabs import VOCABS
     
    -__all__ = ['translate', 'encode_sequence', 'decode_sequence', 'encode_sequences']
    +__all__ = ["translate", "encode_string", "decode_sequence", "encode_sequences", "pre_transform_multiclass"]
    +
    +ImageTensor = TypeVar("ImageTensor")
     
     
     def translate(
         input_string: str,
         vocab_name: str,
    -    unknown_char: str = '■',
    +    unknown_char: str = "■",
     ) -> str:
         """Translate a string input in a given vocabulary
     
         Args:
    +    ----
             input_string: input string to translate
             vocab_name: vocabulary to use (french, latin, ...)
             unknown_char: unknown character for non-translatable characters
     
         Returns:
    -        A string translated in a given vocab"""
    -
    +    -------
    +        A string translated in a given vocab
    +    """
         if VOCABS.get(vocab_name) is None:
             raise KeyError("output vocabulary must be in vocabs dictionnary")
     
    -    translated = ''
    +    translated = ""
         for char in input_string:
             if char not in VOCABS[vocab_name]:
                 # we need to translate char into a vocab char
    @@ -315,51 +350,63 @@ 

    Source code for doctr.datasets.utils

                     # remove whitespaces
                     continue
                 # normalize character if it is not in vocab
    -            char = unicodedata.normalize('NFD', char).encode('ascii', 'ignore').decode('ascii')
    -            if char == '' or char not in VOCABS[vocab_name]:
    +            char = unicodedata.normalize("NFD", char).encode("ascii", "ignore").decode("ascii")
    +            if char == "" or char not in VOCABS[vocab_name]:
                     # if normalization fails or char still not in vocab, return unknown character)
                     char = unknown_char
             translated += char
         return translated
     
     
    -def encode_sequence(
    +def encode_string(
         input_string: str,
         vocab: str,
     ) -> List[int]:
         """Given a predefined mapping, encode the string to a sequence of numbers
     
         Args:
    +    ----
             input_string: string to encode
             vocab: vocabulary (string), the encoding is given by the indexing of the character sequence
     
         Returns:
    -        A list encoding the input_string"""
    -
    -    return list(map(vocab.index, input_string))  # type: ignore[arg-type]
    +    -------
    +        A list encoding the input_string
    +    """
    +    try:
    +        return list(map(vocab.index, input_string))
    +    except ValueError:
    +        raise ValueError(
    +            f"some characters cannot be found in 'vocab'. \
    +                         Please check the input string {input_string} and the vocabulary {vocab}"
    +        )
     
     
     def decode_sequence(
    -    input_array: np.array,
    +    input_seq: Union[np.ndarray, SequenceType[int]],
         mapping: str,
     ) -> str:
         """Given a predefined mapping, decode the sequence of numbers to a string
     
         Args:
    -        input_array: array to decode
    +    ----
    +        input_seq: array to decode
             mapping: vocabulary (string), the encoding is given by the indexing of the character sequence
     
         Returns:
    -        A string, decoded from input_array"""
    -
    -    if not input_array.dtype == np.int_ or input_array.max() >= len(mapping):
    +    -------
    +        A string, decoded from input_seq
    +    """
    +    if not isinstance(input_seq, (Sequence, np.ndarray)):
    +        raise TypeError("Invalid sequence type")
    +    if isinstance(input_seq, np.ndarray) and (input_seq.dtype != np.int_ or input_seq.max() >= len(mapping)):
             raise AssertionError("Input must be an array of int, with max less than mapping size")
    -    decoded = ''.join(mapping[idx] for idx in input_array)
    -    return decoded
    +
    +    return "".join(map(mapping.__getitem__, input_seq))
     
     
     
    -[docs] +[docs] def encode_sequences( sequences: List[str], vocab: str, @@ -367,48 +414,53 @@

    Source code for doctr.datasets.utils

         eos: int = -1,
         sos: Optional[int] = None,
         pad: Optional[int] = None,
    -    **kwargs: Any,
    +    dynamic_seq_length: bool = False,
     ) -> np.ndarray:
         """Encode character sequences using a given vocab as mapping
     
         Args:
    +    ----
             sequences: the list of character sequences of size N
             vocab: the ordered vocab to use for encoding
             target_size: maximum length of the encoded data
             eos: encoding of End Of String
             sos: optional encoding of Start Of String
             pad: optional encoding for padding. In case of padding, all sequences are followed by 1 EOS then PAD
    +        dynamic_seq_length: if `target_size` is specified, uses it as upper bound and enables dynamic sequence size
     
         Returns:
    +    -------
             the padded encoded data as a tensor
         """
    -
         if 0 <= eos < len(vocab):
             raise ValueError("argument 'eos' needs to be outside of vocab possible indices")
     
    -    if not isinstance(target_size, int):
    -        target_size = max(len(w) for w in sequences)
    -        if sos:
    -            target_size += 1
    -        if pad:
    -            target_size += 1
    +    if not isinstance(target_size, int) or dynamic_seq_length:
    +        # Maximum string length + EOS
    +        max_length = max(len(w) for w in sequences) + 1
    +        if isinstance(sos, int):
    +            max_length += 1
    +        if isinstance(pad, int):
    +            max_length += 1
    +        target_size = max_length if not isinstance(target_size, int) else min(max_length, target_size)
     
         # Pad all sequences
    -    if pad:  # pad with padding symbol
    +    if isinstance(pad, int):  # pad with padding symbol
             if 0 <= pad < len(vocab):
                 raise ValueError("argument 'pad' needs to be outside of vocab possible indices")
             # In that case, add EOS at the end of the word before padding
    -        encoded_data = np.full([len(sequences), target_size], pad, dtype=np.int32)
    +        default_symbol = pad
         else:  # pad with eos symbol
    -        encoded_data = np.full([len(sequences), target_size], eos, dtype=np.int32)
    +        default_symbol = eos
    +    encoded_data: np.ndarray = np.full([len(sequences), target_size], default_symbol, dtype=np.int32)
     
    -    for idx, seq in enumerate(sequences):
    -        encoded_seq = encode_sequence(seq, vocab)
    -        if pad:  # add eos at the end of the sequence
    -            encoded_seq.append(eos)
    -        encoded_data[idx, :min(len(encoded_seq), target_size)] = encoded_seq[:min(len(encoded_seq), target_size)]
    +    # Encode the strings
    +    for idx, seq in enumerate(map(partial(encode_string, vocab=vocab), sequences)):
    +        if isinstance(pad, int):  # add eos at the end of the sequence
    +            seq.append(eos)
    +        encoded_data[idx, : min(len(seq), target_size)] = seq[: min(len(seq), target_size)]
     
    -    if sos:  # place eos symbol at the beginning of each sequence
    +    if isinstance(sos, int):  # place sos symbol at the beginning of each sequence
             if 0 <= sos < len(vocab):
                 raise ValueError("argument 'sos' needs to be outside of vocab possible indices")
             encoded_data = np.roll(encoded_data, 1)
    @@ -416,6 +468,59 @@ 

    Source code for doctr.datasets.utils

     
         return encoded_data
    + + +def convert_target_to_relative( + img: ImageTensor, target: Union[np.ndarray, Dict[str, Any]] +) -> Tuple[ImageTensor, Union[Dict[str, Any], np.ndarray]]: + if isinstance(target, np.ndarray): + target = convert_to_relative_coords(target, get_img_shape(img)) + else: + target["boxes"] = convert_to_relative_coords(target["boxes"], get_img_shape(img)) + return img, target + + +def crop_bboxes_from_image(img_path: Union[str, Path], geoms: np.ndarray) -> List[np.ndarray]: + """Crop a set of bounding boxes from an image + + Args: + ---- + img_path: path to the image + geoms: a array of polygons of shape (N, 4, 2) or of straight boxes of shape (N, 4) + + Returns: + ------- + a list of cropped images + """ + with Image.open(img_path) as pil_img: + img: np.ndarray = np.array(pil_img.convert("RGB")) + # Polygon + if geoms.ndim == 3 and geoms.shape[1:] == (4, 2): + return extract_rcrops(img, geoms.astype(dtype=int)) + if geoms.ndim == 2 and geoms.shape[1] == 4: + return extract_crops(img, geoms.astype(dtype=int)) + raise ValueError("Invalid geometry format") + + +def pre_transform_multiclass(img, target: Tuple[np.ndarray, List]) -> Tuple[np.ndarray, Dict[str, List]]: + """Converts multiclass target to relative coordinates. + + Args: + ---- + img: Image + target: tuple of target polygons and their classes names + + Returns: + ------- + Image and dictionary of boxes, with class names as keys + """ + boxes = convert_to_relative_coords(target[0], get_img_shape(img)) + boxes_classes = target[1] + boxes_dict: Dict = {k: [] for k in sorted(set(boxes_classes))} + for k, poly in zip(boxes_classes, boxes): + boxes_dict[k].append(poly) + boxes_dict = {k: np.stack(v, axis=0) for k, v in boxes_dict.items()} + return img, boxes_dict
    @@ -448,8 +553,8 @@

    Source code for doctr.datasets.utils

           
         
       
    - - + + diff --git a/v0.5.1/_modules/doctr/datasets/wildreceipt.html b/v0.5.1/_modules/doctr/datasets/wildreceipt.html index c543ee7cac..12c6aebd14 100644 --- a/v0.5.1/_modules/doctr/datasets/wildreceipt.html +++ b/v0.5.1/_modules/doctr/datasets/wildreceipt.html @@ -13,7 +13,7 @@ - + doctr.datasets.wildreceipt - docTR documentation @@ -454,7 +454,7 @@

    Source code for doctr.datasets.wildreceipt

         
       
    - + diff --git a/v0.5.1/_modules/doctr/documents/elements.html b/v0.5.1/_modules/doctr/documents/elements.html deleted file mode 100644 index 10c1e142d2..0000000000 --- a/v0.5.1/_modules/doctr/documents/elements.html +++ /dev/null @@ -1,577 +0,0 @@ - - - - - - - - - - - - doctr.documents.elements - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.elements

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import matplotlib.pyplot as plt
    -from typing import Tuple, Dict, List, Any, Optional, Union
    -
    -from doctr.utils.geometry import resolve_enclosing_bbox, resolve_enclosing_rbbox
    -from doctr.utils.visualization import visualize_page
    -from doctr.utils.common_types import BoundingBox, RotatedBbox
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['Element', 'Word', 'Artefact', 'Line', 'Block', 'Page', 'Document']
    -
    -
    -class Element(NestedObject):
    -    """Implements an abstract document element with exporting and text rendering capabilities"""
    -
    -    _exported_keys: List[str] = []
    -
    -    def __init__(self, **kwargs: Any) -> None:
    -        self._children_names: List[str] = []
    -        for k, v in kwargs.items():
    -            setattr(self, k, v)
    -            self._children_names.append(k)
    -
    -    def export(self) -> Dict[str, Any]:
    -        """Exports the object into a nested dict format"""
    -
    -        export_dict = {k: getattr(self, k) for k in self._exported_keys}
    -        for children_name in self._children_names:
    -            export_dict[children_name] = [c.export() for c in getattr(self, children_name)]
    -
    -        return export_dict
    -
    -    def render(self) -> str:
    -        raise NotImplementedError
    -
    -
    -
    -[docs] -class Word(Element): - """Implements a word element - - Args: - value: the text string of the word - confidence: the confidence associated with the text prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size - """ - - _exported_keys: List[str] = ["value", "confidence", "geometry"] - - def __init__(self, value: str, confidence: float, geometry: Union[BoundingBox, RotatedBbox]) -> None: - super().__init__() - self.value = value - self.confidence = confidence - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return self.value - - def extra_repr(self) -> str: - return f"value='{self.value}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Artefact(Element): - """Implements a non-textual element - - Args: - artefact_type: the type of artefact - confidence: the confidence of the type prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. - """ - - _exported_keys: List[str] = ["geometry", "type", "confidence"] - - def __init__(self, artefact_type: str, confidence: float, geometry: BoundingBox) -> None: - super().__init__() - self.geometry = geometry - self.type = artefact_type - self.confidence = confidence - - def render(self) -> str: - """Renders the full text of the element""" - return f"[{self.type.upper()}]" - - def extra_repr(self) -> str: - return f"type='{self.type}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Line(Element): - """Implements a line element as a collection of words - - Args: - words: list of word elements - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all words in it. - """ - - _exported_keys: List[str] = ["geometry"] - words: List[Word] = [] - - def __init__( - self, - words: List[Word], - geometry: Optional[Union[BoundingBox, RotatedBbox]] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - # Check whether this is a rotated or straight box - box_resolution_fn = resolve_enclosing_rbbox if len(words[0].geometry) == 5 else resolve_enclosing_bbox - geometry = box_resolution_fn([w.geometry for w in words]) # type: ignore[operator, misc] - - super().__init__(words=words) - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return " ".join(w.render() for w in self.words)
    - - - -
    -[docs] -class Block(Element): - """Implements a block element as a collection of lines and artefacts - - Args: - lines: list of line elements - artefacts: list of artefacts - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all lines and artefacts in it. - """ - - _exported_keys: List[str] = ["geometry"] - lines: List[Line] = [] - artefacts: List[Artefact] = [] - - def __init__( - self, - lines: List[Line] = [], - artefacts: List[Artefact] = [], - geometry: Optional[Union[BoundingBox, RotatedBbox]] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - line_boxes = [word.geometry for line in lines for word in line.words] - artefact_boxes = [artefact.geometry for artefact in artefacts] - box_resolution_fn = resolve_enclosing_rbbox if len(lines[0].geometry) == 5 else resolve_enclosing_bbox - geometry = box_resolution_fn(line_boxes + artefact_boxes) # type: ignore[operator, arg-type] - - super().__init__(lines=lines, artefacts=artefacts) - self.geometry = geometry - - def render(self, line_break: str = '\n') -> str: - """Renders the full text of the element""" - return line_break.join(line.render() for line in self.lines)
    - - - -
    -[docs] -class Page(Element): - """Implements a page element as a collection of blocks - - Args: - blocks: list of block elements - page_idx: the index of the page in the input raw document - dimensions: the page size in pixels in format (width, height) - orientation: a dictionary with the value of the rotation angle in degress and confidence of the prediction - language: a dictionary with the language value and confidence of the prediction - """ - - _exported_keys: List[str] = ["page_idx", "dimensions", "orientation", "language"] - blocks: List[Block] = [] - - def __init__( - self, - blocks: List[Block], - page_idx: int, - dimensions: Tuple[int, int], - orientation: Optional[Dict[str, Any]] = None, - language: Optional[Dict[str, Any]] = None, - ) -> None: - super().__init__(blocks=blocks) - self.page_idx = page_idx - self.dimensions = dimensions - self.orientation = orientation if isinstance(orientation, dict) else dict(value=None, confidence=None) - self.language = language if isinstance(language, dict) else dict(value=None, confidence=None) - - def render(self, block_break: str = '\n\n') -> str: - """Renders the full text of the element""" - return block_break.join(b.render() for b in self.blocks) - - def extra_repr(self) -> str: - return f"dimensions={self.dimensions}" - -
    -[docs] - def show( - self, page: np.ndarray, interactive: bool = True, **kwargs - ) -> None: - """Overlay the result on a given image - - Args: - page: image encoded as a numpy array in uint8 - interactive: whether the display should be interactive - """ - visualize_page(self.export(), page, interactive=interactive) - plt.show(**kwargs)
    -
    - - - -
    -[docs] -class Document(Element): - """Implements a document element as a collection of pages - - Args: - pages: list of page elements - """ - - pages: List[Page] = [] - - def __init__( - self, - pages: List[Page], - ) -> None: - super().__init__(pages=pages) - - def render(self, page_break: str = '\n\n\n\n') -> str: - """Renders the full text of the element""" - return page_break.join(p.render() for p in self.pages) - -
    -[docs] - def show(self, pages: List[np.ndarray], **kwargs) -> None: - """Overlay the result on a given image - - Args: - pages: list of images encoded as numpy arrays in uint8 - """ - for img, result in zip(pages, self.pages): - result.show(img, **kwargs)
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.1/_modules/doctr/documents/reader.html b/v0.5.1/_modules/doctr/documents/reader.html deleted file mode 100644 index cdcd814b6c..0000000000 --- a/v0.5.1/_modules/doctr/documents/reader.html +++ /dev/null @@ -1,612 +0,0 @@ - - - - - - - - - - - - doctr.documents.reader - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.reader

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import cv2
    -from pathlib import Path
    -import fitz
    -from weasyprint import HTML
    -from typing import List, Tuple, Optional, Any, Union, Sequence, Dict
    -
    -__all__ = ['read_pdf', 'read_img', 'read_html', 'DocumentFile', 'PDF']
    -
    -
    -AbstractPath = Union[str, Path]
    -AbstractFile = Union[AbstractPath, bytes]
    -Bbox = Tuple[float, float, float, float]
    -
    -
    -
    -[docs] -def read_img( - file: AbstractFile, - output_size: Optional[Tuple[int, int]] = None, - rgb_output: bool = True, -) -> np.ndarray: - """Read an image file into numpy format - - Example:: - >>> from doctr.documents import read_img - >>> page = read_img("path/to/your/doc.jpg") - - Args: - file: the path to the image file - output_size: the expected output size of each page in format H x W - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - Returns: - the page decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)): - if not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - img = cv2.imread(str(file), cv2.IMREAD_COLOR) - elif isinstance(file, bytes): - file = np.frombuffer(file, np.uint8) - img = cv2.imdecode(file, cv2.IMREAD_COLOR) - else: - raise TypeError("unsupported object type for argument 'file'") - - # Validity check - if img is None: - raise ValueError("unable to read file.") - # Resizing - if isinstance(output_size, tuple): - img = cv2.resize(img, output_size[::-1], interpolation=cv2.INTER_LINEAR) - # Switch the channel order - if rgb_output: - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - return img
    - - - -
    -[docs] -def read_pdf(file: AbstractFile, **kwargs: Any) -> fitz.Document: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_pdf - >>> doc = read_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)) and not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - - fitz_args: Dict[str, AbstractFile] = {} - - if isinstance(file, (str, Path)): - fitz_args['filename'] = file - elif isinstance(file, bytes): - fitz_args['stream'] = file - else: - raise TypeError("unsupported object type for argument 'file'") - - # Read pages with fitz and convert them to numpy ndarrays - return fitz.open(**fitz_args, filetype="pdf", **kwargs)
    - - - -def convert_page_to_numpy( - page: fitz.fitz.Page, - output_size: Optional[Tuple[int, int]] = None, - bgr_output: bool = False, - default_scales: Tuple[float, float] = (2, 2), -) -> np.ndarray: - """Convert a fitz page to a numpy-formatted image - - Args: - page: the page of a file read with PyMuPDF - output_size: the expected output size of each page in format H x W. Default goes to 840 x 595 for A4 pdf, - if you want to increase the resolution while preserving the original A4 aspect ratio can pass (1024, 726) - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - default_scales: spatial scaling to be applied when output_size is not specified where (1, 1) - corresponds to 72 dpi rendering. - - Returns: - the rendered image in numpy format - """ - - # If no output size is specified, keep the origin one - if output_size is not None: - scales = (output_size[1] / page.MediaBox[2], output_size[0] / page.MediaBox[3]) - else: - # Default 72 DPI (scales of (1, 1)) is unnecessarily low - scales = default_scales - - transform_matrix = fitz.Matrix(*scales) - - # Generate the pixel map using the transformation matrix - pixmap = page.getPixmap(matrix=transform_matrix) - # Decode it into a numpy - img = np.frombuffer(pixmap.samples, dtype=np.uint8).reshape(pixmap.height, pixmap.width, 3) - - # Switch the channel order - if bgr_output: - img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) - - return img - - -
    -[docs] -def read_html(url: str, **kwargs: Any) -> bytes: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_html - >>> doc = read_html("https://www.yoursite.com") - - Args: - url: URL of the target web page - Returns: - decoded PDF file as a bytes stream - """ - - return HTML(url, **kwargs).write_pdf()
    - - - -
    -[docs] -class PDF: - """PDF document template - - Args: - doc: input PDF document - """ - def __init__(self, doc: fitz.Document) -> None: - self.doc = doc - -
    -[docs] - def as_images(self, **kwargs) -> List[np.ndarray]: - """Convert all document pages to images - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images() - - Args: - kwargs: keyword arguments of `convert_page_to_numpy` - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - return [convert_page_to_numpy(page, **kwargs) for page in self.doc]
    - - - def get_page_words(self, idx, **kwargs) -> List[Tuple[Bbox, str]]: - """Get the annotations for all words of a given page""" - - # xmin, ymin, xmax, ymax, value, block_idx, line_idx, word_idx - return [(info[:4], info[4]) for info in self.doc[idx].getTextWords(**kwargs)] - -
    -[docs] - def get_words(self, **kwargs) -> List[List[Tuple[Bbox, str]]]: - """Get the annotations for all words in the document - - Example:: - >>> from doctr.documents import DocumentFile - >>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words() - - Args: - kwargs: keyword arguments of `fitz.Page.getTextWords` - Returns: - the list of pages annotations, represented as a list of tuple (bounding box, value) - """ - return [self.get_page_words(idx, **kwargs) for idx in range(len(self.doc))]
    - - - def get_page_artefacts(self, idx) -> List[Tuple[float, float, float, float]]: - return [tuple(self.doc[idx].getImageBbox(artefact)) # type: ignore[misc] - for artefact in self.doc[idx].get_images(full=True)] - -
    -[docs] - def get_artefacts(self) -> List[List[Tuple[float, float, float, float]]]: - """Get the artefacts for the entire document - - Example:: - >>> from doctr.documents import DocumentFile - >>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts() - - Returns: - the list of pages artefacts, represented as a list of bounding boxes - """ - - return [self.get_page_artefacts(idx) for idx in range(len(self.doc))]
    -
    - - - -
    -[docs] -class DocumentFile: - """Read a document from multiple extensions""" - -
    -[docs] - @classmethod - def from_pdf(cls, file: AbstractFile, **kwargs) -> PDF: - """Read a PDF file - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file or a binary stream - Returns: - a PDF document - """ - - doc = read_pdf(file, **kwargs) - - return PDF(doc)
    - - -
    -[docs] - @classmethod - def from_url(cls, url: str, **kwargs) -> PDF: - """Interpret a web page as a PDF document - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_url("https://www.yoursite.com") - - Args: - url: the URL of the target web page - Returns: - a PDF document - """ - pdf_stream = read_html(url) - return cls.from_pdf(pdf_stream, **kwargs)
    - - -
    -[docs] - @classmethod - def from_images(cls, files: Union[Sequence[AbstractFile], AbstractFile], **kwargs) -> List[np.ndarray]: - """Read an image file (or a collection of image files) and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"]) - - Args: - files: the path to the image file or a binary stream, or a collection of those - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - if isinstance(files, (str, Path, bytes)): - files = [files] - - return [read_img(file, **kwargs) for file in files]
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.1/_modules/doctr/io/elements.html b/v0.5.1/_modules/doctr/io/elements.html index 753a47455c..e049d6ce30 100644 --- a/v0.5.1/_modules/doctr/io/elements.html +++ b/v0.5.1/_modules/doctr/io/elements.html @@ -13,7 +13,7 @@ - + doctr.io.elements - docTR documentation @@ -1008,7 +1008,7 @@

    Source code for doctr.io.elements

         
       
    - + diff --git a/v0.5.1/_modules/doctr/io/html.html b/v0.5.1/_modules/doctr/io/html.html index 7ad5b97031..be73631500 100644 --- a/v0.5.1/_modules/doctr/io/html.html +++ b/v0.5.1/_modules/doctr/io/html.html @@ -13,7 +13,7 @@ - + doctr.io.html - docTR documentation @@ -360,7 +360,7 @@

    Source code for doctr.io.html

         
       
    - + diff --git a/v0.5.1/_modules/doctr/io/image/base.html b/v0.5.1/_modules/doctr/io/image/base.html index 336b4bff0e..a50c95d595 100644 --- a/v0.5.1/_modules/doctr/io/image/base.html +++ b/v0.5.1/_modules/doctr/io/image/base.html @@ -13,7 +13,7 @@ - + doctr.io.image.base - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.io.image.base

         
       
    - + diff --git a/v0.5.1/_modules/doctr/io/image/tensorflow.html b/v0.5.1/_modules/doctr/io/image/tensorflow.html index f1846820a3..3b9e731756 100644 --- a/v0.5.1/_modules/doctr/io/image/tensorflow.html +++ b/v0.5.1/_modules/doctr/io/image/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.io.image.tensorflow - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.io.image.tensorflow

         
       
    - + diff --git a/v0.5.1/_modules/doctr/io/pdf.html b/v0.5.1/_modules/doctr/io/pdf.html index e3abf6960b..e5b94811c3 100644 --- a/v0.5.1/_modules/doctr/io/pdf.html +++ b/v0.5.1/_modules/doctr/io/pdf.html @@ -13,7 +13,7 @@ - + doctr.io.pdf - docTR documentation @@ -377,7 +377,7 @@

    Source code for doctr.io.pdf

         
       
    - + diff --git a/v0.5.1/_modules/doctr/io/reader.html b/v0.5.1/_modules/doctr/io/reader.html index c1ddc26edd..d36e5bb553 100644 --- a/v0.5.1/_modules/doctr/io/reader.html +++ b/v0.5.1/_modules/doctr/io/reader.html @@ -13,7 +13,7 @@ - + doctr.io.reader - docTR documentation @@ -426,7 +426,7 @@

    Source code for doctr.io.reader

         
       
    - + diff --git a/v0.5.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html b/v0.5.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html index 9f074805c1..61a010d548 100644 --- a/v0.5.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html +++ b/v0.5.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.magc_resnet.tensorflow - docTR documentation @@ -531,7 +531,7 @@

    Source code for doctr.models.classification.magc_resnet.tensorflow

    - + diff --git a/v0.5.1/_modules/doctr/models/classification/mobilenet/tensorflow.html b/v0.5.1/_modules/doctr/models/classification/mobilenet/tensorflow.html index 6a63851276..7c448394ad 100644 --- a/v0.5.1/_modules/doctr/models/classification/mobilenet/tensorflow.html +++ b/v0.5.1/_modules/doctr/models/classification/mobilenet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.mobilenet.tensorflow - docTR documentation @@ -793,7 +793,7 @@

    Source code for doctr.models.classification.mobilenet.tensorflow

    - + diff --git a/v0.5.1/_modules/doctr/models/classification/resnet/tensorflow.html b/v0.5.1/_modules/doctr/models/classification/resnet/tensorflow.html index 095d377f31..aed4343741 100644 --- a/v0.5.1/_modules/doctr/models/classification/resnet/tensorflow.html +++ b/v0.5.1/_modules/doctr/models/classification/resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.resnet.tensorflow - docTR documentation @@ -749,7 +749,7 @@

    Source code for doctr.models.classification.resnet.tensorflow

    - + diff --git a/v0.5.1/_modules/doctr/models/classification/textnet/tensorflow.html b/v0.5.1/_modules/doctr/models/classification/textnet/tensorflow.html index ad254ebbfb..c5567d7d67 100644 --- a/v0.5.1/_modules/doctr/models/classification/textnet/tensorflow.html +++ b/v0.5.1/_modules/doctr/models/classification/textnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.textnet.tensorflow - docTR documentation @@ -611,7 +611,7 @@

    Source code for doctr.models.classification.textnet.tensorflow

    - + diff --git a/v0.5.1/_modules/doctr/models/classification/vgg/tensorflow.html b/v0.5.1/_modules/doctr/models/classification/vgg/tensorflow.html index 01ae452624..788111ae87 100644 --- a/v0.5.1/_modules/doctr/models/classification/vgg/tensorflow.html +++ b/v0.5.1/_modules/doctr/models/classification/vgg/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vgg.tensorflow - docTR documentation @@ -451,7 +451,7 @@

    Source code for doctr.models.classification.vgg.tensorflow

    - + diff --git a/v0.5.1/_modules/doctr/models/classification/vit/tensorflow.html b/v0.5.1/_modules/doctr/models/classification/vit/tensorflow.html index 1333cf6045..971ba5abe9 100644 --- a/v0.5.1/_modules/doctr/models/classification/vit/tensorflow.html +++ b/v0.5.1/_modules/doctr/models/classification/vit/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vit.tensorflow - docTR documentation @@ -533,7 +533,7 @@

    Source code for doctr.models.classification.vit.tensorflow

    - + diff --git a/v0.5.1/_modules/doctr/models/classification/zoo.html b/v0.5.1/_modules/doctr/models/classification/zoo.html index f7796a7522..3eb2a3ec4e 100644 --- a/v0.5.1/_modules/doctr/models/classification/zoo.html +++ b/v0.5.1/_modules/doctr/models/classification/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.classification.zoo - docTR documentation @@ -447,7 +447,7 @@

    Source code for doctr.models.classification.zoo

    <
    - + diff --git a/v0.5.1/_modules/doctr/models/detection/differentiable_binarization.html b/v0.5.1/_modules/doctr/models/detection/differentiable_binarization.html deleted file mode 100644 index 38e9b36ec2..0000000000 --- a/v0.5.1/_modules/doctr/models/detection/differentiable_binarization.html +++ /dev/null @@ -1,879 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.differentiable_binarization - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.differentiable_binarization

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -import cv2
    -from copy import deepcopy
    -import numpy as np
    -from shapely.geometry import Polygon
    -import pyclipper
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers
    -from typing import Union, List, Tuple, Optional, Any, Dict
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..utils import IntermediateLayerGetter, load_pretrained_params, conv_sequence
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['DBPostProcessor', 'DBNet', 'db_resnet50']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'db_resnet50': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'backbone': 'ResNet50',
    -        'fpn_layers': ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"],
    -        'fpn_channels': 128,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'DBPostProcessor',
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.2.0/db_resnet50-adcafc63.zip',
    -    },
    -}
    -
    -
    -class DBPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for DBNet adapted from the implementation of `xuannianz
    -    <https://github.com/xuannianz/DifferentiableBinarization>`_.
    -
    -    Args:
    -        unclip ratio: ratio used to unshrink polygons
    -        min_size_box: minimal length (pix) to keep a box
    -        max_candidates: maximum boxes to consider in a single page
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        unclip_ratio: Union[float, int] = 1.5,
    -        max_candidates: int = 1000,
    -        box_thresh: float = 0.1,
    -        bin_thresh: float = 0.3,
    -    ) -> None:
    -
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -        self.unclip_ratio = unclip_ratio
    -        self.max_candidates = max_candidates
    -
    -    def polygon_to_box(
    -        self,
    -        points: np.ndarray,
    -    ) -> Optional[Tuple[int, int, int, int]]:
    -        """Expand a polygon (points) by a factor unclip_ratio, and returns a 4-points box
    -
    -        Args:
    -            points: The first parameter.
    -
    -        Returns:
    -            a box in absolute coordinates (x, y, w, h)
    -        """
    -        poly = Polygon(points)
    -        distance = poly.area * self.unclip_ratio / poly.length  # compute distance to expand polygon
    -        offset = pyclipper.PyclipperOffset()
    -        offset.AddPath(points, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        _points = offset.Execute(distance)
    -        # Take biggest stack of points
    -        idx = 0
    -        if len(_points) > 1:
    -            max_size = 0
    -            for _idx, p in enumerate(_points):
    -                if len(p) > max_size:
    -                    idx = _idx
    -                    max_size = len(p)
    -            # We ensure that _points can be correctly casted to a ndarray
    -            _points = [_points[idx]]
    -        expanded_points = np.asarray(_points)  # expand polygon
    -        if len(expanded_points) < 1:
    -            return None
    -        x, y, w, h = cv2.boundingRect(expanded_points)  # compute a 4-points box from expanded polygon
    -        return x, y, w, h
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map
    -
    -        Args:
    -            pred: Pred map from differentiable binarization output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        # get contours from connected components on the bitmap
    -        contours, _ = cv2.findContours(bitmap.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    -        for contour in contours[:self.max_candidates]:
    -            # Check whether smallest enclosing bounding box is not too small
    -            if np.any(contour[:, 0].max(axis=0) - contour[:, 0].min(axis=0) < min_size_box):
    -                continue
    -            x, y, w, h = cv2.boundingRect(contour)
    -            points = np.array([[x, y], [x, y + h], [x + w, y + h], [x + w, y]])
    -            # Compute objectness
    -            score = self.box_score(pred, points)
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            _box = self.polygon_to_box(points)
    -
    -            if _box is None or _box[2] < min_size_box or _box[3] < min_size_box:  # remove to small boxes
    -                continue
    -            x, y, w, h = _box
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -class FeaturePyramidNetwork(layers.Layer, NestedObject):
    -    """Feature Pyramid Network as described in `"Feature Pyramid Networks for Object Detection"
    -    <https://arxiv.org/pdf/1612.03144.pdf>`_.
    -
    -    Args:
    -        channels: number of channel to output
    -    """
    -
    -    def __init__(
    -        self,
    -        channels: int,
    -    ) -> None:
    -        super().__init__()
    -        self.channels = channels
    -        self.upsample = layers.UpSampling2D(size=(2, 2), interpolation='nearest')
    -        self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer='he_normal') for _ in range(4)]
    -        self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2 ** idx) for idx in range(4)]
    -
    -    @staticmethod
    -    def build_upsampling(
    -        channels: int,
    -        dilation_factor: int = 1,
    -    ) -> layers.Layer:
    -        """Module which performs a 3x3 convolution followed by up-sampling
    -
    -        Args:
    -            channels: number of output channels
    -            dilation_factor (int): dilation factor to scale the convolution output before concatenation
    -
    -        Returns:
    -            a keras.layers.Layer object, wrapping these operations in a sequential module
    -
    -        """
    -
    -        _layers = conv_sequence(channels, 'relu', True, kernel_size=3)
    -
    -        if dilation_factor > 1:
    -            _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation='nearest'))
    -
    -        module = keras.Sequential(_layers)
    -
    -        return module
    -
    -    def extra_repr(self) -> str:
    -        return f"channels={self.channels}"
    -
    -    def call(
    -        self,
    -        x: List[tf.Tensor],
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # Channel mapping
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.inner_blocks, x)]
    -        # Upsample & sum
    -        for idx in range(len(results) - 1, -1):
    -            results[idx] += self.upsample(results[idx + 1])
    -        # Conv & upsample
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.layer_blocks, results)]
    -
    -        return layers.concatenate(results)
    -
    -
    -class DBNet(DetectionModel, NestedObject):
    -    """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization"
    -    <https://arxiv.org/pdf/1911.08947.pdf>`_.
    -
    -    Args:
    -        feature extractor: the backbone serving as feature extractor
    -        fpn_channels: number of channels each extracted feature maps is mapped to
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'fpn', 'probability_head', 'threshold_head', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: IntermediateLayerGetter,
    -        fpn_channels: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(cfg=cfg)
    -
    -        self.shrink_ratio = 0.4
    -        self.thresh_min = 0.3
    -        self.thresh_max = 0.7
    -        self.min_size_box = 3
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.fpn = FeaturePyramidNetwork(channels=fpn_channels)
    -        # Initialize kernels
    -        _inputs = [layers.Input(shape=in_shape[1:]) for in_shape in self.feat_extractor.output_shape]
    -        output_shape = tuple(self.fpn(_inputs).shape)
    -
    -        self.probability_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -        self.threshold_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -
    -        self.postprocessor = DBPostProcessor()
    -
    -    @staticmethod
    -    def compute_distance(
    -        xs: np.array,
    -        ys: np.array,
    -        a: np.array,
    -        b: np.array,
    -        eps: float = 1e-7,
    -    ) -> float:
    -        """Compute the distance for each point of the map (xs, ys) to the (a, b) segment
    -
    -        Args:
    -            xs : map of x coordinates (height, width)
    -            ys : map of y coordinates (height, width)
    -            a: first point defining the [ab] segment
    -            b: second point defining the [ab] segment
    -
    -        Returns:
    -            The computed distance
    -
    -        """
    -        square_dist_1 = np.square(xs - a[0]) + np.square(ys - a[1])
    -        square_dist_2 = np.square(xs - b[0]) + np.square(ys - b[1])
    -        square_dist = np.square(a[0] - b[0]) + np.square(a[1] - b[1])
    -        cosin = (square_dist - square_dist_1 - square_dist_2) / (2 * np.sqrt(square_dist_1 * square_dist_2) + eps)
    -        square_sin = 1 - np.square(cosin)
    -        square_sin = np.nan_to_num(square_sin)
    -        result = np.sqrt(square_dist_1 * square_dist_2 * square_sin / square_dist)
    -        result[cosin < 0] = np.sqrt(np.fmin(square_dist_1, square_dist_2))[cosin < 0]
    -        return result
    -
    -    def draw_thresh_map(
    -        self,
    -        polygon: np.array,
    -        canvas: np.array,
    -        mask: np.array,
    -    ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    -        """Draw a polygon treshold map on a canvas, as described in the DB paper
    -
    -        Args:
    -            polygon : array of coord., to draw the boundary of the polygon
    -            canvas : threshold map to fill with polygons
    -            mask : mask for training on threshold polygons
    -        """
    -        if polygon.ndim != 2 or polygon.shape[1] != 2:
    -            raise AttributeError("polygon should be a 2 dimensional array of coords")
    -
    -        # Augment polygon by shrink_ratio
    -        polygon_shape = Polygon(polygon)
    -        distance = polygon_shape.area * (1 - np.power(self.shrink_ratio, 2)) / polygon_shape.length
    -        subject = [tuple(coor) for coor in polygon]  # Get coord as list of tuples
    -        padding = pyclipper.PyclipperOffset()
    -        padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        padded_polygon = np.array(padding.Execute(distance)[0])
    -
    -        # Fill the mask with 1 on the new padded polygon
    -        cv2.fillPoly(mask, [padded_polygon.astype(np.int32)], 1.0)
    -
    -        # Get min/max to recover polygon after distance computation
    -        xmin = padded_polygon[:, 0].min()
    -        xmax = padded_polygon[:, 0].max()
    -        ymin = padded_polygon[:, 1].min()
    -        ymax = padded_polygon[:, 1].max()
    -        width = xmax - xmin + 1
    -        height = ymax - ymin + 1
    -        # Get absolute polygon for distance computation
    -        polygon[:, 0] = polygon[:, 0] - xmin
    -        polygon[:, 1] = polygon[:, 1] - ymin
    -        # Get absolute padded polygon
    -        xs = np.broadcast_to(np.linspace(0, width - 1, num=width).reshape(1, width), (height, width))
    -        ys = np.broadcast_to(np.linspace(0, height - 1, num=height).reshape(height, 1), (height, width))
    -
    -        # Compute distance map to fill the padded polygon
    -        distance_map = np.zeros((polygon.shape[0], height, width), dtype=np.float32)
    -        for i in range(polygon.shape[0]):
    -            j = (i + 1) % polygon.shape[0]
    -            absolute_distance = self.compute_distance(xs, ys, polygon[i], polygon[j])
    -            distance_map[i] = np.clip(absolute_distance / distance, 0, 1)
    -        distance_map = np.min(distance_map, axis=0)
    -
    -        # Clip the padded polygon inside the canvas
    -        xmin_valid = min(max(0, xmin), canvas.shape[1] - 1)
    -        xmax_valid = min(max(0, xmax), canvas.shape[1] - 1)
    -        ymin_valid = min(max(0, ymin), canvas.shape[0] - 1)
    -        ymax_valid = min(max(0, ymax), canvas.shape[0] - 1)
    -
    -        # Fill the canvas with the distances computed inside the valid padded polygon
    -        canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1] = np.fmax(
    -            1 - distance_map[
    -                ymin_valid - ymin:ymax_valid - ymin + 1,
    -                xmin_valid - xmin:xmax_valid - xmin + 1
    -            ],
    -            canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1]
    -        )
    -
    -        return polygon, canvas, mask
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor, tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.uint8)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -        thresh_target = np.zeros(output_shape, dtype=np.uint8)
    -        thresh_mask = np.ones(output_shape, dtype=np.uint8)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            polys = np.stack([
    -                abs_boxes[:, [0, 1]],
    -                abs_boxes[:, [0, 3]],
    -                abs_boxes[:, [2, 3]],
    -                abs_boxes[:, [2, 1]],
    -            ], axis=1)
    -
    -            for box, box_size, poly, is_ambiguous in zip(abs_boxes, boxes_size, polys, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -
    -                # Negative shrink for gt, as described in paper
    -                polygon = Polygon(poly)
    -                distance = polygon.area * (1 - np.power(self.shrink_ratio, 2)) / polygon.length
    -                subject = [tuple(coor) for coor in poly]
    -                padding = pyclipper.PyclipperOffset()
    -                padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -                shrinked = padding.Execute(-distance)
    -
    -                # Draw polygon on gt if it is valid
    -                if len(shrinked) == 0:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                shrinked = np.array(shrinked[0]).reshape(-1, 2)
    -                if shrinked.shape[0] <= 2 or not Polygon(shrinked).is_valid:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                cv2.fillPoly(seg_target[idx], [shrinked.astype(np.int32)], 1)
    -
    -                # Draw on both thresh map and thresh mask
    -                poly, thresh_target[idx], thresh_mask[idx] = self.draw_thresh_map(poly, thresh_target[idx],
    -                                                                                  thresh_mask[idx])
    -
    -        thresh_target = thresh_target.astype(np.float32) * (self.thresh_max - self.thresh_min) + self.thresh_min
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -        thresh_target = tf.convert_to_tensor(thresh_target, dtype=tf.float32)
    -        thresh_mask = tf.convert_to_tensor(thresh_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask, thresh_target, thresh_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        thresh_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts, masks, thresh_gts, thresh_masks from a list of boxes
    -        and a list of masks for each image. From there it computes the loss with the model output
    -
    -        Args:
    -            out_map: output feature map of the model of shape (N, H, W, C)
    -            thresh_map: threshold map of shape (N, H, W, C)
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -
    -        prob_map = tf.math.sigmoid(tf.squeeze(out_map, axis=[-1]))
    -        thresh_map = tf.math.sigmoid(tf.squeeze(thresh_map, axis=[-1]))
    -
    -        seg_target, seg_mask, thresh_target, thresh_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute balanced BCE loss for proba_map
    -        bce_scale = 5.
    -        bce_loss = tf.keras.losses.binary_crossentropy(seg_target[..., None], out_map, from_logits=True)[seg_mask]
    -
    -        neg_target = 1 - seg_target[seg_mask]
    -        positive_count = tf.math.reduce_sum(seg_target[seg_mask])
    -        negative_count = tf.math.reduce_min([tf.math.reduce_sum(neg_target), 3. * positive_count])
    -        negative_loss = bce_loss * neg_target
    -        negative_loss, _ = tf.nn.top_k(negative_loss, tf.cast(negative_count, tf.int32))
    -        sum_losses = tf.math.reduce_sum(bce_loss * seg_target[seg_mask]) + tf.math.reduce_sum(negative_loss)
    -        balanced_bce_loss = sum_losses / (positive_count + negative_count + 1e-6)
    -
    -        # Compute dice loss for approxbin_map
    -        bin_map = 1 / (1 + tf.exp(-50. * (prob_map[seg_mask] - thresh_map[seg_mask])))
    -
    -        bce_min = tf.math.reduce_min(bce_loss)
    -        weights = (bce_loss - bce_min) / (tf.math.reduce_max(bce_loss) - bce_min) + 1.
    -        inter = tf.math.reduce_sum(bin_map * seg_target[seg_mask] * weights)
    -        union = tf.math.reduce_sum(bin_map) + tf.math.reduce_sum(seg_target[seg_mask]) + 1e-8
    -        dice_loss = 1 - 2.0 * inter / union
    -
    -        # Compute l1 loss for thresh_map
    -        l1_scale = 10.
    -        if tf.reduce_any(thresh_mask):
    -            l1_loss = tf.math.reduce_mean(tf.math.abs(thresh_map[thresh_mask] - thresh_target[thresh_mask]))
    -        else:
    -            l1_loss = tf.constant(0.)
    -
    -        return l1_scale * l1_loss + bce_scale * balanced_bce_loss + dice_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        feat_maps = self.feat_extractor(x, **kwargs)
    -        feat_concat = self.fpn(feat_maps, **kwargs)
    -        logits = self.probability_head(feat_concat, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            thresh_map = self.threshold_head(feat_concat, **kwargs)
    -            loss = self.compute_loss(logits, thresh_map, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _db_resnet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> DBNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['fpn_channels'] = kwargs.get('fpn_channels', _cfg['fpn_channels'])
    -
    -    # Feature extractor
    -    resnet = tf.keras.applications.__dict__[_cfg['backbone']](
    -        include_top=False,
    -        weights=None,
    -        input_shape=_cfg['input_shape'],
    -        pooling=None,
    -    )
    -
    -    feat_extractor = IntermediateLayerGetter(
    -        resnet,
    -        _cfg['fpn_layers'],
    -    )
    -
    -    kwargs['fpn_channels'] = _cfg['fpn_channels']
    -
    -    # Build the model
    -    model = DBNet(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def db_resnet50(pretrained: bool = False, **kwargs: Any) -> DBNet: - """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" - <https://arxiv.org/pdf/1911.08947.pdf>`_, using a ResNet-50 backbone. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _db_resnet('db_resnet50', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html b/v0.5.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html index 9145c7c3fd..66cef8663d 100644 --- a/v0.5.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html +++ b/v0.5.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.differentiable_binarization.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     # Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
     
     from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers
    -from typing import List, Tuple, Optional, Any, Dict
    +from typing import Any, Dict, List, Optional, Tuple
     
    +import numpy as np
    +import tensorflow as tf
    +from tensorflow.keras import Model, Sequential, layers, losses
    +from tensorflow.keras.applications import ResNet50
    +
    +from doctr.file_utils import CLASS_NAME
    +from doctr.models.utils import (
    +    IntermediateLayerGetter,
    +    _bf16_to_float32,
    +    _build_model,
    +    conv_sequence,
    +    load_pretrained_params,
    +)
     from doctr.utils.repr import NestedObject
    -from doctr.models.utils import IntermediateLayerGetter, load_pretrained_params, conv_sequence
    +
    +from ...classification import mobilenet_v3_large
     from .base import DBPostProcessor, _DBNet
     
    -__all__ = ['DBNet', 'db_resnet50']
    +__all__ = ["DBNet", "db_resnet50", "db_mobilenet_v3_large"]
     
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'db_resnet50': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'backbone': 'ResNet50',
    -        'fpn_layers': ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"],
    -        'fpn_channels': 128,
    -        'input_shape': (1024, 1024, 3),
    -        'rotated_bbox': False,
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.2.0/db_resnet50-adcafc63.zip',
    +    "db_resnet50": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/db_resnet50-649fa22b.weights.h5&src=0",
    +    },
    +    "db_mobilenet_v3_large": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/db_mobilenet_v3_large-ee2e1dbe.weights.h5&src=0",
         },
     }
     
    @@ -313,6 +348,7 @@ 

    Source code for doctr.models.detection.differentiable_binarization.tensorflo <https://arxiv.org/pdf/1612.03144.pdf>`_. Args: + ---- channels: number of channel to output """ @@ -322,9 +358,9 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo ) -> None: super().__init__() self.channels = channels - self.upsample = layers.UpSampling2D(size=(2, 2), interpolation='nearest') - self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer='he_normal') for _ in range(4)] - self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2 ** idx) for idx in range(4)] + self.upsample = layers.UpSampling2D(size=(2, 2), interpolation="nearest") + self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer="he_normal") for _ in range(4)] + self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2**idx) for idx in range(4)] @staticmethod def build_upsampling( @@ -334,20 +370,21 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo """Module which performs a 3x3 convolution followed by up-sampling Args: + ---- channels: number of output channels dilation_factor (int): dilation factor to scale the convolution output before concatenation Returns: + ------- a keras.layers.Layer object, wrapping these operations in a sequential module """ - - _layers = conv_sequence(channels, 'relu', True, kernel_size=3) + _layers = conv_sequence(channels, "relu", True, kernel_size=3) if dilation_factor > 1: - _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation='nearest')) + _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation="nearest")) - module = keras.Sequential(_layers) + module = Sequential(_layers) return module @@ -359,7 +396,6 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo x: List[tf.Tensor], **kwargs: Any, ) -> tf.Tensor: - # Channel mapping results = [block(fmap, **kwargs) for block, fmap in zip(self.inner_blocks, x)] # Upsample & sum @@ -371,200 +407,324 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo return layers.concatenate(results) -class DBNet(_DBNet, keras.Model, NestedObject): +class DBNet(_DBNet, Model, NestedObject): """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" <https://arxiv.org/pdf/1911.08947.pdf>`_. Args: + ---- feature extractor: the backbone serving as feature extractor fpn_channels: number of channels each extracted feature maps is mapped to + bin_thresh: threshold for binarization + box_thresh: minimal objectness score to consider a box + assume_straight_pages: if True, fit straight bounding boxes only + exportable: onnx exportable returns only logits + cfg: the configuration dict of the model + class_names: list of class names """ - _children_names: List[str] = ['feat_extractor', 'fpn', 'probability_head', 'threshold_head', 'postprocessor'] + _children_names: List[str] = ["feat_extractor", "fpn", "probability_head", "threshold_head", "postprocessor"] def __init__( self, feature_extractor: IntermediateLayerGetter, - fpn_channels: int = 128, - rotated_bbox: bool = False, + fpn_channels: int = 128, # to be set to 256 to represent the author's initial idea + bin_thresh: float = 0.3, + box_thresh: float = 0.1, + assume_straight_pages: bool = True, + exportable: bool = False, cfg: Optional[Dict[str, Any]] = None, + class_names: List[str] = [CLASS_NAME], ) -> None: - super().__init__() + self.class_names = class_names + num_classes: int = len(self.class_names) self.cfg = cfg self.feat_extractor = feature_extractor - self.rotated_bbox = rotated_bbox + self.exportable = exportable + self.assume_straight_pages = assume_straight_pages self.fpn = FeaturePyramidNetwork(channels=fpn_channels) # Initialize kernels _inputs = [layers.Input(shape=in_shape[1:]) for in_shape in self.feat_extractor.output_shape] output_shape = tuple(self.fpn(_inputs).shape) - self.probability_head = keras.Sequential( - [ - *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]), - layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'), - layers.BatchNormalization(), - layers.Activation('relu'), - layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'), - ] + self.probability_head = Sequential([ + *conv_sequence(64, "relu", True, kernel_size=3, input_shape=output_shape[1:]), + layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer="he_normal"), + layers.BatchNormalization(), + layers.Activation("relu"), + layers.Conv2DTranspose(num_classes, 2, strides=2, kernel_initializer="he_normal"), + ]) + self.threshold_head = Sequential([ + *conv_sequence(64, "relu", True, kernel_size=3, input_shape=output_shape[1:]), + layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer="he_normal"), + layers.BatchNormalization(), + layers.Activation("relu"), + layers.Conv2DTranspose(num_classes, 2, strides=2, kernel_initializer="he_normal"), + ]) + + self.postprocessor = DBPostProcessor( + assume_straight_pages=assume_straight_pages, bin_thresh=bin_thresh, box_thresh=box_thresh ) - self.threshold_head = keras.Sequential( - [ - *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]), - layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'), - layers.BatchNormalization(), - layers.Activation('relu'), - layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'), - ] - ) - - self.postprocessor = DBPostProcessor(rotated_bbox=rotated_bbox) def compute_loss( self, out_map: tf.Tensor, thresh_map: tf.Tensor, - target: List[Dict[str, Any]] + target: List[Dict[str, np.ndarray]], + gamma: float = 2.0, + alpha: float = 0.5, + eps: float = 1e-8, ) -> tf.Tensor: """Compute a batch of gts, masks, thresh_gts, thresh_masks from a list of boxes and a list of masks for each image. From there it computes the loss with the model output Args: + ---- out_map: output feature map of the model of shape (N, H, W, C) thresh_map: threshold map of shape (N, H, W, C) target: list of dictionary where each dict has a `boxes` and a `flags` entry + gamma: modulating factor in the focal loss formula + alpha: balancing factor in the focal loss formula + eps: epsilon factor in dice loss Returns: + ------- A loss tensor """ + if gamma < 0: + raise ValueError("Value of gamma should be greater than or equal to zero.") - prob_map = tf.math.sigmoid(tf.squeeze(out_map, axis=[-1])) - thresh_map = tf.math.sigmoid(tf.squeeze(thresh_map, axis=[-1])) + prob_map = tf.math.sigmoid(out_map) + thresh_map = tf.math.sigmoid(thresh_map) - seg_target, seg_mask, thresh_target, thresh_mask = self.compute_target(target, out_map.shape[:3]) - seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32) + seg_target, seg_mask, thresh_target, thresh_mask = self.build_target(target, out_map.shape[1:], True) + seg_target = tf.convert_to_tensor(seg_target, dtype=out_map.dtype) seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool) - thresh_target = tf.convert_to_tensor(thresh_target, dtype=tf.float32) + seg_mask = tf.cast(seg_mask, tf.float32) + thresh_target = tf.convert_to_tensor(thresh_target, dtype=out_map.dtype) thresh_mask = tf.convert_to_tensor(thresh_mask, dtype=tf.bool) - # Compute balanced BCE loss for proba_map - bce_scale = 5. - bce_loss = tf.keras.losses.binary_crossentropy(seg_target[..., None], out_map, from_logits=True)[seg_mask] - - neg_target = 1 - seg_target[seg_mask] - positive_count = tf.math.reduce_sum(seg_target[seg_mask]) - negative_count = tf.math.reduce_min([tf.math.reduce_sum(neg_target), 3. * positive_count]) - negative_loss = bce_loss * neg_target - negative_loss, _ = tf.nn.top_k(negative_loss, tf.cast(negative_count, tf.int32)) - sum_losses = tf.math.reduce_sum(bce_loss * seg_target[seg_mask]) + tf.math.reduce_sum(negative_loss) - balanced_bce_loss = sum_losses / (positive_count + negative_count + 1e-6) - - # Compute dice loss for approxbin_map - bin_map = 1 / (1 + tf.exp(-50. * (prob_map[seg_mask] - thresh_map[seg_mask]))) - - bce_min = tf.math.reduce_min(bce_loss) - weights = (bce_loss - bce_min) / (tf.math.reduce_max(bce_loss) - bce_min) + 1. - inter = tf.math.reduce_sum(bin_map * seg_target[seg_mask] * weights) - union = tf.math.reduce_sum(bin_map) + tf.math.reduce_sum(seg_target[seg_mask]) + 1e-8 - dice_loss = 1 - 2.0 * inter / union + # Focal loss + focal_scale = 10.0 + bce_loss = losses.binary_crossentropy(seg_target[..., None], out_map[..., None], from_logits=True) + + # Convert logits to prob, compute gamma factor + p_t = (seg_target * prob_map) + ((1 - seg_target) * (1 - prob_map)) + alpha_t = seg_target * alpha + (1 - seg_target) * (1 - alpha) + # Unreduced loss + focal_loss = alpha_t * (1 - p_t) ** gamma * bce_loss + # Class reduced + focal_loss = tf.reduce_sum(seg_mask * focal_loss, (0, 1, 2, 3)) / tf.reduce_sum(seg_mask, (0, 1, 2, 3)) + + # Compute dice loss for each class or for approx binary_map + if len(self.class_names) > 1: + dice_map = tf.nn.softmax(out_map, axis=-1) + else: + # compute binary map instead + dice_map = 1.0 / (1.0 + tf.exp(-50 * (prob_map - thresh_map))) + # Class-reduced dice loss + inter = tf.reduce_sum(seg_mask * dice_map * seg_target, axis=[0, 1, 2]) + cardinality = tf.reduce_sum(seg_mask * (dice_map + seg_target), axis=[0, 1, 2]) + dice_loss = tf.reduce_mean(1 - 2 * inter / (cardinality + eps)) # Compute l1 loss for thresh_map - l1_scale = 10. if tf.reduce_any(thresh_mask): - l1_loss = tf.math.reduce_mean(tf.math.abs(thresh_map[thresh_mask] - thresh_target[thresh_mask])) + thresh_mask = tf.cast(thresh_mask, tf.float32) + l1_loss = tf.reduce_sum(tf.abs(thresh_map - thresh_target) * thresh_mask) / ( + tf.reduce_sum(thresh_mask) + eps + ) else: - l1_loss = tf.constant(0.) + l1_loss = tf.constant(0.0) - return l1_scale * l1_loss + bce_scale * balanced_bce_loss + dice_loss + return l1_loss + focal_scale * focal_loss + dice_loss def call( self, x: tf.Tensor, - target: Optional[List[Dict[str, Any]]] = None, + target: Optional[List[Dict[str, np.ndarray]]] = None, return_model_output: bool = False, - return_boxes: bool = False, + return_preds: bool = False, **kwargs: Any, ) -> Dict[str, Any]: - feat_maps = self.feat_extractor(x, **kwargs) feat_concat = self.fpn(feat_maps, **kwargs) logits = self.probability_head(feat_concat, **kwargs) out: Dict[str, tf.Tensor] = {} - if return_model_output or target is None or return_boxes: - prob_map = tf.math.sigmoid(logits) + if self.exportable: + out["logits"] = logits + return out + + if return_model_output or target is None or return_preds: + prob_map = _bf16_to_float32(tf.math.sigmoid(logits)) if return_model_output: out["out_map"] = prob_map - if target is None or return_boxes: - # Post-process boxes - out["preds"] = self.postprocessor(tf.squeeze(prob_map, axis=-1).numpy()) + if target is None or return_preds: + # Post-process boxes (keep only text predictions) + out["preds"] = [dict(zip(self.class_names, preds)) for preds in self.postprocessor(prob_map.numpy())] if target is not None: thresh_map = self.threshold_head(feat_concat, **kwargs) loss = self.compute_loss(logits, thresh_map, target) - out['loss'] = loss + out["loss"] = loss return out -def _db_resnet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> DBNet: +def _db_resnet( + arch: str, + pretrained: bool, + backbone_fn, + fpn_layers: List[str], + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> DBNet: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['fpn_channels'] = kwargs.get('fpn_channels', _cfg['fpn_channels']) - _cfg['rotated_bbox'] = kwargs.get('rotated_bbox', _cfg['rotated_bbox']) + _cfg["input_shape"] = input_shape or _cfg["input_shape"] + if not kwargs.get("class_names", None): + kwargs["class_names"] = _cfg.get("class_names", [CLASS_NAME]) + else: + kwargs["class_names"] = sorted(kwargs["class_names"]) # Feature extractor - resnet = tf.keras.applications.__dict__[_cfg['backbone']]( - include_top=False, - weights=None, - input_shape=_cfg['input_shape'], - pooling=None, + feat_extractor = IntermediateLayerGetter( + backbone_fn( + weights="imagenet" if pretrained_backbone else None, + include_top=False, + pooling=None, + input_shape=_cfg["input_shape"], + ), + fpn_layers, ) + # Build the model + model = DBNet(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) + + # Load pretrained parameters + if pretrained: + # The given class_names differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, + _cfg["url"], + skip_mismatch=kwargs["class_names"] != default_cfgs[arch].get("class_names", [CLASS_NAME]), + ) + + return model + + +def _db_mobilenet( + arch: str, + pretrained: bool, + backbone_fn, + fpn_layers: List[str], + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> DBNet: + pretrained_backbone = pretrained_backbone and not pretrained + + # Patch the config + _cfg = deepcopy(default_cfgs[arch]) + _cfg["input_shape"] = input_shape or _cfg["input_shape"] + if not kwargs.get("class_names", None): + kwargs["class_names"] = default_cfgs[arch].get("class_names", [CLASS_NAME]) + else: + kwargs["class_names"] = sorted(kwargs["class_names"]) + + # Feature extractor feat_extractor = IntermediateLayerGetter( - resnet, - _cfg['fpn_layers'], + backbone_fn( + input_shape=_cfg["input_shape"], + include_top=False, + pretrained=pretrained_backbone, + ), + fpn_layers, ) - kwargs['fpn_channels'] = _cfg['fpn_channels'] - kwargs['rotated_bbox'] = _cfg['rotated_bbox'] - # Build the model model = DBNet(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) # Load pretrained parameters if pretrained: - load_pretrained_params(model, _cfg['url']) + # The given class_names differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, + _cfg["url"], + skip_mismatch=kwargs["class_names"] != default_cfgs[arch].get("class_names", [CLASS_NAME]), + ) return model
    -[docs] +[docs] def db_resnet50(pretrained: bool = False, **kwargs: Any) -> DBNet: """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" <https://arxiv.org/pdf/1911.08947.pdf>`_, using a ResNet-50 backbone. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import db_resnet50 + >>> model = db_resnet50(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the DBNet architecture Returns: + ------- text detection architecture """ + return _db_resnet( + "db_resnet50", + pretrained, + ResNet50, + ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"], + **kwargs, + )
    + + + +
    +[docs] +def db_mobilenet_v3_large(pretrained: bool = False, **kwargs: Any) -> DBNet: + """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" + <https://arxiv.org/pdf/1911.08947.pdf>`_, using a mobilenet v3 large backbone. + + >>> import tensorflow as tf + >>> from doctr.models import db_mobilenet_v3_large + >>> model = db_mobilenet_v3_large(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) - return _db_resnet('db_resnet50', pretrained, **kwargs)
    + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the DBNet architecture + + Returns: + ------- + text detection architecture + """ + return _db_mobilenet( + "db_mobilenet_v3_large", + pretrained, + mobilenet_v3_large, + ["inverted_2", "inverted_5", "inverted_11", "final_block"], + **kwargs, + )

    @@ -598,8 +758,8 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo - - + + diff --git a/v0.5.1/_modules/doctr/models/detection/fast/tensorflow.html b/v0.5.1/_modules/doctr/models/detection/fast/tensorflow.html index 5b84d2dea1..65e1a77af8 100644 --- a/v0.5.1/_modules/doctr/models/detection/fast/tensorflow.html +++ b/v0.5.1/_modules/doctr/models/detection/fast/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.fast.tensorflow - docTR documentation @@ -769,7 +769,7 @@

    Source code for doctr.models.detection.fast.tensorflow

    - + diff --git a/v0.5.1/_modules/doctr/models/detection/linknet.html b/v0.5.1/_modules/doctr/models/detection/linknet.html deleted file mode 100644 index 129cfdce8b..0000000000 --- a/v0.5.1/_modules/doctr/models/detection/linknet.html +++ /dev/null @@ -1,644 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.linknet - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.linknet

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -import numpy as np
    -import cv2
    -from tensorflow.keras import layers, Sequential
    -from typing import Dict, Any, Tuple, Optional, List
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..backbones import ResnetStage
    -from ..utils import conv_sequence, load_pretrained_params
    -from ...utils.repr import NestedObject
    -
    -__all__ = ['LinkNet', 'linknet', 'LinkNetPostProcessor']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'linknet': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'out_chan': 1,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'LinkNetPostProcessor',
    -        'url': None,
    -    },
    -}
    -
    -
    -class LinkNetPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for LinkNet model.
    -
    -    Args:
    -        min_size_box: minimal length (pix) to keep a box
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        min_size_box: int = 3,
    -        bin_thresh: float = 0.15,
    -        box_thresh: float = 0.1,
    -    ) -> None:
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map: find connected components then filter boxes
    -
    -        Args:
    -            pred: Pred map from differentiable linknet output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        label_num, labelimage = cv2.connectedComponents(bitmap.astype(np.uint8), connectivity=4)
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        for label in range(1, label_num + 1):
    -            points = np.array(np.where(labelimage == label)[::-1]).T
    -            if points.shape[0] < 4:  # remove polygons with 3 points or less
    -                continue
    -            score = self.box_score(pred, points.reshape(-1, 2))
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            x, y, w, h = cv2.boundingRect(points)
    -            if min(w, h) < min_size_box:  # filter too small boxes
    -                continue
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -def decoder_block(in_chan: int, out_chan: int) -> Sequential:
    -    """Creates a LinkNet decoder block"""
    -
    -    return Sequential([
    -        *conv_sequence(in_chan // 4, 'relu', True, kernel_size=1),
    -        layers.Conv2DTranspose(
    -            filters=in_chan // 4,
    -            kernel_size=3,
    -            strides=2,
    -            padding="same",
    -            use_bias=False,
    -            kernel_initializer='he_normal'
    -        ),
    -        layers.BatchNormalization(),
    -        layers.Activation('relu'),
    -        *conv_sequence(out_chan, 'relu', True, kernel_size=1),
    -    ])
    -
    -
    -class LinkNetFPN(layers.Layer, NestedObject):
    -    """LinkNet Encoder-Decoder module
    -
    -    """
    -
    -    def __init__(
    -        self,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.encoder_1 = ResnetStage(num_blocks=2, output_channels=64, downsample=True)
    -        self.encoder_2 = ResnetStage(num_blocks=2, output_channels=128, downsample=True)
    -        self.encoder_3 = ResnetStage(num_blocks=2, output_channels=256, downsample=True)
    -        self.encoder_4 = ResnetStage(num_blocks=2, output_channels=512, downsample=True)
    -        self.decoder_1 = decoder_block(in_chan=64, out_chan=64)
    -        self.decoder_2 = decoder_block(in_chan=128, out_chan=64)
    -        self.decoder_3 = decoder_block(in_chan=256, out_chan=128)
    -        self.decoder_4 = decoder_block(in_chan=512, out_chan=256)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor
    -    ) -> tf.Tensor:
    -        x_1 = self.encoder_1(x)
    -        x_2 = self.encoder_2(x_1)
    -        x_3 = self.encoder_3(x_2)
    -        x_4 = self.encoder_4(x_3)
    -        y_4 = self.decoder_4(x_4)
    -        y_3 = self.decoder_3(y_4 + x_3)
    -        y_2 = self.decoder_2(y_3 + x_2)
    -        y_1 = self.decoder_1(y_2 + x_1)
    -        return y_1
    -
    -
    -class LinkNet(DetectionModel, NestedObject):
    -    """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation"
    -    <https://arxiv.org/pdf/1707.03718.pdf>`_.
    -
    -    Args:
    -        out_chan: number of channels for the output
    -    """
    -
    -    _children_names: List[str] = ['stem', 'fpn', 'classifier', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        out_chan: int = 1,
    -        input_shape: Tuple[int, int, int] = (512, 512, 3),
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(cfg=cfg)
    -
    -        self.stem = Sequential([
    -            *conv_sequence(64, 'relu', True, strides=2, kernel_size=7, input_shape=input_shape),
    -            layers.MaxPool2D(pool_size=(3, 3), strides=2, padding='same'),
    -        ])
    -
    -        self.fpn = LinkNetFPN()
    -
    -        self.classifier = Sequential([
    -            layers.Conv2DTranspose(
    -                filters=32,
    -                kernel_size=3,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -            layers.BatchNormalization(),
    -            layers.Activation('relu'),
    -            *conv_sequence(32, 'relu', True, strides=1, kernel_size=3),
    -            layers.Conv2DTranspose(
    -                filters=out_chan,
    -                kernel_size=2,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -        ])
    -
    -        self.min_size_box = 3
    -
    -        self.postprocessor = LinkNetPostProcessor()
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.bool)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            for box, box_size, is_ambiguous in zip(abs_boxes, boxes_size, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Fill polygon with 1
    -                seg_target[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = True
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts and masks from a list of boxes and a list of masks for each image
    -        Then, it computes the loss function with proba_map, gts and masks
    -
    -        Args:
    -            out_map: output feature map of the model of shape N x H x W x 1
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -        seg_target, seg_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute BCE loss
    -        return tf.math.reduce_mean(tf.keras.losses.binary_crossentropy(
    -            seg_target[seg_mask],
    -            tf.squeeze(out_map, axis=[-1])[seg_mask],
    -            from_logits=True
    -        ))
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        logits = self.stem(x)
    -        logits = self.fpn(logits)
    -        logits = self.classifier(logits)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            loss = self.compute_loss(logits, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _linknet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> LinkNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['out_chan'] = kwargs.get('out_chan', _cfg['out_chan'])
    -
    -    kwargs['out_chan'] = _cfg['out_chan']
    -    kwargs['input_shape'] = _cfg['input_shape']
    -    # Build the model
    -    model = LinkNet(cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def linknet(pretrained: bool = False, **kwargs: Any) -> LinkNet: - """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" - <https://arxiv.org/pdf/1707.03718.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import linknet - >>> model = linknet(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _linknet('linknet', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.1/_modules/doctr/models/detection/linknet/tensorflow.html b/v0.5.1/_modules/doctr/models/detection/linknet/tensorflow.html index cd4f446673..ce995f99d4 100644 --- a/v0.5.1/_modules/doctr/models/detection/linknet/tensorflow.html +++ b/v0.5.1/_modules/doctr/models/detection/linknet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.linknet.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.detection.linknet.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     # Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
     
     from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers, Sequential
    -from typing import Dict, Any, Tuple, Optional, List
    +from typing import Any, Dict, List, Optional, Tuple
     
    +import numpy as np
    +import tensorflow as tf
    +from tensorflow.keras import Model, Sequential, layers, losses
    +
    +from doctr.file_utils import CLASS_NAME
    +from doctr.models.classification import resnet18, resnet34, resnet50
    +from doctr.models.utils import (
    +    IntermediateLayerGetter,
    +    _bf16_to_float32,
    +    _build_model,
    +    conv_sequence,
    +    load_pretrained_params,
    +)
     from doctr.utils.repr import NestedObject
    -from doctr.models.backbones import ResnetStage
    -from doctr.models.utils import conv_sequence, load_pretrained_params
    -from .base import LinkNetPostProcessor, _LinkNet
     
    -__all__ = ['LinkNet', 'linknet16']
    +from .base import LinkNetPostProcessor, _LinkNet
     
    +__all__ = ["LinkNet", "linknet_resnet18", "linknet_resnet34", "linknet_resnet50"]
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'linknet16': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'num_classes': 1,
    -        'input_shape': (1024, 1024, 3),
    -        'rotated_bbox': False,
    -        'url': None,
    +    "linknet_resnet18": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/linknet_resnet18-615a82c5.weights.h5&src=0",
    +    },
    +    "linknet_resnet34": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/linknet_resnet34-9d772be5.weights.h5&src=0",
    +    },
    +    "linknet_resnet50": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/linknet_resnet50-6bf6c8b5.weights.h5&src=0",
         },
     }
     
     
    -def decoder_block(in_chan: int, out_chan: int) -> Sequential:
    +def decoder_block(in_chan: int, out_chan: int, stride: int, **kwargs: Any) -> Sequential:
         """Creates a LinkNet decoder block"""
    -
         return Sequential([
    -        *conv_sequence(in_chan // 4, 'relu', True, kernel_size=1),
    +        *conv_sequence(in_chan // 4, "relu", True, kernel_size=1, **kwargs),
             layers.Conv2DTranspose(
                 filters=in_chan // 4,
                 kernel_size=3,
    -            strides=2,
    +            strides=stride,
                 padding="same",
                 use_bias=False,
    -            kernel_initializer='he_normal'
    +            kernel_initializer="he_normal",
             ),
             layers.BatchNormalization(),
    -        layers.Activation('relu'),
    -        *conv_sequence(out_chan, 'relu', True, kernel_size=1),
    +        layers.Activation("relu"),
    +        *conv_sequence(out_chan, "relu", True, kernel_size=1),
         ])
     
     
    -class LinkNetFPN(layers.Layer, NestedObject):
    -    """LinkNet Encoder-Decoder module"""
    +class LinkNetFPN(Model, NestedObject):
    +    """LinkNet Decoder module"""
     
         def __init__(
             self,
    +        out_chans: int,
    +        in_shapes: List[Tuple[int, ...]],
         ) -> None:
    -
             super().__init__()
    -        self.encoder_1 = ResnetStage(num_blocks=2, output_channels=64, downsample=True)
    -        self.encoder_2 = ResnetStage(num_blocks=2, output_channels=128, downsample=True)
    -        self.encoder_3 = ResnetStage(num_blocks=2, output_channels=256, downsample=True)
    -        self.encoder_4 = ResnetStage(num_blocks=2, output_channels=512, downsample=True)
    -        self.decoder_1 = decoder_block(in_chan=64, out_chan=64)
    -        self.decoder_2 = decoder_block(in_chan=128, out_chan=64)
    -        self.decoder_3 = decoder_block(in_chan=256, out_chan=128)
    -        self.decoder_4 = decoder_block(in_chan=512, out_chan=256)
    +        self.out_chans = out_chans
    +        strides = [2] * (len(in_shapes) - 1) + [1]
    +        i_chans = [s[-1] for s in in_shapes[::-1]]
    +        o_chans = i_chans[1:] + [out_chans]
    +        self.decoders = [
    +            decoder_block(in_chan, out_chan, s, input_shape=in_shape)
    +            for in_chan, out_chan, s, in_shape in zip(i_chans, o_chans, strides, in_shapes[::-1])
    +        ]
    +
    +    def call(self, x: List[tf.Tensor], **kwargs: Any) -> tf.Tensor:
    +        out = 0
    +        for decoder, fmap in zip(self.decoders, x[::-1]):
    +            out = decoder(out + fmap, **kwargs)
    +        return out
     
    -    def call(
    -        self,
    -        x: tf.Tensor
    -    ) -> tf.Tensor:
    -        x_1 = self.encoder_1(x)
    -        x_2 = self.encoder_2(x_1)
    -        x_3 = self.encoder_3(x_2)
    -        x_4 = self.encoder_4(x_3)
    -        y_4 = self.decoder_4(x_4)
    -        y_3 = self.decoder_3(y_4 + x_3)
    -        y_2 = self.decoder_2(y_3 + x_2)
    -        y_1 = self.decoder_1(y_2 + x_1)
    -        return y_1
    -
    -
    -class LinkNet(_LinkNet, keras.Model):
    +    def extra_repr(self) -> str:
    +        return f"out_chans={self.out_chans}"
    +
    +
    +class LinkNet(_LinkNet, Model):
         """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation"
         <https://arxiv.org/pdf/1707.03718.pdf>`_.
     
         Args:
    -        num_classes: number of channels for the output
    +    ----
    +        feature extractor: the backbone serving as feature extractor
    +        fpn_channels: number of channels each extracted feature maps is mapped to
    +        bin_thresh: threshold for binarization of the output feature map
    +        box_thresh: minimal objectness score to consider a box
    +        assume_straight_pages: if True, fit straight bounding boxes only
    +        exportable: onnx exportable returns only logits
    +        cfg: the configuration dict of the model
    +        class_names: list of class names
         """
     
    -    _children_names: List[str] = ['stem', 'fpn', 'classifier', 'postprocessor']
    +    _children_names: List[str] = ["feat_extractor", "fpn", "classifier", "postprocessor"]
     
         def __init__(
             self,
    -        num_classes: int = 1,
    -        input_shape: Tuple[int, int, int] = (512, 512, 3),
    -        rotated_bbox: bool = False,
    +        feat_extractor: IntermediateLayerGetter,
    +        fpn_channels: int = 64,
    +        bin_thresh: float = 0.1,
    +        box_thresh: float = 0.1,
    +        assume_straight_pages: bool = True,
    +        exportable: bool = False,
             cfg: Optional[Dict[str, Any]] = None,
    +        class_names: List[str] = [CLASS_NAME],
         ) -> None:
             super().__init__(cfg=cfg)
     
    -        self.rotated_bbox = rotated_bbox
    +        self.class_names = class_names
    +        num_classes: int = len(self.class_names)
     
    -        self.stem = Sequential([
    -            *conv_sequence(64, 'relu', True, strides=2, kernel_size=7, input_shape=input_shape),
    -            layers.MaxPool2D(pool_size=(3, 3), strides=2, padding='same'),
    -        ])
    +        self.exportable = exportable
    +        self.assume_straight_pages = assume_straight_pages
    +
    +        self.feat_extractor = feat_extractor
     
    -        self.fpn = LinkNetFPN()
    +        self.fpn = LinkNetFPN(fpn_channels, [_shape[1:] for _shape in self.feat_extractor.output_shape])
    +        self.fpn.build(self.feat_extractor.output_shape)
     
             self.classifier = Sequential([
                 layers.Conv2DTranspose(
    @@ -393,154 +442,246 @@ 

    Source code for doctr.models.detection.linknet.tensorflow

    strides=2, padding="same", use_bias=False, - kernel_initializer='he_normal' + kernel_initializer="he_normal", + input_shape=self.fpn.decoders[-1].output_shape[1:], ), layers.BatchNormalization(), - layers.Activation('relu'), - *conv_sequence(32, 'relu', True, strides=1, kernel_size=3), + layers.Activation("relu"), + *conv_sequence(32, "relu", True, kernel_size=3, strides=1), layers.Conv2DTranspose( filters=num_classes, kernel_size=2, strides=2, padding="same", - use_bias=False, - kernel_initializer='he_normal' + use_bias=True, + kernel_initializer="he_normal", ), ]) - self.postprocessor = LinkNetPostProcessor(rotated_bbox=rotated_bbox) + self.postprocessor = LinkNetPostProcessor( + assume_straight_pages=assume_straight_pages, bin_thresh=bin_thresh, box_thresh=box_thresh + ) def compute_loss( self, out_map: tf.Tensor, - target: List[Dict[str, Any]], - focal_loss: bool = False, - alpha: float = .5, - gamma: float = 2., - edge_factor: float = 2., + target: List[Dict[str, np.ndarray]], + gamma: float = 2.0, + alpha: float = 0.5, + eps: float = 1e-8, ) -> tf.Tensor: """Compute linknet loss, BCE with boosted box edges or focal loss. Focal loss implementation based on <https://github.com/tensorflow/addons/>`_. Args: + ---- out_map: output feature map of the model of shape N x H x W x 1 target: list of dictionary where each dict has a `boxes` and a `flags` entry - focal_loss: if True, use focal loss instead of BCE - edge_factor: boost factor for box edges (in case of BCE) + gamma: modulating factor in the focal loss formula alpha: balancing factor in the focal loss formula - gammma: modulating factor in the focal loss formula + eps: epsilon factor in dice loss Returns: + ------- A loss tensor """ - seg_target, seg_mask, edge_mask = self.compute_target(target, out_map.shape[:3]) - seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32) - edge_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool) + seg_target, seg_mask = self.build_target(target, out_map.shape[1:], True) + seg_target = tf.convert_to_tensor(seg_target, dtype=out_map.dtype) seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool) - - # Get the cross_entropy for each entry - bce = tf.keras.losses.binary_crossentropy( - seg_target[seg_mask], - tf.squeeze(out_map, axis=[-1])[seg_mask], - from_logits=True) - - if focal_loss: - if gamma and gamma < 0: - raise ValueError("Value of gamma should be greater than or equal to zero.") - - # Convert logits to prob, compute gamma factor - pred_prob = tf.sigmoid(tf.squeeze(out_map, axis=[-1])[seg_mask]) - p_t = (seg_target[seg_mask] * pred_prob) + ((1 - seg_target[seg_mask]) * (1 - pred_prob)) - modulating_factor = tf.pow((1.0 - p_t), gamma) - - # Compute alpha factor - alpha_factor = seg_target[seg_mask] * alpha + (1 - seg_target[seg_mask]) * (1 - alpha) - - # compute the final loss - loss = tf.reduce_mean(alpha_factor * modulating_factor * bce) - - else: - # Compute BCE loss with highlighted edges - loss = tf.math.multiply( - 1 + (edge_factor - 1) * tf.cast(edge_mask, tf.float32), - bce - ) - loss = tf.reduce_mean(loss) - - return loss + seg_mask = tf.cast(seg_mask, tf.float32) + + bce_loss = losses.binary_crossentropy(seg_target[..., None], out_map[..., None], from_logits=True) + proba_map = tf.sigmoid(out_map) + + # Focal loss + if gamma < 0: + raise ValueError("Value of gamma should be greater than or equal to zero.") + # Convert logits to prob, compute gamma factor + p_t = (seg_target * proba_map) + ((1 - seg_target) * (1 - proba_map)) + alpha_t = seg_target * alpha + (1 - seg_target) * (1 - alpha) + # Unreduced loss + focal_loss = alpha_t * (1 - p_t) ** gamma * bce_loss + # Class reduced + focal_loss = tf.reduce_sum(seg_mask * focal_loss, (0, 1, 2, 3)) / tf.reduce_sum(seg_mask, (0, 1, 2, 3)) + + # Compute dice loss for each class + dice_map = tf.nn.softmax(out_map, axis=-1) if len(self.class_names) > 1 else proba_map + # Class-reduced dice loss + inter = tf.reduce_sum(seg_mask * dice_map * seg_target, axis=[0, 1, 2]) + cardinality = tf.reduce_sum(seg_mask * (dice_map + seg_target), axis=[0, 1, 2]) + dice_loss = tf.reduce_mean(1 - 2 * inter / (cardinality + eps)) + + return focal_loss + dice_loss def call( self, x: tf.Tensor, - target: Optional[List[Dict[str, Any]]] = None, + target: Optional[List[Dict[str, np.ndarray]]] = None, return_model_output: bool = False, - return_boxes: bool = False, - focal_loss: bool = True, + return_preds: bool = False, **kwargs: Any, ) -> Dict[str, Any]: - - logits = self.stem(x) - logits = self.fpn(logits) - logits = self.classifier(logits) + feat_maps = self.feat_extractor(x, **kwargs) + logits = self.fpn(feat_maps, **kwargs) + logits = self.classifier(logits, **kwargs) out: Dict[str, tf.Tensor] = {} - if return_model_output or target is None or return_boxes: - prob_map = tf.math.sigmoid(logits) + if self.exportable: + out["logits"] = logits + return out + + if return_model_output or target is None or return_preds: + prob_map = _bf16_to_float32(tf.math.sigmoid(logits)) + if return_model_output: out["out_map"] = prob_map - if target is None or return_boxes: + if target is None or return_preds: # Post-process boxes - out["preds"] = self.postprocessor(tf.squeeze(prob_map, axis=-1).numpy()) + out["preds"] = [dict(zip(self.class_names, preds)) for preds in self.postprocessor(prob_map.numpy())] if target is not None: - loss = self.compute_loss(logits, target, focal_loss) - out['loss'] = loss + loss = self.compute_loss(logits, target) + out["loss"] = loss return out -def _linknet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> LinkNet: +def _linknet( + arch: str, + pretrained: bool, + backbone_fn, + fpn_layers: List[str], + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> LinkNet: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['num_classes'] = kwargs.get('num_classes', _cfg['num_classes']) - _cfg['rotated_bbox'] = kwargs.get('rotated_bbox', _cfg['rotated_bbox']) + _cfg["input_shape"] = input_shape or default_cfgs[arch]["input_shape"] + if not kwargs.get("class_names", None): + kwargs["class_names"] = _cfg.get("class_names", [CLASS_NAME]) + else: + kwargs["class_names"] = sorted(kwargs["class_names"]) + + # Feature extractor + feat_extractor = IntermediateLayerGetter( + backbone_fn( + pretrained=pretrained_backbone, + include_top=False, + input_shape=_cfg["input_shape"], + ), + fpn_layers, + ) - kwargs['num_classes'] = _cfg['num_classes'] - kwargs['input_shape'] = _cfg['input_shape'] - kwargs['rotated_bbox'] = _cfg['rotated_bbox'] # Build the model - model = LinkNet(cfg=_cfg, **kwargs) + model = LinkNet(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) + # Load pretrained parameters if pretrained: - load_pretrained_params(model, _cfg['url']) + # The given class_names differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, + _cfg["url"], + skip_mismatch=kwargs["class_names"] != default_cfgs[arch].get("class_names", [CLASS_NAME]), + ) return model -
    -[docs] -def linknet16(pretrained: bool = False, **kwargs: Any) -> LinkNet: +
    +[docs] +def linknet_resnet18(pretrained: bool = False, **kwargs: Any) -> LinkNet: + """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" + <https://arxiv.org/pdf/1707.03718.pdf>`_. + + >>> import tensorflow as tf + >>> from doctr.models import linknet_resnet18 + >>> model = linknet_resnet18(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the LinkNet architecture + + Returns: + ------- + text detection architecture + """ + return _linknet( + "linknet_resnet18", + pretrained, + resnet18, + ["resnet_block_1", "resnet_block_3", "resnet_block_5", "resnet_block_7"], + **kwargs, + )
    + + + +
    +[docs] +def linknet_resnet34(pretrained: bool = False, **kwargs: Any) -> LinkNet: """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" <https://arxiv.org/pdf/1707.03718.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import linknet16 - >>> model = linknet16(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import linknet_resnet34 + >>> model = linknet_resnet34(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the LinkNet architecture Returns: + ------- text detection architecture """ + return _linknet( + "linknet_resnet34", + pretrained, + resnet34, + ["resnet_block_2", "resnet_block_6", "resnet_block_12", "resnet_block_15"], + **kwargs, + )
    + + + +
    +[docs] +def linknet_resnet50(pretrained: bool = False, **kwargs: Any) -> LinkNet: + """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" + <https://arxiv.org/pdf/1707.03718.pdf>`_. + + >>> import tensorflow as tf + >>> from doctr.models import linknet_resnet50 + >>> model = linknet_resnet50(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the LinkNet architecture - return _linknet('linknet16', pretrained, **kwargs)
    + Returns: + ------- + text detection architecture + """ + return _linknet( + "linknet_resnet50", + pretrained, + resnet50, + ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"], + **kwargs, + )
    @@ -574,8 +715,8 @@

    Source code for doctr.models.detection.linknet.tensorflow

    - +
    + diff --git a/v0.5.1/_modules/doctr/models/detection/zoo.html b/v0.5.1/_modules/doctr/models/detection/zoo.html index d3128b8d14..3651c4e2d3 100644 --- a/v0.5.1/_modules/doctr/models/detection/zoo.html +++ b/v0.5.1/_modules/doctr/models/detection/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.detection.zoo - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.detection.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -from typing import Any
    +from typing import Any, List
     
     from doctr.file_utils import is_tf_available, is_torch_available
    -from .core import DetectionPredictor
    -from ..preprocessor import PreProcessor
    -from .. import detection
     
    +from .. import detection
    +from ..detection.fast import reparameterize
    +from ..preprocessor import PreProcessor
    +from .predictor import DetectionPredictor
     
     __all__ = ["detection_predictor"]
     
    +ARCHS: List[str]
    +
     
     if is_tf_available():
    -    ARCHS = ['db_resnet50', 'linknet16']
    +    ARCHS = [
    +        "db_resnet50",
    +        "db_mobilenet_v3_large",
    +        "linknet_resnet18",
    +        "linknet_resnet34",
    +        "linknet_resnet50",
    +        "fast_tiny",
    +        "fast_small",
    +        "fast_base",
    +    ]
     elif is_torch_available():
    -    ARCHS = ['db_resnet34', 'db_resnet50', 'db_mobilenet_v3', 'linknet16']
    +    ARCHS = [
    +        "db_resnet34",
    +        "db_resnet50",
    +        "db_mobilenet_v3_large",
    +        "linknet_resnet18",
    +        "linknet_resnet34",
    +        "linknet_resnet50",
    +        "fast_tiny",
    +        "fast_small",
    +        "fast_base",
    +    ]
    +
     
    +def _predictor(arch: Any, pretrained: bool, assume_straight_pages: bool = True, **kwargs: Any) -> DetectionPredictor:
    +    if isinstance(arch, str):
    +        if arch not in ARCHS:
    +            raise ValueError(f"unknown architecture '{arch}'")
     
    -def _predictor(arch: str, pretrained: bool, **kwargs: Any) -> DetectionPredictor:
    +        _model = detection.__dict__[arch](
    +            pretrained=pretrained,
    +            pretrained_backbone=kwargs.get("pretrained_backbone", True),
    +            assume_straight_pages=assume_straight_pages,
    +        )
    +        # Reparameterize FAST models by default to lower inference latency and memory usage
    +        if isinstance(_model, detection.FAST):
    +            _model = reparameterize(_model)
    +    else:
    +        if not isinstance(arch, (detection.DBNet, detection.LinkNet, detection.FAST)):
    +            raise ValueError(f"unknown architecture: {type(arch)}")
     
    -    if arch not in ARCHS:
    -        raise ValueError(f"unknown architecture '{arch}'")
    +        _model = arch
    +        _model.assume_straight_pages = assume_straight_pages
    +        _model.postprocessor.assume_straight_pages = assume_straight_pages
     
    -    # Detection
    -    _model = detection.__dict__[arch](pretrained=pretrained)
    -    kwargs['mean'] = kwargs.get('mean', _model.cfg['mean'])
    -    kwargs['std'] = kwargs.get('std', _model.cfg['std'])
    -    kwargs['batch_size'] = kwargs.get('batch_size', 1)
    +    kwargs.pop("pretrained_backbone", None)
    +
    +    kwargs["mean"] = kwargs.get("mean", _model.cfg["mean"])
    +    kwargs["std"] = kwargs.get("std", _model.cfg["std"])
    +    kwargs["batch_size"] = kwargs.get("batch_size", 2)
         predictor = DetectionPredictor(
    -        PreProcessor(_model.cfg['input_shape'][:2], **kwargs),
    -        _model
    +        PreProcessor(_model.cfg["input_shape"][:-1] if is_tf_available() else _model.cfg["input_shape"][1:], **kwargs),
    +        _model,
         )
         return predictor
     
     
     
    -[docs] -def detection_predictor(arch: str = 'db_resnet50', pretrained: bool = False, **kwargs: Any) -> DetectionPredictor: +[docs] +def detection_predictor( + arch: Any = "fast_base", + pretrained: bool = False, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + batch_size: int = 2, + **kwargs: Any, +) -> DetectionPredictor: """Text detection architecture. - Example:: - >>> import numpy as np - >>> from doctr.models import detection_predictor - >>> model = detection_predictor(pretrained=True) - >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) - >>> out = model([input_page]) + >>> import numpy as np + >>> from doctr.models import detection_predictor + >>> model = detection_predictor(arch='db_resnet50', pretrained=True) + >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) Args: - arch: name of the architecture to use ('db_resnet50') + ---- + arch: name of the architecture or model itself to use (e.g. 'db_resnet50') pretrained: If True, returns a model pre-trained on our text detection dataset + assume_straight_pages: If True, fit straight boxes to the page + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right + batch_size: number of samples the model processes in parallel + **kwargs: optional keyword arguments passed to the architecture Returns: + ------- Detection predictor """ - - return _predictor(arch, pretrained, **kwargs)
    + return _predictor( + arch=arch, + pretrained=pretrained, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + batch_size=batch_size, + **kwargs, + )
    @@ -367,8 +449,8 @@

    Source code for doctr.models.detection.zoo

           
         
       
    - - + + diff --git a/v0.5.1/_modules/doctr/models/export.html b/v0.5.1/_modules/doctr/models/export.html deleted file mode 100644 index f25a81aa21..0000000000 --- a/v0.5.1/_modules/doctr/models/export.html +++ /dev/null @@ -1,411 +0,0 @@ - - - - - - - - - - - - doctr.models.export - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.export

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import logging
    -import numpy as np
    -import tensorflow as tf
    -from tensorflow.keras import Model
    -from typing import Tuple
    -
    -logging.getLogger("tensorflow").setLevel(logging.DEBUG)
    -
    -
    -__all__ = ['convert_to_tflite', 'convert_to_fp16', 'quantize_model']
    -
    -
    -
    -[docs] -def convert_to_tflite(tf_model: Model) -> bytes: - """Converts a model to TFLite format - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_tflite, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_tflite(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - return converter.convert()
    - - - -
    -[docs] -def convert_to_fp16(tf_model: Model) -> bytes: - """Converts a model to half precision - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_fp16, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_fp16(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the serialized FP16 model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - converter.target_spec.supported_types = [tf.float16] - return converter.convert()
    - - - -
    -[docs] -def quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) -> bytes: - """Quantize a Tensorflow model - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import quantize_model, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = quantize_model(model, (224, 224, 3)) - - Args: - tf_model: a keras model - input_shape: shape of the expected input tensor (excluding batch dimension) with channel last order - - Returns: - bytes: the serialized quantized model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - - # Float fallback for operators that do not have an integer implementation - def representative_dataset(): - for _ in range(100): - data = np.random.rand(1, *input_shape) - yield [data.astype(np.float32)] - - converter.representative_dataset = representative_dataset - converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] - converter.inference_input_type = tf.int8 - converter.inference_output_type = tf.int8 - - return converter.convert()
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.1/_modules/doctr/models/factory/hub.html b/v0.5.1/_modules/doctr/models/factory/hub.html index 8274a809f5..756b2c7a17 100644 --- a/v0.5.1/_modules/doctr/models/factory/hub.html +++ b/v0.5.1/_modules/doctr/models/factory/hub.html @@ -13,7 +13,7 @@ - + doctr.models.factory.hub - docTR documentation @@ -568,7 +568,7 @@

    Source code for doctr.models.factory.hub

         
       
    - + diff --git a/v0.5.1/_modules/doctr/models/recognition/crnn.html b/v0.5.1/_modules/doctr/models/recognition/crnn.html deleted file mode 100644 index daa2393439..0000000000 --- a/v0.5.1/_modules/doctr/models/recognition/crnn.html +++ /dev/null @@ -1,565 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.crnn - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.crnn

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import layers
    -from tensorflow.keras.models import Sequential
    -from typing import Tuple, Dict, Any, Optional, List
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel, RecognitionPostProcessor
    -
    -__all__ = ['CRNN', 'crnn_vgg16_bn', 'crnn_resnet31', 'CTCPostProcessor']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'crnn_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/crnn_vgg16_bn-748c855f.zip',
    -    },
    -    'crnn_resnet31': {
    -        'mean': (0.694, 0.695, 0.693),
    -        'std': (0.299, 0.296, 0.301),
    -        'backbone': 'resnet31', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.1/crnn_resnet31-69ab71db.zip',
    -    },
    -}
    -
    -
    -class CTCPostProcessor(RecognitionPostProcessor):
    -    """
    -    Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor
    -    ) -> List[Tuple[str, float]]:
    -        """
    -        Performs decoding of raw output with CTC and decoding of CTC predictions
    -        with label_to_idx mapping dictionnary
    -
    -        Args:
    -            logits: raw output of the model, shape BATCH_SIZE X SEQ_LEN X NUM_CLASSES + 1
    -
    -        Returns:
    -            A list of decoded words of length BATCH_SIZE
    -
    -        """
    -        # Decode CTC
    -        _decoded, _log_prob = tf.nn.ctc_beam_search_decoder(
    -            tf.transpose(logits, perm=[1, 0, 2]),
    -            tf.fill(logits.shape[0], logits.shape[1]),
    -            beam_width=1, top_paths=1,
    -        )
    -        out_idxs = tf.sparse.to_dense(_decoded[0], default_value=len(self.vocab))
    -        probs = tf.math.exp(tf.squeeze(_log_prob, axis=1))
    -
    -        # Map it to characters
    -        _decoded_strings_pred = tf.strings.reduce_join(
    -            inputs=tf.nn.embedding_lookup(self._embedding, out_idxs),
    -            axis=-1
    -        )
    -        _decoded_strings_pred = tf.strings.split(_decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -class CRNN(RecognitionModel):
    -    """Implements a CRNN architecture as described in `"An End-to-End Trainable Neural Network for Image-based
    -    Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of units in the LSTM layers
    -        cfg: configuration dictionary
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: tf.keras.Model,
    -        vocab: str,
    -        rnn_units: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(vocab=vocab, cfg=cfg)
    -        self.feat_extractor = feature_extractor
    -
    -        # Initialize kernels
    -        h, w, c = self.feat_extractor.output_shape[1:]
    -        self.max_length = w
    -
    -        self.decoder = Sequential(
    -            [
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Dense(units=len(vocab) + 1)
    -            ]
    -        )
    -        self.decoder.build(input_shape=(None, w, h * c))
    -
    -        self.postprocessor = CTCPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        target: List[str],
    -    ) -> tf.Tensor:
    -        """Compute CTC loss for the model.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        gt, seq_len = self.compute_target(target)
    -        batch_len = model_output.shape[0]
    -        input_length = model_output.shape[1] * tf.ones(shape=(batch_len))
    -        ctc_loss = tf.nn.ctc_loss(
    -            gt, model_output, seq_len, input_length, logits_time_major=False, blank_index=len(self.vocab)
    -        )
    -        return ctc_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        # B x H x W x C --> B x W x H x C
    -        transposed_feat = tf.transpose(features, perm=[0, 2, 1, 3])
    -        w, h, c = transposed_feat.get_shape().as_list()[1:]
    -        # B x W x H x C --> B x W x H * C
    -        features_seq = tf.reshape(transposed_feat, shape=(-1, w, h * c))
    -        logits = self.decoder(features_seq, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = logits
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(logits)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(logits, target)
    -
    -        return out
    -
    -
    -def _crnn(arch: str, pretrained: bool, input_shape: Optional[Tuple[int, int, int]] = None, **kwargs: Any) -> CRNN:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[_cfg['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -
    -    # Build the model
    -    model = CRNN(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a VGG-16 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_vgg16_bn - >>> model = crnn_vgg16_bn(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_vgg16_bn', pretrained, **kwargs)
    - - - -def crnn_resnet31(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a resnet31 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_resnet31 - >>> model = crnn_resnet31(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_resnet31', pretrained, **kwargs) -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.1/_modules/doctr/models/recognition/crnn/tensorflow.html b/v0.5.1/_modules/doctr/models/recognition/crnn/tensorflow.html index 41cc93dd23..bc64da9a1b 100644 --- a/v0.5.1/_modules/doctr/models/recognition/crnn/tensorflow.html +++ b/v0.5.1/_modules/doctr/models/recognition/crnn/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.crnn.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple, Union
    +
     import tensorflow as tf
     from tensorflow.keras import layers
    -from tensorflow.keras.models import Sequential, Model
    -from typing import Tuple, Dict, Any, Optional, List
    +from tensorflow.keras.models import Model, Sequential
    +
    +from doctr.datasets import VOCABS
     
    -from ... import backbones
    -from ...utils import load_pretrained_params
    +from ...classification import mobilenet_v3_large_r, mobilenet_v3_small_r, vgg16_bn_r
    +from ...utils.tensorflow import _bf16_to_float32, _build_model, load_pretrained_params
     from ..core import RecognitionModel, RecognitionPostProcessor
     
    -__all__ = ['CRNN', 'crnn_vgg16_bn', 'crnn_resnet31', 'CTCPostProcessor']
    +__all__ = ["CRNN", "crnn_vgg16_bn", "crnn_mobilenet_v3_small", "crnn_mobilenet_v3_large"]
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'crnn_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/crnn_vgg16_bn-748c855f.zip',
    +    "crnn_vgg16_bn": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["legacy_french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/crnn_vgg16_bn-9c188f45.weights.h5&src=0",
         },
    -    'crnn_resnet31': {
    -        'mean': (0.694, 0.695, 0.693),
    -        'std': (0.299, 0.296, 0.301),
    -        'backbone': 'resnet31', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.1/crnn_resnet31-69ab71db.zip',
    +    "crnn_mobilenet_v3_small": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/crnn_mobilenet_v3_small-54850265.weights.h5&src=0",
    +    },
    +    "crnn_mobilenet_v3_large": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/crnn_mobilenet_v3_large-c64045e5.weights.h5&src=0",
         },
     }
     
     
     class CTCPostProcessor(RecognitionPostProcessor):
    -    """
    -    Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
    +    """Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
     
         Args:
    +    ----
             vocab: string containing the ordered sequence of supported characters
             ignore_case: if True, ignore case of letters
             ignore_accents: if True, ignore accents of letters
    @@ -325,37 +353,57 @@ 

    Source code for doctr.models.recognition.crnn.tensorflow

    def __call__( self, - logits: tf.Tensor - ) -> List[Tuple[str, float]]: - """ - Performs decoding of raw output with CTC and decoding of CTC predictions + logits: tf.Tensor, + beam_width: int = 1, + top_paths: int = 1, + ) -> Union[List[Tuple[str, float]], List[Tuple[List[str], List[float]]]]: + """Performs decoding of raw output with CTC and decoding of CTC predictions with label_to_idx mapping dictionnary Args: + ---- logits: raw output of the model, shape BATCH_SIZE X SEQ_LEN X NUM_CLASSES + 1 + beam_width: An int scalar >= 0 (beam search beam width). + top_paths: An int scalar >= 0, <= beam_width (controls output size). Returns: + ------- A list of decoded words of length BATCH_SIZE + """ # Decode CTC _decoded, _log_prob = tf.nn.ctc_beam_search_decoder( tf.transpose(logits, perm=[1, 0, 2]), - tf.fill(logits.shape[0], logits.shape[1]), - beam_width=1, top_paths=1, + tf.fill(tf.shape(logits)[:1], tf.shape(logits)[1]), + beam_width=beam_width, + top_paths=top_paths, ) - out_idxs = tf.sparse.to_dense(_decoded[0], default_value=len(self.vocab)) - probs = tf.math.exp(tf.squeeze(_log_prob, axis=1)) + + _decoded = tf.sparse.concat( + 1, + [tf.sparse.expand_dims(dec, axis=1) for dec in _decoded], + expand_nonconcat_dims=True, + ) # dim : batchsize x beamwidth x actual_max_len_predictions + out_idxs = tf.sparse.to_dense(_decoded, default_value=len(self.vocab)) # Map it to characters _decoded_strings_pred = tf.strings.reduce_join( inputs=tf.nn.embedding_lookup(tf.constant(self._embedding, dtype=tf.string), out_idxs), - axis=-1 + axis=-1, ) _decoded_strings_pred = tf.strings.split(_decoded_strings_pred, "<eos>") - decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0] - word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] - + decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value="not valid")[ + :, :, 0 + ] # dim : batch_size x beam_width + + if top_paths == 1: + probs = tf.math.exp(tf.squeeze(_log_prob, axis=1)) # dim : batchsize + decoded_strings_pred = tf.squeeze(decoded_strings_pred, axis=1) + word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] + else: + probs = tf.math.exp(_log_prob) # dim : batchsize x beamwidth + word_values = [[word.decode() for word in words] for words in decoded_strings_pred.numpy().tolist()] return list(zip(word_values, probs.numpy().tolist())) @@ -364,19 +412,26 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. Args: + ---- feature_extractor: the backbone serving as feature extractor vocab: vocabulary used for encoding rnn_units: number of units in the LSTM layers + exportable: onnx exportable returns only logits + beam_width: beam width for beam search decoding + top_paths: number of top paths for beam search decoding cfg: configuration dictionary """ - _children_names: List[str] = ['feat_extractor', 'decoder', 'postprocessor'] + _children_names: List[str] = ["feat_extractor", "decoder", "postprocessor"] def __init__( self, - feature_extractor: tf.keras.Model, + feature_extractor: Model, vocab: str, rnn_units: int = 128, + exportable: bool = False, + beam_width: int = 1, + top_paths: int = 1, cfg: Optional[Dict[str, Any]] = None, ) -> None: # Initialize kernels @@ -386,19 +441,21 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    self.vocab = vocab self.max_length = w self.cfg = cfg + self.exportable = exportable self.feat_extractor = feature_extractor - self.decoder = Sequential( - [ - layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), - layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), - layers.Dense(units=len(vocab) + 1) - ] - ) + self.decoder = Sequential([ + layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), + layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), + layers.Dense(units=len(vocab) + 1), + ]) self.decoder.build(input_shape=(None, w, h * c)) self.postprocessor = CTCPostProcessor(vocab=vocab) + self.beam_width = beam_width + self.top_paths = top_paths + def compute_loss( self, model_output: tf.Tensor, @@ -407,16 +464,17 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    """Compute CTC loss for the model. Args: - gt: the encoded tensor with gt labels + ---- model_output: predicted logits of the model - seq_len: lengths of each gt word inside the batch + target: lengths of each gt word inside the batch Returns: + ------- The loss of the model on the batch """ - gt, seq_len = self.compute_target(target) + gt, seq_len = self.build_target(target) batch_len = model_output.shape[0] - input_length = model_output.shape[1] * tf.ones(shape=(batch_len)) + input_length = tf.fill((batch_len,), model_output.shape[1]) ctc_loss = tf.nn.ctc_loss( gt, model_output, seq_len, input_length, logits_time_major=False, blank_index=len(self.vocab) ) @@ -428,8 +486,12 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    target: Optional[List[str]] = None, return_model_output: bool = False, return_preds: bool = False, + beam_width: int = 1, + top_paths: int = 1, **kwargs: Any, ) -> Dict[str, Any]: + if kwargs.get("training", False) and target is None: + raise ValueError("Need to provide labels during training") features = self.feat_extractor(x, **kwargs) # B x H x W x C --> B x W x H x C @@ -437,91 +499,132 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    w, h, c = transposed_feat.get_shape().as_list()[1:] # B x W x H x C --> B x W x H * C features_seq = tf.reshape(transposed_feat, shape=(-1, w, h * c)) - logits = self.decoder(features_seq, **kwargs) + logits = _bf16_to_float32(self.decoder(features_seq, **kwargs)) out: Dict[str, tf.Tensor] = {} + if self.exportable: + out["logits"] = logits + return out + if return_model_output: out["out_map"] = logits if target is None or return_preds: # Post-process boxes - out["preds"] = self.postprocessor(logits) + out["preds"] = self.postprocessor(logits, beam_width=beam_width, top_paths=top_paths) if target is not None: - out['loss'] = self.compute_loss(logits, target) + out["loss"] = self.compute_loss(logits, target) return out -def _crnn(arch: str, pretrained: bool, input_shape: Optional[Tuple[int, int, int]] = None, **kwargs: Any) -> CRNN: +def _crnn( + arch: str, + pretrained: bool, + backbone_fn, + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> CRNN: + pretrained_backbone = pretrained_backbone and not pretrained + + kwargs["vocab"] = kwargs.get("vocab", default_cfgs[arch]["vocab"]) - # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab']) - _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units']) + _cfg["vocab"] = kwargs["vocab"] + _cfg["input_shape"] = input_shape or default_cfgs[arch]["input_shape"] - # Feature extractor - feat_extractor = backbones.__dict__[_cfg['backbone']]( - input_shape=_cfg['input_shape'], + feat_extractor = backbone_fn( + input_shape=_cfg["input_shape"], include_top=False, + pretrained=pretrained_backbone, ) - kwargs['vocab'] = _cfg['vocab'] - kwargs['rnn_units'] = _cfg['rnn_units'] - # Build the model model = CRNN(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) # Load pretrained parameters if pretrained: - load_pretrained_params(model, _cfg['url']) + # The given vocab differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params(model, _cfg["url"], skip_mismatch=kwargs["vocab"] != default_cfgs[arch]["vocab"]) return model
    -[docs] +[docs] def crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> CRNN: """CRNN with a VGG-16 backbone as described in `"An End-to-End Trainable Neural Network for Image-based Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_vgg16_bn - >>> model = crnn_vgg16_bn(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import crnn_vgg16_bn + >>> model = crnn_vgg16_bn(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the CRNN architecture Returns: + ------- text recognition architecture """ + return _crnn("crnn_vgg16_bn", pretrained, vgg16_bn_r, **kwargs)
    + + + +
    +[docs] +def crnn_mobilenet_v3_small(pretrained: bool = False, **kwargs: Any) -> CRNN: + """CRNN with a MobileNet V3 Small backbone as described in `"An End-to-End Trainable Neural Network for Image-based + Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. + + >>> import tensorflow as tf + >>> from doctr.models import crnn_mobilenet_v3_small + >>> model = crnn_mobilenet_v3_small(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the CRNN architecture - return _crnn('crnn_vgg16_bn', pretrained, **kwargs)
    + Returns: + ------- + text recognition architecture + """ + return _crnn("crnn_mobilenet_v3_small", pretrained, mobilenet_v3_small_r, **kwargs)
    -def crnn_resnet31(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a resnet31 backbone as described in `"An End-to-End Trainable Neural Network for Image-based +
    +[docs] +def crnn_mobilenet_v3_large(pretrained: bool = False, **kwargs: Any) -> CRNN: + """CRNN with a MobileNet V3 Large backbone as described in `"An End-to-End Trainable Neural Network for Image-based Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_resnet31 - >>> model = crnn_resnet31(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import crnn_mobilenet_v3_large + >>> model = crnn_mobilenet_v3_large(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the CRNN architecture Returns: + ------- text recognition architecture """ + return _crnn("crnn_mobilenet_v3_large", pretrained, mobilenet_v3_large_r, **kwargs)
    - return _crnn('crnn_resnet31', pretrained, **kwargs)
    @@ -554,8 +657,8 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    - +
    + diff --git a/v0.5.1/_modules/doctr/models/recognition/master/tensorflow.html b/v0.5.1/_modules/doctr/models/recognition/master/tensorflow.html index 2dc5a27717..aa6aa69325 100644 --- a/v0.5.1/_modules/doctr/models/recognition/master/tensorflow.html +++ b/v0.5.1/_modules/doctr/models/recognition/master/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.master.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.master.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import tensorflow as tf
    -from tensorflow.keras import layers, Sequential, Model
    -from typing import Tuple, List, Dict, Any, Optional
     from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple
     
    -from ..core import RecognitionPostProcessor
    -from ...backbones.resnet import ResnetStage
    -from ...utils import conv_sequence, load_pretrained_params
    -from ..transformer import Decoder, positional_encoding, create_look_ahead_mask, create_padding_mask
    -from ....datasets import VOCABS
    -from .base import _MASTER, _MASTERPostProcessor
    +import tensorflow as tf
    +from tensorflow.keras import Model, layers
    +
    +from doctr.datasets import VOCABS
    +from doctr.models.classification import magc_resnet31
    +from doctr.models.modules.transformer import Decoder, PositionalEncoding
     
    +from ...utils.tensorflow import _bf16_to_float32, _build_model, load_pretrained_params
    +from .base import _MASTER, _MASTERPostProcessor
     
    -__all__ = ['MASTER', 'master', 'MASTERPostProcessor']
    +__all__ = ["MASTER", "master"]
     
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'master': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'input_shape': (48, 160, 3),
    -        'vocab': VOCABS['french'],
    -        'url': None,
    +    "master": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/master-d7fdaeff.weights.h5&src=0",
         },
     }
     
     
    -class MAGC(layers.Layer):
    -
    -    """Implements the Multi-Aspect Global Context Attention, as described in
    -    <https://arxiv.org/pdf/1910.02562.pdf>`_.
    -
    -    Args:
    -        inplanes: input channels
    -        headers: number of headers to split channels
    -        att_scale: if True, re-scale attention to counteract the variance distibutions
    -        **kwargs
    -    """
    -
    -    def __init__(
    -        self,
    -        inplanes: int,
    -        headers: int = 1,
    -        att_scale: bool = False,
    -        **kwargs
    -    ) -> None:
    -        super().__init__(**kwargs)
    -
    -        self.headers = headers  # h
    -        self.inplanes = inplanes  # C
    -        self.att_scale = att_scale
    -
    -        self.single_header_inplanes = int(inplanes / headers)  # C / h
    -
    -        self.conv_mask = tf.keras.layers.Conv2D(
    -            filters=1,
    -            kernel_size=1,
    -            kernel_initializer=tf.initializers.he_normal()
    -        )
    -
    -        self.transform = tf.keras.Sequential(
    -            [
    -                tf.keras.layers.Conv2D(
    -                    filters=self.inplanes,
    -                    kernel_size=1,
    -                    kernel_initializer=tf.initializers.he_normal()
    -                ),
    -                tf.keras.layers.LayerNormalization([1, 2, 3]),
    -                tf.keras.layers.ReLU(),
    -                tf.keras.layers.Conv2D(
    -                    filters=self.inplanes,
    -                    kernel_size=1,
    -                    kernel_initializer=tf.initializers.he_normal()
    -                ),
    -            ],
    -            name='transform'
    -        )
    -
    -    @tf.function
    -    def context_modeling(self, inputs: tf.Tensor) -> tf.Tensor:
    -        b, h, w, c = (tf.shape(inputs)[i] for i in range(4))
    -
    -        # B, H, W, C -->> B*h, H, W, C/h
    -        x = tf.reshape(inputs, shape=(b, h, w, self.headers, self.single_header_inplanes))
    -        x = tf.transpose(x, perm=(0, 3, 1, 2, 4))
    -        x = tf.reshape(x, shape=(b * self.headers, h, w, self.single_header_inplanes))
    -
    -        # Compute shorcut
    -        shortcut = x
    -        # B*h, 1, H*W, C/h
    -        shortcut = tf.reshape(shortcut, shape=(b * self.headers, 1, h * w, self.single_header_inplanes))
    -        # B*h, 1, C/h, H*W
    -        shortcut = tf.transpose(shortcut, perm=[0, 1, 3, 2])
    -
    -        # Compute context mask
    -        # B*h, H, W, 1,
    -        context_mask = self.conv_mask(x)
    -        # B*h, 1, H*W, 1
    -        context_mask = tf.reshape(context_mask, shape=(b * self.headers, 1, h * w, 1))
    -        # scale variance
    -        if self.att_scale and self.headers > 1:
    -            context_mask = context_mask / tf.sqrt(self.single_header_inplanes)
    -        # B*h, 1, H*W, 1
    -        context_mask = tf.keras.activations.softmax(context_mask, axis=2)
    -
    -        # Compute context
    -        # B*h, 1, C/h, 1
    -        context = tf.matmul(shortcut, context_mask)
    -        context = tf.reshape(context, shape=(b, 1, c, 1))
    -        # B, 1, 1, C
    -        context = tf.transpose(context, perm=(0, 1, 3, 2))
    -        # Set shape to resolve shape when calling this module in the Sequential MAGCResnet
    -        batch, chan = inputs.get_shape().as_list()[0], inputs.get_shape().as_list()[-1]
    -        context.set_shape([batch, 1, 1, chan])
    -        return context
    -
    -    def call(self, inputs: tf.Tensor, **kwargs) -> tf.Tensor:
    -        # Context modeling: B, H, W, C  ->  B, 1, 1, C
    -        context = self.context_modeling(inputs)
    -        # Transform: B, 1, 1, C  ->  B, 1, 1, C
    -        transformed = self.transform(context)
    -        return inputs + transformed
    -
    -
    -class MAGCResnet(Sequential):
    -
    -    """Implements the modified resnet with MAGC layers, as described in paper.
    -
    -    Args:
    -        headers: number of header to split channels in MAGC layers
    -        input_shape: shape of the model input (without batch dim)
    -    """
    -
    -    def __init__(
    -        self,
    -        headers: int = 1,
    -        input_shape: Tuple[int, int, int] = (48, 160, 3),
    -    ) -> None:
    -        _layers = [
    -            # conv_1x
    -            *conv_sequence(out_channels=64, activation='relu', bn=True, kernel_size=3, input_shape=input_shape),
    -            *conv_sequence(out_channels=128, activation='relu', bn=True, kernel_size=3),
    -            layers.MaxPooling2D((2, 2), (2, 2)),
    -            # conv_2x
    -            ResnetStage(num_blocks=1, output_channels=256),
    -            MAGC(inplanes=256, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=256, activation='relu', bn=True, kernel_size=3),
    -            layers.MaxPooling2D((2, 2), (2, 2)),
    -            # conv_3x
    -            ResnetStage(num_blocks=2, output_channels=512),
    -            MAGC(inplanes=512, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=512, activation='relu', bn=True, kernel_size=3),
    -            layers.MaxPooling2D((2, 1), (2, 1)),
    -            # conv_4x
    -            ResnetStage(num_blocks=5, output_channels=512),
    -            MAGC(inplanes=512, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=512, activation='relu', bn=True, kernel_size=3),
    -            # conv_5x
    -            ResnetStage(num_blocks=3, output_channels=512),
    -            MAGC(inplanes=512, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=512, activation='relu', bn=True, kernel_size=3),
    -        ]
    -        super().__init__(_layers)
    -
    -
     class MASTER(_MASTER, Model):
    -
         """Implements MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_.
         Implementation based on the official TF implementation: <https://github.com/jiangxiluning/MASTER-TF>`_.
     
         Args:
    +    ----
    +        feature_extractor: the backbone serving as feature extractor
             vocab: vocabulary, (without EOS, SOS, PAD)
             d_model: d parameter for the transformer decoder
    -        headers: headers for the MAGC module
             dff: depth of the pointwise feed-forward layer
             num_heads: number of heads for the mutli-head attention module
             num_layers: number of decoder layers to stack
             max_length: maximum length of character sequence handled by the model
    -        input_size: size of the image inputs
    +        dropout: dropout probability of the decoder
    +        input_shape: size of the image inputs
    +        exportable: onnx exportable returns only logits
    +        cfg: dictionary containing information about the model
         """
     
         def __init__(
             self,
    +        feature_extractor: Model,
             vocab: str,
             d_model: int = 512,
    -        headers: int = 1,
             dff: int = 2048,
    -        num_heads: int = 8,
    +        num_heads: int = 8,  # number of heads in the transformer decoder
             num_layers: int = 3,
             max_length: int = 50,
    -        input_shape: Tuple[int, int, int] = (48, 160, 3),
    +        dropout: float = 0.2,
    +        input_shape: Tuple[int, int, int] = (32, 128, 3),  # different from the paper
    +        exportable: bool = False,
             cfg: Optional[Dict[str, Any]] = None,
         ) -> None:
             super().__init__()
     
    -        self.vocab = vocab
    +        self.exportable = exportable
             self.max_length = max_length
    +        self.d_model = d_model
    +        self.vocab = vocab
             self.cfg = cfg
             self.vocab_size = len(vocab)
     
    -        self.feature_extractor = MAGCResnet(headers=headers, input_shape=input_shape)
    -        self.seq_embedding = layers.Embedding(self.vocab_size + 3, d_model)  # 3 more classes: EOS/PAD/SOS
    +        self.feat_extractor = feature_extractor
    +        self.positional_encoding = PositionalEncoding(self.d_model, dropout, max_len=input_shape[0] * input_shape[1])
     
             self.decoder = Decoder(
                 num_layers=num_layers,
    -            d_model=d_model,
    +            d_model=self.d_model,
                 num_heads=num_heads,
    +            vocab_size=self.vocab_size + 3,  # EOS, SOS, PAD
                 dff=dff,
    -            vocab_size=self.vocab_size,
    -            maximum_position_encoding=max_length,
    +            dropout=dropout,
    +            maximum_position_encoding=self.max_length,
             )
    -        self.feature_pe = positional_encoding(input_shape[0] * input_shape[1], d_model)
    -        self.linear = layers.Dense(self.vocab_size + 3, kernel_initializer=tf.initializers.he_uniform())
     
    +        self.linear = layers.Dense(self.vocab_size + 3, kernel_initializer=tf.initializers.he_uniform())
             self.postprocessor = MASTERPostProcessor(vocab=self.vocab)
     
         @tf.function
    -    def make_mask(self, target: tf.Tensor) -> tf.Tensor:
    -        look_ahead_mask = create_look_ahead_mask(tf.shape(target)[1])
    -        target_padding_mask = create_padding_mask(target, self.vocab_size + 2)  # Pad symbol
    -        combined_mask = tf.maximum(target_padding_mask, look_ahead_mask)
    -        return combined_mask
    +    def make_source_and_target_mask(self, source: tf.Tensor, target: tf.Tensor) -> Tuple[tf.Tensor, tf.Tensor]:
    +        # [1, 1, 1, ..., 0, 0, 0] -> 0 is masked
    +        # (N, 1, 1, max_length)
    +        target_pad_mask = tf.cast(tf.math.not_equal(target, self.vocab_size + 2), dtype=tf.uint8)
    +        target_pad_mask = target_pad_mask[:, tf.newaxis, tf.newaxis, :]
    +        target_length = target.shape[1]
    +        # sub mask filled diagonal with 1 = see 0 = masked (max_length, max_length)
    +        target_sub_mask = tf.linalg.band_part(tf.ones((target_length, target_length)), -1, 0)
    +        # source mask filled with ones (max_length, positional_encoded_seq_len)
    +        source_mask = tf.ones((target_length, source.shape[1]))
    +        # combine the two masks into one boolean mask where False is masked (N, 1, max_length, max_length)
    +        target_mask = tf.math.logical_and(
    +            tf.cast(target_sub_mask, dtype=tf.bool), tf.cast(target_pad_mask, dtype=tf.bool)
    +        )
    +        return source_mask, target_mask
     
    +    @staticmethod
         def compute_loss(
    -        self,
             model_output: tf.Tensor,
             gt: tf.Tensor,
             seq_len: List[int],
    @@ -512,11 +413,13 @@ 

    Source code for doctr.models.recognition.master.tensorflow

    Sequences are masked after the EOS character. Args: + ---- gt: the encoded tensor with gt labels model_output: predicted logits of the model seq_len: lengths of each gt word inside the batch Returns: + ------- The loss of the model on the batch """ # Input length : number of timesteps @@ -532,7 +435,7 @@

    Source code for doctr.models.recognition.master.tensorflow

    mask_values = tf.zeros_like(cce) mask_2d = tf.sequence_mask(seq_len, input_len - 1) # delete the last mask timestep as well masked_loss = tf.where(mask_2d, cce, mask_values) - ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32)) + ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, model_output.dtype)) return tf.expand_dims(ce_loss, axis=1) @@ -547,94 +450,103 @@

    Source code for doctr.models.recognition.master.tensorflow

    """Call function for training Args: + ---- x: images target: list of str labels return_model_output: if True, return logits return_preds: if True, decode logits + **kwargs: keyword arguments passed to the decoder - Return: + Returns: + ------- A dictionnary containing eventually loss, logits and predictions. """ - # Encode - feature = self.feature_extractor(x, **kwargs) - b, h, w, c = (tf.shape(feature)[i] for i in range(4)) + feature = self.feat_extractor(x, **kwargs) + b, h, w, c = feature.get_shape() + # (N, H, W, C) --> (N, H * W, C) feature = tf.reshape(feature, shape=(b, h * w, c)) - encoded = feature + self.feature_pe[:, :h * w, :] + # add positional encoding to features + encoded = self.positional_encoding(feature, **kwargs) out: Dict[str, tf.Tensor] = {} + if kwargs.get("training", False) and target is None: + raise ValueError("Need to provide labels during training") + if target is not None: # Compute target: tensor of gts and sequence lengths - gt, seq_len = self.compute_target(target) - - if kwargs.get('training', False): - if target is None: - raise AssertionError("In training mode, you need to pass a value to 'target'") - tgt_mask = self.make_mask(gt) + gt, seq_len = self.build_target(target) + # Compute decoder masks + source_mask, target_mask = self.make_source_and_target_mask(encoded, gt) # Compute logits - output = self.decoder(gt, encoded, tgt_mask, None, **kwargs) + output = self.decoder(gt, encoded, source_mask, target_mask, **kwargs) logits = self.linear(output, **kwargs) - else: - # When not training, we want to compute logits in with the decoder, although - # we have access to gts (we need gts to compute the loss, but not in the decoder) logits = self.decode(encoded, **kwargs) + logits = _bf16_to_float32(logits) + + if self.exportable: + out["logits"] = logits + return out + if target is not None: - out['loss'] = self.compute_loss(logits, gt, seq_len) + out["loss"] = self.compute_loss(logits, gt, seq_len) if return_model_output: - out['out_map'] = logits + out["out_map"] = logits if return_preds: - predictions = self.postprocessor(logits) - out['preds'] = predictions + out["preds"] = self.postprocessor(logits) return out + @tf.function def decode(self, encoded: tf.Tensor, **kwargs: Any) -> tf.Tensor: """Decode function for prediction Args: + ---- encoded: encoded features + **kwargs: keyword arguments passed to the decoder - Return: + Returns: + ------- A Tuple of tf.Tensor: predictions, logits """ - b = tf.shape(encoded)[0] - max_len = tf.constant(self.max_length, dtype=tf.int32) + b = encoded.shape[0] + start_symbol = tf.constant(self.vocab_size + 1, dtype=tf.int32) # SOS padding_symbol = tf.constant(self.vocab_size + 2, dtype=tf.int32) # PAD - ys = tf.fill(dims=(b, max_len - 1), value=padding_symbol) + ys = tf.fill(dims=(b, self.max_length - 1), value=padding_symbol) start_vector = tf.fill(dims=(b, 1), value=start_symbol) ys = tf.concat([start_vector, ys], axis=-1) - logits = tf.zeros(shape=(b, max_len - 1, self.vocab_size + 3), dtype=tf.float32) # 3 symbols - # max_len = len + 2 (sos + eos) + # Final dimension include EOS/SOS/PAD for i in range(self.max_length - 1): - ys_mask = self.make_mask(ys) - output = self.decoder(ys, encoded, ys_mask, None, **kwargs) + source_mask, target_mask = self.make_source_and_target_mask(encoded, ys) + output = self.decoder(ys, encoded, source_mask, target_mask, **kwargs) logits = self.linear(output, **kwargs) prob = tf.nn.softmax(logits, axis=-1) - next_word = tf.argmax(prob, axis=-1, output_type=ys.dtype) - # ys.shape = B, T - i_mesh, j_mesh = tf.meshgrid(tf.range(b), tf.range(max_len), indexing='ij') + next_token = tf.argmax(prob, axis=-1, output_type=ys.dtype) + # update ys with the next token and ignore the first token (SOS) + i_mesh, j_mesh = tf.meshgrid(tf.range(b), tf.range(self.max_length), indexing="ij") indices = tf.stack([i_mesh[:, i + 1], j_mesh[:, i + 1]], axis=1) - ys = tf.tensor_scatter_nd_update(ys, indices, next_word[:, i + 1]) + ys = tf.tensor_scatter_nd_update(ys, indices, next_token[:, i]) - # final_logits of shape (N, max_length - 1, vocab_size + 1) (whithout sos) + # Shape (N, max_length, vocab_size + 1) return logits class MASTERPostProcessor(_MASTERPostProcessor): """Post processor for MASTER architectures + Args: + ---- vocab: string containing the ordered sequence of supported characters - ignore_case: if True, ignore case of letters - ignore_accents: if True, ignore accents of letters """ def __call__( @@ -649,51 +561,66 @@

    Source code for doctr.models.recognition.master.tensorflow

    probs = tf.math.reduce_min(probs, axis=1) # decode raw output of the model with tf_label_to_idx - out_idxs = tf.cast(out_idxs, dtype='int32') + out_idxs = tf.cast(out_idxs, dtype="int32") embedding = tf.constant(self._embedding, dtype=tf.string) decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(embedding, out_idxs), axis=-1) decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>") - decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0] + decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value="not valid")[:, 0] word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] - return list(zip(word_values, probs.numpy().tolist())) + return list(zip(word_values, probs.numpy().clip(0, 1).tolist())) -def _master(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> MASTER: +def _master(arch: str, pretrained: bool, backbone_fn, pretrained_backbone: bool = True, **kwargs: Any) -> MASTER: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab']) + _cfg["input_shape"] = kwargs.get("input_shape", _cfg["input_shape"]) + _cfg["vocab"] = kwargs.get("vocab", _cfg["vocab"]) - kwargs['vocab'] = _cfg['vocab'] + kwargs["vocab"] = _cfg["vocab"] + kwargs["input_shape"] = _cfg["input_shape"] # Build the model - model = MASTER(cfg=_cfg, **kwargs) + model = MASTER( + backbone_fn(pretrained=pretrained_backbone, input_shape=_cfg["input_shape"], include_top=False), + cfg=_cfg, + **kwargs, + ) + _build_model(model) + # Load pretrained parameters if pretrained: - load_pretrained_params(model, default_cfgs[arch]['url']) + # The given vocab differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, default_cfgs[arch]["url"], skip_mismatch=kwargs["vocab"] != default_cfgs[arch]["vocab"] + ) return model
    -[docs] +[docs] def master(pretrained: bool = False, **kwargs: Any) -> MASTER: """MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import master - >>> model = master(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 48, 160, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + + >>> import tensorflow as tf + >>> from doctr.models import master + >>> model = master(pretrained=False) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keywoard arguments passed to the MASTER architecture + Returns: + ------- text recognition architecture """ - - return _master('master', pretrained, **kwargs)
    + return _master("master", pretrained, magc_resnet31, **kwargs)
    @@ -727,8 +654,8 @@

    Source code for doctr.models.recognition.master.tensorflow

    - +
    + diff --git a/v0.5.1/_modules/doctr/models/recognition/parseq/tensorflow.html b/v0.5.1/_modules/doctr/models/recognition/parseq/tensorflow.html index 0819737dfc..b181acef53 100644 --- a/v0.5.1/_modules/doctr/models/recognition/parseq/tensorflow.html +++ b/v0.5.1/_modules/doctr/models/recognition/parseq/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.parseq.tensorflow - docTR documentation @@ -845,7 +845,7 @@

    Source code for doctr.models.recognition.parseq.tensorflow

    - + diff --git a/v0.5.1/_modules/doctr/models/recognition/sar.html b/v0.5.1/_modules/doctr/models/recognition/sar.html deleted file mode 100644 index 2482e9f156..0000000000 --- a/v0.5.1/_modules/doctr/models/recognition/sar.html +++ /dev/null @@ -1,712 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.sar - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.sar

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import Sequential, layers
    -from typing import Tuple, Dict, List, Any, Optional
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel
    -from .core import RecognitionPostProcessor
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['SAR', 'SARPostProcessor', 'sar_vgg16_bn', 'sar_resnet31']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'sar_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1-models/sar_vgg16bn-0d7e2c26.zip',
    -    },
    -    'sar_resnet31': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'resnet31', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/sar_resnet31-ea202587.zip',
    -    },
    -}
    -
    -
    -class AttentionModule(layers.Layer, NestedObject):
    -    """Implements attention module of the SAR model
    -
    -    Args:
    -        attention_units: number of hidden attention units
    -
    -    """
    -    def __init__(
    -        self,
    -        attention_units: int
    -    ) -> None:
    -
    -        super().__init__()
    -        self.hidden_state_projector = layers.Conv2D(
    -            attention_units, 1, strides=1, use_bias=False, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.features_projector = layers.Conv2D(
    -            attention_units, 3, strides=1, use_bias=True, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.attention_projector = layers.Conv2D(
    -            1, 1, strides=1, use_bias=False, padding="same", kernel_initializer='he_normal',
    -        )
    -        self.flatten = layers.Flatten()
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        hidden_state: tf.Tensor,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        [H, W] = features.get_shape().as_list()[1:3]
    -        # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units)
    -        hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs)
    -        # shape (N, H, W, vgg_units) -> (N, H, W, attention_units)
    -        features_projection = self.features_projector(features, **kwargs)
    -        projection = tf.math.tanh(hidden_state_projection + features_projection)
    -        # shape (N, H, W, attention_units) -> (N, H, W, 1)
    -        attention = self.attention_projector(projection, **kwargs)
    -        # shape (N, H, W, 1) -> (N, H * W)
    -        attention = self.flatten(attention)
    -        attention = tf.nn.softmax(attention)
    -        # shape (N, H * W) -> (N, H, W, 1)
    -        attention_map = tf.reshape(attention, [-1, H, W, 1])
    -        glimpse = tf.math.multiply(features, attention_map)
    -        # shape (N, H * W) -> (N, 1)
    -        glimpse = tf.reduce_sum(glimpse, axis=[1, 2])
    -        return glimpse
    -
    -
    -class SARDecoder(layers.Layer, NestedObject):
    -    """Implements decoder module of the SAR model
    -
    -    Args:
    -        rnn_units: number of hidden units in recurrent cells
    -        max_length: maximum length of a sequence
    -        vocab_size: number of classes in the model alphabet
    -        embedding_units: number of hidden embedding units
    -        attention_units: number of hidden attention units
    -        num_decoder_layers: number of LSTM layers to stack
    -
    -    """
    -    def __init__(
    -        self,
    -        rnn_units: int,
    -        max_length: int,
    -        vocab_size: int,
    -        embedding_units: int,
    -        attention_units: int,
    -        num_decoder_layers: int = 2,
    -        input_shape: Optional[List[Tuple[Optional[int]]]] = None,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.vocab_size = vocab_size
    -        self.lstm_decoder = layers.StackedRNNCells(
    -            [layers.LSTMCell(rnn_units, dtype=tf.float32, implementation=1) for _ in range(num_decoder_layers)]
    -        )
    -        self.embed = layers.Dense(embedding_units, use_bias=False, input_shape=(None, self.vocab_size + 1))
    -        self.attention_module = AttentionModule(attention_units)
    -        self.output_dense = layers.Dense(vocab_size + 1, use_bias=True, input_shape=(None, 2 * rnn_units))
    -        self.max_length = max_length
    -
    -        # Initialize kernels
    -        if input_shape is not None:
    -            self.attention_module.call(layers.Input(input_shape[0][1:]), layers.Input((1, 1, rnn_units)))
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        holistic: tf.Tensor,
    -        gt: Optional[tf.Tensor] = None,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # initialize states (each of shape (N, rnn_units))
    -        states = self.lstm_decoder.get_initial_state(
    -            inputs=None, batch_size=features.shape[0], dtype=tf.float32
    -        )
    -        # run first step of lstm
    -        # holistic: shape (N, rnn_units)
    -        _, states = self.lstm_decoder(holistic, states, **kwargs)
    -        # Initialize with the index of virtual START symbol (placed after <eos>)
    -        symbol = tf.fill(features.shape[0], self.vocab_size + 1)
    -        logits_list = []
    -        if kwargs.get('training') and gt is None:
    -            raise ValueError('Need to provide labels during training for teacher forcing')
    -        for t in range(self.max_length + 1):  # keep 1 step for <eos>
    -            # one-hot symbol with depth vocab_size + 1
    -            # embeded_symbol: shape (N, embedding_units)
    -            embeded_symbol = self.embed(tf.one_hot(symbol, depth=self.vocab_size + 1), **kwargs)
    -            logits, states = self.lstm_decoder(embeded_symbol, states, **kwargs)
    -            glimpse = self.attention_module(
    -                features, tf.expand_dims(tf.expand_dims(logits, axis=1), axis=1), **kwargs,
    -            )
    -            # logits: shape (N, rnn_units), glimpse: shape (N, 1)
    -            logits = tf.concat([logits, glimpse], axis=-1)
    -            # shape (N, rnn_units + 1) -> (N, vocab_size + 1)
    -            logits = self.output_dense(logits, **kwargs)
    -            # update symbol with predicted logits for t+1 step
    -            if kwargs.get('training'):
    -                symbol = gt[:, t]
    -            else:
    -                symbol = tf.argmax(logits, axis=-1)
    -            logits_list.append(logits)
    -        outputs = tf.stack(logits_list, axis=1)  # shape (N, max_length + 1, vocab_size + 1)
    -
    -        return outputs
    -
    -
    -class SAR(RecognitionModel):
    -    """Implements a SAR architecture as described in `"Show, Attend and Read:A Simple and Strong Baseline for
    -    Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of hidden units in both encoder and decoder LSTM
    -        embedding_units: number of embedding units
    -        attention_units: number of hidden units in attention module
    -        max_length: maximum word length handled by the model
    -        num_decoders: number of LSTM to stack in decoder layer
    -
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'encoder', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor,
    -        vocab: str,
    -        rnn_units: int = 512,
    -        embedding_units: int = 512,
    -        attention_units: int = 512,
    -        max_length: int = 30,
    -        num_decoders: int = 2,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(vocab=vocab, cfg=cfg)
    -
    -        self.max_length = max_length + 1  # Add 1 timestep for EOS after the longest word
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.encoder = Sequential(
    -            [
    -                layers.LSTM(units=rnn_units, return_sequences=True),
    -                layers.LSTM(units=rnn_units, return_sequences=False)
    -            ]
    -        )
    -        # Initialize the kernels (watch out for reduce_max)
    -        self.encoder.build(input_shape=(None,) + self.feat_extractor.output_shape[2:])
    -
    -        self.decoder = SARDecoder(
    -            rnn_units, max_length, len(vocab), embedding_units, attention_units, num_decoders,
    -            input_shape=[self.feat_extractor.output_shape, self.encoder.output_shape]
    -        )
    -
    -        self.postprocessor = SARPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        gt: tf.Tensor,
    -        seq_len: tf.Tensor,
    -    ) -> tf.Tensor:
    -        """Compute categorical cross-entropy loss for the model.
    -        Sequences are masked after the EOS character.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        # Input length : number of timesteps
    -        input_len = tf.shape(model_output)[1]
    -        # Add one for additional <eos> token
    -        seq_len = seq_len + 1
    -        # One-hot gt labels
    -        oh_gt = tf.one_hot(gt, depth=model_output.shape[2])
    -        # Compute loss
    -        cce = tf.nn.softmax_cross_entropy_with_logits(oh_gt, model_output)
    -        # Compute mask
    -        mask_values = tf.zeros_like(cce)
    -        mask_2d = tf.sequence_mask(seq_len, input_len)
    -        masked_loss = tf.where(mask_2d, cce, mask_values)
    -        ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32))
    -        return tf.expand_dims(ce_loss, axis=1)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        pooled_features = tf.reduce_max(features, axis=1)  # vertical max pooling
    -        encoded = self.encoder(pooled_features, **kwargs)
    -        if target is not None:
    -            gt, seq_len = self.compute_target(target)
    -        decoded_features = self.decoder(features, encoded, gt=None if target is None else gt, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = decoded_features
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(decoded_features)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(decoded_features, gt, seq_len)
    -
    -        return out
    -
    -
    -class SARPostProcessor(RecognitionPostProcessor):
    -    """Post processor for SAR architectures
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor,
    -    ) -> List[Tuple[str, float]]:
    -        # compute pred with argmax for attention models
    -        out_idxs = tf.math.argmax(logits, axis=2)
    -        # N x L
    -        probs = tf.gather(tf.nn.softmax(logits, axis=-1), out_idxs, axis=-1, batch_dims=2)
    -        # Take the minimum confidence of the sequence
    -        probs = tf.math.reduce_min(probs, axis=1)
    -
    -        # decode raw output of the model with tf_label_to_idx
    -        out_idxs = tf.cast(out_idxs, dtype='int32')
    -        decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(self._embedding, out_idxs), axis=-1)
    -        decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -def _sar(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> SAR:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -    _cfg['embedding_units'] = kwargs.get('embedding_units', _cfg['rnn_units'])
    -    _cfg['attention_units'] = kwargs.get('attention_units', _cfg['rnn_units'])
    -    _cfg['max_length'] = kwargs.get('max_length', _cfg['max_length'])
    -    _cfg['num_decoders'] = kwargs.get('num_decoders', _cfg['num_decoders'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[default_cfgs[arch]['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -    kwargs['embedding_units'] = _cfg['embedding_units']
    -    kwargs['attention_units'] = _cfg['attention_units']
    -    kwargs['max_length'] = _cfg['max_length']
    -    kwargs['num_decoders'] = _cfg['num_decoders']
    -
    -    # Build the model
    -    model = SAR(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, default_cfgs[arch]['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a VGG16 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import sar_vgg16_bn - >>> model = sar_vgg16_bn(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_vgg16_bn', pretrained, **kwargs)
    - - - -
    -[docs] -def sar_resnet31(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a resnet-31 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example: - >>> import tensorflow as tf - >>> from doctr.models import sar_resnet31 - >>> model = sar_resnet31(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_resnet31', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.1/_modules/doctr/models/recognition/sar/tensorflow.html b/v0.5.1/_modules/doctr/models/recognition/sar/tensorflow.html index e514e4f0c4..4a591e6451 100644 --- a/v0.5.1/_modules/doctr/models/recognition/sar/tensorflow.html +++ b/v0.5.1/_modules/doctr/models/recognition/sar/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.sar.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.sar.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple
    +
     import tensorflow as tf
    -from tensorflow.keras import Sequential, layers, Model
    -from typing import Tuple, Dict, List, Any, Optional
    +from tensorflow.keras import Model, Sequential, layers
     
    -from ... import backbones
    -from ...utils import load_pretrained_params
    -from ..core import RecognitionModel, RecognitionPostProcessor
    +from doctr.datasets import VOCABS
     from doctr.utils.repr import NestedObject
     
    -__all__ = ['SAR', 'SARPostProcessor', 'sar_vgg16_bn', 'sar_resnet31']
    +from ...classification import resnet31
    +from ...utils.tensorflow import _bf16_to_float32, _build_model, load_pretrained_params
    +from ..core import RecognitionModel, RecognitionPostProcessor
    +
    +__all__ = ["SAR", "sar_resnet31"]
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'sar_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1-models/sar_vgg16bn-0d7e2c26.zip',
    -    },
    -    'sar_resnet31': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'resnet31', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/sar_resnet31-ea202587.zip',
    +    "sar_resnet31": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/sar_resnet31-5a58806c.weights.h5&src=0",
         },
     }
     
     
    +class SAREncoder(layers.Layer, NestedObject):
    +    """Implements encoder module of the SAR model
    +
    +    Args:
    +    ----
    +        rnn_units: number of hidden rnn units
    +        dropout_prob: dropout probability
    +    """
    +
    +    def __init__(self, rnn_units: int, dropout_prob: float = 0.0) -> None:
    +        super().__init__()
    +        self.rnn = Sequential([
    +            layers.LSTM(units=rnn_units, return_sequences=True, recurrent_dropout=dropout_prob),
    +            layers.LSTM(units=rnn_units, return_sequences=False, recurrent_dropout=dropout_prob),
    +        ])
    +
    +    def call(
    +        self,
    +        x: tf.Tensor,
    +        **kwargs: Any,
    +    ) -> tf.Tensor:
    +        # (N, C)
    +        return self.rnn(x, **kwargs)
    +
    +
     class AttentionModule(layers.Layer, NestedObject):
         """Implements attention module of the SAR model
     
         Args:
    +    ----
             attention_units: number of hidden attention units
     
         """
    -    def __init__(
    -        self,
    -        attention_units: int
    -    ) -> None:
     
    +    def __init__(self, attention_units: int) -> None:
             super().__init__()
             self.hidden_state_projector = layers.Conv2D(
    -            attention_units, 1, strides=1, use_bias=False, padding='same', kernel_initializer='he_normal',
    +            attention_units,
    +            1,
    +            strides=1,
    +            use_bias=False,
    +            padding="same",
    +            kernel_initializer="he_normal",
             )
             self.features_projector = layers.Conv2D(
    -            attention_units, 3, strides=1, use_bias=True, padding='same', kernel_initializer='he_normal',
    +            attention_units,
    +            3,
    +            strides=1,
    +            use_bias=True,
    +            padding="same",
    +            kernel_initializer="he_normal",
             )
             self.attention_projector = layers.Conv2D(
    -            1, 1, strides=1, use_bias=False, padding="same", kernel_initializer='he_normal',
    +            1,
    +            1,
    +            strides=1,
    +            use_bias=False,
    +            padding="same",
    +            kernel_initializer="he_normal",
             )
             self.flatten = layers.Flatten()
     
    @@ -343,12 +395,12 @@ 

    Source code for doctr.models.recognition.sar.tensorflow

    hidden_state: tf.Tensor, **kwargs: Any, ) -> tf.Tensor: - [H, W] = features.get_shape().as_list()[1:3] - # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units) - hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs) # shape (N, H, W, vgg_units) -> (N, H, W, attention_units) features_projection = self.features_projector(features, **kwargs) + # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units) + hidden_state = tf.expand_dims(tf.expand_dims(hidden_state, axis=1), axis=1) + hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs) projection = tf.math.tanh(hidden_state_projection + features_projection) # shape (N, H, W, attention_units) -> (N, H, W, 1) attention = self.attention_projector(projection, **kwargs) @@ -358,23 +410,25 @@

    Source code for doctr.models.recognition.sar.tensorflow

    # shape (N, H * W) -> (N, H, W, 1) attention_map = tf.reshape(attention, [-1, H, W, 1]) glimpse = tf.math.multiply(features, attention_map) - # shape (N, H * W) -> (N, 1) - glimpse = tf.reduce_sum(glimpse, axis=[1, 2]) - return glimpse + # shape (N, H * W) -> (N, C) + return tf.reduce_sum(glimpse, axis=[1, 2]) class SARDecoder(layers.Layer, NestedObject): """Implements decoder module of the SAR model Args: + ---- rnn_units: number of hidden units in recurrent cells max_length: maximum length of a sequence vocab_size: number of classes in the model alphabet embedding_units: number of hidden embedding units attention_units: number of hidden attention units - num_decoder_layers: number of LSTM layers to stack + num_decoder_cells: number of LSTMCell layers to stack + dropout_prob: dropout probability """ + def __init__( self, rnn_units: int, @@ -382,23 +436,22 @@

    Source code for doctr.models.recognition.sar.tensorflow

    vocab_size: int, embedding_units: int, attention_units: int, - num_decoder_layers: int = 2, - input_shape: Optional[List[Tuple[Optional[int]]]] = None, + num_decoder_cells: int = 2, + dropout_prob: float = 0.0, ) -> None: - super().__init__() self.vocab_size = vocab_size - self.lstm_decoder = layers.StackedRNNCells( - [layers.LSTMCell(rnn_units, dtype=tf.float32, implementation=1) for _ in range(num_decoder_layers)] - ) - self.embed = layers.Dense(embedding_units, use_bias=False, input_shape=(None, self.vocab_size + 1)) - self.attention_module = AttentionModule(attention_units) - self.output_dense = layers.Dense(vocab_size + 1, use_bias=True, input_shape=(None, 2 * rnn_units)) self.max_length = max_length - # Initialize kernels - if input_shape is not None: - self.attention_module.call(layers.Input(input_shape[0][1:]), layers.Input((1, 1, rnn_units))) + self.embed = layers.Dense(embedding_units, use_bias=False) + self.embed_tgt = layers.Embedding(embedding_units, self.vocab_size + 1) + + self.lstm_cells = layers.StackedRNNCells([ + layers.LSTMCell(rnn_units, implementation=1) for _ in range(num_decoder_cells) + ]) + self.attention_module = AttentionModule(attention_units) + self.output_dense = layers.Dense(self.vocab_size + 1, use_bias=True) + self.dropout = layers.Dropout(dropout_prob) def call( self, @@ -407,40 +460,47 @@

    Source code for doctr.models.recognition.sar.tensorflow

    gt: Optional[tf.Tensor] = None, **kwargs: Any, ) -> tf.Tensor: - - # initialize states (each of shape (N, rnn_units)) - states = self.lstm_decoder.get_initial_state( - inputs=None, batch_size=features.shape[0], dtype=tf.float32 - ) - # run first step of lstm - # holistic: shape (N, rnn_units) - _, states = self.lstm_decoder(holistic, states, **kwargs) - # Initialize with the index of virtual START symbol (placed after <eos>) - symbol = tf.fill(features.shape[0], self.vocab_size + 1) - logits_list = [] - if kwargs.get('training') and gt is None: - raise ValueError('Need to provide labels during training for teacher forcing') - for t in range(self.max_length + 1): # keep 1 step for <eos> - # one-hot symbol with depth vocab_size + 1 - # embeded_symbol: shape (N, embedding_units) - embeded_symbol = self.embed(tf.one_hot(symbol, depth=self.vocab_size + 1), **kwargs) - logits, states = self.lstm_decoder(embeded_symbol, states, **kwargs) - glimpse = self.attention_module( - features, tf.expand_dims(tf.expand_dims(logits, axis=1), axis=1), **kwargs, - ) - # logits: shape (N, rnn_units), glimpse: shape (N, 1) - logits = tf.concat([logits, glimpse], axis=-1) - # shape (N, rnn_units + 1) -> (N, vocab_size + 1) - logits = self.output_dense(logits, **kwargs) - # update symbol with predicted logits for t+1 step - if kwargs.get('training'): - symbol = gt[:, t] # type: ignore[index] + if gt is not None: + gt_embedding = self.embed_tgt(gt, **kwargs) + + logits_list: List[tf.Tensor] = [] + + for t in range(self.max_length + 1): # 32 + if t == 0: + # step to init the first states of the LSTMCell + states = self.lstm_cells.get_initial_state( + inputs=None, batch_size=features.shape[0], dtype=features.dtype + ) + prev_symbol = holistic + elif t == 1: + # step to init a 'blank' sequence of length vocab_size + 1 filled with zeros + # (N, vocab_size + 1) --> (N, embedding_units) + prev_symbol = tf.zeros([features.shape[0], self.vocab_size + 1], dtype=features.dtype) + prev_symbol = self.embed(prev_symbol, **kwargs) else: - symbol = tf.argmax(logits, axis=-1) - logits_list.append(logits) - outputs = tf.stack(logits_list, axis=1) # shape (N, max_length + 1, vocab_size + 1) - - return outputs + if gt is not None and kwargs.get("training", False): + # (N, embedding_units) -2 because of <bos> and <eos> (same) + prev_symbol = self.embed(gt_embedding[:, t - 2], **kwargs) + else: + # -1 to start at timestep where prev_symbol was initialized + index = tf.argmax(logits_list[t - 1], axis=-1) + # update prev_symbol with ones at the index of the previous logit vector + prev_symbol = self.embed(self.embed_tgt(index, **kwargs), **kwargs) + + # (N, C), (N, C) take the last hidden state and cell state from current timestep + _, states = self.lstm_cells(prev_symbol, states, **kwargs) + # states = (hidden_state, cell_state) + hidden_state = states[0][0] + # (N, H, W, C), (N, C) --> (N, C) + glimpse = self.attention_module(features, hidden_state, **kwargs) + # (N, C), (N, C) --> (N, 2 * C) + logits = tf.concat([hidden_state, glimpse], axis=1) + logits = self.dropout(logits, **kwargs) + # (N, vocab_size + 1) + logits_list.append(self.output_dense(logits, **kwargs)) + + # (max_length + 1, N, vocab_size + 1) --> (N, max_length + 1, vocab_size + 1) + return tf.transpose(tf.stack(logits_list[1:]), (1, 0, 2)) class SAR(Model, RecognitionModel): @@ -448,17 +508,20 @@

    Source code for doctr.models.recognition.sar.tensorflow

    Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. Args: + ---- feature_extractor: the backbone serving as feature extractor vocab: vocabulary used for encoding rnn_units: number of hidden units in both encoder and decoder LSTM embedding_units: number of embedding units attention_units: number of hidden units in attention module max_length: maximum word length handled by the model - num_decoders: number of LSTM to stack in decoder layer - + num_decoder_cells: number of LSTMCell layers to stack + dropout_prob: dropout probability for the encoder and decoder + exportable: onnx exportable returns only logits + cfg: dictionary containing information about the model """ - _children_names: List[str] = ['feat_extractor', 'encoder', 'decoder', 'postprocessor'] + _children_names: List[str] = ["feat_extractor", "encoder", "decoder", "postprocessor"] def __init__( self, @@ -468,36 +531,34 @@

    Source code for doctr.models.recognition.sar.tensorflow

    embedding_units: int = 512, attention_units: int = 512, max_length: int = 30, - num_decoders: int = 2, + num_decoder_cells: int = 2, + dropout_prob: float = 0.0, + exportable: bool = False, cfg: Optional[Dict[str, Any]] = None, ) -> None: - super().__init__() self.vocab = vocab + self.exportable = exportable self.cfg = cfg - self.max_length = max_length + 1 # Add 1 timestep for EOS after the longest word self.feat_extractor = feature_extractor - self.encoder = Sequential( - [ - layers.LSTM(units=rnn_units, return_sequences=True), - layers.LSTM(units=rnn_units, return_sequences=False) - ] - ) - # Initialize the kernels (watch out for reduce_max) - self.encoder.build(input_shape=(None,) + self.feat_extractor.output_shape[2:]) - + self.encoder = SAREncoder(rnn_units, dropout_prob) self.decoder = SARDecoder( - rnn_units, max_length, len(vocab), embedding_units, attention_units, num_decoders, - input_shape=[self.feat_extractor.output_shape, self.encoder.output_shape] + rnn_units, + self.max_length, + len(vocab), + embedding_units, + attention_units, + num_decoder_cells, + dropout_prob, ) self.postprocessor = SARPostProcessor(vocab=vocab) + @staticmethod def compute_loss( - self, model_output: tf.Tensor, gt: tf.Tensor, seq_len: tf.Tensor, @@ -506,11 +567,13 @@

    Source code for doctr.models.recognition.sar.tensorflow

    Sequences are masked after the EOS character. Args: + ---- gt: the encoded tensor with gt labels model_output: predicted logits of the model seq_len: lengths of each gt word inside the batch Returns: + ------- The loss of the model on the batch """ # Input length : number of timesteps @@ -525,7 +588,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    mask_values = tf.zeros_like(cce) mask_2d = tf.sequence_mask(seq_len, input_len) masked_loss = tf.where(mask_2d, cce, mask_values) - ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32)) + ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, model_output.dtype)) return tf.expand_dims(ce_loss, axis=1) def call( @@ -536,16 +599,28 @@

    Source code for doctr.models.recognition.sar.tensorflow

    return_preds: bool = False, **kwargs: Any, ) -> Dict[str, Any]: - features = self.feat_extractor(x, **kwargs) - pooled_features = tf.reduce_max(features, axis=1) # vertical max pooling + # vertical max pooling --> (N, C, W) + pooled_features = tf.reduce_max(features, axis=1) + # holistic (N, C) encoded = self.encoder(pooled_features, **kwargs) + if target is not None: - gt, seq_len = self.compute_target(target) + gt, seq_len = self.build_target(target) seq_len = tf.cast(seq_len, tf.int32) - decoded_features = self.decoder(features, encoded, gt=None if target is None else gt, **kwargs) + + if kwargs.get("training", False) and target is None: + raise ValueError("Need to provide labels during training for teacher forcing") + + decoded_features = _bf16_to_float32( + self.decoder(features, encoded, gt=None if target is None else gt, **kwargs) + ) out: Dict[str, tf.Tensor] = {} + if self.exportable: + out["logits"] = decoded_features + return out + if return_model_output: out["out_map"] = decoded_features @@ -554,7 +629,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    out["preds"] = self.postprocessor(decoded_features) if target is not None: - out['loss'] = self.compute_loss(decoded_features, gt, seq_len) + out["loss"] = self.compute_loss(decoded_features, gt, seq_len) return out @@ -563,9 +638,8 @@

    Source code for doctr.models.recognition.sar.tensorflow

    """Post processor for SAR architectures Args: + ---- vocab: string containing the ordered sequence of supported characters - ignore_case: if True, ignore case of letters - ignore_accents: if True, ignore accents of letters """ def __call__( @@ -580,95 +654,75 @@

    Source code for doctr.models.recognition.sar.tensorflow

    probs = tf.math.reduce_min(probs, axis=1) # decode raw output of the model with tf_label_to_idx - out_idxs = tf.cast(out_idxs, dtype='int32') + out_idxs = tf.cast(out_idxs, dtype="int32") embedding = tf.constant(self._embedding, dtype=tf.string) decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(embedding, out_idxs), axis=-1) decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>") - decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0] + decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value="not valid")[:, 0] word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] - return list(zip(word_values, probs.numpy().tolist())) + return list(zip(word_values, probs.numpy().clip(0, 1).tolist())) -def _sar(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> SAR: +def _sar( + arch: str, + pretrained: bool, + backbone_fn, + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> SAR: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab']) - _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units']) - _cfg['embedding_units'] = kwargs.get('embedding_units', _cfg['rnn_units']) - _cfg['attention_units'] = kwargs.get('attention_units', _cfg['rnn_units']) - _cfg['max_length'] = kwargs.get('max_length', _cfg['max_length']) - _cfg['num_decoders'] = kwargs.get('num_decoders', _cfg['num_decoders']) + _cfg["input_shape"] = input_shape or _cfg["input_shape"] + _cfg["vocab"] = kwargs.get("vocab", _cfg["vocab"]) # Feature extractor - feat_extractor = backbones.__dict__[default_cfgs[arch]['backbone']]( - input_shape=_cfg['input_shape'], + feat_extractor = backbone_fn( + pretrained=pretrained_backbone, + input_shape=_cfg["input_shape"], include_top=False, ) - kwargs['vocab'] = _cfg['vocab'] - kwargs['rnn_units'] = _cfg['rnn_units'] - kwargs['embedding_units'] = _cfg['embedding_units'] - kwargs['attention_units'] = _cfg['attention_units'] - kwargs['max_length'] = _cfg['max_length'] - kwargs['num_decoders'] = _cfg['num_decoders'] + kwargs["vocab"] = _cfg["vocab"] # Build the model model = SAR(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) # Load pretrained parameters if pretrained: - load_pretrained_params(model, default_cfgs[arch]['url']) + # The given vocab differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, default_cfgs[arch]["url"], skip_mismatch=kwargs["vocab"] != default_cfgs[arch]["vocab"] + ) return model -
    -[docs] -def sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a VGG16 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import sar_vgg16_bn - >>> model = sar_vgg16_bn(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_vgg16_bn', pretrained, **kwargs)
    - - -
    -[docs] +[docs] def sar_resnet31(pretrained: bool = False, **kwargs: Any) -> SAR: """SAR with a resnet-31 feature extractor as described in `"Show, Attend and Read:A Simple and Strong Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - Example: - >>> import tensorflow as tf - >>> from doctr.models import sar_resnet31 - >>> model = sar_resnet31(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import sar_resnet31 + >>> model = sar_resnet31(pretrained=False) + >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the SAR architecture Returns: + ------- text recognition architecture """ - - return _sar('sar_resnet31', pretrained, **kwargs)
    + return _sar("sar_resnet31", pretrained, resnet31, **kwargs)
    @@ -702,8 +756,8 @@

    Source code for doctr.models.recognition.sar.tensorflow

    - +
    + diff --git a/v0.5.1/_modules/doctr/models/recognition/vitstr/tensorflow.html b/v0.5.1/_modules/doctr/models/recognition/vitstr/tensorflow.html index 6e101893bf..c594d40a56 100644 --- a/v0.5.1/_modules/doctr/models/recognition/vitstr/tensorflow.html +++ b/v0.5.1/_modules/doctr/models/recognition/vitstr/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.vitstr.tensorflow - docTR documentation @@ -621,7 +621,7 @@

    Source code for doctr.models.recognition.vitstr.tensorflow

    - + diff --git a/v0.5.1/_modules/doctr/models/recognition/zoo.html b/v0.5.1/_modules/doctr/models/recognition/zoo.html index bf0ae6af6e..f664304019 100644 --- a/v0.5.1/_modules/doctr/models/recognition/zoo.html +++ b/v0.5.1/_modules/doctr/models/recognition/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.zoo - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -from typing import Any
    +from typing import Any, List
     
    -from doctr.file_utils import is_tf_available, is_torch_available
    -from .core import RecognitionPredictor
    -from ..preprocessor import PreProcessor
    -from .. import recognition
    +from doctr.file_utils import is_tf_available
    +from doctr.models.preprocessor import PreProcessor
     
    +from .. import recognition
    +from .predictor import RecognitionPredictor
     
     __all__ = ["recognition_predictor"]
     
     
    -if is_tf_available():
    -    ARCHS = ['crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31', 'master']
    -elif is_torch_available():
    -    ARCHS = ['crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31']
    +ARCHS: List[str] = [
    +    "crnn_vgg16_bn",
    +    "crnn_mobilenet_v3_small",
    +    "crnn_mobilenet_v3_large",
    +    "sar_resnet31",
    +    "master",
    +    "vitstr_small",
    +    "vitstr_base",
    +    "parseq",
    +]
    +
     
    +def _predictor(arch: Any, pretrained: bool, **kwargs: Any) -> RecognitionPredictor:
    +    if isinstance(arch, str):
    +        if arch not in ARCHS:
    +            raise ValueError(f"unknown architecture '{arch}'")
     
    -def _predictor(arch: str, pretrained: bool, **kwargs: Any) -> RecognitionPredictor:
    +        _model = recognition.__dict__[arch](
    +            pretrained=pretrained, pretrained_backbone=kwargs.get("pretrained_backbone", True)
    +        )
    +    else:
    +        if not isinstance(
    +            arch, (recognition.CRNN, recognition.SAR, recognition.MASTER, recognition.ViTSTR, recognition.PARSeq)
    +        ):
    +            raise ValueError(f"unknown architecture: {type(arch)}")
    +        _model = arch
     
    -    if arch not in ARCHS:
    -        raise ValueError(f"unknown architecture '{arch}'")
    +    kwargs.pop("pretrained_backbone", None)
     
    -    _model = recognition.__dict__[arch](pretrained=pretrained)
    -    kwargs['mean'] = kwargs.get('mean', _model.cfg['mean'])
    -    kwargs['std'] = kwargs.get('std', _model.cfg['std'])
    -    kwargs['batch_size'] = kwargs.get('batch_size', 32)
    -    predictor = RecognitionPredictor(
    -        PreProcessor(_model.cfg['input_shape'][:2], preserve_aspect_ratio=True, **kwargs),
    -        _model
    -    )
    +    kwargs["mean"] = kwargs.get("mean", _model.cfg["mean"])
    +    kwargs["std"] = kwargs.get("std", _model.cfg["std"])
    +    kwargs["batch_size"] = kwargs.get("batch_size", 128)
    +    input_shape = _model.cfg["input_shape"][:2] if is_tf_available() else _model.cfg["input_shape"][-2:]
    +    predictor = RecognitionPredictor(PreProcessor(input_shape, preserve_aspect_ratio=True, **kwargs), _model)
     
         return predictor
     
     
     
    -[docs] -def recognition_predictor(arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) -> RecognitionPredictor: +[docs] +def recognition_predictor( + arch: Any = "crnn_vgg16_bn", + pretrained: bool = False, + symmetric_pad: bool = False, + batch_size: int = 128, + **kwargs: Any, +) -> RecognitionPredictor: """Text recognition architecture. Example:: @@ -326,14 +369,18 @@

    Source code for doctr.models.recognition.zoo

            >>> out = model([input_page])
     
         Args:
    -        arch: name of the architecture to use ('crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31')
    +    ----
    +        arch: name of the architecture or model itself to use (e.g. 'crnn_vgg16_bn')
             pretrained: If True, returns a model pre-trained on our text recognition dataset
    +        symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right
    +        batch_size: number of samples the model processes in parallel
    +        **kwargs: optional parameters to be passed to the architecture
     
         Returns:
    +    -------
             Recognition predictor
         """
    -
    -    return _predictor(arch, pretrained, **kwargs)
    + return _predictor(arch=arch, pretrained=pretrained, symmetric_pad=symmetric_pad, batch_size=batch_size, **kwargs)
    @@ -367,8 +414,8 @@

    Source code for doctr.models.recognition.zoo

       
    -
    - +
    + diff --git a/v0.5.1/_modules/doctr/models/zoo.html b/v0.5.1/_modules/doctr/models/zoo.html index dec6857019..d459671648 100644 --- a/v0.5.1/_modules/doctr/models/zoo.html +++ b/v0.5.1/_modules/doctr/models/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.zoo - docTR documentation @@ -225,15 +225,42 @@

    Source code for doctr.models.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     from typing import Any
    -from .core import OCRPredictor
    +
     from .detection.zoo import detection_predictor
    +from .kie_predictor import KIEPredictor
    +from .predictor import OCRPredictor
     from .recognition.zoo import recognition_predictor
     
    +__all__ = ["ocr_predictor", "kie_predictor"]
     
    -__all__ = ["ocr_predictor"]
    -
    -
    -def _predictor(det_arch: str, reco_arch: str, pretrained: bool, det_bs=2, reco_bs=128) -> OCRPredictor:
     
    +def _predictor(
    +    det_arch: Any,
    +    reco_arch: Any,
    +    pretrained: bool,
    +    pretrained_backbone: bool = True,
    +    assume_straight_pages: bool = True,
    +    preserve_aspect_ratio: bool = True,
    +    symmetric_pad: bool = True,
    +    det_bs: int = 2,
    +    reco_bs: int = 128,
    +    detect_orientation: bool = False,
    +    straighten_pages: bool = False,
    +    detect_language: bool = False,
    +    **kwargs,
    +) -> OCRPredictor:
         # Detection
    -    det_predictor = detection_predictor(det_arch, pretrained=pretrained, batch_size=det_bs)
    +    det_predictor = detection_predictor(
    +        det_arch,
    +        pretrained=pretrained,
    +        pretrained_backbone=pretrained_backbone,
    +        batch_size=det_bs,
    +        assume_straight_pages=assume_straight_pages,
    +        preserve_aspect_ratio=preserve_aspect_ratio,
    +        symmetric_pad=symmetric_pad,
    +    )
     
         # Recognition
    -    reco_predictor = recognition_predictor(reco_arch, pretrained=pretrained, batch_size=reco_bs)
    +    reco_predictor = recognition_predictor(
    +        reco_arch,
    +        pretrained=pretrained,
    +        pretrained_backbone=pretrained_backbone,
    +        batch_size=reco_bs,
    +    )
     
    -    return OCRPredictor(det_predictor, reco_predictor)
    +    return OCRPredictor(
    +        det_predictor,
    +        reco_predictor,
    +        assume_straight_pages=assume_straight_pages,
    +        preserve_aspect_ratio=preserve_aspect_ratio,
    +        symmetric_pad=symmetric_pad,
    +        detect_orientation=detect_orientation,
    +        straighten_pages=straighten_pages,
    +        detect_language=detect_language,
    +        **kwargs,
    +    )
     
     
     
    -[docs] +[docs] def ocr_predictor( - det_arch: str = 'db_resnet50', - reco_arch: str = 'crnn_vgg16_bn', + det_arch: Any = "fast_base", + reco_arch: Any = "crnn_vgg16_bn", pretrained: bool = False, - **kwargs: Any + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + export_as_straight_boxes: bool = False, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs: Any, ) -> OCRPredictor: """End-to-end OCR architecture using one model for localization, and another for text recognition. - Example:: - >>> import numpy as np - >>> from doctr.models import ocr_predictor - >>> model = ocr_predictor(pretrained=True) - >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) - >>> out = model([input_page]) + >>> 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(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) Args: - arch: name of the architecture to use ('db_sar_vgg', 'db_sar_resnet', 'db_crnn_vgg', 'db_crnn_resnet') + ---- + det_arch: name of the detection architecture or the model itself to use + (e.g. 'db_resnet50', 'db_mobilenet_v3_large') + reco_arch: name of the recognition architecture or the model itself to use + (e.g. 'crnn_vgg16_bn', 'sar_resnet31') pretrained: If True, returns a model pre-trained on our OCR dataset + pretrained_backbone: If True, returns a model with a pretrained backbone + assume_straight_pages: if True, speeds up the inference by assuming you only pass straight pages + without rotated textual elements. + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it. + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right. + export_as_straight_boxes: when assume_straight_pages is set to False, export final predictions + (potentially rotated) as straight bounding boxes. + detect_orientation: if True, the estimated general page orientation will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + straighten_pages: if True, estimates the page general orientation + based on the segmentation map median line orientation. + Then, rotates page before passing it again to the deep learning detection module. + Doing so will improve performances for documents with page-uniform rotations. + detect_language: if True, the language prediction will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + kwargs: keyword args of `OCRPredictor` Returns: + ------- OCR predictor """ + return _predictor( + det_arch, + reco_arch, + pretrained, + pretrained_backbone=pretrained_backbone, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + export_as_straight_boxes=export_as_straight_boxes, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + )
    + + - return _predictor(det_arch, reco_arch, pretrained, **kwargs)
    +def _kie_predictor( + det_arch: Any, + reco_arch: Any, + pretrained: bool, + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + det_bs: int = 2, + reco_bs: int = 128, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs, +) -> KIEPredictor: + # Detection + det_predictor = detection_predictor( + det_arch, + pretrained=pretrained, + pretrained_backbone=pretrained_backbone, + batch_size=det_bs, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + ) + + # Recognition + reco_predictor = recognition_predictor( + reco_arch, + pretrained=pretrained, + pretrained_backbone=pretrained_backbone, + batch_size=reco_bs, + ) + + return KIEPredictor( + det_predictor, + reco_predictor, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + ) + + +
    +[docs] +def kie_predictor( + det_arch: Any = "fast_base", + reco_arch: Any = "crnn_vgg16_bn", + pretrained: bool = False, + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + export_as_straight_boxes: bool = False, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs: Any, +) -> KIEPredictor: + """End-to-end KIE architecture using one model for localization, and another for text recognition. + + >>> 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(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) + + Args: + ---- + det_arch: name of the detection architecture or the model itself to use + (e.g. 'db_resnet50', 'db_mobilenet_v3_large') + reco_arch: name of the recognition architecture or the model itself to use + (e.g. 'crnn_vgg16_bn', 'sar_resnet31') + pretrained: If True, returns a model pre-trained on our OCR dataset + pretrained_backbone: If True, returns a model with a pretrained backbone + assume_straight_pages: if True, speeds up the inference by assuming you only pass straight pages + without rotated textual elements. + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it. + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right. + export_as_straight_boxes: when assume_straight_pages is set to False, export final predictions + (potentially rotated) as straight bounding boxes. + detect_orientation: if True, the estimated general page orientation will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + straighten_pages: if True, estimates the page general orientation + based on the segmentation map median line orientation. + Then, rotates page before passing it again to the deep learning detection module. + Doing so will improve performances for documents with page-uniform rotations. + detect_language: if True, the language prediction will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + kwargs: keyword args of `OCRPredictor` + + Returns: + ------- + KIE predictor + """ + return _kie_predictor( + det_arch, + reco_arch, + pretrained, + pretrained_backbone=pretrained_backbone, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + export_as_straight_boxes=export_as_straight_boxes, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + )
    @@ -353,8 +575,8 @@

    Source code for doctr.models.zoo

           
         
       
    - - + + diff --git a/v0.5.1/_modules/doctr/transforms/modules.html b/v0.5.1/_modules/doctr/transforms/modules.html deleted file mode 100644 index ba8269e7ef..0000000000 --- a/v0.5.1/_modules/doctr/transforms/modules.html +++ /dev/null @@ -1,734 +0,0 @@ - - - - - - - - - - - - doctr.transforms.modules - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.transforms.modules

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import random
    -import tensorflow as tf
    -from typing import List, Any, Tuple, Callable
    -
    -from doctr.utils.repr import NestedObject
    -from . import functional as F
    -
    -
    -__all__ = ['Compose', 'Resize', 'Normalize', 'LambdaTransformation', 'ToGray', 'ColorInversion',
    -           'RandomBrightness', 'RandomContrast', 'RandomSaturation', 'RandomHue', 'RandomGamma', 'RandomJpegQuality',
    -           'OneOf', 'RandomApply']
    -
    -
    -
    -[docs] -class Compose(NestedObject): - """Implements a wrapper that will apply transformations sequentially - - Example:: - >>> from doctr.transforms import Compose, Resize - >>> import tensorflow as tf - >>> transfos = Compose([Resize((32, 32))]) - >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformation modules - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, x: Any) -> Any: - for t in self.transforms: - x = t(x) - - return x
    - - - -
    -[docs] -class Resize(NestedObject): - """Resizes a tensor to a target size - - Example:: - >>> from doctr.transforms import Resize - >>> import tensorflow as tf - >>> transfo = Resize((32, 32)) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - output_size: expected output size - method: interpolation method - preserve_aspect_ratio: if `True`, preserve aspect ratio and pad the rest with zeros - symmetric_pad: if `True` while preserving aspect ratio, the padding will be done symmetrically - """ - def __init__( - self, - output_size: Tuple[int, int], - method: str = 'bilinear', - preserve_aspect_ratio: bool = False, - symmetric_pad: bool = False, - ) -> None: - self.output_size = output_size - self.method = method - self.preserve_aspect_ratio = preserve_aspect_ratio - self.symmetric_pad = symmetric_pad - - def extra_repr(self) -> str: - _repr = f"output_size={self.output_size}, method='{self.method}'" - if self.preserve_aspect_ratio: - _repr += f", preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}" - return _repr - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img = tf.image.resize(img, self.output_size, self.method, self.preserve_aspect_ratio) - if self.preserve_aspect_ratio: - # pad width - if not self.symmetric_pad: - offset = (0, 0) - elif self.output_size[0] == img.shape[0]: - offset = (0, int((self.output_size[1] - img.shape[1]) / 2)) - else: - offset = (int((self.output_size[0] - img.shape[0]) / 2), 0) - img = tf.image.pad_to_bounding_box(img, *offset, *self.output_size) - return img
    - - - -
    -[docs] -class Normalize(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - mean: average value per channel - std: standard deviation per channel - """ - def __init__(self, mean: Tuple[float, float, float], std: Tuple[float, float, float]) -> None: - self.mean = tf.constant(mean, dtype=tf.float32) - self.std = tf.constant(std, dtype=tf.float32) - - def extra_repr(self) -> str: - return f"mean={self.mean.numpy().tolist()}, std={self.std.numpy().tolist()}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img -= self.mean - img /= self.std - return img
    - - - -
    -[docs] -class LambdaTransformation(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import LambdaTransformation - >>> import tensorflow as tf - >>> transfo = LambdaTransformation(lambda x: x/ 255.) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - fn: the function to be applied to the input tensor - """ - def __init__(self, fn: Callable[[tf.Tensor], tf.Tensor]) -> None: - self.fn = fn - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return self.fn(img)
    - - - -
    -[docs] -class ToGray(NestedObject): - """Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ToGray() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - """ - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.rgb_to_grayscale(img)
    - - - -
    -[docs] -class ColorInversion(NestedObject): - """Applies the following tranformation to a tensor (image or batch of images): - convert to grayscale, colorize (shift 0-values randomly), and then invert colors - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ColorInversion(min_val=0.6) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_val: range [min_val, 1] to colorize RGB pixels - """ - def __init__(self, min_val: float = 0.6) -> None: - self.min_val = min_val - - def extra_repr(self) -> str: - return f"min_val={self.min_val}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return F.invert_colors(img, self.min_val)
    - - - -
    -[docs] -class RandomBrightness(NestedObject): - """Randomly adjust brightness of a tensor (batch of images or image) by adding a delta - to all pixels - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Brightness() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - p: probability to apply transformation - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_brightness(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomContrast(NestedObject): - """Randomly adjust contrast of a tensor (batch of images or image) by adjusting - each pixel: (img - mean) * contrast_factor + mean. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Contrast() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1) - """ - def __init__(self, delta: float = .3) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_contrast(img, lower=1 - self.delta, upper=1 / (1 - self.delta))
    - - - -
    -[docs] -class RandomSaturation(NestedObject): - """Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and - increasing saturation by a factor. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Saturation() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1) - """ - def __init__(self, delta: float = .5) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_saturation(img, lower=1 - self.delta, upper=1 + self.delta)
    - - - -
    -[docs] -class RandomHue(NestedObject): - """Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Hue() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_hue(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomGamma(NestedObject): - """randomly performs gamma correction for a tensor (batch of images or image) - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Gamma() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_gamma: non-negative real number, lower bound for gamma param - max_gamma: non-negative real number, upper bound for gamma - min_gain: lower bound for constant multiplier - max_gain: upper bound for constant multiplier - """ - def __init__( - self, - min_gamma: float = 0.5, - max_gamma: float = 1.5, - min_gain: float = 0.8, - max_gain: float = 1.2, - ) -> None: - self.min_gamma = min_gamma - self.max_gamma = max_gamma - self.min_gain = min_gain - self.max_gain = max_gain - - def extra_repr(self) -> str: - return f"""gamma_range=({self.min_gamma}, {self.max_gamma}), - gain_range=({self.min_gain}, {self.max_gain})""" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - gamma = random.uniform(self.min_gamma, self.max_gamma) - gain = random.uniform(self.min_gain, self.max_gain) - return tf.image.adjust_gamma(img, gamma=gamma, gain=gain)
    - - - -
    -[docs] -class RandomJpegQuality(NestedObject): - """Randomly adjust jpeg quality of a 3 dimensional RGB image - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = JpegQuality() - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - min_quality: int between [0, 100] - max_quality: int between [0, 100] - """ - def __init__(self, min_quality: int = 60, max_quality: int = 100) -> None: - self.min_quality = min_quality - self.max_quality = max_quality - - def extra_repr(self) -> str: - return f"min_quality={self.min_quality}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_jpeg_quality( - img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality - )
    - - - -
    -[docs] -class OneOf(NestedObject): - """Randomly apply one of the input transformations - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = OneOf([JpegQuality(), Gamma()]) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformations, one only will be picked - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - # Pick transformation - transfo = self.transforms[int(random.random() * len(self.transforms))] - # Apply - return transfo(img)
    - - - -
    -[docs] -class RandomApply(NestedObject): - """Apply with a probability p the input transformation - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = RandomApply(Gamma(), p=.5) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transform: transformation to apply - p: probability to apply - """ - def __init__(self, transform: NestedObject, p: float = .5) -> None: - self.transform = transform - self.p = p - - def extra_repr(self) -> str: - return f"transform={self.transform}, p={self.p}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - if random.random() < self.p: - return self.transform(img) - return img
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.1/_modules/doctr/transforms/modules/base.html b/v0.5.1/_modules/doctr/transforms/modules/base.html index c42079a8fd..4596df3848 100644 --- a/v0.5.1/_modules/doctr/transforms/modules/base.html +++ b/v0.5.1/_modules/doctr/transforms/modules/base.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.base - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.transforms.modules.base

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    +import math
     import random
    -from typing import List, Any, Callable
    +from typing import Any, Callable, List, Optional, Tuple, Union
    +
    +import numpy as np
     
     from doctr.utils.repr import NestedObject
    +
     from .. import functional as F
     
    +__all__ = ["SampleCompose", "ImageTransform", "ColorInversion", "OneOf", "RandomApply", "RandomRotate", "RandomCrop"]
    +
    +
    +class SampleCompose(NestedObject):
    +    """Implements a wrapper that will apply transformations sequentially on both image and target
    +
    +    .. tabs::
    +
    +        .. tab:: TensorFlow
    +
    +            .. code:: python
    +
    +                >>> import numpy as np
    +                >>> import tensorflow as tf
    +                >>> from doctr.transforms import SampleCompose, ImageTransform, ColorInversion, RandomRotate
    +                >>> transfo = SampleCompose([ImageTransform(ColorInversion((32, 32))), RandomRotate(30)])
    +                >>> out, out_boxes = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1), np.zeros((2, 4)))
    +
    +        .. tab:: PyTorch
    +
    +            .. code:: python
    +
    +                >>> import numpy as np
    +                >>> import torch
    +                >>> from doctr.transforms import SampleCompose, ImageTransform, ColorInversion, RandomRotate
    +                >>> transfos = SampleCompose([ImageTransform(ColorInversion((32, 32))), RandomRotate(30)])
    +                >>> out, out_boxes = transfos(torch.rand(8, 64, 64, 3), np.zeros((2, 4)))
    +
    +    Args:
    +    ----
    +        transforms: list of transformation modules
    +    """
    +
    +    _children_names: List[str] = ["sample_transforms"]
    +
    +    def __init__(self, transforms: List[Callable[[Any, Any], Tuple[Any, Any]]]) -> None:
    +        self.sample_transforms = transforms
    +
    +    def __call__(self, x: Any, target: Any) -> Tuple[Any, Any]:
    +        for t in self.sample_transforms:
    +            x, target = t(x, target)
    +
    +        return x, target
    +
    +
    +class ImageTransform(NestedObject):
    +    """Implements a transform wrapper to turn an image-only transformation into an image+target transform
    +
    +    .. tabs::
    +
    +        .. tab:: TensorFlow
    +
    +            .. code:: python
    +
    +                >>> import tensorflow as tf
    +                >>> from doctr.transforms import ImageTransform, ColorInversion
    +                >>> transfo = ImageTransform(ColorInversion((32, 32)))
    +                >>> out, _ = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1), None)
    +
    +        .. tab:: PyTorch
    +
    +            .. code:: python
    +
    +                >>> import torch
    +                >>> from doctr.transforms import ImageTransform, ColorInversion
    +                >>> transfo = ImageTransform(ColorInversion((32, 32)))
    +                >>> out, _ = transfo(torch.rand(8, 64, 64, 3), None)
    +
    +    Args:
    +    ----
    +        transform: the image transformation module to wrap
    +    """
    +
    +    _children_names: List[str] = ["img_transform"]
    +
    +    def __init__(self, transform: Callable[[Any], Any]) -> None:
    +        self.img_transform = transform
     
    -__all__ = ['ColorInversion', 'OneOf', 'RandomApply']
    +    def __call__(self, img: Any, target: Any) -> Tuple[Any, Any]:
    +        img = self.img_transform(img)
    +        return img, target
     
     
     
    -[docs] +[docs] class ColorInversion(NestedObject): """Applies the following tranformation to a tensor (image or batch of images): convert to grayscale, colorize (shift 0-values randomly), and then invert colors - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ColorInversion(min_val=0.6) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + .. tabs:: + + .. tab:: TensorFlow + + .. code:: python + + >>> import tensorflow as tf + >>> from doctr.transforms import ColorInversion + >>> transfo = ColorInversion(min_val=0.6) + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + + .. tab:: PyTorch + + .. code:: python + + >>> import torch + >>> from doctr.transforms import ColorInversion + >>> transfo = ColorInversion(min_val=0.6) + >>> out = transfo(torch.rand(8, 64, 64, 3)) Args: + ---- min_val: range [min_val, 1] to colorize RGB pixels """ + def __init__(self, min_val: float = 0.5) -> None: self.min_val = min_val @@ -316,59 +437,178 @@

    Source code for doctr.transforms.modules.base

    -[docs] +[docs] class OneOf(NestedObject): """Randomly apply one of the input transformations - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = OneOf([JpegQuality(), Gamma()]) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + .. tabs:: + + .. tab:: TensorFlow + + .. code:: python + + >>> import tensorflow as tf + >>> from doctr.transforms import OneOf + >>> transfo = OneOf([JpegQuality(), Gamma()]) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + .. tab:: PyTorch + + .. code:: python + + >>> import torch + >>> from doctr.transforms import OneOf + >>> transfo = OneOf([JpegQuality(), Gamma()]) + >>> out = transfo(torch.rand(1, 64, 64, 3)) Args: + ---- transforms: list of transformations, one only will be picked """ - _children_names: List[str] = ['transforms'] + _children_names: List[str] = ["transforms"] def __init__(self, transforms: List[Callable[[Any], Any]]) -> None: self.transforms = transforms - def __call__(self, img: Any) -> Any: + def __call__(self, img: Any, target: Optional[np.ndarray] = None) -> Union[Any, Tuple[Any, np.ndarray]]: # Pick transformation transfo = self.transforms[int(random.random() * len(self.transforms))] # Apply - return transfo(img)
    + return transfo(img) if target is None else transfo(img, target) # type: ignore[call-arg]
    -[docs] +[docs] class RandomApply(NestedObject): """Apply with a probability p the input transformation - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = RandomApply(Gamma(), p=.5) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + .. tabs:: + + .. tab:: TensorFlow + + .. code:: python + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomApply + >>> transfo = RandomApply(Gamma(), p=.5) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + .. tab:: PyTorch + + .. code:: python + + >>> import torch + >>> from doctr.transforms import RandomApply + >>> transfo = RandomApply(Gamma(), p=.5) + >>> out = transfo(torch.rand(1, 64, 64, 3)) Args: + ---- transform: transformation to apply p: probability to apply """ - def __init__(self, transform: Callable[[Any], Any], p: float = .5) -> None: + + def __init__(self, transform: Callable[[Any], Any], p: float = 0.5) -> None: self.transform = transform self.p = p def extra_repr(self) -> str: return f"transform={self.transform}, p={self.p}" - def __call__(self, img: Any) -> Any: + def __call__(self, img: Any, target: Optional[np.ndarray] = None) -> Union[Any, Tuple[Any, np.ndarray]]: if random.random() < self.p: - return self.transform(img) - return img
    + return self.transform(img) if target is None else self.transform(img, target) # type: ignore[call-arg] + return img if target is None else (img, target)
    + + + +
    +[docs] +class RandomRotate(NestedObject): + """Randomly rotate a tensor image and its boxes + + .. image:: https://doctr-static.mindee.com/models?id=v0.4.0/rotation_illustration.png&src=0 + :align: center + + Args: + ---- + max_angle: maximum angle for rotation, in degrees. Angles will be uniformly picked in + [-max_angle, max_angle] + expand: whether the image should be padded before the rotation + """ + + def __init__(self, max_angle: float = 5.0, expand: bool = False) -> None: + self.max_angle = max_angle + self.expand = expand + + def extra_repr(self) -> str: + return f"max_angle={self.max_angle}, expand={self.expand}" + + def __call__(self, img: Any, target: np.ndarray) -> Tuple[Any, np.ndarray]: + angle = random.uniform(-self.max_angle, self.max_angle) + r_img, r_polys = F.rotate_sample(img, target, angle, self.expand) + # Removes deleted boxes + is_kept = (r_polys.max(1) > r_polys.min(1)).sum(1) == 2 + return r_img, r_polys[is_kept]
    + + + +
    +[docs] +class RandomCrop(NestedObject): + """Randomly crop a tensor image and its boxes + + Args: + ---- + scale: tuple of floats, relative (min_area, max_area) of the crop + ratio: tuple of float, relative (min_ratio, max_ratio) where ratio = h/w + """ + + def __init__(self, scale: Tuple[float, float] = (0.08, 1.0), ratio: Tuple[float, float] = (0.75, 1.33)) -> None: + self.scale = scale + self.ratio = ratio + + def extra_repr(self) -> str: + return f"scale={self.scale}, ratio={self.ratio}" + + def __call__(self, img: Any, target: np.ndarray) -> Tuple[Any, np.ndarray]: + scale = random.uniform(self.scale[0], self.scale[1]) + ratio = random.uniform(self.ratio[0], self.ratio[1]) + + height, width = img.shape[:2] + + # Calculate crop size + crop_area = scale * width * height + aspect_ratio = ratio * (width / height) + crop_width = int(round(math.sqrt(crop_area * aspect_ratio))) + crop_height = int(round(math.sqrt(crop_area / aspect_ratio))) + + # Ensure crop size does not exceed image dimensions + crop_width = min(crop_width, width) + crop_height = min(crop_height, height) + + # Randomly select crop position + x = random.randint(0, width - crop_width) + y = random.randint(0, height - crop_height) + + # relative crop box + crop_box = (x / width, y / height, (x + crop_width) / width, (y + crop_height) / height) + if target.shape[1:] == (4, 2): + min_xy = np.min(target, axis=1) + max_xy = np.max(target, axis=1) + _target = np.concatenate((min_xy, max_xy), axis=1) + else: + _target = target + + # Crop image and targets + croped_img, crop_boxes = F.crop_detection(img, _target, crop_box) + # hard fallback if no box is kept + if crop_boxes.shape[0] == 0: + return img, target + # clip boxes + return croped_img, np.clip(crop_boxes, 0, 1)
    @@ -402,8 +642,8 @@

    Source code for doctr.transforms.modules.base

    - - + + diff --git a/v0.5.1/_modules/doctr/transforms/modules/tensorflow.html b/v0.5.1/_modules/doctr/transforms/modules/tensorflow.html index 1d192a876b..acbbe96225 100644 --- a/v0.5.1/_modules/doctr/transforms/modules/tensorflow.html +++ b/v0.5.1/_modules/doctr/transforms/modules/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.transforms.modules.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import random
    +from typing import Any, Callable, Iterable, List, Optional, Tuple, Union
    +
    +import numpy as np
     import tensorflow as tf
    -from typing import List, Any, Tuple, Callable
     
     from doctr.utils.repr import NestedObject
     
    -
    -__all__ = ['Compose', 'Resize', 'Normalize', 'LambdaTransformation', 'ToGray', 'RandomBrightness',
    -           'RandomContrast', 'RandomSaturation', 'RandomHue', 'RandomGamma', 'RandomJpegQuality']
    +from ..functional.tensorflow import _gaussian_filter, random_shadow
    +
    +__all__ = [
    +    "Compose",
    +    "Resize",
    +    "Normalize",
    +    "LambdaTransformation",
    +    "ToGray",
    +    "RandomBrightness",
    +    "RandomContrast",
    +    "RandomSaturation",
    +    "RandomHue",
    +    "RandomGamma",
    +    "RandomJpegQuality",
    +    "GaussianBlur",
    +    "ChannelShuffle",
    +    "GaussianNoise",
    +    "RandomHorizontalFlip",
    +    "RandomShadow",
    +    "RandomResize",
    +]
     
     
     
    -[docs] +[docs] class Compose(NestedObject): """Implements a wrapper that will apply transformations sequentially - Example:: - >>> from doctr.transforms import Compose, Resize - >>> import tensorflow as tf - >>> transfos = Compose([Resize((32, 32))]) - >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import Compose, Resize + >>> transfos = Compose([Resize((32, 32))]) + >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) Args: + ---- transforms: list of transformation modules """ - _children_names: List[str] = ['transforms'] + _children_names: List[str] = ["transforms"] def __init__(self, transforms: List[Callable[[Any], Any]]) -> None: self.transforms = transforms @@ -319,26 +361,27 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class Resize(NestedObject): """Resizes a tensor to a target size - Example:: - >>> from doctr.transforms import Resize - >>> import tensorflow as tf - >>> transfo = Resize((32, 32)) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import Resize + >>> transfo = Resize((32, 32)) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) Args: + ---- output_size: expected output size method: interpolation method preserve_aspect_ratio: if `True`, preserve aspect ratio and pad the rest with zeros symmetric_pad: if `True` while preserving aspect ratio, the padding will be done symmetrically """ + def __init__( self, - output_size: Tuple[int, int], - method: str = 'bilinear', + output_size: Union[int, Tuple[int, int]], + method: str = "bilinear", preserve_aspect_ratio: bool = False, symmetric_pad: bool = False, ) -> None: @@ -346,6 +389,14 @@

    Source code for doctr.transforms.modules.tensorflow

    self.method = method self.preserve_aspect_ratio = preserve_aspect_ratio self.symmetric_pad = symmetric_pad + self.antialias = True + + if isinstance(self.output_size, int): + self.wanted_size = (self.output_size, self.output_size) + elif isinstance(self.output_size, (tuple, list)): + self.wanted_size = self.output_size + else: + raise AssertionError("Output size should be either a list, a tuple or an int") def extra_repr(self) -> str: _repr = f"output_size={self.output_size}, method='{self.method}'" @@ -353,64 +404,106 @@

    Source code for doctr.transforms.modules.tensorflow

    _repr += f", preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}" return _repr - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img = tf.image.resize(img, self.output_size, self.method, self.preserve_aspect_ratio) + def __call__( + self, + img: tf.Tensor, + target: Optional[np.ndarray] = None, + ) -> Union[tf.Tensor, Tuple[tf.Tensor, np.ndarray]]: + input_dtype = img.dtype + self.output_size = ( + (self.output_size, self.output_size) if isinstance(self.output_size, int) else self.output_size + ) + + img = tf.image.resize(img, self.wanted_size, self.method, self.preserve_aspect_ratio, self.antialias) + # It will produce an un-padded resized image, with a side shorter than wanted if we preserve aspect ratio + raw_shape = img.shape[:2] + if self.symmetric_pad: + half_pad = (int((self.output_size[0] - img.shape[0]) / 2), 0) if self.preserve_aspect_ratio: - # pad width - if not self.symmetric_pad: - offset = (0, 0) - elif self.output_size[0] == img.shape[0]: - offset = (0, int((self.output_size[1] - img.shape[1]) / 2)) - else: - offset = (int((self.output_size[0] - img.shape[0]) / 2), 0) - img = tf.image.pad_to_bounding_box(img, *offset, *self.output_size) - return img
    + if isinstance(self.output_size, (tuple, list)): + # In that case we need to pad because we want to enforce both width and height + if not self.symmetric_pad: + half_pad = (0, 0) + elif self.output_size[0] == img.shape[0]: + half_pad = (0, int((self.output_size[1] - img.shape[1]) / 2)) + # Pad image + img = tf.image.pad_to_bounding_box(img, *half_pad, *self.output_size) + + # In case boxes are provided, resize boxes if needed (for detection task if preserve aspect ratio) + if target is not None: + if self.symmetric_pad: + offset = half_pad[0] / img.shape[0], half_pad[1] / img.shape[1] + + if self.preserve_aspect_ratio: + # Get absolute coords + if target.shape[1:] == (4,): + if isinstance(self.output_size, (tuple, list)) and self.symmetric_pad: + target[:, [0, 2]] = offset[1] + target[:, [0, 2]] * raw_shape[1] / img.shape[1] + target[:, [1, 3]] = offset[0] + target[:, [1, 3]] * raw_shape[0] / img.shape[0] + else: + target[:, [0, 2]] *= raw_shape[1] / img.shape[1] + target[:, [1, 3]] *= raw_shape[0] / img.shape[0] + elif target.shape[1:] == (4, 2): + if isinstance(self.output_size, (tuple, list)) and self.symmetric_pad: + target[..., 0] = offset[1] + target[..., 0] * raw_shape[1] / img.shape[1] + target[..., 1] = offset[0] + target[..., 1] * raw_shape[0] / img.shape[0] + else: + target[..., 0] *= raw_shape[1] / img.shape[1] + target[..., 1] *= raw_shape[0] / img.shape[0] + else: + raise AssertionError("Boxes should be in the format (n_boxes, 4, 2) or (n_boxes, 4)") + + return tf.cast(img, dtype=input_dtype), np.clip(target, 0, 1) + + return tf.cast(img, dtype=input_dtype)
    -[docs] +[docs] class Normalize(NestedObject): """Normalize a tensor to a Gaussian distribution for each channel - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import Normalize + >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- mean: average value per channel std: standard deviation per channel """ + def __init__(self, mean: Tuple[float, float, float], std: Tuple[float, float, float]) -> None: - self.mean = tf.constant(mean, dtype=tf.float32) - self.std = tf.constant(std, dtype=tf.float32) + self.mean = tf.constant(mean) + self.std = tf.constant(std) def extra_repr(self) -> str: return f"mean={self.mean.numpy().tolist()}, std={self.std.numpy().tolist()}" def __call__(self, img: tf.Tensor) -> tf.Tensor: - img -= self.mean - img /= self.std + img -= tf.cast(self.mean, dtype=img.dtype) + img /= tf.cast(self.std, dtype=img.dtype) return img
    -[docs] +[docs] class LambdaTransformation(NestedObject): """Normalize a tensor to a Gaussian distribution for each channel - Example:: - >>> from doctr.transforms import LambdaTransformation - >>> import tensorflow as tf - >>> transfo = LambdaTransformation(lambda x: x/ 255.) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import LambdaTransformation + >>> transfo = LambdaTransformation(lambda x: x/ 255.) + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- fn: the function to be applied to the input tensor """ + def __init__(self, fn: Callable[[tf.Tensor], tf.Tensor]) -> None: self.fn = fn @@ -420,37 +513,42 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class ToGray(NestedObject): """Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ToGray() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import ToGray + >>> transfo = ToGray() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) """ + + def __init__(self, num_output_channels: int = 1): + self.num_output_channels = num_output_channels + def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.rgb_to_grayscale(img)
    + img = tf.image.rgb_to_grayscale(img) + return img if self.num_output_channels == 1 else tf.repeat(img, self.num_output_channels, axis=-1)
    -[docs] +[docs] class RandomBrightness(NestedObject): """Randomly adjust brightness of a tensor (batch of images or image) by adding a delta to all pixels - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Brightness() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomBrightness + >>> transfo = RandomBrightness() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] p: probability to apply transformation """ + def __init__(self, max_delta: float = 0.3) -> None: self.max_delta = max_delta @@ -463,21 +561,22 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomContrast(NestedObject): """Randomly adjust contrast of a tensor (batch of images or image) by adjusting each pixel: (img - mean) * contrast_factor + mean. - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Contrast() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomContrast + >>> transfo = RandomContrast() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1) """ - def __init__(self, delta: float = .3) -> None: + + def __init__(self, delta: float = 0.3) -> None: self.delta = delta def extra_repr(self) -> str: @@ -489,21 +588,22 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomSaturation(NestedObject): """Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and increasing saturation by a factor. - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Saturation() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomSaturation + >>> transfo = RandomSaturation() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1) """ - def __init__(self, delta: float = .5) -> None: + + def __init__(self, delta: float = 0.5) -> None: self.delta = delta def extra_repr(self) -> str: @@ -515,19 +615,20 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomHue(NestedObject): """Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Hue() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomHue + >>> transfo = RandomHue() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] """ + def __init__(self, max_delta: float = 0.3) -> None: self.max_delta = max_delta @@ -540,22 +641,23 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomGamma(NestedObject): """randomly performs gamma correction for a tensor (batch of images or image) - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Gamma() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomGamma + >>> transfo = RandomGamma() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- min_gamma: non-negative real number, lower bound for gamma param max_gamma: non-negative real number, upper bound for gamma min_gain: lower bound for constant multiplier max_gain: upper bound for constant multiplier """ + def __init__( self, min_gamma: float = 0.5, @@ -580,20 +682,21 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomJpegQuality(NestedObject): """Randomly adjust jpeg quality of a 3 dimensional RGB image - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = JpegQuality() - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomJpegQuality + >>> transfo = RandomJpegQuality() + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) Args: + ---- min_quality: int between [0, 100] max_quality: int between [0, 100] """ + def __init__(self, min_quality: int = 60, max_quality: int = 100) -> None: self.min_quality = min_quality self.max_quality = max_quality @@ -602,10 +705,224 @@

    Source code for doctr.transforms.modules.tensorflow

    return f"min_quality={self.min_quality}" def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_jpeg_quality( - img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality + return tf.image.random_jpeg_quality(img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality)
    + + + +
    +[docs] +class GaussianBlur(NestedObject): + """Randomly adjust jpeg quality of a 3 dimensional RGB image + + >>> import tensorflow as tf + >>> from doctr.transforms import GaussianBlur + >>> transfo = GaussianBlur(3, (.1, 5)) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + kernel_shape: size of the blurring kernel + std: min and max value of the standard deviation + """ + + def __init__(self, kernel_shape: Union[int, Iterable[int]], std: Tuple[float, float]) -> None: + self.kernel_shape = kernel_shape + self.std = std + + def extra_repr(self) -> str: + return f"kernel_shape={self.kernel_shape}, std={self.std}" + + def __call__(self, img: tf.Tensor) -> tf.Tensor: + return tf.squeeze( + _gaussian_filter( + img[tf.newaxis, ...], + kernel_size=self.kernel_shape, + sigma=random.uniform(self.std[0], self.std[1]), + mode="REFLECT", + ), + axis=0, )
    + + +
    +[docs] +class ChannelShuffle(NestedObject): + """Randomly shuffle channel order of a given image""" + + def __init__(self): + pass + + def __call__(self, img: tf.Tensor) -> tf.Tensor: + return tf.transpose(tf.random.shuffle(tf.transpose(img, perm=[2, 0, 1])), perm=[1, 2, 0])
    + + + +
    +[docs] +class GaussianNoise(NestedObject): + """Adds Gaussian Noise to the input tensor + + >>> import tensorflow as tf + >>> from doctr.transforms import GaussianNoise + >>> transfo = GaussianNoise(0., 1.) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + mean : mean of the gaussian distribution + std : std of the gaussian distribution + """ + + def __init__(self, mean: float = 0.0, std: float = 1.0) -> None: + super().__init__() + self.std = std + self.mean = mean + + def __call__(self, x: tf.Tensor) -> tf.Tensor: + # Reshape the distribution + noise = self.mean + 2 * self.std * tf.random.uniform(x.shape) - self.std + if x.dtype == tf.uint8: + return tf.cast( + tf.clip_by_value(tf.math.round(tf.cast(x, dtype=tf.float32) + 255 * noise), 0, 255), dtype=tf.uint8 + ) + else: + return tf.cast(tf.clip_by_value(x + noise, 0, 1), dtype=x.dtype) + + def extra_repr(self) -> str: + return f"mean={self.mean}, std={self.std}"
    + + + +
    +[docs] +class RandomHorizontalFlip(NestedObject): + """Adds random horizontal flip to the input tensor/np.ndarray + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomHorizontalFlip + >>> transfo = RandomHorizontalFlip(p=0.5) + >>> image = tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1) + >>> target = np.array([[0.1, 0.1, 0.4, 0.5] ], dtype= np.float32) + >>> out = transfo(image, target) + + Args: + ---- + p : probability of Horizontal Flip + """ + + def __init__(self, p: float) -> None: + super().__init__() + self.p = p + + def __call__(self, img: Union[tf.Tensor, np.ndarray], target: np.ndarray) -> Tuple[tf.Tensor, np.ndarray]: + if np.random.rand(1) <= self.p: + _img = tf.image.flip_left_right(img) + _target = target.copy() + # Changing the relative bbox coordinates + if target.shape[1:] == (4,): + _target[:, ::2] = 1 - target[:, [2, 0]] + else: + _target[..., 0] = 1 - target[..., 0] + return _img, _target + return img, target
    + + + +
    +[docs] +class RandomShadow(NestedObject): + """Adds random shade to the input image + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomShadow + >>> transfo = RandomShadow(0., 1.) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + opacity_range : minimum and maximum opacity of the shade + """ + + def __init__(self, opacity_range: Optional[Tuple[float, float]] = None) -> None: + super().__init__() + self.opacity_range = opacity_range if isinstance(opacity_range, tuple) else (0.2, 0.8) + + def __call__(self, x: tf.Tensor) -> tf.Tensor: + # Reshape the distribution + if x.dtype == tf.uint8: + return tf.cast( + tf.clip_by_value( + tf.math.round(255 * random_shadow(tf.cast(x, dtype=tf.float32) / 255, self.opacity_range)), + 0, + 255, + ), + dtype=tf.uint8, + ) + else: + return tf.clip_by_value(random_shadow(x, self.opacity_range), 0, 1) + + def extra_repr(self) -> str: + return f"opacity_range={self.opacity_range}"
    + + + +
    +[docs] +class RandomResize(NestedObject): + """Randomly resize the input image and align corresponding targets + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomResize + >>> transfo = RandomResize((0.3, 0.9), preserve_aspect_ratio=True, symmetric_pad=True, p=0.5) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + scale_range: range of the resizing factor for width and height (independently) + preserve_aspect_ratio: whether to preserve the aspect ratio of the image, + given a float value, the aspect ratio will be preserved with this probability + symmetric_pad: whether to symmetrically pad the image, + given a float value, the symmetric padding will be applied with this probability + p: probability to apply the transformation + """ + + def __init__( + self, + scale_range: Tuple[float, float] = (0.3, 0.9), + preserve_aspect_ratio: Union[bool, float] = False, + symmetric_pad: Union[bool, float] = False, + p: float = 0.5, + ): + super().__init__() + self.scale_range = scale_range + self.preserve_aspect_ratio = preserve_aspect_ratio + self.symmetric_pad = symmetric_pad + self.p = p + self._resize = Resize + + def __call__(self, img: tf.Tensor, target: np.ndarray) -> Tuple[tf.Tensor, np.ndarray]: + if np.random.rand(1) <= self.p: + scale_h = random.uniform(*self.scale_range) + scale_w = random.uniform(*self.scale_range) + new_size = (int(img.shape[-3] * scale_h), int(img.shape[-2] * scale_w)) + + _img, _target = self._resize( + new_size, + preserve_aspect_ratio=self.preserve_aspect_ratio + if isinstance(self.preserve_aspect_ratio, bool) + else bool(np.random.rand(1) <= self.symmetric_pad), + symmetric_pad=self.symmetric_pad + if isinstance(self.symmetric_pad, bool) + else bool(np.random.rand(1) <= self.symmetric_pad), + )(img, target) + + return _img, _target + return img, target + + def extra_repr(self) -> str: + return f"scale_range={self.scale_range}, preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}, p={self.p}" # noqa: E501
    +
    @@ -638,8 +955,8 @@

    Source code for doctr.transforms.modules.tensorflow

    - +
    + diff --git a/v0.5.1/_modules/doctr/utils/metrics.html b/v0.5.1/_modules/doctr/utils/metrics.html index 460c64a385..8a37d5949a 100644 --- a/v0.5.1/_modules/doctr/utils/metrics.html +++ b/v0.5.1/_modules/doctr/utils/metrics.html @@ -13,7 +13,7 @@ - + doctr.utils.metrics - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.utils.metrics

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
    +
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +from typing import Dict, List, Optional, Tuple
     
     import numpy as np
    -import cv2
    -from typing import List, Tuple, Dict, Optional
    -from unidecode import unidecode
    +from anyascii import anyascii
     from scipy.optimize import linear_sum_assignment
    -from doctr.utils.geometry import rbbox_to_polygon
    +from shapely.geometry import Polygon
     
    -__all__ = ['TextMatch', 'box_iou', 'box_ioa', 'mask_iou', 'rbox_to_mask',
    -           'nms', 'LocalizationConfusion', 'OCRMetric']
    +__all__ = [
    +    "TextMatch",
    +    "box_iou",
    +    "polygon_iou",
    +    "nms",
    +    "LocalizationConfusion",
    +    "OCRMetric",
    +    "DetectionMetric",
    +]
     
     
     def string_match(word1: str, word2: str) -> Tuple[bool, bool, bool, bool]:
    -    """Perform string comparison with multiple levels of tolerance
    +    """Performs string comparison with multiple levels of tolerance
     
         Args:
    +    ----
             word1: a string
             word2: another string
     
         Returns:
    +    -------
             a tuple with booleans specifying respectively whether the raw strings, their lower-case counterparts, their
    -            unidecode counterparts and their lower-case unidecode counterparts match
    +            anyascii counterparts and their lower-case anyascii counterparts match
         """
    -    raw_match = (word1 == word2)
    -    caseless_match = (word1.lower() == word2.lower())
    -    unidecode_match = (unidecode(word1) == unidecode(word2))
    +    raw_match = word1 == word2
    +    caseless_match = word1.lower() == word2.lower()
    +    anyascii_match = anyascii(word1) == anyascii(word2)
     
         # Warning: the order is important here otherwise the pair ("EUR", "€") cannot be matched
    -    unicase_match = (unidecode(word1).lower() == unidecode(word2).lower())
    +    unicase_match = anyascii(word1).lower() == anyascii(word2).lower()
     
    -    return raw_match, caseless_match, unidecode_match, unicase_match
    +    return raw_match, caseless_match, anyascii_match, unicase_match
     
     
     
    -[docs] +[docs] class TextMatch: - """Implements text match metric (word-level accuracy) for recognition task. + r"""Implements text match metric (word-level accuracy) for recognition task. The raw aggregated metric is computed as follows: .. math:: - \\forall X, Y \\in \\mathcal{W}^N, - TextMatch(X, Y) = \\frac{1}{N} \\sum\\limits_{i=1}^N f_{Y_i}(X_i) + \forall X, Y \in \mathcal{W}^N, + TextMatch(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N f_{Y_i}(X_i) with the indicator function :math:`f_{a}` defined as: .. math:: - \\forall a, x \\in \\mathcal{W}, - f_a(x) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } x = a \\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{W}` is the set of all possible character sequences, + \forall a, x \in \mathcal{W}, + f_a(x) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } x = a \\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{W}` is the set of all possible character sequences, :math:`N` is a strictly positive integer. - Example:: - >>> from doctr.utils import TextMatch - >>> metric = TextMatch() - >>> metric.update(['Hello', 'world'], ['hello', 'world']) - >>> metric.summary() + >>> from doctr.utils import TextMatch + >>> metric = TextMatch() + >>> metric.update(['Hello', 'world'], ['hello', 'world']) + >>> metric.summary() """ def __init__(self) -> None: self.reset() +
    +[docs] def update( self, gt: List[str], @@ -354,29 +386,32 @@

    Source code for doctr.utils.metrics

             """Update the state of the metric with new predictions
     
             Args:
    +        ----
                 gt: list of groung-truth character sequences
    -            pred: list of predicted character sequences"""
    -
    +            pred: list of predicted character sequences
    +        """
             if len(gt) != len(pred):
                 raise AssertionError("prediction size does not match with ground-truth labels size")
     
             for gt_word, pred_word in zip(gt, pred):
    -            _raw, _caseless, _unidecode, _unicase = string_match(gt_word, pred_word)
    +            _raw, _caseless, _anyascii, _unicase = string_match(gt_word, pred_word)
                 self.raw += int(_raw)
                 self.caseless += int(_caseless)
    -            self.unidecode += int(_unidecode)
    +            self.anyascii += int(_anyascii)
                 self.unicase += int(_unicase)
     
    -        self.total += len(gt)
    +        self.total += len(gt)
    +
    -[docs] +[docs] def summary(self) -> Dict[str, float]: """Computes the aggregated metrics - Returns: - a dictionary with the exact match score for the raw data, its lower-case counterpart, its unidecode - counterpart and its lower-case unidecode counterpart + Returns + ------- + a dictionary with the exact match score for the raw data, its lower-case counterpart, its anyascii + counterpart and its lower-case anyascii counterpart """ if self.total == 0: raise AssertionError("you need to update the metric before getting the summary") @@ -384,7 +419,7 @@

    Source code for doctr.utils.metrics

             return dict(
                 raw=self.raw / self.total,
                 caseless=self.caseless / self.total,
    -            unidecode=self.unidecode / self.total,
    +            anyascii=self.anyascii / self.total,
                 unicase=self.unicase / self.total,
             )
    @@ -392,23 +427,25 @@

    Source code for doctr.utils.metrics

         def reset(self) -> None:
             self.raw = 0
             self.caseless = 0
    -        self.unidecode = 0
    +        self.anyascii = 0
             self.unicase = 0
             self.total = 0
    def box_iou(boxes_1: np.ndarray, boxes_2: np.ndarray) -> np.ndarray: - """Compute the IoU between two sets of bounding boxes + """Computes the IoU between two sets of bounding boxes Args: + ---- boxes_1: bounding boxes of shape (N, 4) in format (xmin, ymin, xmax, ymax) boxes_2: bounding boxes of shape (M, 4) in format (xmin, ymin, xmax, ymax) + Returns: + ------- the IoU matrix of shape (N, M) """ - - iou_mat = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32) + iou_mat: np.ndarray = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32) if boxes_1.shape[0] > 0 and boxes_2.shape[0] > 0: l1, t1, r1, b1 = np.split(boxes_1, 4, axis=1) @@ -419,107 +456,54 @@

    Source code for doctr.utils.metrics

             right = np.minimum(r1, r2.T)
             bot = np.minimum(b1, b2.T)
     
    -        intersection = np.clip(right - left, 0, np.Inf) * np.clip(bot - top, 0, np.Inf)
    +        intersection = np.clip(right - left, 0, np.inf) * np.clip(bot - top, 0, np.inf)
             union = (r1 - l1) * (b1 - t1) + ((r2 - l2) * (b2 - t2)).T - intersection
             iou_mat = intersection / union
     
         return iou_mat
     
     
    -def box_ioa(boxes_1: np.ndarray, boxes_2: np.ndarray) -> np.ndarray:
    -    """Compute the IoA (intersection over area) between two sets of bounding boxes:
    -    ioa(i, j) = inter(i, j) / area(i)
    -
    -    Args:
    -        boxes_1: bounding boxes of shape (N, 4) in format (xmin, ymin, xmax, ymax)
    -        boxes_2: bounding boxes of shape (M, 4) in format (xmin, ymin, xmax, ymax)
    -    Returns:
    -        the IoA matrix of shape (N, M)
    -    """
    -
    -    ioa_mat = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32)
    -
    -    if boxes_1.shape[0] > 0 and boxes_2.shape[0] > 0:
    -        l1, t1, r1, b1 = np.split(boxes_1, 4, axis=1)
    -        l2, t2, r2, b2 = np.split(boxes_2, 4, axis=1)
    -
    -        left = np.maximum(l1, l2.T)
    -        top = np.maximum(t1, t2.T)
    -        right = np.minimum(r1, r2.T)
    -        bot = np.minimum(b1, b2.T)
    -
    -        intersection = np.clip(right - left, 0, np.Inf) * np.clip(bot - top, 0, np.Inf)
    -        area = (r1 - l1) * (b1 - t1)
    -        ioa_mat = intersection / area
    -
    -    return ioa_mat
    -
    -
    -def mask_iou(masks_1: np.ndarray, masks_2: np.ndarray) -> np.ndarray:
    -    """Compute the IoU between two sets of boolean masks
    +def polygon_iou(polys_1: np.ndarray, polys_2: np.ndarray) -> np.ndarray:
    +    """Computes the IoU between two sets of rotated bounding boxes
     
         Args:
    -        masks_1: boolean masks of shape (N, H, W)
    -        masks_2: boolean masks of shape (M, H, W)
    +    ----
    +        polys_1: rotated bounding boxes of shape (N, 4, 2)
    +        polys_2: rotated bounding boxes of shape (M, 4, 2)
    +        mask_shape: spatial shape of the intermediate masks
    +        use_broadcasting: if set to True, leverage broadcasting speedup by consuming more memory
     
         Returns:
    +    -------
             the IoU matrix of shape (N, M)
         """
    +    if polys_1.ndim != 3 or polys_2.ndim != 3:
    +        raise AssertionError("expects boxes to be in format (N, 4, 2)")
     
    -    if masks_1.shape[1:] != masks_2.shape[1:]:
    -        raise AssertionError("both boolean masks should have the same spatial shape")
    +    iou_mat = np.zeros((polys_1.shape[0], polys_2.shape[0]), dtype=np.float32)
     
    -    iou_mat = np.zeros((masks_1.shape[0], masks_2.shape[0]), dtype=np.float32)
    +    shapely_polys_1 = [Polygon(poly) for poly in polys_1]
    +    shapely_polys_2 = [Polygon(poly) for poly in polys_2]
     
    -    if masks_1.shape[0] > 0 and masks_2.shape[0] > 0:
    -        intersection = np.logical_and(masks_1[:, None, ...], masks_2[None, ...])
    -        union = np.logical_or(masks_1[:, None, ...], masks_2[None, ...])
    -        axes = tuple(range(2, masks_1.ndim + 1))
    -        iou_mat = intersection.sum(axis=axes) / union.sum(axis=axes)
    +    for i, poly1 in enumerate(shapely_polys_1):
    +        for j, poly2 in enumerate(shapely_polys_2):
    +            intersection_area = poly1.intersection(poly2).area
    +            union_area = poly1.area + poly2.area - intersection_area
    +            iou_mat[i, j] = intersection_area / union_area
     
         return iou_mat
     
     
    -def rbox_to_mask(boxes: np.ndarray, shape: Tuple[int, int]) -> np.ndarray:
    -    """Convert boxes to masks
    -
    -    Args:
    -        boxes: rotated bounding boxes of shape (N, 5) in format (x, y, w, h, alpha)
    -        shape: spatial shapes of the output masks
    -
    -    Returns:
    -        the boolean masks of shape (N, H, W)
    -    """
    -
    -    masks = np.zeros((boxes.shape[0], *shape), dtype=np.uint8)
    -
    -    if boxes.shape[0] > 0:
    -        # Get absolute coordinates
    -        if boxes.dtype != np.int:
    -            abs_boxes = boxes.copy()
    -            abs_boxes[:, [0, 2]] = abs_boxes[:, [0, 2]] * shape[1]
    -            abs_boxes[:, [1, 3]] = abs_boxes[:, [1, 3]] * shape[0]
    -            abs_boxes = abs_boxes.round().astype(np.int)
    -        else:
    -            abs_boxes = boxes
    -            abs_boxes[:, 2:] = abs_boxes[:, 2:] + 1
    -
    -        # TODO: optimize slicing to improve vectorization
    -        for idx, _box in enumerate(abs_boxes):
    -            box = rbbox_to_polygon(_box)
    -            cv2.fillPoly(masks[idx], [np.array(box, np.int32)], 1)
    -
    -    return masks.astype(bool)
    -
    -
    -def nms(boxes: np.ndarray, thresh: float = .5) -> List[int]:
    +def nms(boxes: np.ndarray, thresh: float = 0.5) -> List[int]:
         """Perform non-max suppression, borrowed from <https://github.com/rbgirshick/fast-rcnn>`_.
     
         Args:
    +    ----
             boxes: np array of straight boxes: (*, 5), (xmin, ymin, xmax, ymax, score)
             thresh: iou threshold to perform box suppression.
     
         Returns:
    +    -------
             A list of box indexes to keep
         """
         x1 = boxes[:, 0]
    @@ -551,66 +535,71 @@ 

    Source code for doctr.utils.metrics

     
     
     
    -[docs] +[docs] class LocalizationConfusion: - """Implements common confusion metrics and mean IoU for localization evaluation. + r"""Implements common confusion metrics and mean IoU for localization evaluation. The aggregated metrics are computed as follows: .. math:: - \\forall Y \\in \\mathcal{B}^N, \\forall X \\in \\mathcal{B}^M, \\\\ - Recall(X, Y) = \\frac{1}{N} \\sum\\limits_{i=1}^N g_{X}(Y_i) \\\\ - Precision(X, Y) = \\frac{1}{M} \\sum\\limits_{i=1}^N g_{X}(Y_i) \\\\ - meanIoU(X, Y) = \\frac{1}{M} \\sum\\limits_{i=1}^M \\max\\limits_{j \\in [1, N]} IoU(X_i, Y_j) + \forall Y \in \mathcal{B}^N, \forall X \in \mathcal{B}^M, \\ + Recall(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N g_{X}(Y_i) \\ + Precision(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M g_{X}(Y_i) \\ + meanIoU(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(X_i, Y_j) with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and :math:`y`, and the function :math:`g_{X}` defined as: .. math:: - \\forall y \\in \\mathcal{B}, - g_X(y) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } y\\mbox{ has been assigned to any }(X_i)_i\\mbox{ with an }IoU \\geq 0.5 \\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{B}` is the set of possible bounding boxes, + \forall y \in \mathcal{B}, + g_X(y) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } y\mbox{ has been assigned to any }(X_i)_i\mbox{ with an }IoU \geq 0.5 \\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. - Example:: - >>> import numpy as np - >>> from doctr.utils import LocalizationConfusion - >>> metric = LocalizationConfusion(iou_thresh=0.5) - >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]])) - >>> metric.summary() + >>> import numpy as np + >>> from doctr.utils import LocalizationConfusion + >>> metric = LocalizationConfusion(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]])) + >>> metric.summary() Args: + ---- iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format """ def __init__( self, iou_thresh: float = 0.5, - rotated_bbox: bool = False, - mask_shape: Tuple[int, int] = (1024, 1024), + use_polygons: bool = False, ) -> None: self.iou_thresh = iou_thresh - self.rotated_bbox = rotated_bbox - self.mask_shape = mask_shape + self.use_polygons = use_polygons self.reset() +
    +[docs] def update(self, gts: np.ndarray, preds: np.ndarray) -> None: + """Updates the metric + Args: + ---- + gts: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones + preds: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones + """ if preds.shape[0] > 0: # Compute IoU - if self.rotated_bbox: - mask_gts = rbox_to_mask(gts, shape=self.mask_shape) - mask_preds = rbox_to_mask(preds, shape=self.mask_shape) - iou_mat = mask_iou(mask_gts, mask_preds) + if self.use_polygons: + iou_mat = polygon_iou(gts, preds) else: iou_mat = box_iou(gts, preds) - self.tot_iou += float(iou_mat.max(axis=1).sum()) + self.tot_iou += float(iou_mat.max(axis=0).sum()) # Assign pairs gt_indices, pred_indices = linear_sum_assignment(-iou_mat) @@ -618,17 +607,18 @@

    Source code for doctr.utils.metrics

     
             # Update counts
             self.num_gts += gts.shape[0]
    -        self.num_preds += preds.shape[0]
    +        self.num_preds += preds.shape[0]
    +
    -[docs] +[docs] def summary(self) -> Tuple[Optional[float], Optional[float], Optional[float]]: """Computes the aggregated metrics - Returns: + Returns + ------- a tuple with the recall, precision and meanIoU scores """ - # Recall recall = self.matches / self.num_gts if self.num_gts > 0 else None @@ -636,7 +626,7 @@

    Source code for doctr.utils.metrics

             precision = self.matches / self.num_preds if self.num_preds > 0 else None
     
             # mean IoU
    -        mean_iou = self.tot_iou / self.num_preds if self.num_preds > 0 else None
    +        mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None
     
             return recall, precision, mean_iou
    @@ -645,64 +635,65 @@

    Source code for doctr.utils.metrics

             self.num_gts = 0
             self.num_preds = 0
             self.matches = 0
    -        self.tot_iou = 0.
    + self.tot_iou = 0.0
    -[docs] +[docs] class OCRMetric: - """Implements end-to-end OCR metric. + r"""Implements an end-to-end OCR metric. The aggregated metrics are computed as follows: .. math:: - \\forall (B, L) \\in \\mathcal{B}^N \\times \\mathcal{L}^N, - \\forall (\\hat{B}, \\hat{L}) \\in \\mathcal{B}^M \\times \\mathcal{L}^M, \\\\ - Recall(B, \\hat{B}, L, \\hat{L}) = \\frac{1}{N} \\sum\\limits_{i=1}^N h_{B,L}(\\hat{B}_i, \\hat{L}_i) \\\\ - Precision(B, \\hat{B}, L, \\hat{L}) = \\frac{1}{M} \\sum\\limits_{i=1}^N h_{B,L}(\\hat{B}_i, \\hat{L}_i) \\\\ - meanIoU(B, \\hat{B}) = \\frac{1}{M} \\sum\\limits_{i=1}^M \\max\\limits_{j \\in [1, N]} IoU(\\hat{B}_i, B_j) + \forall (B, L) \in \mathcal{B}^N \times \mathcal{L}^N, + \forall (\hat{B}, \hat{L}) \in \mathcal{B}^M \times \mathcal{L}^M, \\ + Recall(B, \hat{B}, L, \hat{L}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ + Precision(B, \hat{B}, L, \hat{L}) = \frac{1}{M} \sum\limits_{i=1}^M h_{B,L}(\hat{B}_i, \hat{L}_i) \\ + meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j) with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and :math:`y`, and the function :math:`h_{B, L}` defined as: .. math:: - \\forall (b, l) \\in \\mathcal{B} \\times \\mathcal{L}, - h_{B,L}(b, l) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } b\\mbox{ has been assigned to a given }B_j\\mbox{ with an } \\\\ - & IoU \\geq 0.5 \\mbox{ and that for this assignment, } l = L_j\\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{B}` is the set of possible bounding boxes, - :math:`\\mathcal{L}` is the set of possible character sequences, + \forall (b, l) \in \mathcal{B} \times \mathcal{L}, + h_{B,L}(b, l) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ + & IoU \geq 0.5 \mbox{ and that for this assignment, } l = L_j\\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, + :math:`\mathcal{L}` is the set of possible character sequences, :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. - Example:: - >>> import numpy as np - >>> from doctr.utils import OCRMetric - >>> metric = OCRMetric(iou_thresh=0.5) - >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), - ['hello'], ['hello', 'world']) - >>> metric.summary() + >>> import numpy as np + >>> from doctr.utils import OCRMetric + >>> metric = OCRMetric(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), + >>> ['hello'], ['hello', 'world']) + >>> metric.summary() Args: + ---- iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format """ def __init__( self, iou_thresh: float = 0.5, - rotated_bbox: bool = False, - mask_shape: Tuple[int, int] = (1024, 1024), + use_polygons: bool = False, ) -> None: self.iou_thresh = iou_thresh - self.rotated_bbox = rotated_bbox - self.mask_shape = mask_shape + self.use_polygons = use_polygons self.reset() +
    +[docs] def update( self, gt_boxes: np.ndarray, @@ -710,50 +701,58 @@

    Source code for doctr.utils.metrics

             gt_labels: List[str],
             pred_labels: List[str],
         ) -> None:
    +        """Updates the metric
     
    +        Args:
    +        ----
    +            gt_boxes: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones
    +            pred_boxes: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones
    +            gt_labels: a list of N string labels
    +            pred_labels: a list of M string labels
    +        """
             if gt_boxes.shape[0] != len(gt_labels) or pred_boxes.shape[0] != len(pred_labels):
    -            raise AssertionError("there should be the same number of boxes and string both for the ground truth "
    -                                 "and the predictions")
    +            raise AssertionError(
    +                "there should be the same number of boxes and string both for the ground truth and the predictions"
    +            )
     
             # Compute IoU
             if pred_boxes.shape[0] > 0:
    -            if self.rotated_bbox:
    -                mask_gts = rbox_to_mask(gt_boxes, shape=self.mask_shape)
    -                mask_preds = rbox_to_mask(pred_boxes, shape=self.mask_shape)
    -                iou_mat = mask_iou(mask_gts, mask_preds)
    +            if self.use_polygons:
    +                iou_mat = polygon_iou(gt_boxes, pred_boxes)
                 else:
                     iou_mat = box_iou(gt_boxes, pred_boxes)
     
    -            self.tot_iou += float(iou_mat.max(axis=1).sum())
    +            self.tot_iou += float(iou_mat.max(axis=0).sum())
     
                 # Assign pairs
                 gt_indices, pred_indices = linear_sum_assignment(-iou_mat)
                 is_kept = iou_mat[gt_indices, pred_indices] >= self.iou_thresh
                 # String comparison
                 for gt_idx, pred_idx in zip(gt_indices[is_kept], pred_indices[is_kept]):
    -                _raw, _caseless, _unidecode, _unicase = string_match(gt_labels[gt_idx], pred_labels[pred_idx])
    +                _raw, _caseless, _anyascii, _unicase = string_match(gt_labels[gt_idx], pred_labels[pred_idx])
                     self.raw_matches += int(_raw)
                     self.caseless_matches += int(_caseless)
    -                self.unidecode_matches += int(_unidecode)
    +                self.anyascii_matches += int(_anyascii)
                     self.unicase_matches += int(_unicase)
     
             self.num_gts += gt_boxes.shape[0]
    -        self.num_preds += pred_boxes.shape[0]
    +        self.num_preds += pred_boxes.shape[0]
    +
    -[docs] +[docs] def summary(self) -> Tuple[Dict[str, Optional[float]], Dict[str, Optional[float]], Optional[float]]: """Computes the aggregated metrics - Returns: - a tuple with the recall & precision for each string comparison flexibility and the mean IoU + Returns + ------- + a tuple with the recall & precision for each string comparison and the mean IoU """ - # Recall recall = dict( raw=self.raw_matches / self.num_gts if self.num_gts > 0 else None, caseless=self.caseless_matches / self.num_gts if self.num_gts > 0 else None, - unidecode=self.unidecode_matches / self.num_gts if self.num_gts > 0 else None, + anyascii=self.anyascii_matches / self.num_gts if self.num_gts > 0 else None, unicase=self.unicase_matches / self.num_gts if self.num_gts > 0 else None, ) @@ -761,12 +760,12 @@

    Source code for doctr.utils.metrics

             precision = dict(
                 raw=self.raw_matches / self.num_preds if self.num_preds > 0 else None,
                 caseless=self.caseless_matches / self.num_preds if self.num_preds > 0 else None,
    -            unidecode=self.unidecode_matches / self.num_preds if self.num_preds > 0 else None,
    +            anyascii=self.anyascii_matches / self.num_preds if self.num_preds > 0 else None,
                 unicase=self.unicase_matches / self.num_preds if self.num_preds > 0 else None,
             )
     
             # mean IoU (overall detected boxes)
    -        mean_iou = self.tot_iou / self.num_preds if self.num_preds > 0 else None
    +        mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None
     
             return recall, precision, mean_iou
    @@ -774,12 +773,136 @@

    Source code for doctr.utils.metrics

         def reset(self) -> None:
             self.num_gts = 0
             self.num_preds = 0
    -        self.tot_iou = 0.
    +        self.tot_iou = 0.0
             self.raw_matches = 0
             self.caseless_matches = 0
    -        self.unidecode_matches = 0
    +        self.anyascii_matches = 0
             self.unicase_matches = 0
    + + +
    +[docs] +class DetectionMetric: + r"""Implements an object detection metric. + + The aggregated metrics are computed as follows: + + .. math:: + \forall (B, C) \in \mathcal{B}^N \times \mathcal{C}^N, + \forall (\hat{B}, \hat{C}) \in \mathcal{B}^M \times \mathcal{C}^M, \\ + Recall(B, \hat{B}, C, \hat{C}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,C}(\hat{B}_i, \hat{C}_i) \\ + Precision(B, \hat{B}, C, \hat{C}) = \frac{1}{M} \sum\limits_{i=1}^M h_{B,C}(\hat{B}_i, \hat{C}_i) \\ + meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j) + + with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and + :math:`y`, and the function :math:`h_{B, C}` defined as: + + .. math:: + \forall (b, c) \in \mathcal{B} \times \mathcal{C}, + h_{B,C}(b, c) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ + & IoU \geq 0.5 \mbox{ and that for this assignment, } c = C_j\\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, + :math:`\mathcal{C}` is the set of possible class indices, + :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. + + >>> import numpy as np + >>> from doctr.utils import DetectionMetric + >>> metric = DetectionMetric(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), + >>> np.zeros(1, dtype=np.int64), np.array([0, 1], dtype=np.int64)) + >>> metric.summary() + + Args: + ---- + iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format + """ + + def __init__( + self, + iou_thresh: float = 0.5, + use_polygons: bool = False, + ) -> None: + self.iou_thresh = iou_thresh + self.use_polygons = use_polygons + self.reset() + +
    +[docs] + def update( + self, + gt_boxes: np.ndarray, + pred_boxes: np.ndarray, + gt_labels: np.ndarray, + pred_labels: np.ndarray, + ) -> None: + """Updates the metric + + Args: + ---- + gt_boxes: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones + pred_boxes: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones + gt_labels: an array of class indices of shape (N,) + pred_labels: an array of class indices of shape (M,) + """ + if gt_boxes.shape[0] != gt_labels.shape[0] or pred_boxes.shape[0] != pred_labels.shape[0]: + raise AssertionError( + "there should be the same number of boxes and string both for the ground truth and the predictions" + ) + + # Compute IoU + if pred_boxes.shape[0] > 0: + if self.use_polygons: + iou_mat = polygon_iou(gt_boxes, pred_boxes) + else: + iou_mat = box_iou(gt_boxes, pred_boxes) + + self.tot_iou += float(iou_mat.max(axis=0).sum()) + + # Assign pairs + gt_indices, pred_indices = linear_sum_assignment(-iou_mat) + is_kept = iou_mat[gt_indices, pred_indices] >= self.iou_thresh + # Category comparison + self.num_matches += int((gt_labels[gt_indices[is_kept]] == pred_labels[pred_indices[is_kept]]).sum()) + + self.num_gts += gt_boxes.shape[0] + self.num_preds += pred_boxes.shape[0]
    + + +
    +[docs] + def summary(self) -> Tuple[Optional[float], Optional[float], Optional[float]]: + """Computes the aggregated metrics + + Returns + ------- + a tuple with the recall & precision for each class prediction and the mean IoU + """ + # Recall + recall = self.num_matches / self.num_gts if self.num_gts > 0 else None + + # Precision + precision = self.num_matches / self.num_preds if self.num_preds > 0 else None + + # mean IoU (overall detected boxes) + mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None + + return recall, precision, mean_iou
    + + + def reset(self) -> None: + self.num_gts = 0 + self.num_preds = 0 + self.tot_iou = 0.0 + self.num_matches = 0
    +
    @@ -812,8 +935,8 @@

    Source code for doctr.utils.metrics

           
         
       
    - - + + diff --git a/v0.5.1/_modules/doctr/utils/visualization.html b/v0.5.1/_modules/doctr/utils/visualization.html index 8e7dcca811..c818be6d7b 100644 --- a/v0.5.1/_modules/doctr/utils/visualization.html +++ b/v0.5.1/_modules/doctr/utils/visualization.html @@ -13,7 +13,7 @@ - + doctr.utils.visualization - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.utils.visualization

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
    +import colorsys
    +from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple, Union
     
    -import matplotlib.pyplot as plt
    -from matplotlib.figure import Figure
    +import cv2
     import matplotlib.patches as patches
    -import mplcursors
    -from PIL import ImageFont, ImageDraw, Image
    +import matplotlib.pyplot as plt
     import numpy as np
    -import cv2
    -from typing import Tuple, List, Dict, Any, Union
    +from matplotlib.figure import Figure
     
    -from .common_types import BoundingBox, RotatedBbox
    +from .common_types import BoundingBox, Polygon4P
     
    -__all__ = ['visualize_page', 'synthetize_page']
    +__all__ = ["visualize_page", "visualize_kie_page", "draw_boxes"]
     
     
    -def create_rect_patch(
    -    geometry: Union[BoundingBox, RotatedBbox],
    -    label: str,
    +def rect_patch(
    +    geometry: BoundingBox,
         page_dimensions: Tuple[int, int],
    -    color: Tuple[int, int, int],
    +    label: Optional[str] = None,
    +    color: Tuple[float, float, float] = (0, 0, 0),
         alpha: float = 0.3,
         linewidth: int = 2,
         fill: bool = True,
    -) -> patches.Patch:
    -    """Create a matplotlib patch (rectangle) bounding the element
    +    preserve_aspect_ratio: bool = False,
    +) -> patches.Rectangle:
    +    """Create a matplotlib rectangular patch for the element
     
         Args:
    +    ----
             geometry: bounding box of the element
    +        page_dimensions: dimensions of the Page in format (height, width)
             label: label to display when hovered
    -        page_dimensions: dimensions of the Page
             color: color to draw box
             alpha: opacity parameter to fill the boxes, 0 = transparent
             linewidth: line width
    +        fill: whether the patch should be filled
    +        preserve_aspect_ratio: pass True if you passed True to the predictor
     
         Returns:
    +    -------
             a rectangular Patch
         """
    +    if len(geometry) != 2 or any(not isinstance(elt, tuple) or len(elt) != 2 for elt in geometry):
    +        raise ValueError("invalid geometry format")
    +
    +    # Unpack
         height, width = page_dimensions
    -    if len(geometry) == 5:
    -        x, y, w, h, a = geometry  # type: ignore[misc]
    -        x, w = x * width, w * width
    -        y, h = y * height, h * height
    -        points = cv2.boxPoints(((x, y), (w, h), a))
    -        return patches.Polygon(
    -            points,
    -            fill=fill,
    -            linewidth=linewidth,
    -            edgecolor=(*color, alpha),
    -            facecolor=(*color, alpha),
    -            label=label
    -        )
    -    else:
    -        (xmin, ymin), (xmax, ymax) = geometry  # type: ignore[misc]
    -        xmin, xmax = xmin * width, xmax * width
    -        ymin, ymax = ymin * height, ymax * height
    -        return patches.Rectangle(
    -            (xmin, ymin),
    -            xmax - xmin,
    -            ymax - ymin,
    -            fill=fill,
    -            linewidth=linewidth,
    -            edgecolor=(*color, alpha),
    -            facecolor=(*color, alpha),
    -            label=label
    -        )
    +    (xmin, ymin), (xmax, ymax) = geometry
    +    # Switch to absolute coords
    +    if preserve_aspect_ratio:
    +        width = height = max(height, width)
    +    xmin, w = xmin * width, (xmax - xmin) * width
    +    ymin, h = ymin * height, (ymax - ymin) * height
    +
    +    return patches.Rectangle(
    +        (xmin, ymin),
    +        w,
    +        h,
    +        fill=fill,
    +        linewidth=linewidth,
    +        edgecolor=(*color, alpha),
    +        facecolor=(*color, alpha),
    +        label=label,
    +    )
    +
    +
    +def polygon_patch(
    +    geometry: np.ndarray,
    +    page_dimensions: Tuple[int, int],
    +    label: Optional[str] = None,
    +    color: Tuple[float, float, float] = (0, 0, 0),
    +    alpha: float = 0.3,
    +    linewidth: int = 2,
    +    fill: bool = True,
    +    preserve_aspect_ratio: bool = False,
    +) -> patches.Polygon:
    +    """Create a matplotlib polygon patch for the element
    +
    +    Args:
    +    ----
    +        geometry: bounding box of the element
    +        page_dimensions: dimensions of the Page in format (height, width)
    +        label: label to display when hovered
    +        color: color to draw box
    +        alpha: opacity parameter to fill the boxes, 0 = transparent
    +        linewidth: line width
    +        fill: whether the patch should be filled
    +        preserve_aspect_ratio: pass True if you passed True to the predictor
    +
    +    Returns:
    +    -------
    +        a polygon Patch
    +    """
    +    if not geometry.shape == (4, 2):
    +        raise ValueError("invalid geometry format")
    +
    +    # Unpack
    +    height, width = page_dimensions
    +    geometry[:, 0] = geometry[:, 0] * (max(width, height) if preserve_aspect_ratio else width)
    +    geometry[:, 1] = geometry[:, 1] * (max(width, height) if preserve_aspect_ratio else height)
    +
    +    return patches.Polygon(
    +        geometry,
    +        fill=fill,
    +        linewidth=linewidth,
    +        edgecolor=(*color, alpha),
    +        facecolor=(*color, alpha),
    +        label=label,
    +    )
    +
    +
    +def create_obj_patch(
    +    geometry: Union[BoundingBox, Polygon4P, np.ndarray],
    +    page_dimensions: Tuple[int, int],
    +    **kwargs: Any,
    +) -> patches.Patch:
    +    """Create a matplotlib patch for the element
    +
    +    Args:
    +    ----
    +        geometry: bounding box (straight or rotated) of the element
    +        page_dimensions: dimensions of the page in format (height, width)
    +        **kwargs: keyword arguments for the patch
    +
    +    Returns:
    +    -------
    +        a matplotlib Patch
    +    """
    +    if isinstance(geometry, tuple):
    +        if len(geometry) == 2:  # straight word BB (2 pts)
    +            return rect_patch(geometry, page_dimensions, **kwargs)
    +        elif len(geometry) == 4:  # rotated word BB (4 pts)
    +            return polygon_patch(np.asarray(geometry), page_dimensions, **kwargs)
    +    elif isinstance(geometry, np.ndarray) and geometry.shape == (4, 2):  # rotated line
    +        return polygon_patch(geometry, page_dimensions, **kwargs)
    +    raise ValueError("invalid geometry format")
    +
    +
    +def get_colors(num_colors: int) -> List[Tuple[float, float, float]]:
    +    """Generate num_colors color for matplotlib
    +
    +    Args:
    +    ----
    +        num_colors: number of colors to generate
    +
    +    Returns:
    +    -------
    +        colors: list of generated colors
    +    """
    +    colors = []
    +    for i in np.arange(0.0, 360.0, 360.0 / num_colors):
    +        hue = i / 360.0
    +        lightness = (50 + np.random.rand() * 10) / 100.0
    +        saturation = (90 + np.random.rand() * 10) / 100.0
    +        colors.append(colorsys.hls_to_rgb(hue, lightness, saturation))
    +    return colors
     
     
     
    -[docs] +[docs] def visualize_page( page: Dict[str, Any], image: np.ndarray, @@ -359,18 +472,18 @@

    Source code for doctr.utils.visualization

     ) -> Figure:
         """Visualize a full page with predicted blocks, lines and words
     
    -    Example::
    -        >>> import numpy as np
    -        >>> import matplotlib.pyplot as plt
    -        >>> from doctr.utils.visualization import visualize_page
    -        >>> from doctr.models import ocr_db_crnn
    -        >>> model = ocr_db_crnn(pretrained=True)
    -        >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    -        >>> out = model([[input_page]])
    -        >>> visualize_page(out[0].pages[0].export(), input_page)
    -        >>> plt.show()
    +    >>> import numpy as np
    +    >>> import matplotlib.pyplot as plt
    +    >>> from doctr.utils.visualization import visualize_page
    +    >>> from doctr.models import ocr_db_crnn
    +    >>> model = ocr_db_crnn(pretrained=True)
    +    >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    +    >>> out = model([[input_page]])
    +    >>> visualize_page(out[0].pages[0].export(), input_page)
    +    >>> plt.show()
     
         Args:
    +    ----
             page: the exported Page of a Document
             image: np array of the page, needs to have the same shape than page['dimensions']
             words_only: whether only words should be displayed
    @@ -378,6 +491,11 @@ 

    Source code for doctr.utils.visualization

             scale: figsize of the largest windows side
             interactive: whether the plot should be interactive
             add_labels: for static plot, adds text labels on top of bounding box
    +        **kwargs: keyword arguments for the polygon patch
    +
    +    Returns:
    +    -------
    +        the matplotlib figure
         """
         # Get proper scale and aspect ratio
         h, w = image.shape[:2]
    @@ -386,128 +504,189 @@ 

    Source code for doctr.utils.visualization

         # Display the image
         ax.imshow(image)
         # hide both axis
    -    ax.axis('off')
    +    ax.axis("off")
     
         if interactive:
             artists: List[patches.Patch] = []  # instantiate an empty list of patches (to be drawn on the page)
     
    -    for block in page['blocks']:
    +    for block in page["blocks"]:
             if not words_only:
    -            rect = create_rect_patch(block['geometry'], 'block', page['dimensions'], (0, 1, 0), linewidth=1, **kwargs)
    +            rect = create_obj_patch(
    +                block["geometry"], page["dimensions"], label="block", color=(0, 1, 0), linewidth=1, **kwargs
    +            )
                 # add patch on figure
                 ax.add_patch(rect)
                 if interactive:
                     # add patch to cursor's artists
                     artists.append(rect)
     
    -        for line in block['lines']:
    +        for line in block["lines"]:
                 if not words_only:
    -                rect = create_rect_patch(line['geometry'], 'line', page['dimensions'], (1, 0, 0), linewidth=1, **kwargs)
    +                rect = create_obj_patch(
    +                    line["geometry"], page["dimensions"], label="line", color=(1, 0, 0), linewidth=1, **kwargs
    +                )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
     
    -            for word in line['words']:
    -                rect = create_rect_patch(word['geometry'], f"{word['value']} (confidence: {word['confidence']:.2%})",
    -                                         page['dimensions'], (0, 0, 1), **kwargs)
    +            for word in line["words"]:
    +                rect = create_obj_patch(
    +                    word["geometry"],
    +                    page["dimensions"],
    +                    label=f"{word['value']} (confidence: {word['confidence']:.2%})",
    +                    color=(0, 0, 1),
    +                    **kwargs,
    +                )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
                     elif add_labels:
    -                    if len(word['geometry']) == 5:
    +                    if len(word["geometry"]) == 5:
                             text_loc = (
    -                            int(page['dimensions'][1] * (word['geometry'][0] - word['geometry'][2] / 2)),
    -                            int(page['dimensions'][0] * (word['geometry'][1] - word['geometry'][3] / 2))
    +                            int(page["dimensions"][1] * (word["geometry"][0] - word["geometry"][2] / 2)),
    +                            int(page["dimensions"][0] * (word["geometry"][1] - word["geometry"][3] / 2)),
                             )
                         else:
                             text_loc = (
    -                            int(page['dimensions'][1] * word['geometry'][0][0]),
    -                            int(page['dimensions'][0] * word['geometry'][0][1])
    +                            int(page["dimensions"][1] * word["geometry"][0][0]),
    +                            int(page["dimensions"][0] * word["geometry"][0][1]),
    +                        )
    +
    +                    if len(word["geometry"]) == 2:
    +                        # We draw only if boxes are in straight format
    +                        ax.text(
    +                            *text_loc,
    +                            word["value"],
    +                            size=10,
    +                            alpha=0.5,
    +                            color=(0, 0, 1),
                             )
    -                    ax.text(
    -                        *text_loc,
    -                        word['value'],
    -                        size=10,
    -                        alpha=0.5,
    -                        color=(0, 0, 1),
    -                    )
     
             if display_artefacts:
    -            for artefact in block['artefacts']:
    -                rect = create_rect_patch(
    -                    artefact['geometry'],
    -                    'artefact',
    -                    page['dimensions'],
    -                    (0.5, 0.5, 0.5),  # type: ignore[arg-type]
    +            for artefact in block["artefacts"]:
    +                rect = create_obj_patch(
    +                    artefact["geometry"],
    +                    page["dimensions"],
    +                    label="artefact",
    +                    color=(0.5, 0.5, 0.5),
                         linewidth=1,
    -                    **kwargs
    +                    **kwargs,
                     )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
     
         if interactive:
    +        import mplcursors
    +
             # Create mlp Cursor to hover patches in artists
             mplcursors.Cursor(artists, hover=2).connect("add", lambda sel: sel.annotation.set_text(sel.artist.get_label()))
    -    fig.tight_layout(pad=0.)
    +    fig.tight_layout(pad=0.0)
     
         return fig
    -def synthetize_page( +def visualize_kie_page( page: Dict[str, Any], - draw_proba: bool = False, - font_size: int = 13, -) -> np.ndarray: - """Draw a the content of the element page (OCR response) on a blank page. + image: np.ndarray, + words_only: bool = False, + display_artefacts: bool = True, + scale: float = 10, + interactive: bool = True, + add_labels: bool = True, + **kwargs: Any, +) -> Figure: + """Visualize a full page with predicted blocks, lines and words + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from doctr.utils.visualization import visualize_page + >>> from doctr.models import ocr_db_crnn + >>> model = ocr_db_crnn(pretrained=True) + >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) + >>> out = model([[input_page]]) + >>> visualize_kie_page(out[0].pages[0].export(), input_page) + >>> plt.show() Args: - page: exported Page object to represent - draw_proba: if True, draw words in colors to represent confidence. Blue: p=1, red: p=0 - font_size: size of the font, default font = 13 + ---- + page: the exported Page of a Document + image: np array of the page, needs to have the same shape than page['dimensions'] + words_only: whether only words should be displayed + display_artefacts: whether artefacts should be displayed + scale: figsize of the largest windows side + interactive: whether the plot should be interactive + add_labels: for static plot, adds text labels on top of bounding box + **kwargs: keyword arguments for the polygon patch - Return: - A np array (drawn page) + Returns: + ------- + the matplotlib figure """ - # Draw template - h, w = page["dimensions"] - response = 255 * np.ones((h, w, 3), dtype=np.int32) + # Get proper scale and aspect ratio + h, w = image.shape[:2] + size = (scale * w / h, scale) if h > w else (scale, h / w * scale) + fig, ax = plt.subplots(figsize=size) + # Display the image + ax.imshow(image) + # hide both axis + ax.axis("off") - # Draw each word - for block in page["blocks"]: - for line in block["lines"]: - for word in line["words"]: - # Get aboslute word geometry - (xmin, ymin), (xmax, ymax) = word["geometry"] - xmin, xmax = int(w * xmin), int(w * xmax) - ymin, ymax = int(h * ymin), int(h * ymax) - - # White drawing context adapted to font size, 0.75 factor to convert pts --> pix - h_box, w_box = ymax - ymin, xmax - xmin - h_font, w_font = font_size, int(font_size * w_box / (h_box * 0.75)) - img = Image.new('RGB', (w_font, h_font), color=(255, 255, 255)) - d = ImageDraw.Draw(img) - - # Draw in black the value of the word - d.text((0, 0), word["value"], font=ImageFont.load_default(), fill=(0, 0, 0)) - - # Resize back to box size - img = img.resize((w_box, h_box), Image.NEAREST) - - # Colorize if draw_proba - if draw_proba: - p = int(255 * word["confidence"]) - mask = np.where(np.array(img) == 0, 1, 0) - proba = np.array([255 - p, 0, p]) - color = mask * proba[np.newaxis, np.newaxis, :] - white_mask = 255 * (1 - mask) - img = color + white_mask - - # Write to response page - response[ymin:ymax, xmin:xmax, :] = np.array(img) - - return response + if interactive: + artists: List[patches.Patch] = [] # instantiate an empty list of patches (to be drawn on the page) + + colors = {k: color for color, k in zip(get_colors(len(page["predictions"])), page["predictions"])} + for key, value in page["predictions"].items(): + for prediction in value: + if not words_only: + rect = create_obj_patch( + prediction["geometry"], + page["dimensions"], + label=f"{key} \n {prediction['value']} (confidence: {prediction['confidence']:.2%}", + color=colors[key], + linewidth=1, + **kwargs, + ) + # add patch on figure + ax.add_patch(rect) + if interactive: + # add patch to cursor's artists + artists.append(rect) + + if interactive: + import mplcursors + + # Create mlp Cursor to hover patches in artists + mplcursors.Cursor(artists, hover=2).connect("add", lambda sel: sel.annotation.set_text(sel.artist.get_label())) + fig.tight_layout(pad=0.0) + + return fig + + +def draw_boxes(boxes: np.ndarray, image: np.ndarray, color: Optional[Tuple[int, int, int]] = None, **kwargs) -> None: + """Draw an array of relative straight boxes on an image + + Args: + ---- + boxes: array of relative boxes, of shape (*, 4) + image: np array, float32 or uint8 + color: color to use for bounding box edges + **kwargs: keyword arguments from `matplotlib.pyplot.plot` + """ + h, w = image.shape[:2] + # Convert boxes to absolute coords + _boxes = deepcopy(boxes) + _boxes[:, [0, 2]] *= w + _boxes[:, [1, 3]] *= h + _boxes = _boxes.astype(np.int32) + for box in _boxes.tolist(): + xmin, ymin, xmax, ymax = box + image = cv2.rectangle( + image, (xmin, ymin), (xmax, ymax), color=color if isinstance(color, tuple) else (0, 0, 255), thickness=2 + ) + plt.imshow(image) + plt.plot(**kwargs)
    @@ -540,8 +719,8 @@

    Source code for doctr.utils.visualization

           
         
       
    - - + + diff --git a/v0.5.1/_modules/index.html b/v0.5.1/_modules/index.html index e86abcd4d4..5793c44f20 100644 --- a/v0.5.1/_modules/index.html +++ b/v0.5.1/_modules/index.html @@ -13,7 +13,7 @@ - + Overview: module code - docTR documentation @@ -225,20 +225,42 @@ - - + + diff --git a/v0.5.1/_sources/changelog.rst.txt b/v0.5.1/_sources/changelog.rst.txt index 430097d6c8..35befe7b96 100644 --- a/v0.5.1/_sources/changelog.rst.txt +++ b/v0.5.1/_sources/changelog.rst.txt @@ -1,6 +1,54 @@ Changelog ========= +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 `_ diff --git a/v0.5.1/_sources/datasets.rst.txt b/v0.5.1/_sources/datasets.rst.txt deleted file mode 100644 index 354122f1e5..0000000000 --- a/v0.5.1/_sources/datasets.rst.txt +++ /dev/null @@ -1,68 +0,0 @@ -doctr.datasets -============== - -.. currentmodule:: doctr.datasets - -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. - - -.. _datasets: - -Available Datasets ------------------- -The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL. - -.. autoclass:: doctr.datasets.datasets.VisionDataset - - -Here are all datasets that are available through DocTR: - -.. autoclass:: FUNSD -.. autoclass:: SROIE -.. autoclass:: CORD -.. autoclass:: OCRDataset - - -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. - -.. autoclass:: doctr.datasets.loader.DataLoader - - -.. _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 - * - digits - - 10 - - 0123456789 - * - ascii_letters - - 52 - - abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ - * - punctuation - - 32 - - !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ - * - currency - - 5 - - £€¥¢฿ - * - latin - - 96 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~° - * - french - - 154 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿ - -.. autofunction:: encode_sequences diff --git a/v0.5.1/_sources/documents.rst.txt b/v0.5.1/_sources/documents.rst.txt deleted file mode 100644 index 655730073e..0000000000 --- a/v0.5.1/_sources/documents.rst.txt +++ /dev/null @@ -1,87 +0,0 @@ -doctr.documents -=============== - - -.. currentmodule:: doctr.documents - -The documents module enables users to easily access content from documents and export analysis -results to structured formats. - - -Document structure ------------------- - -Structural organization of the documents. - -Word -^^^^ -A Word is an uninterrupted sequence of characters. - -.. autoclass:: Word - -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 - -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 - - -Document -^^^^^^^^ - -A Document is a collection of Pages. - -.. autoclass:: Document - - .. automethod:: show - - -File reading ------------- - -High-performance file reading and conversion to processable structured data. - -.. autofunction:: read_pdf - -.. autofunction:: read_img - -.. autofunction:: read_html - - -.. autoclass:: DocumentFile - - .. automethod:: from_pdf - - .. automethod:: from_url - - .. automethod:: from_images - -.. autoclass:: PDF - - .. automethod:: as_images - - .. automethod:: get_words - - .. automethod:: get_artefacts diff --git a/v0.5.1/_sources/getting_started/installing.rst.txt b/v0.5.1/_sources/getting_started/installing.rst.txt index e764e734a7..39e79aa3dd 100644 --- a/v0.5.1/_sources/getting_started/installing.rst.txt +++ b/v0.5.1/_sources/getting_started/installing.rst.txt @@ -3,7 +3,7 @@ Installation ************ -This library requires `Python `_ 3.9 or higher. +This library requires `Python `_ 3.10 or higher. Prerequisites diff --git a/v0.5.1/_sources/index.rst.txt b/v0.5.1/_sources/index.rst.txt index fc3ff89fdf..53251db142 100644 --- a/v0.5.1/_sources/index.rst.txt +++ b/v0.5.1/_sources/index.rst.txt @@ -1,7 +1,8 @@ -DocTR: Document Text Recognition -================================ +******************************** +docTR: Document Text Recognition +******************************** -State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 (PyTorch now in beta) +State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 & PyTorch .. image:: https://github.com/mindee/doctr/releases/download/v0.2.0/ocr.png :align: center @@ -9,38 +10,29 @@ State-of-the-art Optical Character Recognition made seamless & accessible to any DocTR provides an easy and powerful way to extract valuable information from your documents: -* |:receipt:| **for automation**: seemlessly process documents for Natural Language Understanding tasks: we provide OCR predictors to parse textual information (localize and identify each word) 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. -Welcome to the documentation of `DocTR `_! - - Main Features ------------- * |:robot:| Robust 2-stage (detection + recognition) OCR predictors with pretrained parameters * |:zap:| User-friendly, 3 lines of code to load a document and extract text with a predictor -* |:rocket:| State-of-the-art performances on public document datasets, comparable with GoogleVision/AWS Textract +* |: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, small dependencies -* |:tools:| Daily maintained -* |:factory:| Easy integration - +* |:bird:| Light package, minimal dependencies +* |:tools:| Actively maintained by Mindee +* |:factory:| Easy integration (available templates for browser demo & API deployment) -Getting Started ---------------- .. toctree:: :maxdepth: 2 + :caption: Getting started + :hidden: - installing - - -Build & train your predictor -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* Compose your own end-to-end OCR predictor: mix and match detection & recognition predictors (all-pretrained) -* Fine-tune or train from scratch any detection or recognition model to specialize on your data + getting_started/installing + notebooks Model zoo @@ -48,36 +40,83 @@ Model zoo Text detection models """"""""""""""""""""" - * `DBNet `_ (Differentiable Binarization) - * `LinkNet `_ +* 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 `_ (Show, Attend and Read) - * `CRNN `_ (Convolutional Recurrent Neural Network) - * `MASTER `_ (Multi-Aspect Non-local Network for Scene Text Recognition) +* 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" `_ 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 `_. +* 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" `_. .. toctree:: :maxdepth: 2 - :caption: Notes + :caption: Using docTR + :hidden: - changelog + 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 + + +.. toctree:: + :maxdepth: 2 + :caption: Community + :hidden: + + community/resources .. toctree:: :maxdepth: 2 :caption: Package Reference + :hidden: - datasets - documents - models - transforms - utils + 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/v0.5.1/_sources/installing.rst.txt b/v0.5.1/_sources/installing.rst.txt deleted file mode 100644 index 5c8779dc1c..0000000000 --- a/v0.5.1/_sources/installing.rst.txt +++ /dev/null @@ -1,46 +0,0 @@ - -************ -Installation -************ - -This library requires Python 3.6 or higher. - - -Prerequisites -============= - -Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so: - -* TensorFlow: `installation page `_. -* PyTorch: `installation page `_. - -If you are running another OS than Linux, you will need a few extra dependencies. - -For MacOS users, you can install them as follows: - -.. code:: shell - - brew install cairo pango gdk-pixbuf libffi - -For Windows users, those dependencies are included in GTK. You can find the latest installer over `here `_. - - -Via Python Package -================== - -Install the last stable release of the package using pip: - -.. code:: bash - - pip install python-doctr - - -Via Git -======= - -Install the library in developper mode: - -.. code:: bash - - git clone https://github.com/mindee/doctr.git - pip install -e doctr/. diff --git a/v0.5.1/_sources/models.rst.txt b/v0.5.1/_sources/models.rst.txt deleted file mode 100644 index 9830c6c153..0000000000 --- a/v0.5.1/_sources/models.rst.txt +++ /dev/null @@ -1,215 +0,0 @@ -doctr.models -============ - -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. - -.. currentmodule:: doctr.models - -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 TensorFlow model. -* Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable. - - -Text Detection --------------- -Localizing text elements in images - -+---------------------------------------------------+----------------------------+----------------------------+---------+ -| | FUNSD | CORD | | -+==================+=================+==============+============+===============+============+===============+=========+ -| **Architecture** | **Input shape** | **# params** | **Recall** | **Precision** | **Recall** | **Precision** | **FPS** | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ -| db_resnet50 | (1024, 1024, 3) | 25.2 M | 82.14 | 87.64 | 92.49 | 89.66 | 2.1 | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ - -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 combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for detection -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for detection is the following: - -1. resize each input image to the target size (bilinear interpolation by default) with potential deformation. -2. batch images together -3. normalize the batch using the training data statistics - - -Detection models -^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - -.. autofunction:: doctr.models.detection.db_resnet50 -.. autofunction:: doctr.models.detection.linknet16 - -Detection predictors -^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information. - -.. autofunction:: doctr.models.detection.detection_predictor - - -Text Recognition ----------------- -Identifying strings in images - -.. list-table:: Text recognition model zoo - :widths: 20 20 15 10 10 10 - :header-rows: 1 - - * - Architecture - - Input shape - - # params - - FUNSD - - CORD - - FPS - * - crnn_vgg16_bn - - (32, 128, 3) - - 15.8M - - 86.02 - - 91.3 - - 12.8 - * - sar_vgg16_bn - - (32, 128, 3) - - 21.5M - - 86.2 - - 91.7 - - 3.3 - * - sar_resnet31 - - (32, 128, 3) - - 53.1M - - **86.3** - - **92.1** - - 2.7 - -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 metrics being used are available in :ref:`metrics`. - -All these recognition models are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for recognition -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for recognition is the following: - -1. resize each input image to the target size (bilinear interpolation by default) without deformation. -2. pad the image to the target size (with zeros by default) -3. batch images together -4. normalize the batch using the training data statistics - -Recognition models -^^^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - - -.. autofunction:: doctr.models.recognition.crnn_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_resnet31 -.. autofunction:: doctr.models.recognition.master - - -Recognition predictors -^^^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage. - -.. autofunction:: doctr.models.recognition.recognition_predictor - - -End-to-End OCR --------------- -Predictors that localize and identify text elements in images - -+-----------------------------+--------------------------------------+--------------------------------------+ -| | FUNSD | CORD | -+=============================+============+===============+=========+============+===============+=========+ -| **Architecture** | **Recall** | **Precision** | **FPS** | **Recall** | **Precision** | **FPS** | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + crnn_vgg16_bn | 70.08 | 74.77 | 0.85 | 82.19 | **79.67** | 1.6 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_vgg16_bn | N/A | N/A | 0.49 | N/A | N/A | 1.0 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_resnet31 | N/A | N/A | 0.27 | N/A | N/A | 0.83 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision text detection | 59.50 | 62.50 | | 75.30 | 70.00 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision doc. text detection | 64.00 | 53.30 | | 68.90 | 61.10 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| AWS textract | **78.10** | **83.00** | | **87.50** | 66.00 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ - -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`. - -All recognition models of predictors are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Results on private ocr datasets - -+------------------------------------+----------------------------+----------------------------+----------------------------+ -| | Receipts | Invoices | IDs | -+====================================+============+===============+============+===============+============+===============+ -| **Architecture** | **Recall** | **Precision** | **Recall** | **Precision** | **Recall** | **Precision** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| db_resnet50 + crnn_vgg16_bn (ours) | **78.90** | **81.01** | 65.68 | **69.86** | **49.48** | **50.46** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| Gvision doc. text detection | 68.91 | 59.89 | 63.20 | 52.85 | 43.70 | 29.21 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| AWS textract | 75.77 | 77.70 | **70.47** | 69.13 | 46.39 | 43.32 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ - - -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. - -.. autofunction:: doctr.models.zoo.ocr_predictor - - -Model export ------------- -Utility functions to make the most of document analysis models. - -.. currentmodule:: doctr.models.export - -Model compression -^^^^^^^^^^^^^^^^^ - -.. autofunction:: convert_to_tflite - -.. autofunction:: convert_to_fp16 - -.. autofunction:: quantize_model - -Using SavedModel -^^^^^^^^^^^^^^^^ - -Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -`SavedModel `_ format as follows: - - - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> _ = model(input_t, training=False) - >>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/') - -And loaded just as easily: - - - >>> import tensorflow as tf - >>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/') diff --git a/v0.5.1/_sources/transforms.rst.txt b/v0.5.1/_sources/transforms.rst.txt deleted file mode 100644 index 0230fe75f5..0000000000 --- a/v0.5.1/_sources/transforms.rst.txt +++ /dev/null @@ -1,32 +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: - -.. autoclass:: Resize -.. autoclass:: Normalize -.. autoclass:: LambdaTransformation -.. autoclass:: ToGray -.. autoclass:: ColorInversion -.. autoclass:: RandomBrightness -.. autoclass:: RandomContrast -.. autoclass:: RandomSaturation -.. autoclass:: RandomHue -.. autoclass:: RandomGamma -.. autoclass:: RandomJpegQuality - - -Composing transformations ---------------------------------------------- -It is common to require several transformations to be performed consecutively. - -.. autoclass:: Compose -.. autoclass:: OneOf -.. autoclass:: RandomApply diff --git a/v0.5.1/_sources/utils.rst.txt b/v0.5.1/_sources/utils.rst.txt deleted file mode 100644 index 69c1abe0eb..0000000000 --- a/v0.5.1/_sources/utils.rst.txt +++ /dev/null @@ -1,36 +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 - - -.. _metrics: - -Task evaluation ---------------- -Implementations of task-specific metrics to easily assess your model performances. - -.. currentmodule:: doctr.utils.metrics - -.. autoclass:: TextMatch - - .. automethod:: summary - -.. autoclass:: LocalizationConfusion - - .. automethod:: summary - -.. autoclass:: OCRMetric - - .. automethod:: summary diff --git a/v0.5.1/_static/basic.css b/v0.5.1/_static/basic.css index f316efcb47..7ebbd6d07b 100644 --- a/v0.5.1/_static/basic.css +++ b/v0.5.1/_static/basic.css @@ -1,12 +1,5 @@ /* - * basic.css - * ~~~~~~~~~ - * * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ /* -- main layout ----------------------------------------------------------- */ @@ -115,15 +108,11 @@ img { /* -- search page ----------------------------------------------------------- */ ul.search { - margin: 10px 0 0 20px; - padding: 0; + margin-top: 10px; } ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; + padding: 5px 0; } ul.search li a { diff --git a/v0.5.1/_static/doctools.js b/v0.5.1/_static/doctools.js index 4d67807d17..0398ebb9f0 100644 --- a/v0.5.1/_static/doctools.js +++ b/v0.5.1/_static/doctools.js @@ -1,12 +1,5 @@ /* - * doctools.js - * ~~~~~~~~~~~ - * * Base JavaScript utilities for all Sphinx HTML documentation. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; diff --git a/v0.5.1/_static/documentation_options.js b/v0.5.1/_static/documentation_options.js index a7b5cbe04a..4f656fdbea 100644 --- a/v0.5.1/_static/documentation_options.js +++ b/v0.5.1/_static/documentation_options.js @@ -1,5 +1,5 @@ const DOCUMENTATION_OPTIONS = { - VERSION: '0.3.0a0-git', + VERSION: '0.10.1a0-git', LANGUAGE: 'en', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/v0.5.1/_static/language_data.js b/v0.5.1/_static/language_data.js index 367b8ed81b..c7fe6c6faf 100644 --- a/v0.5.1/_static/language_data.js +++ b/v0.5.1/_static/language_data.js @@ -1,13 +1,6 @@ /* - * language_data.js - * ~~~~~~~~~~~~~~~~ - * * This script contains the language-specific data used by searchtools.js, * namely the list of stopwords, stemmer, scorer and splitter. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; diff --git a/v0.5.1/_static/searchtools.js b/v0.5.1/_static/searchtools.js index b08d58c9b9..2c774d17af 100644 --- a/v0.5.1/_static/searchtools.js +++ b/v0.5.1/_static/searchtools.js @@ -1,12 +1,5 @@ /* - * searchtools.js - * ~~~~~~~~~~~~~~~~ - * * Sphinx JavaScript utilities for the full-text search. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; @@ -20,7 +13,7 @@ if (typeof Scorer === "undefined") { // and returns the new score. /* score: result => { - const [docname, title, anchor, descr, score, filename] = result + const [docname, title, anchor, descr, score, filename, kind] = result return score }, */ @@ -47,6 +40,14 @@ if (typeof Scorer === "undefined") { }; } +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + const _removeChildren = (element) => { while (element && element.lastChild) element.removeChild(element.lastChild); }; @@ -64,9 +65,13 @@ const _displayItem = (item, searchTerms, highlightTerms) => { const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; const contentRoot = document.documentElement.dataset.content_root; - const [docName, title, anchor, descr, score, _filename] = item; + const [docName, title, anchor, descr, score, _filename, kind] = item; let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); let requestUrl; let linkUrl; if (docBuilder === "dirhtml") { @@ -115,8 +120,10 @@ const _finishSearch = (resultCount) => { "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." ); else - Search.status.innerText = _( - "Search finished, found ${resultCount} page(s) matching the search query." + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, ).replace('${resultCount}', resultCount); }; const _displayNextItem = ( @@ -138,7 +145,7 @@ const _displayNextItem = ( else _finishSearch(resultCount); }; // Helper function used by query() to order search results. -// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. // Order the results by score (in opposite order of appearance, since the // `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. const _orderResultsByScoreThenName = (a, b) => { @@ -248,6 +255,7 @@ const Search = { searchSummary.classList.add("search-summary"); searchSummary.innerText = ""; const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); searchList.classList.add("search"); const out = document.getElementById("search-results"); @@ -318,7 +326,7 @@ const Search = { const indexEntries = Search._index.indexentries; // Collect multiple result groups to be sorted separately and then ordered. - // Each is an array of [docname, title, anchor, descr, score, filename]. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. const normalResults = []; const nonMainIndexResults = []; @@ -337,6 +345,7 @@ const Search = { null, score + boost, filenames[file], + SearchResultKind.title, ]); } } @@ -354,6 +363,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.index, ]; if (isMain) { normalResults.push(result); @@ -475,6 +485,7 @@ const Search = { descr, score, filenames[match[0]], + SearchResultKind.object, ]); }; Object.keys(objects).forEach((prefix) => @@ -585,6 +596,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.text, ]); } return results; diff --git a/v0.5.1/changelog.html b/v0.5.1/changelog.html index eafac3a877..fc45a50384 100644 --- a/v0.5.1/changelog.html +++ b/v0.5.1/changelog.html @@ -12,9 +12,9 @@ gtag('js', new Date()); gtag('config', 'G-40DVRMX8T4'); - + - + Changelog - docTR documentation @@ -226,20 +226,42 @@ + diff --git a/v0.5.1/community/resources.html b/v0.5.1/community/resources.html index 2564037893..9a1988258c 100644 --- a/v0.5.1/community/resources.html +++ b/v0.5.1/community/resources.html @@ -14,7 +14,7 @@ - + Community resources - docTR documentation @@ -389,7 +389,7 @@

    Community resources - + diff --git a/v0.5.1/contributing/code_of_conduct.html b/v0.5.1/contributing/code_of_conduct.html index 5ea4a1f99d..03422dbb4d 100644 --- a/v0.5.1/contributing/code_of_conduct.html +++ b/v0.5.1/contributing/code_of_conduct.html @@ -14,7 +14,7 @@ - + Contributor Covenant Code of Conduct - docTR documentation @@ -504,7 +504,7 @@

    Attribution - + diff --git a/v0.5.1/contributing/contributing.html b/v0.5.1/contributing/contributing.html index e5a85682c6..05e2b3641b 100644 --- a/v0.5.1/contributing/contributing.html +++ b/v0.5.1/contributing/contributing.html @@ -14,7 +14,7 @@ - + Contributing to docTR - docTR documentation @@ -481,7 +481,7 @@

    Let’s connect - + diff --git a/v0.5.1/datasets.html b/v0.5.1/datasets.html deleted file mode 100644 index 193e576c57..0000000000 --- a/v0.5.1/datasets.html +++ /dev/null @@ -1,578 +0,0 @@ - - - - - - - - - - - - - doctr.datasets - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.datasets

    -

    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.

    -
    -

    Available Datasets

    -

    The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL.

    -
    -
    -class doctr.datasets.datasets.VisionDataset(url: str, file_name: str | None = None, file_hash: str | None = None, extract_archive: bool = False, download: bool = False, overwrite: bool = False)[source]
    -
    - -

    Here are all datasets that are available through DocTR:

    -
    -
    -class doctr.datasets.FUNSD(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    FUNSD dataset from “FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents”.

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD
    ->>> train_set = FUNSD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.SROIE(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    SROIE dataset from “ICDAR2019 Competition on Scanned Receipt OCR and Information Extraction”.

    -
    -
    Example::
    >>> from doctr.datasets import SROIE
    ->>> train_set = SROIE(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.CORD(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    CORD dataset from “CORD: A Consolidated Receipt Dataset forPost-OCR Parsing”.

    -
    -
    Example::
    >>> from doctr.datasets import CORD
    ->>> train_set = CORD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.OCRDataset(img_folder: str, label_file: str, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    Implements an OCR dataset

    -
    -
    Parameters:
    -
      -
    • img_folder – local path to image folder (all jpg at the root)

    • -
    • label_file – local path to the label file

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -

    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.

    -
    -
    -class doctr.datasets.loader.DataLoader(dataset, shuffle: bool = True, batch_size: int = 1, drop_last: bool = False, workers: int | None = None)[source]
    -

    Implements a dataset wrapper for fast data loading

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD, 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)
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • dataset – the dataset

    • -
    • shuffle – whether the samples should be shuffled before passing it to the iterator

    • -
    • batch_size – number of elements in each batch

    • -
    • drop_last – if True, drops the last batch if it isn’t full

    • -
    • workers – number of workers to use for data loading

    • -
    -
    -
    -
    - -
    -
    -

    Supported Vocabs

    -

    Since textual content has to be encoded properly for models to interpret them efficiently, DocTR supports multiple sets -of vocabs.

    -
    - - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    DocTR Vocabs

    Name

    size

    characters

    digits

    10

    0123456789

    ascii_letters

    52

    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

    punctuation

    32

    !”#$%&'()*+,-./:;<=>?@[\]^_`{|}~

    currency

    5

    £€¥¢฿

    latin

    96

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°

    french

    154

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿

    -
    -
    -
    -doctr.datasets.encode_sequences(sequences: List[str], vocab: str, target_size: int | None = None, eos: int = -1, sos: int | None = None, pad: int | None = None, **kwargs: Any) ndarray[source]
    -

    Encode character sequences using a given vocab as mapping

    -
    -
    Parameters:
    -
      -
    • sequences – the list of character sequences of size N

    • -
    • vocab – the ordered vocab to use for encoding

    • -
    • target_size – maximum length of the encoded data

    • -
    • eos – encoding of End Of String

    • -
    • sos – optional encoding of Start Of String

    • -
    • pad – optional encoding for padding. In case of padding, all sequences are followed by 1 EOS then PAD

    • -
    -
    -
    Returns:
    -

    the padded encoded data as a tensor

    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.1/documents.html b/v0.5.1/documents.html deleted file mode 100644 index 98cbb2c5ef..0000000000 --- a/v0.5.1/documents.html +++ /dev/null @@ -1,772 +0,0 @@ - - - - - - - - - - - - - doctr.documents - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.documents

    -

    The documents module enables users to easily access content from documents and export analysis -results to structured formats.

    -
    -

    Document structure

    -

    Structural organization of the documents.

    -
    -

    Word

    -

    A Word is an uninterrupted sequence of characters.

    -
    -
    -class doctr.documents.Word(value: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float])[source]
    -

    Implements a word element

    -
    -
    Parameters:
    -
      -
    • value – the text string of the word

    • -
    • confidence – the confidence associated with the text prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to

    • -
    • size (the page's)

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Line(words: List[Word], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float] | None = None)[source]
    -

    Implements a line element as a collection of words

    -
    -
    Parameters:
    -
      -
    • words – list of word elements

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all words in it.

    • -
    -
    -
    -
    - -
    -
    -

    Artefact

    -

    An Artefact is a non-textual element (e.g. QR code, picture, chart, signature, logo, etc.).

    -
    -
    -class doctr.documents.Artefact(artefact_type: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]])[source]
    -

    Implements a non-textual element

    -
    -
    Parameters:
    -
      -
    • artefact_type – the type of artefact

    • -
    • confidence – the confidence of the type prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size.

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Block(lines: List[Line] = [], artefacts: List[Artefact] = [], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float] | None = None)[source]
    -

    Implements a block element as a collection of lines and artefacts

    -
    -
    Parameters:
    -
      -
    • lines – list of line elements

    • -
    • artefacts – list of artefacts

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all lines and artefacts in it.

    • -
    -
    -
    -
    - -
    -
    -

    Page

    -

    A Page is a collection of Blocks that were on the same physical page.

    -
    -
    -class doctr.documents.Page(blocks: List[Block], page_idx: int, dimensions: Tuple[int, int], orientation: Dict[str, Any] | None = None, language: Dict[str, Any] | None = None)[source]
    -

    Implements a page element as a collection of blocks

    -
    -
    Parameters:
    -
      -
    • blocks – list of block elements

    • -
    • page_idx – the index of the page in the input raw document

    • -
    • dimensions – the page size in pixels in format (width, height)

    • -
    • orientation – a dictionary with the value of the rotation angle in degress and confidence of the prediction

    • -
    • language – a dictionary with the language value and confidence of the prediction

    • -
    -
    -
    -
    -
    -show(page: ndarray, interactive: bool = True, **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -
      -
    • page – image encoded as a numpy array in uint8

    • -
    • interactive – whether the display should be interactive

    • -
    -
    -
    -
    - -
    - -
    -
    -

    Document

    -

    A Document is a collection of Pages.

    -
    -
    -class doctr.documents.Document(pages: List[Page])[source]
    -

    Implements a document element as a collection of pages

    -
    -
    Parameters:
    -

    pages – list of page elements

    -
    -
    -
    -
    -show(pages: List[ndarray], **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -

    pages – list of images encoded as numpy arrays in uint8

    -
    -
    -
    - -
    - -
    -
    -
    -

    File reading

    -

    High-performance file reading and conversion to processable structured data.

    -
    -
    -doctr.documents.read_pdf(file: str | Path | bytes, **kwargs: Any) Document[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_pdf
    ->>> doc = read_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_img(file: str | Path | bytes, output_size: Tuple[int, int] | None = None, rgb_output: bool = True) ndarray[source]
    -

    Read an image file into numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_img
    ->>> page = read_img("path/to/your/doc.jpg")
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • file – the path to the image file

    • -
    • output_size – the expected output size of each page in format H x W

    • -
    • rgb_output – whether the output ndarray channel order should be RGB instead of BGR.

    • -
    -
    -
    Returns:
    -

    the page decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_html(url: str, **kwargs: Any) bytes[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_html
    ->>> doc = read_html("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – URL of the target web page

    -
    -
    Returns:
    -

    decoded PDF file as a bytes stream

    -
    -
    -
    - -
    -
    -class doctr.documents.DocumentFile[source]
    -

    Read a document from multiple extensions

    -
    -
    -classmethod from_pdf(file: str | Path | bytes, **kwargs) PDF[source]
    -

    Read a PDF file

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file or a binary stream

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_url(url: str, **kwargs) PDF[source]
    -

    Interpret a web page as a PDF document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_url("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – the URL of the target web page

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_images(files: Sequence[str | Path | bytes] | str | Path | bytes, **kwargs) List[ndarray][source]
    -

    Read an image file (or a collection of image files) and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"])
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    files – the path to the image file or a binary stream, or a collection of those

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    - -
    -
    -class doctr.documents.PDF(doc: Document)[source]
    -

    PDF document template

    -
    -
    Parameters:
    -

    doc – input PDF document

    -
    -
    -
    -
    -as_images(**kwargs) List[ndarray][source]
    -

    Convert all document pages to images

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of convert_page_to_numpy

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -get_words(**kwargs) List[List[Tuple[Tuple[float, float, float, float], str]]][source]
    -

    Get the annotations for all words in the document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of fitz.Page.getTextWords

    -
    -
    Returns:
    -

    the list of pages annotations, represented as a list of tuple (bounding box, value)

    -
    -
    -
    - -
    -
    -get_artefacts() List[List[Tuple[float, float, float, float]]][source]
    -

    Get the artefacts for the entire document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts()
    -
    -
    -
    -
    -
    -
    Returns:
    -

    the list of pages artefacts, represented as a list of bounding boxes

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.1/genindex.html b/v0.5.1/genindex.html index a19b433943..21520455b4 100644 --- a/v0.5.1/genindex.html +++ b/v0.5.1/genindex.html @@ -13,7 +13,7 @@ - Index - docTR documentation + Index - docTR documentation @@ -224,20 +224,42 @@

    +
    +

    U

    + + +
    +
    +

    V

    @@ -561,7 +711,13 @@

    V

    W

    +
    @@ -599,8 +755,8 @@

    W

    - - + + diff --git a/v0.5.1/getting_started/installing.html b/v0.5.1/getting_started/installing.html index a488e9a030..af3b58193e 100644 --- a/v0.5.1/getting_started/installing.html +++ b/v0.5.1/getting_started/installing.html @@ -14,7 +14,7 @@ - + Installation - docTR documentation @@ -305,7 +305,7 @@

    Installation

    -

    This library requires Python 3.9 or higher.

    +

    This library requires Python 3.10 or higher.

    Prerequisites

    Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so:

    @@ -435,7 +435,7 @@

    Via Git - + diff --git a/v0.5.1/index.html b/v0.5.1/index.html index 4c6a28c66a..3a06afc6d9 100644 --- a/v0.5.1/index.html +++ b/v0.5.1/index.html @@ -12,9 +12,9 @@ gtag('js', new Date()); gtag('config', 'G-40DVRMX8T4'); - + - + docTR documentation @@ -226,20 +226,42 @@
    -

    DocTR: Document Text Recognition

    -

    State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 (PyTorch now in beta)

    +

    docTR: Document Text Recognition

    +

    State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 & PyTorch

    https://github.com/mindee/doctr/releases/download/v0.2.0/ocr.png

    DocTR provides an easy and powerful way to extract valuable information from your documents:

      -
    • 🧾 for automation: seemlessly process documents for Natural Language Understanding tasks: we provide OCR predictors to parse textual information (localize and identify each word) from your documents.

    • +
    • 🧾 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.

    • 👩‍🔬 for research: quickly compare your own architectures speed & performances with state-of-art models on public datasets.

    -

    Welcome to the documentation of DocTR!

    Main Features

    • 🤖 Robust 2-stage (detection + recognition) OCR predictors with pretrained parameters

    • ⚡ User-friendly, 3 lines of code to load a document and extract text with a predictor

    • -
    • 🚀 State-of-the-art performances on public document datasets, comparable with GoogleVision/AWS Textract

    • +
    • 🚀 State-of-the-art performance on public document datasets, comparable with GoogleVision/AWS Textract

    • ⚡ Optimized for inference speed on both CPU & GPU

    • -
    • 🐦 Light package, small dependencies

    • -
    • 🛠️ Daily maintained

    • -
    • 🏭 Easy integration

    • +
    • 🐦 Light package, minimal dependencies

    • +
    • 🛠️ Actively maintained by Mindee

    • +
    • 🏭 Easy integration (available templates for browser demo & API deployment)

    -
    -
    -

    Getting Started

    -
    -

    Build & train your predictor

    -
      -
    • Compose your own end-to-end OCR predictor: mix and match detection & recognition predictors (all-pretrained)

    • -
    • Fine-tune or train from scratch any detection or recognition model to specialize on your data

    • -
    -

    Model zoo

    Text detection models

    -
    -

    Text recognition models

    -
    -

    Supported datasets

    -
    -
    +
    +
    +
    +
    +
    @@ -406,7 +381,7 @@

    Supported datasets - +
    Next @@ -446,10 +421,8 @@

    Supported datasets + diff --git a/v0.5.1/installing.html b/v0.5.1/installing.html deleted file mode 100644 index b61c60134b..0000000000 --- a/v0.5.1/installing.html +++ /dev/null @@ -1,395 +0,0 @@ - - - - - - - - - - - - - Installation - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    Installation

    -

    This library requires Python 3.6 or higher.

    -
    -

    Prerequisites

    -

    Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so:

    - -

    If you are running another OS than Linux, you will need a few extra dependencies.

    -

    For MacOS users, you can install them as follows:

    -
    brew install cairo pango gdk-pixbuf libffi
    -
    -
    -

    For Windows users, those dependencies are included in GTK. You can find the latest installer over here.

    -
    -
    -

    Via Python Package

    -

    Install the last stable release of the package using pip:

    -
    pip install python-doctr
    -
    -
    -
    -
    -

    Via Git

    -

    Install the library in developper mode:

    -
    git clone https://github.com/mindee/doctr.git
    -pip install -e doctr/.
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.1/models.html b/v0.5.1/models.html deleted file mode 100644 index b5cd44c9fa..0000000000 --- a/v0.5.1/models.html +++ /dev/null @@ -1,1002 +0,0 @@ - - - - - - - - - - - - - doctr.models - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.models

    -

    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 TensorFlow model.

    • -
    • Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable.

    • -
    -
    -

    Text Detection

    -

    Localizing text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Input shape

    # params

    Recall

    Precision

    Recall

    Precision

    FPS

    db_resnet50

    (1024, 1024, 3)

    25.2 M

    82.14

    87.64

    92.49

    89.66

    2.1

    -
    -

    All text detection models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for detection

    -

    In DocTR, the pre-processing scheme for detection is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) with potential deformation.

    2. -
    3. batch images together

    4. -
    5. normalize the batch using the training data statistics

    6. -
    -
    -
    -

    Detection models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.detection.db_resnet50(pretrained: bool = False, **kwargs: Any) DBNet[source]
    -

    DBNet as described in “Real-time Scene Text Detection with Differentiable Binarization”, using a ResNet-50 backbone.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -doctr.models.detection.linknet16(pretrained: bool = False, **kwargs: Any) LinkNet[source]
    -

    LinkNet as described in “LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import linknet16
    ->>> model = linknet16(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -

    Detection predictors

    -

    Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information.

    -
    -
    -doctr.models.detection.detection_predictor(arch: str = 'db_resnet50', pretrained: bool = False, **kwargs: Any) DetectionPredictor[source]
    -

    Text detection architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import detection_predictor
    ->>> model = detection_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_resnet50’)

    • -
    • pretrained – If True, returns a model pre-trained on our text detection dataset

    • -
    -
    -
    Returns:
    -

    Detection predictor

    -
    -
    -
    - -
    -
    -
    -

    Text Recognition

    -

    Identifying strings in images

    -
    - - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Text recognition model zoo

    Architecture

    Input shape

    # params

    FUNSD

    CORD

    FPS

    crnn_vgg16_bn

    (32, 128, 3)

    15.8M

    86.02

    91.3

    12.8

    sar_vgg16_bn

    (32, 128, 3)

    21.5M

    86.2

    91.7

    3.3

    sar_resnet31

    (32, 128, 3)

    53.1M

    86.3

    92.1

    2.7

    -
    -

    All text recognition models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All these recognition models are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for recognition

    -

    In DocTR, the pre-processing scheme for recognition is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) without deformation.

    2. -
    3. pad the image to the target size (with zeros by default)

    4. -
    5. batch images together

    6. -
    7. normalize the batch using the training data statistics

    8. -
    -
    -
    -

    Recognition models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.recognition.crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) CRNN[source]
    -

    CRNN with a VGG-16 backbone as described in “An End-to-End Trainable Neural Network for Image-based -Sequence Recognition and Its Application to Scene Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import crnn_vgg16_bn
    ->>> model = crnn_vgg16_bn(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a VGG16 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_vgg16_bn
    ->>> model = sar_vgg16_bn(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_resnet31(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a resnet-31 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -

    Example

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_resnet31
    ->>> model = sar_resnet31(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.master(pretrained: bool = False, **kwargs: Any) MASTER[source]
    -

    MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_. -Example:

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import master
    ->>> model = master(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 48, 160, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -

    Recognition predictors

    -

    Combining the right components around a given architecture for easier usage.

    -
    -
    -doctr.models.recognition.recognition_predictor(arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) RecognitionPredictor[source]
    -

    Text recognition architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import recognition_predictor
    ->>> model = recognition_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(32, 128, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘crnn_vgg16_bn’, ‘crnn_resnet31’, ‘sar_vgg16_bn’, ‘sar_resnet31’)

    • -
    • pretrained – If True, returns a model pre-trained on our text recognition dataset

    • -
    -
    -
    Returns:
    -

    Recognition predictor

    -
    -
    -
    - -
    -
    -
    -

    End-to-End OCR

    -

    Predictors that localize and identify text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Recall

    Precision

    FPS

    Recall

    Precision

    FPS

    db_resnet50 + crnn_vgg16_bn

    70.08

    74.77

    0.85

    82.19

    79.67

    1.6

    db_resnet50 + sar_vgg16_bn

    N/A

    N/A

    0.49

    N/A

    N/A

    1.0

    db_resnet50 + sar_resnet31

    N/A

    N/A

    0.27

    N/A

    N/A

    0.83

    Gvision text detection

    59.50

    62.50

    75.30

    70.00

    Gvision doc. text detection

    64.00

    53.30

    68.90

    61.10

    AWS textract

    78.10

    83.00

    87.50

    66.00

    -
    -

    All OCR models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All recognition models of predictors are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -

    Results on private ocr datasets

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Receipts

    Invoices

    IDs

    Architecture

    Recall

    Precision

    Recall

    Precision

    Recall

    Precision

    db_resnet50 + crnn_vgg16_bn (ours)

    78.90

    81.01

    65.68

    69.86

    49.48

    50.46

    Gvision doc. text detection

    68.91

    59.89

    63.20

    52.85

    43.70

    29.21

    AWS textract

    75.77

    77.70

    70.47

    69.13

    46.39

    43.32

    -
    -
    -

    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.

    -
    -
    -doctr.models.zoo.ocr_predictor(det_arch: str = 'db_resnet50', reco_arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) OCRPredictor[source]
    -

    End-to-end OCR architecture using one model for localization, and another for text recognition.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import ocr_predictor
    ->>> model = ocr_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_sar_vgg’, ‘db_sar_resnet’, ‘db_crnn_vgg’, ‘db_crnn_resnet’)

    • -
    • pretrained – If True, returns a model pre-trained on our OCR dataset

    • -
    -
    -
    Returns:
    -

    OCR predictor

    -
    -
    -
    - -
    -
    -
    -

    Model export

    -

    Utility functions to make the most of document analysis models.

    -
    -

    Model compression

    -
    -
    -doctr.models.export.convert_to_tflite(tf_model: Model) bytes[source]
    -

    Converts a model to TFLite format

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_tflite, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_tflite(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.convert_to_fp16(tf_model: Model) bytes[source]
    -

    Converts a model to half precision

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_fp16, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_fp16(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the serialized FP16 model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) bytes[source]
    -

    Quantize a Tensorflow model

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import quantize_model, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = quantize_model(model, (224, 224, 3))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • tf_model – a keras model

    • -
    • input_shape – shape of the expected input tensor (excluding batch dimension) with channel last order

    • -
    -
    -
    Returns:
    -

    the serialized quantized model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -

    Using SavedModel

    -

    Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -SavedModel format as follows:

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> _ = model(input_t, training=False)
    ->>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/')
    -
    -
    -

    And loaded just as easily:

    -
    >>> import tensorflow as tf
    ->>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/')
    -
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.1/modules/contrib.html b/v0.5.1/modules/contrib.html index e99f6b3f74..7fb86b8b38 100644 --- a/v0.5.1/modules/contrib.html +++ b/v0.5.1/modules/contrib.html @@ -14,7 +14,7 @@ - + doctr.contrib - docTR documentation @@ -380,7 +380,7 @@

    Supported contribution modules - + diff --git a/v0.5.1/modules/datasets.html b/v0.5.1/modules/datasets.html index 456e10b172..380a986793 100644 --- a/v0.5.1/modules/datasets.html +++ b/v0.5.1/modules/datasets.html @@ -14,7 +14,7 @@ - + doctr.datasets - docTR documentation @@ -1081,7 +1081,7 @@

    Returns:

    - + diff --git a/v0.5.1/modules/io.html b/v0.5.1/modules/io.html index 01eadaa4b8..24c41954be 100644 --- a/v0.5.1/modules/io.html +++ b/v0.5.1/modules/io.html @@ -14,7 +14,7 @@ - + doctr.io - docTR documentation @@ -760,7 +760,7 @@

    Returns: - + diff --git a/v0.5.1/modules/models.html b/v0.5.1/modules/models.html index c465cc0586..91b8810a6a 100644 --- a/v0.5.1/modules/models.html +++ b/v0.5.1/modules/models.html @@ -14,7 +14,7 @@ - + doctr.models - docTR documentation @@ -1612,7 +1612,7 @@

    Args: - + diff --git a/v0.5.1/modules/transforms.html b/v0.5.1/modules/transforms.html index 30f7a2631a..c5ead3f3ce 100644 --- a/v0.5.1/modules/transforms.html +++ b/v0.5.1/modules/transforms.html @@ -14,7 +14,7 @@ - + doctr.transforms - docTR documentation @@ -835,7 +835,7 @@

    Args:< - + diff --git a/v0.5.1/modules/utils.html b/v0.5.1/modules/utils.html index 888a32c321..b7f6fc570b 100644 --- a/v0.5.1/modules/utils.html +++ b/v0.5.1/modules/utils.html @@ -14,7 +14,7 @@ - + doctr.utils - docTR documentation @@ -715,7 +715,7 @@

    Args: - + diff --git a/v0.5.1/notebooks.html b/v0.5.1/notebooks.html index f97771aebb..d36539f59e 100644 --- a/v0.5.1/notebooks.html +++ b/v0.5.1/notebooks.html @@ -14,7 +14,7 @@ - + docTR Notebooks - docTR documentation @@ -391,7 +391,7 @@

    docTR Notebooks - + diff --git a/v0.5.1/objects.inv b/v0.5.1/objects.inv index a22d2ce821026792e7b2f75cbac9aecf06cc0d89..c1700f291b7b6bc7252a625eb1acde3a63b67eca 100644 GIT binary patch delta 4090 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 delta 1868 zcmV-S2ebIcAgT|LI|DN=Fp)nzf2CSYZ`(K!z4KQH*kG}Hpb@8NQRLRfO*V_9$=YuA zXwVYv2$4jUq@1R|zWk6VlKNIpp``cTkeuOgNGBp8r;7-2#4u-ztB3U}$lor*1ThvY z1M}gTr^z2@0R)jtxc>HaGmY_ZwO;A=B-&>EaQkHvBP2BP1_XUt)H3{re~@WB#VjV- zoZg!T#~CB^kdW6dwV^(C^rm4FXCaC3j^XcxXksQU9EvRDf;6Vf0?Q)bzeAnV@P<}G zP=x71_VrFRCrus+X=~IBb;jZ}G#Mo^_Je9jP{WND35yhG;{7Me@d1TyNSLqwu*`?g z2?<{&a#m$)CT+o<$*bh1f0#d$Xd3xCPVh{-lDarhlJ4RZ9d$6y?Sj_Hqr>lu6f~JC zau;V)C*g5*J)N;YZ01}^@)7eLDx-3?z^h20)5)UCQ%5T(vjX#f(ZPwfaDuzmENlpL zuJayUZ&446YC?};d}I4?+)uJe|%GqVifHp!QTLf zgGEu}^f;4Qrl{#mxmC92-0_ZAyiRo|BaQHsfLB_nC@Ki`fZNzT;$f$bVOXACj2k85-XU zl=1i{-l^A91Sk&4e=&>IJcKmMcBkWU`C+%u=8B|x+F}gRGQ{33e@A1^ zl>8(_)}Ipx!70kvfzVVODM&)-tqcuWt=qem1?r=xIbnD*?+&H=2yLquh|e+pRWcJ1 zkCT}X8GblSX01^ck@QoZCvP*kpJ{x2<{4&eroa`+#5=}kf6{MInxdK9e+oR4`EQPo z{}sYheD1t$5HIMwAX#HJCqlO5hN9`+73-}?Fk9@!iL7&N!KT>Ix*Rg)1@ssTnldEZ z8uMf1ZDNlR>%yUtOUBKUCXF-EpLWLBYgHI$yd_ikkw(3wJj#^jj5FY;?=c2S zPGz9rv8bHHd7s9iSob89i%<c7_oe3s#6wq;JmU!U(a4tM$EVPV9u? z%{9EmKlnZ3;qyephBwvlDQ1P4H}iydd3m6>%3V*Tf4X7{7>_O!v=?U<*mBCP?o9wX zp;*Ag(X_f#^_b4>t3gA9{$vEon_UL>>i(p^1L}?i?29;wf$tPIhqf5m4zZj>hZeY3^uXK( zlqWOSqJ3I3NLV6Q9@Ww^ZXKm(p;s*u-cJ#|Gb)jF^f&Hvdc(dX8?1Y>?HP#Tm8j-! z&>vr#Y@wZ0<8rNJqG=jaGA_+{0xmLJV4dbWe;2DeWGH#i=B-G$U0(*~c2uU!U|j=% zG1;F_Dghf$i%@LRc*rHXXEl7)PynubtEhLB0xu8%&P0+TQ2Yro~2f=5J$*@C6wV@5Y_sON4zSSI3 zDpB_qrR4u4q^D-rh<7Fe0B^oM0Ff3UNkgtmlf{syZLnscuz-!foTJ=E_%`I`n1 zvmo{W63V#{;UV&1P;Vujli{+UV8NR8*dy6yuQOQ=ShRVMy|U6O#ovpV#k?7%{?d80 z!N8j4f%$)Y6Xk2>y20+_&|YR?s~v6KpgW3Vwt8FD!mnFp0K*8d_ue*|J2o z?K;3K0?Qoh#ZKTCjDWuqxp|A^e~EIDP*C`cdlr{LLkqoakpP9tOAMWGr1y(XO@)LO z@|&V=r!b_=gb9mIRfOCz}Q~EsFI53>6&YH - - - - - - - - - - Python Module Index - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    - -
    -

    Python Module Index

    - -
    - - - - - - - - - - - -
     
    d
    - doctr -
    - -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.5.1/search.html b/v0.5.1/search.html index 73772822d2..d050f5eac7 100644 --- a/v0.5.1/search.html +++ b/v0.5.1/search.html @@ -14,7 +14,7 @@ - + Search - docTR documentation @@ -226,20 +226,42 @@ - - + + diff --git a/v0.5.1/searchindex.js b/v0.5.1/searchindex.js index 803f4f4bcf..6f154115ab 100644 --- a/v0.5.1/searchindex.js +++ b/v0.5.1/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles": {"Artefact": [[2, "artefact"]], "Available Datasets": [[1, "available-datasets"]], "Block": [[2, "block"]], "Build & train your predictor": [[3, "build-train-your-predictor"]], "Changelog": [[0, null]], "Composing transformations": [[6, "composing-transformations"]], "Data Loading": [[1, "data-loading"]], "Detection models": [[5, "detection-models"]], "Detection predictors": [[5, "detection-predictors"]], "DocTR Vocabs": [[1, "id1"]], "DocTR: Document Text Recognition": [[3, null]], "Document": [[2, "document"]], "Document structure": [[2, "document-structure"]], "End-to-End OCR": [[5, "end-to-end-ocr"]], "File reading": [[2, "file-reading"]], "Getting Started": [[3, "getting-started"]], "Installation": [[4, null]], "Line": [[2, "line"]], "Main Features": [[3, "main-features"]], "Model compression": [[5, "model-compression"]], "Model export": [[5, "model-export"]], "Model zoo": [[3, "model-zoo"]], "Notes": [[3, null]], "Package Reference": [[3, null]], "Page": [[2, "page"]], "Pre-processing for detection": [[5, "pre-processing-for-detection"]], "Pre-processing for recognition": [[5, "pre-processing-for-recognition"]], "Prerequisites": [[4, "prerequisites"]], "Recognition models": [[5, "recognition-models"]], "Recognition predictors": [[5, "recognition-predictors"]], "Supported Vocabs": [[1, "supported-vocabs"]], "Supported datasets": [[3, "supported-datasets"]], "Supported transformations": [[6, "supported-transformations"]], "Task evaluation": [[7, "task-evaluation"]], "Text Detection": [[5, "text-detection"]], "Text Recognition": [[5, "text-recognition"]], "Text detection models": [[3, "text-detection-models"]], "Text recognition model zoo": [[5, "id2"]], "Text recognition models": [[3, "text-recognition-models"]], "Two-stage approaches": [[5, "two-stage-approaches"]], "Using SavedModel": [[5, "using-savedmodel"]], "Via Git": [[4, "via-git"]], "Via Python Package": [[4, "via-python-package"]], "Visualization": [[7, "visualization"]], "Word": [[2, "word"]], "doctr.datasets": [[1, null]], "doctr.documents": [[2, null]], "doctr.models": [[5, null]], "doctr.transforms": [[6, null]], "doctr.utils": [[7, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]]}, "docnames": ["changelog", "datasets", "documents", "index", "installing", "models", "transforms", "utils"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "datasets.rst", "documents.rst", "index.rst", "installing.rst", "models.rst", "transforms.rst", "utils.rst"], "indexentries": {"artefact (class in doctr.documents)": [[2, "doctr.documents.Artefact", false]], "as_images() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.as_images", false]], "block (class in doctr.documents)": [[2, "doctr.documents.Block", false]], "colorinversion (class in doctr.transforms)": [[6, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[6, "doctr.transforms.Compose", false]], "convert_to_fp16() (in module doctr.models.export)": [[5, "doctr.models.export.convert_to_fp16", false]], "convert_to_tflite() (in module doctr.models.export)": [[5, "doctr.models.export.convert_to_tflite", false]], "cord (class in doctr.datasets)": [[1, "doctr.datasets.CORD", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.crnn_vgg16_bn", false]], "dataloader (class in doctr.datasets.loader)": [[1, "doctr.datasets.loader.DataLoader", false]], "db_resnet50() (in module doctr.models.detection)": [[5, "doctr.models.detection.db_resnet50", false]], "detection_predictor() (in module doctr.models.detection)": [[5, "doctr.models.detection.detection_predictor", false]], "document (class in doctr.documents)": [[2, "doctr.documents.Document", false]], "documentfile (class in doctr.documents)": [[2, "doctr.documents.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[1, "doctr.datasets.encode_sequences", false]], "from_images() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_images", false]], "from_pdf() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_pdf", false]], "from_url() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[1, "doctr.datasets.FUNSD", false]], "get_artefacts() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.get_artefacts", false]], "get_words() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.get_words", false]], "lambdatransformation (class in doctr.transforms)": [[6, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.documents)": [[2, "doctr.documents.Line", false]], "linknet16() (in module doctr.models.detection)": [[5, "doctr.models.detection.linknet16", false]], "localizationconfusion (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.LocalizationConfusion", false]], "master() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.master", false]], "normalize (class in doctr.transforms)": [[6, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models.zoo)": [[5, "doctr.models.zoo.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[1, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[6, "doctr.transforms.OneOf", false]], "page (class in doctr.documents)": [[2, "doctr.documents.Page", false]], "pdf (class in doctr.documents)": [[2, "doctr.documents.PDF", false]], "quantize_model() (in module doctr.models.export)": [[5, "doctr.models.export.quantize_model", false]], "randomapply (class in doctr.transforms)": [[6, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[6, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[6, "doctr.transforms.RandomContrast", false]], "randomgamma (class in doctr.transforms)": [[6, "doctr.transforms.RandomGamma", false]], "randomhue (class in doctr.transforms)": [[6, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[6, "doctr.transforms.RandomJpegQuality", false]], "randomsaturation (class in doctr.transforms)": [[6, "doctr.transforms.RandomSaturation", false]], "read_html() (in module doctr.documents)": [[2, "doctr.documents.read_html", false]], "read_img() (in module doctr.documents)": [[2, "doctr.documents.read_img", false]], "read_pdf() (in module doctr.documents)": [[2, "doctr.documents.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.recognition_predictor", false]], "resize (class in doctr.transforms)": [[6, "doctr.transforms.Resize", false]], "sar_resnet31() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.sar_resnet31", false]], "sar_vgg16_bn() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.sar_vgg16_bn", false]], "show() (doctr.documents.document method)": [[2, "doctr.documents.Document.show", false]], "show() (doctr.documents.page method)": [[2, "doctr.documents.Page.show", false]], "sroie (class in doctr.datasets)": [[1, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[7, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[7, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[7, "doctr.utils.metrics.TextMatch.summary", false]], "textmatch (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.TextMatch", false]], "togray (class in doctr.transforms)": [[6, "doctr.transforms.ToGray", false]], "visiondataset (class in doctr.datasets.datasets)": [[1, "doctr.datasets.datasets.VisionDataset", false]], "visualize_page() (in module doctr.utils.visualization)": [[7, "doctr.utils.visualization.visualize_page", false]], "word (class in doctr.documents)": [[2, "doctr.documents.Word", false]]}, "objects": {"doctr.datasets": [[1, 0, 1, "", "CORD"], [1, 0, 1, "", "FUNSD"], [1, 0, 1, "", "OCRDataset"], [1, 0, 1, "", "SROIE"], [1, 1, 1, "", "encode_sequences"]], "doctr.datasets.datasets": [[1, 0, 1, "", "VisionDataset"]], "doctr.datasets.loader": [[1, 0, 1, "", "DataLoader"]], "doctr.documents": [[2, 0, 1, "", "Artefact"], [2, 0, 1, "", "Block"], [2, 0, 1, "", "Document"], [2, 0, 1, "", "DocumentFile"], [2, 0, 1, "", "Line"], [2, 0, 1, "", "PDF"], [2, 0, 1, "", "Page"], [2, 0, 1, "", "Word"], [2, 1, 1, "", "read_html"], [2, 1, 1, "", "read_img"], [2, 1, 1, "", "read_pdf"]], "doctr.documents.Document": [[2, 2, 1, "", "show"]], "doctr.documents.DocumentFile": [[2, 2, 1, "", "from_images"], [2, 2, 1, "", "from_pdf"], [2, 2, 1, "", "from_url"]], "doctr.documents.PDF": [[2, 2, 1, "", "as_images"], [2, 2, 1, "", "get_artefacts"], [2, 2, 1, "", "get_words"]], "doctr.documents.Page": [[2, 2, 1, "", "show"]], "doctr.models.detection": [[5, 1, 1, "", "db_resnet50"], [5, 1, 1, "", "detection_predictor"], [5, 1, 1, "", "linknet16"]], "doctr.models.export": [[5, 1, 1, "", "convert_to_fp16"], [5, 1, 1, "", "convert_to_tflite"], [5, 1, 1, "", "quantize_model"]], "doctr.models.recognition": [[5, 1, 1, "", "crnn_vgg16_bn"], [5, 1, 1, "", "master"], [5, 1, 1, "", "recognition_predictor"], [5, 1, 1, "", "sar_resnet31"], [5, 1, 1, "", "sar_vgg16_bn"]], "doctr.models.zoo": [[5, 1, 1, "", "ocr_predictor"]], "doctr.transforms": [[6, 0, 1, "", "ColorInversion"], [6, 0, 1, "", "Compose"], [6, 0, 1, "", "LambdaTransformation"], [6, 0, 1, "", "Normalize"], [6, 0, 1, "", "OneOf"], [6, 0, 1, "", "RandomApply"], [6, 0, 1, "", "RandomBrightness"], [6, 0, 1, "", "RandomContrast"], [6, 0, 1, "", "RandomGamma"], [6, 0, 1, "", "RandomHue"], [6, 0, 1, "", "RandomJpegQuality"], [6, 0, 1, "", "RandomSaturation"], [6, 0, 1, "", "Resize"], [6, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[7, 0, 1, "", "LocalizationConfusion"], [7, 0, 1, "", "OCRMetric"], [7, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.LocalizationConfusion": [[7, 2, 1, "", "summary"]], "doctr.utils.metrics.OCRMetric": [[7, 2, 1, "", "summary"]], "doctr.utils.metrics.TextMatch": [[7, 2, 1, "", "summary"]], "doctr.utils.visualization": [[7, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [2, 7], "0": [1, 3, 5, 6, 7], "00": 5, "01": 5, "0123456789": 1, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 1, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": [], "02": 5, "02562": 5, "03": 3, "035": [], "0361328125": [], "04": [], "05": 3, "06": [], "06640625": [], "07": [], "08": 5, "09": [], "0966796875": [], "1": [1, 3, 5, 6, 7], "10": [1, 5, 7], "100": [5, 6, 7], "1000": 5, "101": [], "1024": [5, 7], "104": [], "106": [], "108": [], "1095": [], "11": 3, "110": 7, "1107": [], "114": [], "115": [], "1156": [], "116": [], "118": [], "11800h": [], "11th": [], "12": 5, "120": [], "123": [], "126": [], "1268": [], "128": 5, "13": 5, "130": [], "13068": [], "131": [], "1337891": [], "1357421875": [], "1396484375": [], "14": 5, "1420": [], "14470v1": [], "149": [], "15": 5, "150": 7, "154": 1, "1552": [], "16": 5, "160": 5, "1630859375": [], "1684": [], "16x16": [], "17": [], "1778": [], "1782": [], "18": 3, "185546875": [], "19": 5, "1900": [], "1910": 5, "19342": [], "19370": [], "195": [], "19598": [], "199": 5, "1999": [], "1m": 5, "2": [3, 5, 6], "20": 5, "200": 7, "2000": [], "2003": [], "2012": [], "2013": [], "2015": [], "2019": 3, "2021": 3, "2023": [], "207901": [], "21": 5, "2103": [], "2186": [], "21888": [], "22": [], "224": [5, 6], "225": 6, "22672": [], "229": 6, "23": [], "233": [], "236": [], "24": [], "246": [], "249": [], "25": 5, "2504": [], "255": [5, 6, 7], "256": 5, "257": [], "26": [], "26032": [], "264": [], "27": 5, "2700": [], "2710": [], "2749": [], "28": 3, "287": [], "29": 5, "296": [], "299": [], "2d": [], "3": [2, 3, 4, 5, 6, 7], "30": 5, "300": [], "3000": [], "301": [], "30595": 5, "30ghz": [], "31": 5, "32": [1, 5, 6], "3232421875": [], "33": [], "33402": [], "33608": [], "34": [], "340": [], "3456": [], "3515625": [], "36": [], "360": [], "37": [], "38": [], "39": 5, "4": [], "40": [], "406": 6, "41": [], "42": [], "43": 5, "44": [], "45": [], "456": 6, "46": 5, "47": 5, "472": [], "48": 5, "485": 6, "49": 5, "49377": [], "5": [1, 6, 7], "50": 5, "51": [], "51171875": [], "512": [], "52": [1, 5], "529": [], "53": 5, "533": [], "54": [], "540": [], "5478515625": [], "55": [], "56": [], "57": [], "58": [], "580": [], "5810546875": [], "583": [], "59": 5, "595": [], "597": [], "5k": [], "5m": 5, "6": [4, 5, 6], "60": 6, "600": [5, 7], "61": 5, "611": [], "62": 5, "625": [], "626": [], "629": [], "63": 5, "630": [], "64": [5, 6], "640": [], "641": [], "647": [], "65": 5, "66": 5, "660": [], "664": [], "666": [], "67": 5, "672": [], "68": 5, "689": [], "69": 5, "693": [], "694": [], "695": [], "6m": [], "7": 5, "70": [5, 7], "700": [], "701": [], "702": [], "707470": [], "71": [], "7100000": [], "713": [], "7141797": [], "7149": [], "72": [], "72dpi": [], "73": [], "73257": [], "733": [], "74": 5, "745": [], "75": 5, "753": [], "7581382": [], "76": [], "77": 5, "772": [], "772875": [], "78": 5, "780": [], "781": [], "783": [], "785": [], "789": [], "79": 5, "793533": [], "796": [], "798": [], "7m": [], "8": [5, 6], "80": [], "800": [5, 7], "81": 5, "817": [], "82": 5, "8275l": 5, "83": 5, "830": [], "84": [], "849": [], "85": 5, "8564453125": [], "857": [], "85875": [], "86": 5, "860": [], "8603515625": [], "862": [], "863": [], "87": 5, "8707": [], "875": [], "88": [], "89": 5, "8m": 5, "9": [], "90": 5, "90k": [], "90kdict32px": [], "91": 5, "913": [], "914085328578949": [], "917": [], "92": 5, "921": [], "93": [], "94": [], "95": 7, "9578408598899841": [], "96": 1, "97": [], "98": [], "99": [], "9949972033500671": [], "A": [1, 2, 3, 5], "And": 5, "As": [], "Be": [], "Being": [], "By": [], "For": [4, 5], "If": [2, 4, 5], "In": [1, 5], "It": 6, "Its": 5, "No": [], "Of": 1, "Or": [], "The": [1, 2, 5, 7], "Then": 5, "To": [], "_": [1, 5], "__call__": [], "_build": [], "_i": 7, "ab": [], "abc": [], "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 1, "abdef": [], "abl": [], "about": 5, "abov": 5, "abstract": 1, "abstractdataset": [], "abus": [], "accent": [], "accept": [], "access": [1, 2, 3], "account": [], "accur": [], "accuraci": 7, "achiev": [], "act": [], "action": [], "activ": [], "ad": 6, "adapt": [], "add": [6, 7], "add_hook": [], "add_label": 7, "addit": [], "addition": 5, "address": 2, "adjust": 6, "advanc": [], "advantag": [], "advis": [], "aesthet": [], "affect": [], "after": [], "ag": [], "again": [], "aggreg": [1, 7], "aggress": [], "align": 2, "all": [1, 2, 3, 5, 6, 7], "allow": [], "along": 5, "alreadi": [], "also": [], "alwai": [], "an": [1, 2, 3, 5, 7], "analysi": [2, 5], "ancient_greek": [], "andrej": [], "angl": 2, "ani": [1, 2, 3, 5, 6, 7], "annot": 2, "anot": [], "anoth": [1, 4, 5], "answer": [], "anyascii": [], "anyon": 3, "anyth": [], "api": [], "apolog": [], "apologi": [], "app": [], "appear": [], "appli": [1, 6], "applic": 5, "appoint": [], "appreci": [], "appropri": [], "ar": [1, 2, 4, 5, 6, 7], "arab": [], "arabic_diacrit": [], "arabic_lett": [], "arabic_punctu": [], "arbitrarili": [], "arch": 5, "architectur": [3, 5], "archiv": [], "area": [], "argument": [1, 2], "around": 5, "arrai": [2, 7], "art": 3, "artefact": 7, "artefact_typ": 2, "articl": [], "artifici": [], "arxiv": 5, "as_imag": 2, "asarrai": 7, "ascii_lett": 1, "aspect": [3, 6], "assess": 7, "assign": 7, "associ": 2, "assum": [], "assume_straight_pag": [], "astyp": [5, 7], "attack": [], "attend": [3, 5], "attent": [], "autoclass": [], "autom": 3, "automat": [], "autoregress": [], "avail": [3, 5, 6], "averag": [5, 6], "avoid": [], "aw": [3, 5], "awar": [], "azur": [], "b": 7, "b_j": 7, "back": [], "backbon": 5, "backend": 5, "background": [], "bangla": [], "bar": [], "bar_cod": [], "baranovskij": [], "base": 5, "baselin": 5, "batch": [1, 5, 6], "batch_siz": 1, "bblanchon": [], "bbox": [], "becaus": [], "been": [5, 7], "befor": 1, "begin": 7, "behavior": [], "being": [5, 7], "belong": [], "benchmark": [], "best": [], "beta": 3, "better": [], "between": [6, 7], "bgr": 2, "bilinear": [5, 6], "bin_thresh": [], "binar": [3, 5], "binari": 2, "bit": [], "block": [5, 7], "block_1_1": [], "blur": [], "bmvc": [], "bn": [], "bodi": [], "bool": [1, 2, 5, 6, 7], "boolean": [], "both": [3, 5, 6], "bottom": [], "bound": [1, 2, 6, 7], "box": [1, 2, 7], "box_thresh": [], "brew": 4, "bright": 6, "browser": [], "build": [], "built": [], "byte": [2, 5], "c": [], "c5": 5, "c_j": [], "cach": [], "cache_sampl": [], "cairo": 4, "call": [], "callabl": [1, 6], "can": [1, 4, 5], "capabl": 5, "case": [1, 7], "cf": 5, "cfg": [], "challeng": [], "challenge2_test_task12_imag": [], "challenge2_test_task1_gt": [], "challenge2_training_task12_imag": [], "challenge2_training_task1_gt": [], "chang": [], "changelog": 3, "channel": [2, 5, 6], "channel_prior": [], "channelshuffl": [], "charact": [1, 2, 3, 5, 7], "charactergener": [], "characterist": [], "charg": 5, "charset": [], "chart": 2, "check": [], "checkpoint": [], "chip": [], "christian": [], "ci": [], "clarifi": [], "clariti": [], "class": [1, 2, 6, 7], "class_nam": [], "classif": [], "classmethod": 2, "clear": [], "clone": 4, "close": [], "co": [], "code": [2, 3], "codecov": [], "colab": [], "collate_fn": [], "collect": 2, "color": 6, "colorinvers": 6, "column": 2, "com": [2, 4], "combin": 5, "command": [], "comment": [], "commit": [], "common": [6, 7], "commun": [], "compar": 3, "comparison": 7, "competit": 1, "compil": [], "complaint": [], "complementari": 7, "complet": [], "compon": 5, "compos": [1, 3, 5], "comprehens": [], "comput": [5, 7], "conf_threshold": [], "confid": 2, "config": [], "configur": [], "confus": 7, "consecut": [5, 6], "consequ": [], "consid": [1, 2, 7], "consist": [], "consolid": [1, 3], "constant": 6, "construct": [], "contact": [], "contain": [], "content": [1, 2], "context": [], "contib": [], "continu": [], "contrast": 6, "contrast_factor": 6, "contrib": [], "contribut": [], "contributor": [], "conv_sequ": 5, "convers": 2, "convert": [2, 5, 6], "convert_page_to_numpi": 2, "convert_to_fp16": 5, "convert_to_tflit": 5, "convolut": 3, "cool": [], "coordin": 2, "cord": [1, 3, 5], "core": 7, "corner": [], "correct": 6, "correspond": [4, 5], "could": [], "counterpart": 7, "cover": [], "coverag": [], "cpu": [3, 5], "creat": [], "crnn": [3, 5], "crnn_mobilenet_v3_larg": [], "crnn_mobilenet_v3_smal": [], "crnn_resnet31": 5, "crnn_vgg16_bn": 5, "crop": 5, "crop_orient": [], "crop_orientation_predictor": [], "crop_param": [], "cuda": [], "currenc": 1, "current": [], "custom": [], "custom_crop_orientation_model": [], "custom_page_orientation_model": [], "customhook": [], "cvit": [], "czczup": [], "czech": [], "d": [], "daili": 3, "danish": [], "data": [2, 3, 5, 6, 7], "dataload": 1, "dataset": 5, "dataset_info": [], "date": [], "db": [], "db_crnn_resnet": 5, "db_crnn_vgg": 5, "db_mobilenet_v3_larg": [], "db_resnet34": [], "db_resnet50": 5, "db_sar_resnet": 5, "db_sar_vgg": 5, "dbnet": [3, 5], "deal": [], "decis": [], "decod": 2, "decode_img_as_tensor": [], "dedic": [], "deem": [], "deep": 5, "def": [], "default": [2, 5], "defer": 1, "defin": 7, "deform": 5, "degre": [], "degress": 2, "delet": [], "delimit": [], "delta": 6, "demo": [], "demonstr": [], "depend": [3, 4], "deploi": [], "deploy": [], "derogatori": [], "describ": 5, "descript": [], "design": 6, "desir": [], "det_arch": 5, "det_b": [], "det_model": [], "det_param": [], "det_predictor": [], "detail": [], "detect": [], "detect_languag": [], "detect_orient": [], "detection_predictor": 5, "detection_task": [], "detectiondataset": [], "detectionmetr": [], "detectionpredictor": 5, "detector": [], "deterior": [], "determin": [], "dev": [], "develop": [], "developp": 4, "deviat": 6, "devic": [], "dict": [2, 7], "dictionari": [2, 7], "differ": [], "differenti": [3, 5], "digit": 1, "dimens": [2, 5, 7], "dimension": 6, "direct": [], "directli": 5, "directori": [], "disabl": [], "disable_crop_orient": [], "disable_page_orient": [], "disclaim": 5, "discuss": [], "disk": [], "disparag": [], "displai": [2, 7], "display_artefact": 7, "distanc": [], "distribut": 6, "div": [], "divers": [], "divid": [], "do": 4, "doc": [2, 5], "docartefact": [], "docstr": [], "doctr": 4, "doctr_cache_dir": [], "doctr_multiprocessing_dis": [], "document": [1, 5, 7], "documentbuild": [], "documentfil": 2, "doesn": [], "don": [], "done": 6, "download": 1, "downsiz": [], "draw": 6, "drop": 1, "drop_last": 1, "dtype": 5, "dual": [], "dummi": [], "dummy_img": [], "dummy_input": [], "dure": [], "dutch": [], "dynam": [], "dynamic_seq_length": [], "e": [2, 4], "each": [1, 2, 3, 5, 6, 7], "eas": [], "easi": [3, 7], "easier": 5, "easili": [2, 5, 7], "econom": [], "edit": [], "educ": [], "effect": [], "effici": [1, 5], "either": 5, "element": [1, 2, 5], "els": [], "email": [], "empathi": [], "en": [], "enabl": 2, "enclos": 2, "encod": [1, 2, 5], "encode_sequ": 1, "encount": [], "encrypt": [], "end": [1, 3, 7], "english": [], "enough": 5, "ensur": [], "entir": 2, "entri": [], "environ": [], "eo": 1, "equiv": [], "error": [], "estim": [], "etc": 2, "ethnic": [], "evalu": [1, 3, 5], "event": [], "everyon": [], "everyth": [], "exact": 7, "exactmatch": [], "exampl": [1, 2, 5, 6, 7], "exchang": [], "exclud": 5, "execut": [], "exist": [], "expand": [], "expect": [2, 5, 6], "experi": 5, "explan": 5, "explicit": [], "exploit": 5, "export": [2, 3, 7], "export_as_straight_box": [], "export_as_xml": [], "export_model_to_onnx": [], "express": 6, "extens": 2, "extern": [], "extra": 4, "extract": [1, 3], "extract_arch": 1, "extractor": 5, "f_": 7, "f_a": 7, "factor": 6, "fair": [], "fairli": [], "fals": [1, 5, 6, 7], "faq": [], "fascan": [], "fast": 1, "fast_bas": [], "fast_smal": [], "fast_tini": [], "faster": [], "fasterrcnn_mobilenet_v3_large_fpn": [], "favorit": [], "featur": [5, 7], "feed": 5, "feedback": [], "feel": [], "felix92": [], "few": 4, "figsiz": 7, "figur": 7, "file": [1, 3], "file_hash": 1, "file_nam": 1, "final": [], "find": 4, "fine": 3, "finnish": [], "first": [], "firsthand": [], "fit": [], "fitz": 2, "flag": [], "flexibl": 7, "flip": [], "float": [2, 6, 7], "float32": 5, "fn": 6, "focu": [], "focus": [], "folder": [1, 5], "follow": [1, 4, 5, 6, 7], "font": [], "font_famili": [], "foral": 7, "forc": [], "forg": [], "form": [1, 3], "format": [2, 5], "forpost": [1, 3], "forum": [], "found": [], "fp": 5, "fp16": 5, "frac": 7, "frame": 5, "framework": 1, "free": [], "french": [1, 5], "friendli": 3, "from": [1, 2, 3, 5, 6, 7], "from_hub": [], "from_imag": 2, "from_pdf": 2, "from_url": 2, "full": [1, 5, 7], "fulli": [], "function": [5, 6, 7], "funsd": [1, 3, 5], "further": [], "futur": [], "g": 2, "g_": 7, "g_x": 7, "gallagh": [], "gamma": 6, "gaussian": 6, "gaussianblur": [], "gaussiannois": [], "gdk": 4, "gen": [], "gender": [], "gener": [], "generic_cyrillic_lett": [], "geometri": 2, "geq": 7, "german": [], "get": 2, "get_artefact": 2, "get_word": 2, "gettextword": 2, "git": 3, "github": 4, "give": [], "given": [1, 2, 5, 7], "global": [], "go": [], "good": [], "googl": [], "googlevis": 3, "gpu": 3, "gracefulli": [], "graph": 2, "grayscal": 6, "ground": 7, "groung": [], "group": [], "gt": [], "gt_box": [], "gt_label": [], "gtk": 4, "guid": [], "guidanc": [], "gvision": 5, "h": 2, "h_": 7, "ha": [1, 7], "half": 5, "handl": 1, "handwrit": [], "handwritten": [], "harass": [], "hardwar": [], "harm": [], "hat": 7, "have": [1, 5, 7], "head": [], "healthi": [], "hebrew": [], "height": 2, "hello": 7, "help": [], "here": [1, 4, 6], "hf": [], "hf_hub_download": [], "high": 2, "higher": 4, "hindi": [], "hindi_digit": [], "hocr": [], "hook": [], "horizont": 2, "hous": [], "how": [], "howev": [], "hsv": 6, "html": [], "http": [2, 4, 5], "hub": [], "hue": 6, "huggingfac": [], "hw": [], "i": [1, 2, 5, 6, 7], "i7": [], "ibrahimov": [], "ic03": [], "ic13": [], "icdar": 3, "icdar2019": 1, "id": 5, "ident": [], "identifi": [3, 5], "ignor": [], "ignore_acc": [], "ignore_cas": [], "iiit": [], "iiit5k": [], "iiithw": [], "imag": [1, 2, 5, 6, 7], "imagenet": [], "imageri": [], "images_90k_norm": [], "img": [1, 6], "img_cont": [], "img_fold": 1, "img_path": [], "img_transform": [], "imgur5k": [], "imgur5k_annot": [], "imlist": [], "impact": [], "implement": [1, 2, 5, 6, 7], "import": [1, 2, 5, 6, 7], "improv": [], "inappropri": [], "incid": [], "includ": [4, 5], "inclus": [], "increas": 6, "independ": [], "index": 2, "indic": 7, "individu": [], "infer": [3, 6], "inform": [1, 3, 5], "inherit": [1, 5], "input": [2, 5, 6], "input_crop": [], "input_pag": [5, 7], "input_shap": 5, "input_t": 5, "input_tensor": 5, "inspir": 6, "instal": 3, "instanc": 5, "instanti": 5, "instead": [1, 2], "insult": [], "int": [1, 2, 5, 6, 7], "int64": [], "integ": 7, "integr": 3, "intel": [], "interact": [2, 7], "interfac": [], "interoper": [], "interpol": [5, 6], "interpret": [1, 2], "intersect": 7, "invert": 6, "investig": [], "invis": [], "invoic": 5, "involv": 5, "io": [], "iou": 7, "iou_thresh": 7, "iou_threshold": [], "irregular": 5, "isn": 1, "issu": [], "italian": [], "iter": 1, "its": [1, 2, 5, 7], "itself": [], "j": 7, "jame": [], "job": [], "join": [], "jpeg": 6, "jpegqual": 6, "jpg": [1, 2], "json": [], "json_output": [], "jump": [], "just": 5, "kei": [], "kera": 5, "kernel": [], "kernel_s": 5, "kernel_shap": [], "keywoard": [], "keyword": [1, 2], "kie": [], "kie_predictor": [], "kiepredictor": [], "kind": [], "know": [], "kwarg": [1, 2, 5, 7], "l": 7, "l_j": 7, "label": [1, 7], "label_fil": 1, "label_fold": [], "label_path": [], "labels_path": [], "ladder": [], "lambda": 6, "lambdatransform": 6, "lang": [], "languag": [2, 3], "larg": [], "largest": 7, "last": [1, 4, 5], "latenc": [], "later": [], "latest": 4, "latin": 1, "layer": [], "layout": [], "lead": [], "leader": [], "learn": 5, "least": 4, "left": 7, "legacy_french": [], "length": 1, "less": [], "let": 5, "letter": [], "level": [5, 7], "levenshtein": [], "leverag": [], "lf": [], "libffi": 4, "librari": 4, "light": 3, "lightweight": [], "like": [], "limits_": 7, "line": [3, 7], "line_1_1": [], "link": [], "linknet": [3, 5], "linknet16": 5, "linknet_resnet18": [], "linknet_resnet34": [], "linknet_resnet50": [], "linux": 4, "list": [1, 2, 6], "ll": 7, "load": [3, 5], "load_state_dict": [], "load_weight": [], "loader": 1, "loc_pr": [], "local": [1, 3, 5, 7], "localis": [], "localizationconfus": 7, "locat": [], "login": [], "login_to_hub": [], "logo": 2, "love": [], "lower": [6, 7], "m": [5, 7], "m1": [], "macbook": [], "machin": [], "maco": 4, "made": 3, "magc_resnet31": [], "mai": [], "mail": [], "main": [], "maintain": 3, "mainten": [], "make": [5, 7], "mani": [], "manipul": [], "map": 1, "map_loc": [], "mask_shap": 7, "master": [3, 5], "match": [3, 7], "mathcal": 7, "matplotlib": 7, "max": 7, "max_angl": [], "max_area": [], "max_char": [], "max_delta": 6, "max_dist": [], "max_gain": 6, "max_gamma": 6, "max_qual": 6, "max_ratio": [], "maximum": 1, "maxval": [5, 6], "mbox": 7, "mean": [6, 7], "meaniou": 7, "meant": 2, "measur": 5, "media": [], "median": [], "meet": [], "member": [], "memori": [], "mention": [], "merg": [], "messag": [], "meta": [], "metadata": [], "metal": [], "method": 6, "metric": [5, 7], "middl": [], "might": 5, "min": [], "min_area": [], "min_char": [], "min_gain": 6, "min_gamma": 6, "min_qual": 6, "min_ratio": [], "min_val": 6, "minde": 4, "minim": [], "minimalist": [], "minimum": 7, "minval": 6, "miss": [], "mistak": [], "mix": 3, "mixed_float16": [], "mixed_precis": [], "mjsynth": [], "mnt": [], "mobilenet": [], "mobilenet_v3_larg": [], "mobilenet_v3_large_r": [], "mobilenet_v3_smal": [], "mobilenet_v3_small_crop_orient": [], "mobilenet_v3_small_page_orient": [], "mobilenet_v3_small_r": [], "mobilenetv3": [], "modal": [], "mode": 4, "model": [1, 7], "model_nam": [], "model_path": [], "moder": [], "modif": [], "modifi": [], "modul": [2, 5, 6, 7], "more": [], "moscardi": [], "most": 5, "mozilla": [], "multi": 3, "multilingu": [], "multipl": [1, 2, 6], "multipli": 6, "multiprocess": [], "my": [], "my_awesome_model": [], "my_hook": [], "n": [1, 5, 7], "na": [], "name": [1, 5], "nation": [], "natur": 3, "ndarrai": [1, 2, 7], "necessari": [], "need": [4, 7], "neg": 6, "nest": [], "nestedobject": [], "netraj": [], "network": [3, 5], "neural": [3, 5], "new": [], "newer": [], "next": 1, "nois": [], "noisi": [1, 3], "non": [2, 3, 6, 7], "none": [1, 2, 7], "normal": [5, 6], "norwegian": [], "note": 0, "now": 3, "np": [5, 7], "num_output_channel": [], "num_sampl": [], "number": [1, 6, 7], "numpi": [2, 5, 7], "o": 4, "obb": [], "obj_detect": [], "object": 1, "objectness_scor": [], "oblig": [], "obtain": [], "occupi": [], "ocr": [1, 3, 7], "ocr_carea": [], "ocr_db_crnn": 7, "ocr_lin": [], "ocr_pag": [], "ocr_par": [], "ocr_predictor": 5, "ocrdataset": 1, "ocrmetr": 7, "ocrpredictor": 5, "ocrx_word": [], "offens": [], "offici": [], "offlin": [], "offset": 6, "onc": 5, "one": [1, 5, 6], "oneof": 6, "ones": 1, "onli": [6, 7], "onlin": [], "onnx": [], "onnxruntim": [], "onnxtr": [], "opac": [], "opacity_rang": [], "open": [], "opinion": [], "optic": [3, 5], "optim": 3, "option": 1, "order": [1, 2, 5], "org": 5, "organ": 2, "orient": 2, "orientationpredictor": [], "other": [], "otherwis": 7, "our": 5, "out": [5, 6, 7], "outpout": [], "output": [2, 5, 6], "output_s": [2, 6], "outsid": [], "over": [4, 7], "overal": [], "overlai": 2, "overview": [], "overwrit": 1, "overwritten": [], "own": 3, "p": 6, "packag": 7, "pad": [1, 5, 6], "page": [4, 5, 7], "page1": 2, "page2": 2, "page_1": [], "page_idx": 2, "page_orientation_predictor": [], "page_param": [], "pair": 7, "pango": 4, "paper": 5, "par_1_1": [], "paragraph": [], "paragraph_break": [], "parallel": [], "param": [5, 6], "paramet": [1, 2, 3, 5, 6, 7], "pars": [1, 3], "parseq": [], "part": 6, "parti": [], "partial": [], "particip": [], "pass": [1, 5], "password": [], "patch": [], "path": [1, 2, 5], "path_to_checkpoint": [], "path_to_custom_model": [], "path_to_pt": [], "patil": [], "pattern": [], "pdf": [2, 5], "pdfpage": [], "peopl": [], "per": [5, 6], "perform": [2, 3, 5, 6, 7], "period": [], "permiss": [], "permut": [], "persian_lett": [], "person": [], "phase": [], "photo": [], "physic": 2, "pick": 6, "pictur": 2, "pip": 4, "pipelin": [], "pixbuf": 4, "pixel": [2, 6], "platinum": 5, "pleas": [], "plot": 7, "plt": 7, "plug": [], "plugin": [], "png": 2, "point": [], "polici": [], "polish": [], "polit": [], "polygon": 1, "pool": [], "portugues": [], "posit": 7, "possibl": 7, "post": 5, "postprocessor": [], "potenti": 5, "power": 3, "ppageno": [], "pre": [], "precis": [5, 7], "pred": [], "pred_box": [], "pred_label": [], "predefin": 1, "predict": [2, 7], "predictor": [], "prefer": 1, "preinstal": [], "preprocessor": 5, "prerequisit": 3, "present": [], "preserv": 6, "preserve_aspect_ratio": 6, "pretrain": [3, 5, 7], "pretrained_backbon": [], "print": [], "prior": [], "privaci": [], "privat": 5, "probabl": 6, "problem": [], "procedur": 6, "process": [2, 3], "processor": 5, "produc": 5, "product": [], "profession": [], "project": [], "promptli": [], "proper": [], "properli": 1, "properti": 5, "provid": [3, 5], "public": 3, "publicli": [], "publish": [], "pull": [], "punctuat": 1, "pure": [], "purpos": [], "push_to_hf_hub": [], "py": [], "pypdfium2": [], "pyplot": 7, "python": 3, "python3": [], "pytorch": [3, 4], "q": [], "qr": 2, "qr_code": [], "qualiti": 6, "quantiz": 5, "quantize_model": 5, "question": [], "quickli": 3, "quicktour": [], "r": [], "race": [], "ramdisk": [], "rand": [5, 7], "random": [5, 6, 7], "randomappli": 6, "randombright": 6, "randomcontrast": 6, "randomcrop": [], "randomgamma": 6, "randomhorizontalflip": [], "randomhu": 6, "randomjpegqu": 6, "randomli": 6, "randomres": [], "randomrot": [], "randomsatur": 6, "randomshadow": [], "rang": 6, "rassi": [], "ratio": 6, "raw": [2, 7], "re": [], "read": [3, 5], "read_html": 2, "read_img": 2, "read_img_as_numpi": [], "read_img_as_tensor": [], "read_pdf": 2, "readi": [], "real": [5, 6], "realli": [], "reason": [], "rebuild": [], "rebuilt": [], "recal": [5, 7], "receipt": [1, 3, 5], "reco_arch": 5, "reco_b": [], "reco_model": [], "reco_param": [], "reco_predictor": [], "recogn": [], "recognit": 7, "recognition_predictor": 5, "recognition_task": [], "recognitiondataset": [], "recognitionpredictor": 5, "rectangular": [], "recurr": 3, "reduc": 6, "refer": 4, "regardless": [], "region": [], "regroup": 7, "regular": [], "reject": [], "rel": 2, "relat": [], "releas": [0, 4], "relev": [], "religion": [], "relu": 5, "remov": [], "render": [], "repo": [], "repo_id": [], "report": [], "repositori": [], "repres": [2, 5], "represent": 5, "request": [], "requir": [4, 6], "research": 3, "residu": [], "resiz": [5, 6], "resnet": 5, "resnet18": [], "resnet31": [], "resnet34": [], "resnet50": [], "resolv": 2, "resolve_block": [], "resolve_lin": [], "resourc": [], "respect": [], "rest": [6, 7], "restrict": [], "result": [2, 5], "return": [1, 2, 5, 7], "reusabl": 5, "review": [], "rgb": [2, 6], "rgb_mode": [], "rgb_output": 2, "right": [5, 7], "roboflow": [], "robust": 3, "root": 1, "rotat": [1, 2], "rotated_bbox": [1, 7], "run": 4, "same": [2, 7], "sampl": 1, "sample_transform": 1, "sanjin": [], "sar": [3, 5], "sar_resnet31": 5, "sar_vgg16_bn": 5, "satur": 6, "save": [1, 5], "saved_model": 5, "scale": 7, "scale_rang": [], "scan": [1, 3], "scene": [3, 5], "scheme": 5, "score": 7, "scratch": 3, "script": [], "seamless": 3, "seamlessli": [], "search": [], "searchabl": [], "sec": [], "second": 5, "section": [], "secur": [], "see": [], "seemlessli": 3, "seen": 5, "segment": 5, "self": [], "semant": 5, "send": [], "sens": 7, "sensit": [], "separ": 5, "sequenc": [1, 2, 5, 7], "sequenti": [5, 6], "seri": [], "serial": 5, "serialized_model": 5, "seriou": [], "set": [1, 5, 7], "set_global_polici": [], "sever": [2, 6], "sex": [], "sexual": [], "sha256": [], "shade": [], "shape": [2, 5, 6, 7], "share": [], "shift": 6, "shm": [], "should": [1, 2, 7], "show": [2, 3, 5, 7], "showcas": [], "shuffl": 1, "side": 7, "signatur": 2, "signific": 1, "simpl": 5, "simpler": [], "sinc": 1, "singl": [], "single_img_doc": [], "size": [1, 2, 5, 6], "skew": [], "slack": [], "slightli": [], "small": 3, "smallest": 2, "snapshot_download": [], "snippet": [], "so": [1, 4], "social": [], "socio": [], "some": [], "someth": [], "somewher": [], "sort": [], "sourc": [1, 2, 5, 6, 7], "space": [], "span": [], "spanish": [], "spatial": 2, "special": 3, "specif": [1, 5, 7], "specifi": 2, "speed": [3, 5], "sphinx": [], "sroie": [1, 3], "stabl": 4, "stackoverflow": [], "stage": 3, "standalon": [], "standard": 6, "start": 1, "state": 3, "static": 7, "statist": 5, "statu": [], "std": 6, "step": [], "still": [], "str": [1, 2, 5, 6, 7], "straight": 1, "straighten": [], "straighten_pag": [], "straigten_pag": [], "stream": 2, "street": [], "strict": [], "strictli": 7, "string": [1, 2, 5, 7], "strive": [], "strong": 5, "structur": [3, 5], "subset": [1, 5], "suggest": [], "sum": 7, "summari": 7, "support": 5, "sustain": [], "svhn": [], "svt": [], "swedish": [], "symbol": [], "symmetr": 6, "symmetric_pad": 6, "synthet": [], "synthtext": [], "system": [], "t": 1, "tabl": [], "take": [], "target": [1, 2, 5, 6], "target_s": 1, "task": [1, 3, 5], "task2": [], "team": [], "techminde": [], "templat": 2, "tensor": [1, 5, 6], "tensorflow": [3, 4, 5, 6], "tensorspec": [], "term": [], "test": [], "test_set": [], "text": [2, 7], "text_output": [], "textmatch": 7, "textnet": [], "textnet_bas": [], "textnet_smal": [], "textnet_tini": [], "textract": [3, 5], "textstylebrush": [], "textual": [1, 2, 3], "tf": [5, 6], "tf_model": 5, "tflite": 5, "than": [4, 7], "thank": [], "thei": [], "them": [1, 4], "thi": [4, 5, 7], "thing": [], "third": [], "those": [2, 4, 5], "threaten": [], "threshold": [], "through": [1, 6], "tilman": [], "time": [1, 5, 7], "tini": [], "titl": 2, "tm": [], "tmp": [], "togeth": [2, 5], "tograi": 6, "tool": [], "top": 7, "topic": [], "torch": [], "torchvis": 6, "total": [], "toward": [], "train": [1, 5, 6], "train_it": 1, "train_load": 1, "train_pytorch": [], "train_set": 1, "train_tensorflow": [], "trainabl": 5, "tranform": 6, "transcrib": [], "transfer": [], "transfo": 6, "transform": [1, 3], "translat": [], "troll": [], "true": [1, 2, 5, 6, 7], "truth": 7, "tune": 3, "tupl": [2, 5, 6, 7], "turn": [], "two": 2, "txt": [], "type": [2, 5], "typic": [], "u": [], "ucsd": [], "udac": [], "uint8": [2, 5, 7], "ukrainian": [], "unaccept": [], "underli": 1, "underneath": 2, "understand": [1, 3], "unidecod": 7, "uniform": [5, 6], "uniformli": [], "uninterrupt": 2, "union": 7, "unit": [], "unittest": [], "unlock": [], "unoffici": [], "unprofession": [], "unsolicit": [], "unsupervis": [], "unwelcom": [], "up": 5, "updat": 7, "upgrad": [], "upper": 6, "uppercas": [], "url": [1, 2], "us": [1, 4, 7], "usabl": 5, "usag": 5, "use_polygon": [], "useabl": [], "user": [2, 3, 4], "utf": [], "util": [3, 5], "v0": 3, "v1": [], "v3": [], "valid": [], "valu": [2, 6], "valuabl": 3, "variabl": [], "varieti": [], "veri": [], "verifi": 1, "verma": [], "version": 5, "vgg": 5, "vgg16": 5, "vgg16_bn_r": [], "via": 3, "video": [], "vietnames": [], "view": [], "viewpoint": [], "violat": [], "visibl": [], "vision": [], "visiondataset": 1, "visiontransform": [], "visual": 3, "visualize_pag": 7, "vit_": [], "vit_b": [], "vitstr": [], "vitstr_bas": [], "vitstr_smal": [], "viz": [], "vocab": [3, 5], "vocabulari": [], "w": [2, 7], "w3": [], "wa": [], "wai": [1, 3, 5], "want": [], "warm": 5, "warmup": [], "wasn": [], "we": [2, 3, 5, 6], "weasyprint": [], "web": 2, "websit": [], "welcom": 3, "well": [], "were": 2, "what": [], "when": [], "whenev": [], "where": [2, 7], "whether": [1, 2, 7], "which": 5, "whichev": 4, "while": 6, "why": [], "width": 2, "wiki": [], "wildreceipt": [], "window": [4, 7], "wish": [], "within": [], "without": 5, "wonder": [], "word": [3, 5, 7], "word_1_1": [], "word_1_2": [], "word_1_3": [], "wordgener": [], "words_onli": 7, "work": [], "worker": 1, "workflow": [], "worklow": [], "world": 7, "worth": [], "wrap": [], "wrapper": [1, 6], "write": [], "written": 2, "www": 2, "x": [2, 6, 7], "x12larg": 5, "x_ascend": [], "x_descend": [], "x_i": 7, "x_size": [], "x_wconf": [], "xeon": 5, "xhtml": [], "xmax": 2, "xmin": 2, "xml": [], "xml_bytes_str": [], "xml_element": [], "xml_output": [], "xmln": [], "y": 7, "y_i": 7, "y_j": 7, "yet": [], "ymax": 2, "ymin": 2, "yolov8": [], "you": [4, 5], "your": [1, 2, 5, 7], "yoursit": 2, "yugesh": [], "zero": [5, 6], "zoo": [], "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 1, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": [], "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": [], "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": [], "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": [], "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": [], "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": [], "\u00e4\u00f6\u00e4\u00f6": [], "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": [], "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": [], "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": [], "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": [], "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": [], "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": [], "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": [], "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": [], "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": [], "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": [], "\u067e\u0686\u06a2\u06a4\u06af": [], "\u0905": [], "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": [], "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": [], "\u0950": [], "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": [], "\u09bd": [], "\u09ce": [], "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": []}, "titles": ["Changelog", "doctr.datasets", "doctr.documents", "DocTR: Document Text Recognition", "Installation", "doctr.models", "doctr.transforms", "doctr.utils"], "titleterms": {"": [], "0": 0, "01": [], "02": [], "03": 0, "04": [], "05": 0, "07": [], "08": [], "09": [], "1": 0, "10": [], "11": 0, "12": [], "18": 0, "2": 0, "2021": 0, "2022": [], "2023": [], "2024": [], "21": [], "22": [], "27": [], "28": 0, "29": [], "3": [], "31": [], "4": [], "5": [], "6": [], "7": [], "8": [], "9": [], "advanc": [], "approach": 5, "architectur": [], "arg": [], "artefact": 2, "artefactdetect": [], "attribut": [], "avail": 1, "aw": [], "ban": [], "block": 2, "bug": [], "build": 3, "changelog": 0, "choos": [], "classif": [], "code": [], "codebas": [], "commit": [], "commun": [], "compos": 6, "compress": 5, "conda": [], "conduct": [], "connect": [], "content": [], "continu": [], "contrib": [], "contribut": [], "contributor": [], "convent": [], "correct": [], "coven": [], "custom": [], "data": 1, "dataload": [], "dataset": [1, 3], "detect": [3, 5], "develop": [], "do": [], "doctr": [1, 2, 3, 5, 6, 7], "document": [2, 3], "end": 5, "enforc": [], "evalu": 7, "export": 5, "factori": [], "featur": 3, "feedback": [], "file": 2, "from": [], "gener": [], "get": 3, "git": 4, "guidelin": [], "half": [], "hub": [], "huggingfac": [], "i": [], "implement": [], "infer": [], "instal": 4, "integr": [], "io": [], "lambda": [], "let": [], "line": 2, "linux": [], "load": 1, "loader": [], "main": 3, "mode": [], "model": [3, 5], "modifi": [], "modul": [], "name": [], "note": 3, "notebook": [], "object": [], "ocr": 5, "onli": [], "onnx": [], "optim": [], "option": [], "orient": [], "our": [], "output": [], "own": [], "packag": [3, 4], "page": 2, "perman": [], "pipelin": [], "pledg": [], "post": [], "pre": 5, "precis": [], "predictor": [3, 5], "prepar": [], "prerequisit": 4, "pretrain": [], "process": 5, "push": [], "python": 4, "qualiti": [], "question": [], "read": 2, "readi": [], "recognit": [3, 5], "refer": 3, "report": [], "request": [], "resourc": [], "respons": [], "return": [], "right": [], "savedmodel": 5, "scope": [], "share": [], "should": [], "stage": 5, "standard": [], "start": 3, "structur": 2, "style": [], "support": [1, 3, 6], "synthet": [], "task": 7, "temporari": [], "test": [], "text": [3, 5], "train": 3, "transform": 6, "two": 5, "unit": [], "us": 5, "util": 7, "v0": 0, "verif": [], "via": 4, "visual": 7, "vocab": 1, "warn": [], "what": [], "word": 2, "your": 3, "zoo": [3, 5]}}) \ No newline at end of file +Search.setIndex({"alltitles": {"1. Correction": [[2, "correction"]], "2. Warning": [[2, "warning"]], "3. Temporary Ban": [[2, "temporary-ban"]], "4. Permanent Ban": [[2, "permanent-ban"]], "AWS Lambda": [[14, null]], "Advanced options": [[19, "advanced-options"]], "Args:": [[7, "args"], [7, "id4"], [7, "id7"], [7, "id10"], [7, "id13"], [7, "id16"], [7, "id19"], [7, "id22"], [7, "id25"], [7, "id29"], [7, "id32"], [7, "id37"], [7, "id40"], [7, "id46"], [7, "id49"], [7, "id50"], [7, "id51"], [7, "id54"], [7, "id57"], [7, "id60"], [7, "id61"], [8, "args"], [8, "id2"], [8, "id3"], [8, "id4"], [8, "id5"], [8, "id6"], [8, "id7"], [8, "id10"], [8, "id12"], [8, "id14"], [8, "id16"], [8, "id20"], [8, "id24"], [8, "id28"], [9, "args"], [9, "id3"], [9, "id8"], [9, "id13"], [9, "id17"], [9, "id21"], [9, "id26"], [9, "id31"], [9, "id36"], [9, "id41"], [9, "id46"], [9, "id50"], [9, "id54"], [9, "id59"], [9, "id63"], [9, "id68"], [9, "id73"], [9, "id77"], [9, "id81"], [9, "id85"], [9, "id90"], [9, "id95"], [9, "id99"], [9, "id104"], [9, "id109"], [9, "id114"], [9, "id119"], [9, "id123"], [9, "id127"], [9, "id132"], [9, "id137"], [9, "id142"], [9, "id146"], [9, "id150"], [9, "id155"], [9, "id159"], [9, "id163"], [9, "id167"], [9, "id169"], [9, "id171"], [9, "id173"], [10, "args"], [10, "id1"], [10, "id2"], [10, "id3"], [10, "id4"], [10, "id5"], [10, "id6"], [10, "id7"], [10, "id8"], [10, "id9"], [10, "id10"], [10, "id11"], [10, "id12"], [10, "id13"], [10, "id14"], [10, "id15"], [10, "id16"], [10, "id17"], [10, "id18"], [10, "id19"], [11, "args"], [11, "id3"], [11, "id4"], [11, "id5"], [11, "id6"], [11, "id7"], [11, "id8"], [11, "id9"]], "Artefact": [[8, "artefact"]], "ArtefactDetection": [[16, "artefactdetection"]], "Attribution": [[2, "attribution"]], "Available Datasets": [[17, "available-datasets"]], "Available architectures": [[19, "available-architectures"], [19, "id1"], [19, "id2"]], "Available contribution modules": [[16, "available-contribution-modules"]], "Block": [[8, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[17, null]], "Choosing the right model": [[19, null]], "Classification": [[15, "classification"]], "Code quality": [[3, "code-quality"]], "Code style verification": [[3, "code-style-verification"]], "Codebase structure": [[3, "codebase-structure"]], "Commits": [[3, "commits"]], "Community resources": [[1, null]], "Composing transformations": [[10, "composing-transformations"]], "Continuous Integration": [[3, "continuous-integration"]], "Contributing to docTR": [[3, null]], "Contributor Covenant Code of Conduct": [[2, null]], "Custom dataset loader": [[7, "custom-dataset-loader"]], "Custom orientation classification models": [[13, "custom-orientation-classification-models"]], "Data Loading": [[17, "data-loading"]], "Dataloader": [[7, "dataloader"]], "Detection": [[15, "detection"], [17, "detection"]], "Detection predictors": [[19, "detection-predictors"]], "Developer mode installation": [[3, "developer-mode-installation"]], "Developing docTR": [[3, "developing-doctr"]], "Document": [[8, "document"]], "Document structure": [[8, "document-structure"]], "End-to-End OCR": [[19, "end-to-end-ocr"]], "Enforcement": [[2, "enforcement"]], "Enforcement Guidelines": [[2, "enforcement-guidelines"]], "Enforcement Responsibilities": [[2, "enforcement-responsibilities"]], "Export to ONNX": [[18, "export-to-onnx"]], "Feature requests & bug report": [[3, "feature-requests-bug-report"]], "Feedback": [[3, "feedback"]], "File reading": [[8, "file-reading"]], "Half-precision": [[18, "half-precision"]], "Installation": [[4, null]], "Integrate contributions into your pipeline": [[16, null]], "Let\u2019s connect": [[3, "let-s-connect"]], "Line": [[8, "line"]], "Loading from Huggingface Hub": [[15, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[13, "loading-your-custom-trained-model"]], "Loading your custom trained orientation classification model": [[13, "loading-your-custom-trained-orientation-classification-model"]], "Main Features": [[5, "main-features"]], "Model optimization": [[18, "model-optimization"]], "Model zoo": [[5, "model-zoo"]], "Modifying the documentation": [[3, "modifying-the-documentation"]], "Naming conventions": [[15, "naming-conventions"]], "OCR": [[17, "ocr"]], "Object Detection": [[17, "object-detection"]], "Our Pledge": [[2, "our-pledge"]], "Our Standards": [[2, "our-standards"]], "Page": [[8, "page"]], "Preparing your model for inference": [[18, null]], "Prerequisites": [[4, "prerequisites"]], "Pretrained community models": [[15, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[15, "pushing-to-the-huggingface-hub"]], "Questions": [[3, "questions"]], "Recognition": [[15, "recognition"], [17, "recognition"]], "Recognition predictors": [[19, "recognition-predictors"]], "Returns:": [[7, "returns"], [8, "returns"], [8, "id11"], [8, "id13"], [8, "id15"], [8, "id19"], [8, "id23"], [8, "id27"], [8, "id31"], [9, "returns"], [9, "id6"], [9, "id11"], [9, "id16"], [9, "id20"], [9, "id24"], [9, "id29"], [9, "id34"], [9, "id39"], [9, "id44"], [9, "id49"], [9, "id53"], [9, "id57"], [9, "id62"], [9, "id66"], [9, "id71"], [9, "id76"], [9, "id80"], [9, "id84"], [9, "id88"], [9, "id93"], [9, "id98"], [9, "id102"], [9, "id107"], [9, "id112"], [9, "id117"], [9, "id122"], [9, "id126"], [9, "id130"], [9, "id135"], [9, "id140"], [9, "id145"], [9, "id149"], [9, "id153"], [9, "id158"], [9, "id162"], [9, "id166"], [9, "id168"], [9, "id170"], [9, "id172"], [11, "returns"]], "Scope": [[2, "scope"]], "Share your model with the community": [[15, null]], "Supported Vocabs": [[7, "supported-vocabs"]], "Supported contribution modules": [[6, "supported-contribution-modules"]], "Supported datasets": [[5, "supported-datasets"]], "Supported transformations": [[10, "supported-transformations"]], "Synthetic dataset generator": [[7, "synthetic-dataset-generator"], [17, "synthetic-dataset-generator"]], "Task evaluation": [[11, "task-evaluation"]], "Text Detection": [[19, "text-detection"]], "Text Recognition": [[19, "text-recognition"]], "Text detection models": [[5, "text-detection-models"]], "Text recognition models": [[5, "text-recognition-models"]], "Train your own model": [[13, null]], "Two-stage approaches": [[19, "two-stage-approaches"]], "Unit tests": [[3, "unit-tests"]], "Use your own datasets": [[17, "use-your-own-datasets"]], "Using your ONNX exported model": [[18, "using-your-onnx-exported-model"]], "Via Conda (Only for Linux)": [[4, "via-conda-only-for-linux"]], "Via Git": [[4, "via-git"]], "Via Python Package": [[4, "via-python-package"]], "Visualization": [[11, "visualization"]], "What should I do with the output?": [[19, "what-should-i-do-with-the-output"]], "Word": [[8, "word"]], "docTR Notebooks": [[12, null]], "docTR Vocabs": [[7, "id62"]], "docTR: Document Text Recognition": [[5, null]], "doctr.contrib": [[6, null]], "doctr.datasets": [[7, null], [7, "datasets"]], "doctr.io": [[8, null]], "doctr.models": [[9, null]], "doctr.models.classification": [[9, "doctr-models-classification"]], "doctr.models.detection": [[9, "doctr-models-detection"]], "doctr.models.factory": [[9, "doctr-models-factory"]], "doctr.models.recognition": [[9, "doctr-models-recognition"]], "doctr.models.zoo": [[9, "doctr-models-zoo"]], "doctr.transforms": [[10, null]], "doctr.utils": [[11, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.10.0 (2024-10-21)": [[0, "v0-10-0-2024-10-21"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]], "v0.7.0 (2023-09-09)": [[0, "v0-7-0-2023-09-09"]], "v0.8.0 (2024-02-28)": [[0, "v0-8-0-2024-02-28"]], "v0.8.1 (2024-03-04)": [[0, "v0-8-1-2024-03-04"]], "v0.9.0 (2024-08-08)": [[0, "v0-9-0-2024-08-08"]]}, "docnames": ["changelog", "community/resources", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/contrib", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_contrib_modules", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "community/resources.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/contrib.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_contrib_modules.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[8, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[8, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[10, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[7, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[10, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[10, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[7, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[7, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[9, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[7, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[7, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[8, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[8, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[7, "doctr.datasets.encode_sequences", false]], "fast_base() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_base", false]], "fast_small() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_small", false]], "fast_tiny() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_tiny", false]], "from_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[7, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[10, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[10, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[7, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[7, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[7, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[7, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[7, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[9, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[10, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[8, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet34() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[7, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_crop_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_crop_orientation", false]], "mobilenet_v3_small_page_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_page_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[10, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[9, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[7, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[10, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[8, "doctr.io.Page", false]], "page_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.page_orientation_predictor", false]], "parseq() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[10, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[10, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[10, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[10, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[10, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[10, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[10, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[10, "doctr.transforms.RandomJpegQuality", false]], "randomresize (class in doctr.transforms)": [[10, "doctr.transforms.RandomResize", false]], "randomrotate (class in doctr.transforms)": [[10, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[10, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[10, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[8, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[8, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[8, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[7, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[10, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[8, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[8, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[7, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[7, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[7, "doctr.datasets.SVT", false]], "synthtext (class in doctr.datasets)": [[7, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.TextMatch", false]], "textnet_base() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_base", false]], "textnet_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_small", false]], "textnet_tiny() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_tiny", false]], "togray (class in doctr.transforms)": [[10, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[11, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_small", false]], "wildreceipt (class in doctr.datasets)": [[7, "doctr.datasets.WILDRECEIPT", false]], "word (class in doctr.io)": [[8, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[7, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[7, 0, 1, "", "CORD"], [7, 0, 1, "", "CharacterGenerator"], [7, 0, 1, "", "DetectionDataset"], [7, 0, 1, "", "DocArtefacts"], [7, 0, 1, "", "FUNSD"], [7, 0, 1, "", "IC03"], [7, 0, 1, "", "IC13"], [7, 0, 1, "", "IIIT5K"], [7, 0, 1, "", "IIITHWS"], [7, 0, 1, "", "IMGUR5K"], [7, 0, 1, "", "MJSynth"], [7, 0, 1, "", "OCRDataset"], [7, 0, 1, "", "RecognitionDataset"], [7, 0, 1, "", "SROIE"], [7, 0, 1, "", "SVHN"], [7, 0, 1, "", "SVT"], [7, 0, 1, "", "SynthText"], [7, 0, 1, "", "WILDRECEIPT"], [7, 0, 1, "", "WordGenerator"], [7, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[7, 0, 1, "", "DataLoader"]], "doctr.io": [[8, 0, 1, "", "Artefact"], [8, 0, 1, "", "Block"], [8, 0, 1, "", "Document"], [8, 0, 1, "", "DocumentFile"], [8, 0, 1, "", "Line"], [8, 0, 1, "", "Page"], [8, 0, 1, "", "Word"], [8, 1, 1, "", "decode_img_as_tensor"], [8, 1, 1, "", "read_html"], [8, 1, 1, "", "read_img_as_numpy"], [8, 1, 1, "", "read_img_as_tensor"], [8, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[8, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[8, 2, 1, "", "from_images"], [8, 2, 1, "", "from_pdf"], [8, 2, 1, "", "from_url"]], "doctr.io.Page": [[8, 2, 1, "", "show"]], "doctr.models": [[9, 1, 1, "", "kie_predictor"], [9, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[9, 1, 1, "", "crop_orientation_predictor"], [9, 1, 1, "", "magc_resnet31"], [9, 1, 1, "", "mobilenet_v3_large"], [9, 1, 1, "", "mobilenet_v3_large_r"], [9, 1, 1, "", "mobilenet_v3_small"], [9, 1, 1, "", "mobilenet_v3_small_crop_orientation"], [9, 1, 1, "", "mobilenet_v3_small_page_orientation"], [9, 1, 1, "", "mobilenet_v3_small_r"], [9, 1, 1, "", "page_orientation_predictor"], [9, 1, 1, "", "resnet18"], [9, 1, 1, "", "resnet31"], [9, 1, 1, "", "resnet34"], [9, 1, 1, "", "resnet50"], [9, 1, 1, "", "textnet_base"], [9, 1, 1, "", "textnet_small"], [9, 1, 1, "", "textnet_tiny"], [9, 1, 1, "", "vgg16_bn_r"], [9, 1, 1, "", "vit_b"], [9, 1, 1, "", "vit_s"]], "doctr.models.detection": [[9, 1, 1, "", "db_mobilenet_v3_large"], [9, 1, 1, "", "db_resnet50"], [9, 1, 1, "", "detection_predictor"], [9, 1, 1, "", "fast_base"], [9, 1, 1, "", "fast_small"], [9, 1, 1, "", "fast_tiny"], [9, 1, 1, "", "linknet_resnet18"], [9, 1, 1, "", "linknet_resnet34"], [9, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[9, 1, 1, "", "from_hub"], [9, 1, 1, "", "login_to_hub"], [9, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[9, 1, 1, "", "crnn_mobilenet_v3_large"], [9, 1, 1, "", "crnn_mobilenet_v3_small"], [9, 1, 1, "", "crnn_vgg16_bn"], [9, 1, 1, "", "master"], [9, 1, 1, "", "parseq"], [9, 1, 1, "", "recognition_predictor"], [9, 1, 1, "", "sar_resnet31"], [9, 1, 1, "", "vitstr_base"], [9, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[10, 0, 1, "", "ChannelShuffle"], [10, 0, 1, "", "ColorInversion"], [10, 0, 1, "", "Compose"], [10, 0, 1, "", "GaussianBlur"], [10, 0, 1, "", "GaussianNoise"], [10, 0, 1, "", "LambdaTransformation"], [10, 0, 1, "", "Normalize"], [10, 0, 1, "", "OneOf"], [10, 0, 1, "", "RandomApply"], [10, 0, 1, "", "RandomBrightness"], [10, 0, 1, "", "RandomContrast"], [10, 0, 1, "", "RandomCrop"], [10, 0, 1, "", "RandomGamma"], [10, 0, 1, "", "RandomHorizontalFlip"], [10, 0, 1, "", "RandomHue"], [10, 0, 1, "", "RandomJpegQuality"], [10, 0, 1, "", "RandomResize"], [10, 0, 1, "", "RandomRotate"], [10, 0, 1, "", "RandomSaturation"], [10, 0, 1, "", "RandomShadow"], [10, 0, 1, "", "Resize"], [10, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[11, 0, 1, "", "DetectionMetric"], [11, 0, 1, "", "LocalizationConfusion"], [11, 0, 1, "", "OCRMetric"], [11, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.visualization": [[11, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [2, 8, 9, 11, 15, 18], "0": [2, 4, 7, 10, 11, 13, 16, 17, 19], "00": 19, "01": 19, "0123456789": 7, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "02562": 9, "03": 19, "035": 19, "0361328125": 19, "04": 19, "05": 19, "06": 19, "06640625": 19, "07": 19, "08": [10, 19], "09": 19, "0966796875": 19, "1": [7, 8, 9, 10, 11, 13, 17, 19], "10": [4, 7, 11, 19], "100": [7, 10, 11, 17, 19], "1000": 19, "101": 7, "1024": [9, 13, 19], "104": 7, "106": 7, "108": 7, "1095": 17, "11": 19, "110": 11, "1107": 17, "114": 7, "115": 7, "1156": 17, "116": 7, "118": 7, "11800h": 19, "11th": 19, "12": 19, "120": 7, "123": 7, "126": 7, "1268": 17, "128": [9, 13, 18, 19], "13": 19, "130": 7, "13068": 17, "131": 7, "1337891": 17, "1357421875": 19, "1396484375": 19, "14": 19, "1420": 19, "14470v1": 7, "149": 17, "15": 19, "150": [11, 19], "1552": 19, "16": [9, 18, 19], "1630859375": 19, "1684": 19, "16x16": 9, "17": 19, "1778": 19, "1782": 19, "18": [9, 19], "185546875": 19, "1900": 19, "1910": 9, "19342": 17, "19370": 17, "195": 7, "19598": 17, "199": 19, "1999": 19, "2": [4, 5, 7, 8, 9, 10, 16, 19], "20": 19, "200": 11, "2000": 17, "2003": [5, 7], "2012": 7, "2013": [5, 7], "2015": 7, "2019": 5, "2023": 1, "207901": 17, "21": 19, "2103": 7, "2186": 17, "21888": 17, "22": 19, "224": [9, 10], "225": 10, "22672": 17, "229": [10, 17], "23": 19, "233": 17, "236": 7, "24": 19, "246": 17, "249": 17, "25": 19, "2504": 19, "255": [8, 9, 10, 11, 19], "256": 9, "257": 17, "26": 19, "26032": 17, "264": 13, "27": 19, "2700": 17, "2710": 19, "2749": 13, "28": 19, "287": 13, "29": 19, "296": 13, "299": 13, "2d": 19, "3": [4, 5, 8, 9, 10, 11, 18, 19], "30": 19, "300": 17, "3000": 17, "301": 13, "30595": 19, "30ghz": 19, "31": 9, "32": [7, 9, 10, 13, 17, 18, 19], "3232421875": 19, "33": [10, 19], "33402": 17, "33608": 17, "34": [9, 19], "340": 19, "3456": 19, "3515625": 19, "36": 19, "360": 17, "37": [7, 19], "38": 19, "39": 19, "4": [9, 10, 11, 19], "40": 19, "406": 10, "41": 19, "42": 19, "43": 19, "44": 19, "45": 19, "456": 10, "46": 19, "47": 19, "472": 17, "48": [7, 19], "485": 10, "49": 19, "49377": 17, "5": [7, 10, 11, 16, 19], "50": [9, 17, 19], "51": 19, "51171875": 19, "512": 9, "52": [7, 19], "529": 19, "53": 19, "54": 19, "540": 19, "5478515625": 19, "55": 19, "56": 19, "57": 19, "58": [7, 19], "580": 19, "5810546875": 19, "583": 19, "59": 19, "597": 19, "5k": [5, 7], "5m": 19, "6": [10, 19], "60": 10, "600": [9, 11, 19], "61": 19, "62": 19, "626": 17, "63": 19, "64": [9, 10, 19], "641": 19, "647": 17, "65": 19, "66": 19, "67": 19, "68": 19, "69": 19, "693": 13, "694": 13, "695": 13, "6m": 19, "7": 19, "70": [7, 11, 19], "707470": 17, "71": [7, 19], "7100000": 17, "7141797": 17, "7149": 17, "72": 19, "72dpi": 8, "73": 19, "73257": 17, "74": 19, "75": [10, 19], "7581382": 17, "76": 19, "77": 19, "772": 13, "772875": 17, "78": 19, "785": 13, "79": 19, "793533": 17, "796": 17, "798": 13, "7m": 19, "8": [9, 10, 19], "80": 19, "800": [9, 11, 17, 19], "81": 19, "82": 19, "83": 19, "84": 19, "849": 17, "85": 19, "8564453125": 19, "857": 19, "85875": 17, "86": 19, "8603515625": 19, "87": 19, "8707": 17, "88": 19, "89": 19, "9": [10, 19], "90": 19, "90k": 7, "90kdict32px": 7, "91": 19, "914085328578949": 19, "92": 19, "93": 19, "94": [7, 19], "95": [11, 19], "9578408598899841": 19, "96": 19, "97": 19, "98": 19, "99": 19, "9949972033500671": 19, "A": [2, 3, 5, 7, 8, 9, 12, 18], "As": 3, "Be": 19, "Being": 2, "By": 14, "For": [2, 3, 4, 13, 19], "If": [3, 8, 9, 13, 19], "In": [3, 7, 17], "It": [10, 15, 16, 18], "Its": [5, 9], "No": [2, 19], "Of": 7, "Or": [16, 18], "The": [2, 3, 7, 8, 11, 14, 16, 17, 18, 19], "Then": 9, "To": [3, 4, 14, 15, 16, 18, 19], "_": [2, 7, 9], "__call__": 19, "_build": 3, "_i": 11, "ab": 7, "abc": 18, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "abdef": [7, 17], "abl": [17, 19], "about": [2, 17, 19], "abov": 19, "abstract": 1, "abstractdataset": 7, "abus": 2, "accept": 2, "access": [5, 8, 17, 19], "account": [2, 15], "accur": 19, "accuraci": 11, "achiev": 18, "act": 2, "action": 2, "activ": 5, "ad": [3, 9, 10], "adapt": 2, "add": [10, 11, 15, 19], "add_hook": 19, "add_label": 11, "addit": [3, 4, 8, 16, 19], "addition": [3, 19], "address": [2, 8], "adjust": 10, "advanc": 2, "advantag": 18, "advis": 3, "aesthet": [5, 7], "affect": 2, "after": [15, 19], "ag": 2, "again": 9, "aggreg": [11, 17], "aggress": 2, "align": [2, 8, 10], "all": [2, 3, 6, 7, 8, 10, 11, 16, 17, 19], "allow": [2, 18], "along": 19, "alreadi": [3, 18], "also": [2, 9, 15, 16, 17, 19], "alwai": 17, "an": [2, 3, 5, 7, 8, 9, 11, 16, 18, 19], "analysi": [8, 16], "ancient_greek": 7, "andrej": 1, "angl": [8, 10], "ani": [2, 7, 8, 9, 10, 11, 18, 19], "annot": 7, "anot": 17, "anoth": [9, 13, 17], "answer": 2, "anyascii": 11, "anyon": 5, "anyth": 16, "api": [3, 5], "apolog": 2, "apologi": 2, "app": 3, "appear": 2, "appli": [2, 7, 10], "applic": [5, 9], "appoint": 2, "appreci": 15, "appropri": [2, 3, 19], "ar": [2, 3, 4, 6, 7, 8, 10, 11, 12, 16, 17, 19], "arab": 7, "arabic_diacrit": 7, "arabic_lett": 7, "arabic_punctu": 7, "arbitrarili": [5, 9], "arch": [9, 15], "architectur": [5, 9, 15, 16], "area": 19, "argument": [7, 8, 9, 11, 13, 19], "around": 2, "arrai": [8, 10, 11], "art": [5, 16], "artefact": [11, 16, 19], "artefact_typ": 8, "articl": 1, "artifici": [5, 7], "arxiv": [7, 9], "asarrai": 11, "ascii_lett": 7, "aspect": [5, 9, 10, 19], "assess": 11, "assign": 11, "associ": 8, "assum": 9, "assume_straight_pag": [9, 13, 19], "astyp": [9, 11, 19], "attack": 2, "attend": [5, 9], "attent": [2, 9], "autom": 5, "automat": 19, "autoregress": [5, 9], "avail": [2, 5, 6, 10], "averag": [10, 19], "avoid": [2, 4], "aw": [5, 19], "awar": 19, "azur": 19, "b": [9, 11, 19], "b_j": 11, "back": 3, "backbon": 9, "backend": 19, "background": 17, "bangla": 7, "bar": 16, "bar_cod": 17, "baranovskij": 1, "base": [5, 9, 16], "baselin": [5, 9, 19], "batch": [7, 9, 10, 16, 17, 19], "batch_siz": [7, 9, 13, 16, 17, 18], "bblanchon": 4, "bbox": 19, "becaus": 14, "been": [3, 11, 17, 19], "befor": [7, 9, 10, 19], "begin": 11, "behavior": [2, 19], "being": [11, 19], "belong": 19, "benchmark": 19, "best": [1, 2], "better": [12, 19], "between": [10, 11, 19], "bgr": 8, "bilinear": 10, "bin_thresh": 19, "binar": [5, 9, 19], "binari": [8, 18, 19], "bit": 18, "block": [11, 19], "block_1_1": 19, "blur": 10, "bmvc": 7, "bn": 15, "bodi": [2, 19], "bool": [7, 8, 9, 10, 11], "boolean": [9, 19], "both": [5, 7, 10, 17, 19], "bottom": [9, 19], "bound": [7, 8, 9, 10, 11, 16, 17, 19], "box": [7, 8, 9, 10, 11, 16, 17, 19], "box_thresh": 19, "bright": 10, "browser": [3, 5], "build": [3, 4, 18], "built": 3, "byte": [8, 19], "c": [4, 8, 11], "c_j": 11, "cach": [3, 7, 14], "cache_sampl": 7, "call": 18, "callabl": [7, 10], "can": [3, 4, 13, 14, 15, 16, 17, 19], "capabl": [3, 12, 19], "case": [7, 11], "cf": 19, "cfg": 19, "challeng": 7, "challenge2_test_task12_imag": 7, "challenge2_test_task1_gt": 7, "challenge2_training_task12_imag": 7, "challenge2_training_task1_gt": 7, "chang": [14, 19], "channel": [2, 3, 8, 10], "channel_prior": 4, "channelshuffl": 10, "charact": [5, 7, 8, 11, 17, 19], "charactergener": [7, 17], "characterist": 2, "charg": 19, "charset": 19, "chart": 8, "check": [3, 15, 19], "checkpoint": 9, "chip": 4, "christian": 1, "ci": 3, "clarifi": 2, "clariti": 2, "class": [2, 7, 8, 10, 11, 19], "class_nam": 13, "classif": [17, 19], "classmethod": 8, "clear": 3, "clone": 4, "close": 3, "co": 15, "code": [5, 8, 16], "codecov": 3, "colab": 12, "collate_fn": 7, "collect": [8, 16], "color": 10, "colorinvers": 10, "column": 8, "com": [2, 4, 8, 9, 15], "combin": 19, "command": [3, 16], "comment": 2, "commit": 2, "common": [2, 10, 11, 18], "commun": 2, "compar": 5, "comparison": [11, 19], "competit": 7, "compil": [12, 19], "complaint": 2, "complementari": 11, "complet": 3, "compon": 19, "compos": [7, 19], "comprehens": 19, "comput": [7, 11, 18, 19], "conf_threshold": 16, "confid": [8, 19], "config": [4, 9], "configur": 9, "confus": 11, "consecut": [10, 19], "consequ": 2, "consid": [2, 3, 7, 8, 11, 19], "consist": 19, "consolid": [5, 7], "constant": 10, "construct": 2, "contact": 2, "contain": [1, 6, 7, 12, 17, 19], "content": [7, 8, 19], "context": 9, "contib": 4, "continu": 2, "contrast": 10, "contrast_factor": 10, "contrib": [4, 16], "contribut": 2, "contributor": 3, "convers": 8, "convert": [8, 10], "convolut": 9, "cool": 1, "coordin": [8, 19], "cord": [5, 7, 17, 19], "core": [11, 19], "corner": 19, "correct": 10, "correspond": [4, 8, 10, 19], "could": [2, 16], "counterpart": 11, "cover": 3, "coverag": 3, "cpu": [5, 13, 18], "creat": [1, 15], "crnn": [5, 9, 15], "crnn_mobilenet_v3_larg": [9, 15, 19], "crnn_mobilenet_v3_smal": [9, 18, 19], "crnn_vgg16_bn": [9, 13, 15, 19], "crop": [8, 9, 10, 13, 17, 19], "crop_orient": [8, 19], "crop_orientation_predictor": [9, 13], "crop_param": 13, "cuda": 18, "currenc": 7, "current": [3, 13, 19], "custom": [15, 16, 18, 19], "custom_crop_orientation_model": 13, "custom_page_orientation_model": 13, "customhook": 19, "cvit": 5, "czczup": 9, "czech": 7, "d": [7, 17], "danish": 7, "data": [5, 7, 8, 10, 11, 13, 15], "dataload": 17, "dataset": [9, 13, 19], "dataset_info": 7, "date": [13, 19], "db": 15, "db_mobilenet_v3_larg": [9, 15, 19], "db_resnet34": 19, "db_resnet50": [9, 13, 15, 19], "dbnet": [5, 9], "deal": [12, 19], "decis": 2, "decod": 8, "decode_img_as_tensor": 8, "dedic": 18, "deem": 2, "deep": [9, 19], "def": 19, "default": [4, 8, 13, 14, 19], "defer": 17, "defin": [11, 18], "degre": [8, 10, 19], "degress": 8, "delet": 3, "delimit": 19, "delta": 10, "demo": [3, 5], "demonstr": 2, "depend": [3, 4, 5, 19], "deploi": 3, "deploy": 5, "derogatori": 2, "describ": 9, "descript": 12, "design": 10, "desir": 8, "det_arch": [9, 13, 15, 18], "det_b": 19, "det_model": [13, 15, 18], "det_param": 13, "det_predictor": [13, 19], "detail": [13, 19], "detect": [1, 7, 8, 11, 12, 13, 16], "detect_languag": 9, "detect_orient": [9, 13, 19], "detection_predictor": [9, 19], "detection_task": [7, 17], "detectiondataset": [7, 17], "detectionmetr": 11, "detectionpredictor": [9, 13], "detector": [5, 9, 16], "deterior": 9, "determin": 2, "dev": [3, 14], "develop": 4, "deviat": 10, "devic": 18, "dict": [8, 11, 19], "dictionari": [8, 11], "differ": 2, "differenti": [5, 9], "digit": [5, 7, 17], "dimens": [8, 11, 19], "dimension": 10, "direct": 7, "directli": [15, 19], "directori": [3, 14], "disabl": [2, 14, 19], "disable_crop_orient": 19, "disable_page_orient": 19, "disclaim": 19, "discuss": 3, "disparag": 2, "displai": [8, 11], "display_artefact": 11, "distribut": 10, "div": 19, "divers": 2, "divid": 8, "do": [3, 4, 9], "doc": [3, 8, 16, 18, 19], "docartefact": [7, 17], "docstr": 3, "doctr": [1, 4, 13, 14, 15, 16, 17, 18, 19], "doctr_cache_dir": 14, "doctr_multiprocessing_dis": 14, "document": [1, 7, 9, 11, 12, 13, 16, 17, 18, 19], "documentbuild": 19, "documentfil": [8, 13, 15, 16, 18], "doesn": 18, "don": [13, 19], "done": 10, "download": [7, 17], "downsiz": 9, "draw": 10, "drop": 7, "drop_last": 7, "dtype": [8, 9, 10, 11, 18], "dual": [5, 7], "dummi": 15, "dummy_img": 19, "dummy_input": 18, "dure": 2, "dutch": 7, "dynam": [7, 16], "dynamic_seq_length": 7, "e": [2, 3, 4, 8, 9], "each": [5, 7, 8, 9, 10, 11, 17, 19], "eas": 3, "easi": [5, 11, 15, 18], "easili": [8, 11, 13, 15, 17, 19], "econom": 2, "edit": 2, "educ": 2, "effect": 19, "effici": [3, 5, 7, 9], "either": [11, 19], "element": [7, 8, 9, 19], "els": [3, 16], "email": 2, "empathi": 2, "en": 19, "enabl": [7, 8], "enclos": 8, "encod": [5, 7, 8, 9, 19], "encode_sequ": 7, "encount": 3, "encrypt": 8, "end": [5, 7, 9, 11], "english": [7, 17], "enough": [3, 19], "ensur": 3, "entri": 7, "environ": [2, 14], "eo": 7, "equiv": 19, "estim": 9, "etc": [8, 16], "ethnic": 2, "evalu": [17, 19], "event": 2, "everyon": 2, "everyth": [3, 19], "exact": [11, 19], "exampl": [2, 3, 5, 7, 9, 15, 19], "exchang": 18, "execut": 19, "exist": 15, "expand": 10, "expect": [8, 10, 11], "experi": 2, "explan": [2, 19], "explicit": 2, "exploit": [5, 9], "export": [8, 9, 11, 12, 16, 19], "export_as_straight_box": [9, 19], "export_as_xml": 19, "export_model_to_onnx": 18, "express": [2, 10], "extens": 8, "extern": [2, 17], "extract": [1, 5, 7], "extractor": 9, "f_": 11, "f_a": 11, "factor": 10, "fair": 2, "fairli": 2, "fals": [7, 8, 9, 10, 11, 13, 19], "faq": 2, "fascan": 15, "fast": [5, 7, 9], "fast_bas": [9, 19], "fast_smal": [9, 19], "fast_tini": [9, 19], "faster": [5, 9, 18], "fasterrcnn_mobilenet_v3_large_fpn": 9, "favorit": 19, "featur": [4, 9, 11, 12, 13, 16], "feedback": 2, "feel": [3, 15], "felix92": 15, "few": [18, 19], "figsiz": 11, "figur": [11, 16], "file": [3, 7], "final": 9, "find": [3, 17], "fine": 1, "finnish": 7, "first": [3, 7], "firsthand": 7, "fit": [9, 19], "flag": 19, "flip": 10, "float": [8, 10, 11, 18], "float32": [8, 9, 10, 18], "fn": 10, "focu": 15, "focus": [2, 7], "folder": 7, "follow": [2, 3, 4, 7, 10, 11, 13, 14, 15, 16, 19], "font": 7, "font_famili": 7, "foral": 11, "forc": 3, "forg": 4, "form": [5, 7, 19], "format": [8, 11, 13, 17, 18, 19], "forpost": [5, 7], "forum": 3, "found": 1, "fp16": 18, "frac": 11, "framework": [4, 15, 17, 19], "free": [2, 3, 15], "french": [7, 13, 15, 19], "friendli": 5, "from": [1, 2, 5, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19], "from_hub": [9, 15], "from_imag": [8, 15, 16, 18], "from_pdf": 8, "from_url": 8, "full": [7, 11, 19], "function": [7, 10, 11, 16], "funsd": [5, 7, 17, 19], "further": 17, "futur": 7, "g": [8, 9], "g_": 11, "g_x": 11, "gallagh": 1, "gamma": 10, "gaussian": 10, "gaussianblur": 10, "gaussiannois": 10, "gen": 19, "gender": 2, "gener": [3, 5, 8, 9], "generic_cyrillic_lett": 7, "geometri": [5, 8, 19], "geq": 11, "german": [7, 13, 15], "get": [18, 19], "git": 15, "github": [3, 4, 9, 15], "give": [2, 16], "given": [7, 8, 10, 11, 19], "global": 9, "go": 19, "good": 18, "googl": 3, "googlevis": 5, "gpu": [5, 16, 18], "gracefulli": 2, "graph": [5, 7, 8], "grayscal": 10, "ground": 11, "groung": 11, "group": [5, 19], "gt": 11, "gt_box": 11, "gt_label": 11, "guid": 3, "guidanc": 17, "gvision": 19, "h": [8, 9, 10], "h_": 11, "ha": [3, 7, 11, 17], "handl": [12, 17, 19], "handwrit": 7, "handwritten": 17, "harass": 2, "hardwar": 19, "harm": 2, "hat": 11, "have": [2, 3, 11, 13, 15, 17, 18, 19], "head": [9, 19], "healthi": 2, "hebrew": 7, "height": [8, 10], "hello": [11, 19], "help": 18, "here": [6, 10, 12, 16, 17, 19], "hf": 9, "hf_hub_download": 9, "high": 8, "higher": [4, 7, 19], "hindi": 7, "hindi_digit": 7, "hocr": 19, "hook": 19, "horizont": [8, 10, 19], "hous": 7, "how": [1, 3, 12, 13, 15, 17], "howev": 17, "hsv": 10, "html": [2, 3, 4, 8, 19], "http": [2, 4, 7, 8, 9, 15, 19], "hub": 9, "hue": 10, "huggingfac": 9, "hw": 7, "i": [2, 3, 7, 8, 9, 10, 11, 14, 15, 16, 17, 18], "i7": 19, "ibrahimov": 1, "ic03": [5, 7, 17], "ic13": [5, 7, 17], "icdar": [5, 7], "icdar2019": 7, "id": 19, "ident": 2, "identifi": 5, "iiit": [5, 7], "iiit5k": [7, 17], "iiithw": [5, 7, 17], "imag": [1, 5, 7, 8, 9, 10, 11, 15, 16, 17, 19], "imagenet": 9, "imageri": 2, "images_90k_norm": 7, "img": [7, 10, 17, 18], "img_cont": 8, "img_fold": [7, 17], "img_path": 8, "img_transform": 7, "imgur5k": [5, 7, 17], "imgur5k_annot": 7, "imlist": 7, "impact": 2, "implement": [7, 8, 9, 10, 11, 19], "import": [7, 8, 9, 10, 11, 13, 15, 16, 17, 18, 19], "improv": 9, "inappropri": 2, "incid": 2, "includ": [2, 7, 17, 18], "inclus": 2, "increas": 10, "independ": 10, "index": [3, 8], "indic": 11, "individu": 2, "infer": [5, 9, 10, 16, 19], "inform": [1, 2, 3, 5, 7, 17], "input": [3, 8, 9, 10, 18, 19], "input_crop": 9, "input_pag": [9, 11, 19], "input_shap": 18, "input_tensor": 9, "inspir": [2, 10], "instal": [15, 16, 18], "instanc": [2, 19], "instanti": [9, 19], "instead": [7, 8, 9], "insult": 2, "int": [7, 8, 9, 10], "int64": 11, "integ": 11, "integr": [1, 5, 15, 17], "intel": 19, "interact": [2, 8, 11], "interfac": [15, 18], "interoper": 18, "interpol": 10, "interpret": [7, 8], "intersect": 11, "invert": 10, "investig": 2, "invis": 2, "involv": [2, 19], "io": [13, 15, 16, 18], "iou": 11, "iou_thresh": 11, "iou_threshold": 16, "irregular": [5, 9, 17], "isn": 7, "issu": [2, 3, 15], "italian": 7, "iter": [7, 10, 17, 19], "its": [8, 9, 10, 11, 17, 19], "itself": [9, 15], "j": 11, "jame": 1, "job": 3, "join": 3, "jpeg": 10, "jpegqual": 10, "jpg": [7, 8, 15, 18], "json": [7, 17, 19], "json_output": 19, "jump": 3, "just": 2, "kei": [5, 7], "kera": [9, 18], "kernel": [5, 9, 10], "kernel_shap": 10, "keywoard": 9, "keyword": [7, 8, 9, 11], "kie": [9, 13], "kie_predictor": [9, 13], "kiepredictor": 9, "kind": 2, "know": [3, 18], "kwarg": [7, 8, 9, 11], "l": 11, "l_j": 11, "label": [7, 11, 16, 17], "label_fil": [7, 17], "label_fold": 7, "label_path": [7, 17], "labels_path": [7, 17], "ladder": 2, "lambda": 10, "lambdatransform": 10, "lang": 19, "languag": [2, 5, 7, 8, 9, 15, 19], "larg": [9, 15], "largest": 11, "last": [4, 7], "latenc": 9, "later": 3, "latest": 19, "latin": 7, "layer": 18, "layout": 19, "lead": 2, "leader": 2, "learn": [2, 5, 9, 18, 19], "least": 4, "left": [11, 19], "legacy_french": 7, "length": [7, 19], "less": [18, 19], "level": [2, 7, 11, 19], "leverag": 12, "lf": 15, "librari": [3, 4, 12, 13], "light": 5, "lightweight": 18, "like": 2, "limits_": 11, "line": [5, 9, 11, 19], "line_1_1": 19, "link": 13, "linknet": [5, 9], "linknet_resnet18": [9, 13, 18, 19], "linknet_resnet34": [9, 18, 19], "linknet_resnet50": [9, 19], "list": [7, 8, 10, 11, 15], "ll": 11, "load": [5, 7, 9, 16, 18], "load_state_dict": 13, "load_weight": 13, "loc_pr": 19, "local": [3, 5, 7, 9, 11, 17, 19], "localis": 7, "localizationconfus": 11, "locat": [3, 8, 19], "login": 9, "login_to_hub": [9, 15], "logo": [8, 16, 17], "love": 15, "lower": [10, 11, 19], "m": [3, 11, 19], "m1": 4, "macbook": 4, "machin": 18, "made": 5, "magc_resnet31": 9, "mai": [2, 3], "mail": 2, "main": 12, "maintain": 5, "mainten": 3, "make": [2, 3, 11, 13, 14, 15, 18, 19], "mani": [17, 19], "manipul": 19, "map": [7, 9], "map_loc": 13, "master": [5, 9, 19], "match": [11, 19], "mathcal": 11, "matplotlib": [8, 11], "max": [7, 10, 11], "max_angl": 10, "max_area": 10, "max_char": [7, 17], "max_delta": 10, "max_gain": 10, "max_gamma": 10, "max_qual": 10, "max_ratio": 10, "maximum": [7, 10], "maxval": [9, 10], "mbox": 11, "mean": [10, 11, 13], "meaniou": 11, "meant": [8, 18], "measur": 19, "media": 2, "median": 9, "meet": 13, "member": 2, "memori": [14, 18], "mention": 19, "merg": 7, "messag": 3, "meta": 19, "metadata": 18, "metal": 4, "method": [8, 10, 19], "metric": [11, 19], "middl": 19, "might": [18, 19], "min": 10, "min_area": 10, "min_char": [7, 17], "min_gain": 10, "min_gamma": 10, "min_qual": 10, "min_ratio": 10, "min_val": 10, "minde": [1, 2, 4, 5, 9], "minim": [3, 5], "minimalist": [5, 9], "minimum": [4, 7, 10, 11, 19], "minval": 10, "miss": 4, "mistak": 2, "mixed_float16": 18, "mixed_precis": 18, "mjsynth": [5, 7, 17], "mnt": 7, "mobilenet": [9, 15], "mobilenet_v3_larg": 9, "mobilenet_v3_large_r": 9, "mobilenet_v3_smal": [9, 13], "mobilenet_v3_small_crop_orient": [9, 13], "mobilenet_v3_small_page_orient": [9, 13], "mobilenet_v3_small_r": 9, "mobilenetv3": 9, "modal": [5, 7], "mode": 4, "model": [7, 11, 14, 16, 17], "model_nam": [9, 15, 18], "model_path": [16, 18], "moder": 2, "modif": 3, "modifi": [9, 14, 19], "modul": [4, 8, 9, 10, 11, 19], "more": [3, 17, 19], "moscardi": 1, "most": 19, "mozilla": 2, "multi": [5, 9], "multilingu": [7, 15], "multipl": [7, 8, 10, 19], "multipli": 10, "multiprocess": 14, "my": 9, "my_awesome_model": 15, "my_hook": 19, "n": [7, 11], "name": [7, 9, 18, 19], "nation": 2, "natur": [2, 5, 7], "ndarrai": [7, 8, 10, 11], "necessari": [4, 13, 14], "need": [3, 4, 7, 11, 13, 14, 15, 16, 19], "neg": 10, "nest": 19, "netraj": 1, "network": [5, 7, 9, 18], "neural": [5, 7, 9, 18], "new": [3, 11], "next": [7, 17], "nois": 10, "noisi": [5, 7], "non": [5, 7, 8, 9, 10, 11], "none": [7, 8, 9, 10, 11, 19], "normal": [9, 10], "norwegian": 7, "note": [0, 3, 7, 9, 13, 15, 16, 18], "now": 3, "np": [9, 10, 11, 19], "num_output_channel": 10, "num_sampl": [7, 17], "number": [7, 9, 10, 11, 19], "numpi": [8, 9, 11, 19], "o": 4, "obb": 16, "obj_detect": 15, "object": [7, 8, 11, 16, 19], "objectness_scor": [8, 19], "oblig": 2, "obtain": 19, "occupi": 18, "ocr": [1, 5, 7, 9, 11, 15], "ocr_carea": 19, "ocr_db_crnn": 11, "ocr_lin": 19, "ocr_pag": 19, "ocr_par": 19, "ocr_predictor": [9, 13, 15, 18, 19], "ocrdataset": [7, 17], "ocrmetr": 11, "ocrpredictor": [9, 13], "ocrx_word": 19, "offens": 2, "offici": [2, 9], "offlin": 2, "offset": 10, "onc": 19, "one": [3, 7, 9, 10, 13, 15, 19], "oneof": 10, "ones": [7, 11], "onli": [3, 9, 10, 11, 13, 15, 17, 18, 19], "onlin": 2, "onnx": 16, "onnxruntim": [16, 18], "onnxtr": 18, "opac": 10, "opacity_rang": 10, "open": [1, 2, 3, 15, 18], "opinion": 2, "optic": [5, 19], "optim": [5, 19], "option": [7, 9, 13], "order": [3, 7, 8, 10], "org": [2, 7, 9, 19], "organ": 8, "orient": [2, 8, 9, 12, 16, 19], "orientationpredictor": 9, "other": [2, 3], "otherwis": [2, 8, 11], "our": [1, 3, 9, 19], "out": [3, 9, 10, 11, 19], "outpout": 19, "output": [8, 10, 18], "output_s": [8, 10], "outsid": 14, "over": [7, 11, 19], "overal": [2, 9], "overlai": 8, "overview": 16, "overwrit": 13, "overwritten": 15, "own": 5, "p": [10, 19], "packag": [3, 5, 11, 14, 16, 17, 18], "pad": [7, 9, 10, 19], "page": [4, 7, 9, 11, 13, 19], "page1": 8, "page2": 8, "page_1": 19, "page_idx": [8, 19], "page_orientation_predictor": [9, 13], "page_param": 13, "pair": 11, "paper": 9, "par_1_1": 19, "paragraph": 19, "paragraph_break": 19, "parallel": 9, "param": [10, 19], "paramet": [5, 8, 9, 18], "pars": [5, 7], "parseq": [5, 9, 15, 18, 19], "part": [7, 10, 19], "parti": 4, "partial": 19, "particip": 2, "pass": [7, 8, 9, 13, 19], "password": 8, "patch": [9, 11], "path": [7, 8, 16, 17, 18], "path_to_checkpoint": 13, "path_to_custom_model": 18, "path_to_pt": 13, "patil": 1, "pattern": 2, "pdf": [8, 9, 12], "pdfpage": 8, "peopl": 2, "per": [10, 19], "perform": [5, 8, 9, 10, 11, 14, 18, 19], "period": 2, "permiss": 2, "permut": [5, 9], "persian_lett": 7, "person": [2, 17], "phase": 19, "photo": 17, "physic": [2, 8], "pick": 10, "pictur": 8, "pip": [3, 4, 16, 18], "pipelin": 19, "pixel": [8, 10, 19], "pleas": 3, "plot": 11, "plt": 11, "plug": 15, "plugin": 4, "png": 8, "point": 18, "polici": 14, "polish": 7, "polit": 2, "polygon": [7, 11, 19], "pool": 9, "portugues": 7, "posit": [2, 11], "possibl": [3, 11, 15, 19], "post": [2, 19], "postprocessor": 19, "potenti": 9, "power": 5, "ppageno": 19, "pre": [3, 9, 18], "precis": [11, 19], "pred": 11, "pred_box": 11, "pred_label": 11, "predefin": 17, "predict": [8, 9, 11, 19], "predictor": [5, 8, 9, 12, 13, 15, 18], "prefer": 17, "preinstal": 4, "preprocessor": [13, 19], "prerequisit": 15, "present": 12, "preserv": [9, 10, 19], "preserve_aspect_ratio": [8, 9, 10, 13, 19], "pretrain": [5, 9, 11, 13, 18, 19], "pretrained_backbon": [9, 13], "print": 19, "prior": 7, "privaci": 2, "privat": 2, "probabl": [1, 10], "problem": 3, "procedur": 10, "process": [3, 5, 8, 9, 13, 19], "processor": 19, "produc": [12, 19], "product": 18, "profession": 2, "project": [3, 17], "promptli": 2, "proper": 3, "properli": 7, "provid": [2, 3, 5, 15, 16, 17, 19], "public": [2, 5], "publicli": 19, "publish": 2, "pull": 15, "punctuat": 7, "pure": 7, "purpos": 3, "push_to_hf_hub": [9, 15], "py": 15, "pypdfium2": [4, 8], "pyplot": [8, 11], "python": [1, 3, 16], "python3": 15, "pytorch": [4, 5, 9, 10, 13, 15, 18, 19], "q": 3, "qr": [8, 16], "qr_code": 17, "qualiti": 10, "question": 2, "quickli": 5, "quicktour": 12, "r": 19, "race": 2, "ramdisk": 7, "rand": [9, 10, 11, 18, 19], "random": [9, 10, 11, 19], "randomappli": 10, "randombright": 10, "randomcontrast": 10, "randomcrop": 10, "randomgamma": 10, "randomhorizontalflip": 10, "randomhu": 10, "randomjpegqu": 10, "randomli": 10, "randomres": 10, "randomrot": 10, "randomsatur": 10, "randomshadow": 10, "rang": 10, "rassi": 15, "ratio": [9, 10, 19], "raw": [8, 11], "re": 18, "read": [5, 7, 9], "read_html": 8, "read_img_as_numpi": 8, "read_img_as_tensor": 8, "read_pdf": 8, "readi": 18, "real": [1, 5, 9, 10], "realli": 1, "reason": [2, 5, 7], "rebuild": 3, "rebuilt": 3, "recal": [11, 19], "receipt": [5, 7, 19], "reco_arch": [9, 13, 15, 18], "reco_b": 19, "reco_model": [13, 15, 18], "reco_param": 13, "reco_predictor": 13, "recogn": 19, "recognit": [7, 11, 12, 13], "recognition_predictor": [9, 19], "recognition_task": [7, 17], "recognitiondataset": [7, 17], "recognitionpredictor": [9, 13], "rectangular": 9, "reduc": [4, 10], "refer": [3, 4, 13, 15, 16, 17, 19], "regardless": 2, "region": 19, "regroup": 11, "regular": 17, "reject": 2, "rel": [8, 10, 11, 19], "relat": 8, "releas": [0, 4], "relev": 16, "religion": 2, "remov": 2, "render": [8, 19], "repo": 9, "repo_id": [9, 15], "report": 2, "repositori": [7, 9, 15], "repres": [2, 18, 19], "represent": [5, 9], "request": [2, 15], "requir": [4, 10, 18], "research": 5, "residu": 9, "resiz": [10, 19], "resnet": 9, "resnet18": [9, 15], "resnet31": 9, "resnet34": 9, "resnet50": [9, 15], "resolv": 8, "resolve_block": 19, "resolve_lin": 19, "resourc": 17, "respect": 2, "rest": [3, 10, 11], "restrict": 14, "result": [3, 7, 8, 12, 15, 18, 19], "return": 19, "reusabl": 19, "review": 2, "rgb": [8, 10], "rgb_mode": 8, "rgb_output": 8, "right": [2, 9, 11], "roboflow": 1, "robust": [5, 7], "root": 7, "rotat": [7, 8, 9, 10, 11, 12, 13, 17, 19], "run": [3, 4, 9], "same": [3, 8, 11, 17, 18, 19], "sampl": [7, 9, 17, 19], "sample_transform": 7, "sanjin": 1, "sar": [5, 9], "sar_resnet31": [9, 19], "satur": 10, "save": [9, 17], "scale": [8, 9, 10, 11], "scale_rang": 10, "scan": [5, 7], "scene": [5, 7, 9], "score": [8, 11], "script": [3, 17], "seamless": 5, "seamlessli": [5, 19], "search": [1, 9], "searchabl": 12, "sec": 19, "second": 19, "section": [1, 13, 15, 16, 18, 19], "secur": [2, 14], "see": [2, 3], "seen": 19, "segment": [5, 9, 19], "self": 19, "semant": [5, 9], "send": 19, "sens": 11, "sensit": 17, "separ": 19, "sequenc": [5, 7, 8, 9, 11, 19], "sequenti": [10, 19], "seri": 2, "seriou": 2, "set": [2, 4, 7, 9, 11, 14, 16, 19], "set_global_polici": 18, "sever": [8, 10, 19], "sex": 2, "sexual": 2, "shade": 10, "shape": [5, 8, 9, 10, 11, 19], "share": [14, 17], "shift": 10, "shm": 14, "should": [3, 7, 8, 10, 11], "show": [5, 8, 9, 11, 13, 15, 16], "showcas": [3, 12], "shuffl": [7, 10], "side": 11, "signatur": 8, "signific": 17, "simpl": [5, 9, 18], "simpler": 9, "sinc": [7, 17], "singl": [2, 3, 5, 7], "single_img_doc": 18, "size": [2, 7, 8, 10, 16, 19], "skew": 19, "slack": 3, "slightli": 9, "small": [3, 9, 19], "smallest": 8, "snapshot_download": 9, "snippet": 19, "so": [3, 4, 7, 9, 15, 17], "social": 2, "socio": 2, "some": [1, 4, 12, 15, 17], "someth": 3, "somewher": 3, "sort": 2, "sourc": [1, 7, 8, 9, 10, 11, 15], "space": [2, 19], "span": 19, "spanish": 7, "spatial": [5, 7, 8], "specif": [3, 4, 11, 13, 17, 19], "specifi": [2, 7, 8], "speed": [5, 9, 19], "sphinx": 3, "sroie": [5, 7, 17], "stabl": 4, "stackoverflow": 3, "stage": 5, "standalon": 12, "standard": 10, "start": 7, "state": [1, 5, 11, 16], "static": 11, "statist": 1, "statu": 2, "std": [10, 13], "step": 14, "still": 19, "str": [7, 8, 9, 10, 11], "straight": [7, 9, 17, 19], "straighten": 19, "straighten_pag": [9, 13, 19], "straigten_pag": 13, "stream": 8, "street": [5, 7], "strict": 4, "strictli": 11, "string": [7, 8, 11, 19], "strive": 4, "strong": [5, 9], "structur": [18, 19], "subset": [7, 19], "suggest": [3, 15], "sum": 11, "summari": 11, "support": [4, 13, 16, 18, 19], "sustain": 2, "svhn": [5, 7, 17], "svt": [7, 17], "swedish": 7, "symmetr": [9, 10, 19], "symmetric_pad": [9, 10, 19], "synthet": 5, "synthtext": [5, 7, 17], "system": 19, "t": [3, 7, 13, 18, 19], "tabl": [15, 16, 17], "take": [2, 7, 19], "target": [7, 8, 10, 11, 17], "target_s": 7, "task": [5, 7, 9, 15, 17, 19], "task2": 7, "team": 4, "techminde": 4, "templat": [3, 5], "tensor": [7, 8, 10, 19], "tensorflow": [4, 5, 8, 9, 10, 13, 15, 18, 19], "tensorspec": 18, "term": 2, "test": [7, 17], "test_set": 7, "text": [1, 7, 8, 9, 11, 17], "text_output": 19, "textmatch": 11, "textnet": 9, "textnet_bas": 9, "textnet_smal": 9, "textnet_tini": 9, "textract": [5, 19], "textstylebrush": [5, 7], "textual": [5, 7, 8, 9, 19], "tf": [4, 8, 9, 10, 15, 18], "than": [3, 11, 15], "thank": 3, "thei": [2, 11], "them": [7, 19], "thi": [1, 2, 3, 4, 6, 7, 10, 11, 13, 14, 15, 17, 18, 19], "thing": [18, 19], "third": 4, "those": [2, 8, 19], "threaten": 2, "threshold": 19, "through": [2, 10, 16, 17], "tilman": 15, "time": [1, 2, 5, 9, 11, 17], "tini": 9, "titl": [8, 19], "tm": 19, "tmp": 14, "togeth": [3, 8], "tograi": 10, "tool": [1, 17], "top": [11, 18, 19], "topic": 3, "torch": [4, 10, 13, 15, 18], "torchvis": 10, "total": 13, "toward": [2, 4], "train": [3, 7, 9, 10, 15, 16, 17, 18, 19], "train_it": [7, 17], "train_load": [7, 17], "train_pytorch": 15, "train_set": [7, 17], "train_tensorflow": 15, "trainabl": [5, 9], "tranform": 10, "transcrib": 19, "transfer": [5, 7], "transfo": 10, "transform": [5, 7, 9], "translat": 2, "troll": 2, "true": [7, 8, 9, 10, 11, 13, 14, 15, 17, 18, 19], "truth": 11, "tune": [1, 18], "tupl": [7, 8, 10, 11], "two": [8, 14], "txt": 7, "type": [8, 11, 15, 18, 19], "typic": 19, "u": [2, 3], "ucsd": 7, "udac": 3, "uint8": [8, 9, 11, 19], "ukrainian": 7, "unaccept": 2, "underli": [17, 19], "underneath": 8, "understand": [5, 7, 19], "uniform": [9, 10], "uniformli": 10, "uninterrupt": [8, 19], "union": 11, "unit": 1, "unittest": 3, "unlock": 8, "unoffici": 9, "unprofession": 2, "unsolicit": 2, "unsupervis": 5, "unwelcom": 2, "up": [9, 19], "updat": 11, "upgrad": 3, "upper": [7, 10], "uppercas": 17, "url": 8, "us": [2, 3, 4, 7, 9, 11, 12, 13, 14, 15, 16, 19], "usabl": 19, "usag": [14, 18], "use_polygon": [7, 11, 17], "useabl": 19, "user": [5, 8, 12], "utf": 19, "util": 18, "v1": 15, "v3": [9, 15, 19], "valid": 17, "valu": [3, 8, 10, 19], "valuabl": 5, "variabl": 14, "varieti": 7, "veri": 9, "verma": 1, "version": [2, 3, 4, 18, 19], "vgg": 9, "vgg16": 15, "vgg16_bn_r": 9, "via": 2, "video": 1, "vietnames": 7, "view": [5, 7], "viewpoint": 2, "violat": 2, "visibl": 2, "vision": [5, 7, 9], "visiondataset": 7, "visiontransform": 9, "visual": [4, 5, 16], "visualize_pag": 11, "vit_": 9, "vit_b": 9, "vitstr": [5, 9, 18], "vitstr_bas": [9, 19], "vitstr_smal": [9, 13, 18, 19], "viz": 4, "vocab": [13, 15, 17, 18, 19], "vocabulari": [7, 13, 15], "w": [8, 9, 10, 11], "w3": 19, "wa": 2, "wai": [2, 5, 17], "want": [3, 18, 19], "warmup": 19, "wasn": 3, "we": [1, 2, 3, 4, 5, 8, 10, 13, 15, 17, 18, 19], "weasyprint": 8, "web": [3, 8], "websit": 7, "welcom": 2, "well": [1, 2, 18], "were": [2, 8, 19], "what": [1, 2], "when": [2, 3, 9], "whenev": 3, "where": [3, 8, 10, 11], "whether": [3, 7, 8, 10, 11, 17, 19], "which": [2, 9, 14, 16, 17, 19], "whichev": 4, "while": [10, 19], "why": 2, "width": [8, 10], "wiki": 2, "wildreceipt": [5, 7, 17], "window": [9, 11], "wish": 3, "within": 2, "without": [2, 7, 9], "wonder": 3, "word": [5, 7, 9, 11, 19], "word_1_1": 19, "word_1_2": 19, "word_1_3": 19, "wordgener": [7, 17], "words_onli": 11, "work": [1, 13, 14, 19], "workflow": 3, "worklow": 3, "world": [11, 19], "worth": 9, "wrap": 19, "wrapper": [7, 10], "write": 14, "written": [2, 8], "www": [2, 8, 19], "x": [8, 10, 11], "x_ascend": 19, "x_descend": 19, "x_i": 11, "x_size": 19, "x_wconf": 19, "xhtml": 19, "xmax": 8, "xmin": 8, "xml": 19, "xml_bytes_str": 19, "xml_element": 19, "xml_output": 19, "xmln": 19, "y": 11, "y_i": 11, "y_j": 11, "yet": 16, "ymax": 8, "ymin": 8, "yolov8": 16, "you": [3, 4, 7, 8, 9, 13, 14, 15, 16, 17, 18, 19], "your": [3, 5, 8, 11, 19], "yoursit": 8, "yugesh": 1, "zero": [10, 11], "zoo": 13, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 7, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 7, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": 7, "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 7, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 7, "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 7, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 7, "\u00e4\u00f6\u00e4\u00f6": 7, "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 7, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": 7, "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": 7, "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": 7, "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": 7, "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": 7, "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0905": 7, "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": 7, "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": 7, "\u0950": 7, "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": 7, "\u09bd": 7, "\u09ce": 7, "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": 7}, "titles": ["Changelog", "Community resources", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.contrib", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Integrate contributions into your pipeline", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 3, "0": 0, "01": 0, "02": 0, "03": 0, "04": 0, "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 2], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 2], "2021": 0, "2022": 0, "2023": 0, "2024": 0, "21": 0, "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 2], "31": 0, "4": [0, 2], "5": 0, "6": 0, "7": 0, "8": 0, "9": 0, "advanc": 19, "approach": 19, "architectur": 19, "arg": [7, 8, 9, 10, 11], "artefact": 8, "artefactdetect": 16, "attribut": 2, "avail": [16, 17, 19], "aw": 14, "ban": 2, "block": 8, "bug": 3, "changelog": 0, "choos": [17, 19], "classif": [9, 13, 15], "code": [2, 3], "codebas": 3, "commit": 3, "commun": [1, 15], "compos": 10, "conda": 4, "conduct": 2, "connect": 3, "continu": 3, "contrib": 6, "contribut": [3, 6, 16], "contributor": 2, "convent": 15, "correct": 2, "coven": 2, "custom": [7, 13], "data": 17, "dataload": 7, "dataset": [5, 7, 17], "detect": [5, 9, 15, 17, 19], "develop": 3, "do": 19, "doctr": [3, 5, 6, 7, 8, 9, 10, 11, 12], "document": [3, 5, 8], "end": 19, "enforc": 2, "evalu": 11, "export": 18, "factori": 9, "featur": [3, 5], "feedback": 3, "file": 8, "from": 15, "gener": [7, 17], "git": 4, "guidelin": 2, "half": 18, "hub": 15, "huggingfac": 15, "i": 19, "infer": 18, "instal": [3, 4], "integr": [3, 16], "io": 8, "lambda": 14, "let": 3, "line": 8, "linux": 4, "load": [13, 15, 17], "loader": 7, "main": 5, "mode": 3, "model": [5, 9, 13, 15, 18, 19], "modifi": 3, "modul": [6, 16], "name": 15, "notebook": 12, "object": 17, "ocr": [17, 19], "onli": 4, "onnx": 18, "optim": 18, "option": 19, "orient": 13, "our": 2, "output": 19, "own": [13, 17], "packag": 4, "page": 8, "perman": 2, "pipelin": 16, "pledg": 2, "precis": 18, "predictor": 19, "prepar": 18, "prerequisit": 4, "pretrain": 15, "push": 15, "python": 4, "qualiti": 3, "question": 3, "read": 8, "readi": 17, "recognit": [5, 9, 15, 17, 19], "report": 3, "request": 3, "resourc": 1, "respons": 2, "return": [7, 8, 9, 11], "right": 19, "scope": 2, "share": 15, "should": 19, "stage": 19, "standard": 2, "structur": [3, 8], "style": 3, "support": [5, 6, 7, 10], "synthet": [7, 17], "task": 11, "temporari": 2, "test": 3, "text": [5, 19], "train": 13, "transform": 10, "two": 19, "unit": 3, "us": [17, 18], "util": 11, "v0": 0, "verif": 3, "via": 4, "visual": 11, "vocab": 7, "warn": 2, "what": 19, "word": 8, "your": [13, 15, 16, 17, 18], "zoo": [5, 9]}}) \ No newline at end of file diff --git a/v0.5.1/transforms.html b/v0.5.1/transforms.html deleted file mode 100644 index 85e94d8a76..0000000000 --- a/v0.5.1/transforms.html +++ /dev/null @@ -1,684 +0,0 @@ - - - - - - - - - - - - - doctr.transforms - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    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:

    -
    -
    -class doctr.transforms.Resize(output_size: Tuple[int, int], method: str = 'bilinear', preserve_aspect_ratio: bool = False, symmetric_pad: bool = False)[source]
    -

    Resizes a tensor to a target size

    -
    -
    Example::
    >>> from doctr.transforms import Resize
    ->>> import tensorflow as tf
    ->>> transfo = Resize((32, 32))
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • output_size – expected output size

    • -
    • method – interpolation method

    • -
    • preserve_aspect_ratio – if True, preserve aspect ratio and pad the rest with zeros

    • -
    • symmetric_pad – if True while preserving aspect ratio, the padding will be done symmetrically

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.Normalize(mean: Tuple[float, float, float], std: Tuple[float, float, float])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • mean – average value per channel

    • -
    • std – standard deviation per channel

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.LambdaTransformation(fn: Callable[[Tensor], Tensor])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import LambdaTransformation
    ->>> import tensorflow as tf
    ->>> transfo = LambdaTransformation(lambda x: x/ 255.)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    fn – the function to be applied to the input tensor

    -
    -
    -
    - -
    -
    -class doctr.transforms.ToGray[source]
    -

    Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ToGray()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    - -
    -
    -class doctr.transforms.ColorInversion(min_val: float = 0.5)[source]
    -

    Applies the following tranformation to a tensor (image or batch of images): -convert to grayscale, colorize (shift 0-values randomly), and then invert colors

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ColorInversion(min_val=0.6)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    min_val – range [min_val, 1] to colorize RGB pixels

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomBrightness(max_delta: float = 0.3)[source]
    -

    Randomly adjust brightness of a tensor (batch of images or image) by adding a delta -to all pixels

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Brightness()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    • -
    • p – probability to apply transformation

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomContrast(delta: float = 0.3)[source]
    -

    Randomly adjust contrast of a tensor (batch of images or image) by adjusting -each pixel: (img - mean) * contrast_factor + mean.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Contrast()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomSaturation(delta: float = 0.5)[source]
    -

    Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and -increasing saturation by a factor.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Saturation()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomHue(max_delta: float = 0.3)[source]
    -

    Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Hue()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomGamma(min_gamma: float = 0.5, max_gamma: float = 1.5, min_gain: float = 0.8, max_gain: float = 1.2)[source]
    -

    randomly performs gamma correction for a tensor (batch of images or image)

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Gamma()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_gamma – non-negative real number, lower bound for gamma param

    • -
    • max_gamma – non-negative real number, upper bound for gamma

    • -
    • min_gain – lower bound for constant multiplier

    • -
    • max_gain – upper bound for constant multiplier

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomJpegQuality(min_quality: int = 60, max_quality: int = 100)[source]
    -

    Randomly adjust jpeg quality of a 3 dimensional RGB image

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = JpegQuality()
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_quality – int between [0, 100]

    • -
    • max_quality – int between [0, 100]

    • -
    -
    -
    -
    - -
    -
    -

    Composing transformations

    -

    It is common to require several transformations to be performed consecutively.

    -
    -
    -class doctr.transforms.Compose(transforms: List[Callable[[Any], Any]])[source]
    -

    Implements a wrapper that will apply transformations sequentially

    -
    -
    Example::
    >>> from doctr.transforms import Compose, Resize
    ->>> import tensorflow as tf
    ->>> transfos = Compose([Resize((32, 32))])
    ->>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformation modules

    -
    -
    -
    - -
    -
    -class doctr.transforms.OneOf(transforms: List[Callable[[Any], Any]])[source]
    -

    Randomly apply one of the input transformations

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = OneOf([JpegQuality(), Gamma()])
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformations, one only will be picked

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomApply(transform: Callable[[Any], Any], p: float = 0.5)[source]
    -

    Apply with a probability p the input transformation

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = RandomApply(Gamma(), p=.5)
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • transform – transformation to apply

    • -
    • p – probability to apply

    • -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.5.1/using_doctr/custom_models_training.html b/v0.5.1/using_doctr/custom_models_training.html index df39d8d568..b714c1f971 100644 --- a/v0.5.1/using_doctr/custom_models_training.html +++ b/v0.5.1/using_doctr/custom_models_training.html @@ -14,7 +14,7 @@ - + Train your own model - docTR documentation @@ -619,7 +619,7 @@

    Loading your custom trained orientation classification model - + diff --git a/v0.5.1/using_doctr/running_on_aws.html b/v0.5.1/using_doctr/running_on_aws.html index 16ceaca7a1..808ea541cd 100644 --- a/v0.5.1/using_doctr/running_on_aws.html +++ b/v0.5.1/using_doctr/running_on_aws.html @@ -14,7 +14,7 @@ - + AWS Lambda - docTR documentation @@ -362,7 +362,7 @@

    AWS Lambda - + diff --git a/v0.5.1/using_doctr/sharing_models.html b/v0.5.1/using_doctr/sharing_models.html index d76b4017f4..c9e978400a 100644 --- a/v0.5.1/using_doctr/sharing_models.html +++ b/v0.5.1/using_doctr/sharing_models.html @@ -14,7 +14,7 @@ - + Share your model with the community - docTR documentation @@ -544,7 +544,7 @@

    Recognition - + diff --git a/v0.5.1/using_doctr/using_contrib_modules.html b/v0.5.1/using_doctr/using_contrib_modules.html index 50598dae5d..0c5fffdf9f 100644 --- a/v0.5.1/using_doctr/using_contrib_modules.html +++ b/v0.5.1/using_doctr/using_contrib_modules.html @@ -14,7 +14,7 @@ - + Integrate contributions into your pipeline - docTR documentation @@ -415,7 +415,7 @@

    ArtefactDetection - + diff --git a/v0.5.1/using_doctr/using_datasets.html b/v0.5.1/using_doctr/using_datasets.html index 460476dbbf..8a7d4f0a64 100644 --- a/v0.5.1/using_doctr/using_datasets.html +++ b/v0.5.1/using_doctr/using_datasets.html @@ -14,7 +14,7 @@ - + Choose a ready to use dataset - docTR documentation @@ -642,7 +642,7 @@

    Data Loading - + diff --git a/v0.5.1/using_doctr/using_model_export.html b/v0.5.1/using_doctr/using_model_export.html index 6124c00ebe..6790dd0642 100644 --- a/v0.5.1/using_doctr/using_model_export.html +++ b/v0.5.1/using_doctr/using_model_export.html @@ -14,7 +14,7 @@ - + Preparing your model for inference - docTR documentation @@ -467,7 +467,7 @@

    Using your ONNX exported model - + diff --git a/v0.5.1/using_doctr/using_models.html b/v0.5.1/using_doctr/using_models.html index 61f1f5ab7a..9ead8498e1 100644 --- a/v0.5.1/using_doctr/using_models.html +++ b/v0.5.1/using_doctr/using_models.html @@ -14,7 +14,7 @@ - + Choosing the right model - docTR documentation @@ -1253,7 +1253,7 @@

    Advanced options - + diff --git a/v0.5.1/utils.html b/v0.5.1/utils.html deleted file mode 100644 index e2f223f06a..0000000000 --- a/v0.5.1/utils.html +++ /dev/null @@ -1,574 +0,0 @@ - - - - - - - - - - - - - doctr.utils - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.utils

    -

    This module regroups non-core features that are complementary to the rest of the package.

    -
    -

    Visualization

    -

    Easy-to-use functions to make sense of your model’s predictions.

    -
    -
    -doctr.utils.visualization.visualize_page(page: Dict[str, Any], image: ndarray, words_only: bool = True, display_artefacts: bool = True, scale: float = 10, interactive: bool = True, add_labels: bool = True, **kwargs: Any) Figure[source]
    -

    Visualize a full page with predicted blocks, lines and words

    -
    -
    Example::
    >>> import numpy as np
    ->>> import matplotlib.pyplot as plt
    ->>> from doctr.utils.visualization import visualize_page
    ->>> from doctr.models import ocr_db_crnn
    ->>> model = ocr_db_crnn(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([[input_page]])
    ->>> visualize_page(out[0].pages[0].export(), input_page)
    ->>> plt.show()
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • page – the exported Page of a Document

    • -
    • image – np array of the page, needs to have the same shape than page[‘dimensions’]

    • -
    • words_only – whether only words should be displayed

    • -
    • display_artefacts – whether artefacts should be displayed

    • -
    • scale – figsize of the largest windows side

    • -
    • interactive – whether the plot should be interactive

    • -
    • add_labels – for static plot, adds text labels on top of bounding box

    • -
    -
    -
    -
    - -
    -
    -

    Task evaluation

    -

    Implementations of task-specific metrics to easily assess your model performances.

    -
    -
    -class doctr.utils.metrics.TextMatch[source]
    -

    Implements text match metric (word-level accuracy) for recognition task.

    -

    The raw aggregated metric is computed as follows:

    -
    -
    -\[\forall X, Y \in \mathcal{W}^N, -TextMatch(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N f_{Y_i}(X_i)\]
    -
    -

    with the indicator function \(f_{a}\) defined as:

    -
    -
    -\[\begin{split}\forall a, x \in \mathcal{W}, -f_a(x) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } x = a \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{W}\) is the set of all possible character sequences, -\(N\) is a strictly positive integer.

    -
    -
    Example::
    >>> from doctr.utils import TextMatch
    ->>> metric = TextMatch()
    ->>> metric.update(['Hello', 'world'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    -summary() Dict[str, float][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a dictionary with the exact match score for the raw data, its lower-case counterpart, its unidecode -counterpart and its lower-case unidecode counterpart

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.LocalizationConfusion(iou_thresh: float = 0.5, rotated_bbox: bool = False, mask_shape: Tuple[int, int] = (1024, 1024))[source]
    -

    Implements common confusion metrics and mean IoU for localization evaluation.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall Y \in \mathcal{B}^N, \forall X \in \mathcal{B}^M, \\ -Recall(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -Precision(X, Y) = \frac{1}{M} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -meanIoU(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(X_i, Y_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(g_{X}\) defined as:

    -
    -
    -\[\begin{split}\forall y \in \mathcal{B}, -g_X(y) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } y\mbox{ has been assigned to any }(X_i)_i\mbox{ with an }IoU \geq 0.5 \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import LocalizationConfusion
    ->>> metric = LocalizationConfusion(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]))
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[float | None, float | None, float | None][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall, precision and meanIoU scores

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.OCRMetric(iou_thresh: float = 0.5, rotated_bbox: bool = False, mask_shape: Tuple[int, int] = (1024, 1024))[source]
    -

    Implements end-to-end OCR metric.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall (B, L) \in \mathcal{B}^N \times \mathcal{L}^N, -\forall (\hat{B}, \hat{L}) \in \mathcal{B}^M \times \mathcal{L}^M, \\ -Recall(B, \hat{B}, L, \hat{L}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -Precision(B, \hat{B}, L, \hat{L}) = \frac{1}{M} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(h_{B, L}\) defined as:

    -
    -
    -\[\begin{split}\forall (b, l) \in \mathcal{B} \times \mathcal{L}, -h_{B,L}(b, l) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ - & IoU \geq 0.5 \mbox{ and that for this assignment, } l = L_j\\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(\mathcal{L}\) is the set of possible character sequences, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import OCRMetric
    ->>> metric = OCRMetric(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]),
    -['hello'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[Dict[str, float | None], Dict[str, float | None], float | None][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall & precision for each string comparison flexibility and the mean IoU

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.6.0/_modules/doctr/datasets/cord.html b/v0.6.0/_modules/doctr/datasets/cord.html index f98ee6901c..55b0584830 100644 --- a/v0.6.0/_modules/doctr/datasets/cord.html +++ b/v0.6.0/_modules/doctr/datasets/cord.html @@ -13,7 +13,7 @@ - + doctr.datasets.cord - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.cord

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
     from .datasets import VisionDataset
    -from doctr.utils.geometry import fit_rbbox
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['CORD']
    +__all__ = ["CORD"]
     
     
     
    -[docs] +[docs] class CORD(VisionDataset): """CORD dataset from `"CORD: A Consolidated Receipt Dataset forPost-OCR Parsing" <https://openreview.net/pdf?id=SJl3z659UH>`_. - Example:: - >>> from doctr.datasets import CORD - >>> train_set = CORD(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/cord-grid.png&src=0 + :align: center + + >>> from doctr.datasets import CORD + >>> train_set = CORD(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - TRAIN = ('https://github.com/mindee/doctr/releases/download/v0.1.1/cord_train.zip', - '45f9dc77f126490f3e52d7cb4f70ef3c57e649ea86d19d862a2757c9c455d7f8') - TEST = ('https://github.com/mindee/doctr/releases/download/v0.1.1/cord_test.zip', - '8c895e3d6f7e1161c5b7245e3723ce15c04d84be89eaa6093949b75a66fb3c58') + TRAIN = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/cord_train.zip&src=0", + "45f9dc77f126490f3e52d7cb4f70ef3c57e649ea86d19d862a2757c9c455d7f8", + "cord_train.zip", + ) + + TEST = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/cord_test.zip&src=0", + "8c895e3d6f7e1161c5b7245e3723ce15c04d84be89eaa6093949b75a66fb3c58", + "cord_test.zip", + ) def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + url, sha256, name = self.TRAIN if train else self.TEST + super().__init__( + url, + name, + sha256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - url, sha256 = self.TRAIN if train else self.TEST - super().__init__(url, None, sha256, True, **kwargs) - - # # List images - self.root = os.path.join(self._root, 'image') - self.data: List[Tuple[str, Dict[str, Any]]] = [] + # List images + tmp_root = os.path.join(self.root, "image") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] self.train = train - self.sample_transforms = sample_transforms - for img_path in os.listdir(self.root): + np_dtype = np.float32 + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking CORD", total=len(os.listdir(tmp_root))): # File existence check - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem _targets = [] - with open(os.path.join(self._root, 'json', f"{stem}.json"), 'rb') as f: + with open(os.path.join(self.root, "json", f"{stem}.json"), "rb") as f: label = json.load(f) for line in label["valid_line"]: for word in line["words"]: if len(word["text"]) > 0: x = word["quad"]["x1"], word["quad"]["x2"], word["quad"]["x3"], word["quad"]["x4"] y = word["quad"]["y1"], word["quad"]["y2"], word["quad"]["y3"], word["quad"]["y4"] - if rotated_bbox: - box = list(fit_rbbox(np.array([ - [x[0], y[0]], - [x[1], y[1]], - [x[2], y[2]], - [x[3], y[3]], - ], dtype=np.float32))) + box: Union[List[float], np.ndarray] + if use_polygons: + # (x, y) coordinates of top left, top right, bottom right, bottom left corners + box = np.array( + [ + [x[0], y[0]], + [x[1], y[1]], + [x[2], y[2]], + [x[3], y[3]], + ], + dtype=np_dtype, + ) else: - # Reduce 8 coords to 4 + # Reduce 8 coords to 4 -> xmin, ymin, xmax, ymax box = [min(x), min(y), max(x), max(y)] - _targets.append((word['text'], box)) + _targets.append((word["text"], box)) text_targets, box_targets = zip(*_targets) - self.data.append(( - img_path, - dict(boxes=np.asarray(box_targets, dtype=int).clip(min=0), labels=text_targets) - )) + if recognition_task: + crops = crop_bboxes_from_image( + img_path=os.path.join(tmp_root, img_path), geoms=np.asarray(box_targets, dtype=int).clip(min=0) + ) + for crop, label in zip(crops, list(text_targets)): + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, np.asarray(box_targets, dtype=int).clip(min=0))) + else: + self.data.append(( + img_path, + dict(boxes=np.asarray(box_targets, dtype=int).clip(min=0), labels=list(text_targets)), + )) + + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -397,8 +461,8 @@

    Source code for doctr.datasets.cord

           
         
       
    -
    - + + diff --git a/v0.6.0/_modules/doctr/datasets/core.html b/v0.6.0/_modules/doctr/datasets/core.html deleted file mode 100644 index b3dcc29ff9..0000000000 --- a/v0.6.0/_modules/doctr/datasets/core.html +++ /dev/null @@ -1,417 +0,0 @@ - - - - - - - - - - - - doctr.datasets.core - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.core

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from pathlib import Path
    -from zipfile import ZipFile
    -from typing import List, Any, Optional, Tuple
    -import tensorflow as tf
    -
    -from doctr.models.utils import download_from_url
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset:
    -
    -    data: List[Any] = []
    -
    -    def __len__(self):
    -        return len(self.data)
    -
    -    def __getitem__(
    -        self,
    -        index: int
    -    ) -> Tuple[tf.Tensor, Any]:
    -
    -        img_name, target = self.data[index]
    -        # Read image
    -        img = tf.io.read_file(os.path.join(self.root, img_name))
    -        img = tf.image.decode_jpeg(img, channels=3)
    -        if self.sample_transforms is not None:
    -            img = self.sample_transforms(img)
    -
    -        return img, target
    -
    -    def extra_repr(self) -> str:
    -        return ""
    -
    -    def __repr__(self) -> str:
    -        return f"{self.__class__.__name__}({self.extra_repr()})"
    -
    -    @staticmethod
    -    def collate_fn(samples: List[Tuple[tf.Tensor, Any]]) -> Tuple[tf.Tensor, List[Any]]:
    -
    -        images, targets = zip(*samples)
    -        images = tf.stack(images, axis=0)
    -
    -        return images, list(targets)
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset): - """Implements an abstract dataset - - Args: - url: URL of the dataset - file_name: name of the file once downloaded - file_hash: expected SHA256 of the file - extract_archive: whether the downloaded file is an archive to be extracted - download: whether the dataset should be downloaded if not present on disk - overwrite: whether the archive should be re-extracted - """ - - def __init__( - self, - url: str, - file_name: Optional[str] = None, - file_hash: Optional[str] = None, - extract_archive: bool = False, - download: bool = False, - overwrite: bool = False, - ) -> None: - - dataset_cache = os.path.join(os.path.expanduser('~'), '.cache', 'doctr', 'datasets') - - file_name = file_name if isinstance(file_name, str) else os.path.basename(url) - # Download the file if not present - archive_path = os.path.join(dataset_cache, file_name) - - if not os.path.exists(archive_path) and not download: - raise ValueError("the dataset needs to be downloaded first with download=True") - - archive_path = download_from_url(url, file_name, file_hash, cache_subdir='datasets') - - # Extract the archive - if extract_archive: - archive_path = Path(archive_path) - dataset_path = archive_path.parent.joinpath(archive_path.stem) - if not dataset_path.is_dir() or overwrite: - with ZipFile(archive_path, 'r') as f: - f.extractall(path=dataset_path) - - # List images - self._root = dataset_path if extract_archive else archive_path - self.data: List[Any] = []
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.6.0/_modules/doctr/datasets/datasets/tensorflow.html b/v0.6.0/_modules/doctr/datasets/datasets/tensorflow.html deleted file mode 100644 index a236abd9fe..0000000000 --- a/v0.6.0/_modules/doctr/datasets/datasets/tensorflow.html +++ /dev/null @@ -1,356 +0,0 @@ - - - - - - - - - - - - doctr.datasets.datasets.tensorflow - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.datasets.tensorflow

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from typing import List, Any, Tuple
    -import tensorflow as tf
    -
    -from .base import _AbstractDataset, _VisionDataset
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset(_AbstractDataset):
    -
    -    def _read_sample(self, index: int) -> Tuple[tf.Tensor, Any]:
    -        img_name, target = self.data[index]
    -        # Read image
    -        img = tf.io.read_file(os.path.join(self.root, img_name))
    -        img = tf.image.decode_jpeg(img, channels=3)
    -
    -        return img, target
    -
    -    @staticmethod
    -    def collate_fn(samples: List[Tuple[tf.Tensor, Any]]) -> Tuple[tf.Tensor, List[Any]]:
    -
    -        images, targets = zip(*samples)
    -        images = tf.stack(images, axis=0)
    -
    -        return images, list(targets)
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset, _VisionDataset): - pass
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.6.0/_modules/doctr/datasets/detection.html b/v0.6.0/_modules/doctr/datasets/detection.html index 739563e466..718001e4cf 100644 --- a/v0.6.0/_modules/doctr/datasets/detection.html +++ b/v0.6.0/_modules/doctr/datasets/detection.html @@ -13,7 +13,7 @@ - + doctr.datasets.detection - docTR documentation @@ -430,7 +430,7 @@

    Source code for doctr.datasets.detection

         
       
    - + diff --git a/v0.6.0/_modules/doctr/datasets/doc_artefacts.html b/v0.6.0/_modules/doctr/datasets/doc_artefacts.html index 3313ae4660..94c32aaa0f 100644 --- a/v0.6.0/_modules/doctr/datasets/doc_artefacts.html +++ b/v0.6.0/_modules/doctr/datasets/doc_artefacts.html @@ -13,7 +13,7 @@ - + doctr.datasets.doc_artefacts - docTR documentation @@ -414,7 +414,7 @@

    Source code for doctr.datasets.doc_artefacts

       
    - + diff --git a/v0.6.0/_modules/doctr/datasets/funsd.html b/v0.6.0/_modules/doctr/datasets/funsd.html index 35d7ad4cf5..f08612f9fa 100644 --- a/v0.6.0/_modules/doctr/datasets/funsd.html +++ b/v0.6.0/_modules/doctr/datasets/funsd.html @@ -13,7 +13,7 @@ - + doctr.datasets.funsd - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.funsd

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
     from .datasets import VisionDataset
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['FUNSD']
    +__all__ = ["FUNSD"]
     
     
     
    -[docs] +[docs] class FUNSD(VisionDataset): """FUNSD dataset from `"FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents" <https://arxiv.org/pdf/1905.13538.pdf>`_. - Example:: - >>> from doctr.datasets import FUNSD - >>> train_set = FUNSD(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/funsd-grid.png&src=0 + :align: center + + >>> from doctr.datasets import FUNSD + >>> train_set = FUNSD(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - URL = 'https://guillaumejaume.github.io/FUNSD/dataset.zip' - SHA256 = 'c31735649e4f441bcbb4fd0f379574f7520b42286e80b01d80b445649d54761f' - FILE_NAME = 'funsd.zip' + URL = "https://guillaumejaume.github.io/FUNSD/dataset.zip" + SHA256 = "c31735649e4f441bcbb4fd0f379574f7520b42286e80b01d80b445649d54761f" + FILE_NAME = "funsd.zip" def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + super().__init__( + self.URL, + self.FILE_NAME, + self.SHA256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - super().__init__(self.URL, self.FILE_NAME, self.SHA256, True, **kwargs) self.train = train - self.sample_transforms = sample_transforms + np_dtype = np.float32 # Use the subset - subfolder = os.path.join('dataset', 'training_data' if train else 'testing_data') + subfolder = os.path.join("dataset", "training_data" if train else "testing_data") # # List images - self.root = os.path.join(self._root, subfolder, 'images') - self.data: List[Tuple[str, Dict[str, Any]]] = [] - for img_path in os.listdir(self.root): + tmp_root = os.path.join(self.root, subfolder, "images") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking FUNSD", total=len(os.listdir(tmp_root))): # File existence check - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem - with open(os.path.join(self._root, subfolder, 'annotations', f"{stem}.json"), 'rb') as f: + with open(os.path.join(self.root, subfolder, "annotations", f"{stem}.json"), "rb") as f: data = json.load(f) - _targets = [(word['text'], word['box']) for block in data['form'] - for word in block['words'] if len(word['text']) > 0] + _targets = [ + (word["text"], word["box"]) + for block in data["form"] + for word in block["words"] + if len(word["text"]) > 0 + ] text_targets, box_targets = zip(*_targets) - if rotated_bbox: - # box_targets: xmin, ymin, xmax, ymax -> x, y, w, h, alpha = 0 - box_targets = [ + if use_polygons: + # xmin, ymin, xmax, ymax -> (x, y) coordinates of top left, top right, bottom right, bottom left corners + box_targets = [ # type: ignore[assignment] [ - (box[0] + box[2]) / 2, (box[1] + box[3]) / 2, box[2] - box[0], box[3] - box[1], 0 - ] for box in box_targets + [box[0], box[1]], + [box[2], box[1]], + [box[2], box[3]], + [box[0], box[3]], + ] + for box in box_targets ] - self.data.append((img_path, dict(boxes=np.asarray(box_targets, dtype=int), labels=text_targets))) + if recognition_task: + crops = crop_bboxes_from_image( + img_path=os.path.join(tmp_root, img_path), geoms=np.asarray(box_targets, dtype=np_dtype) + ) + for crop, label in zip(crops, list(text_targets)): + # filter labels with unknown characters + if not any(char in label for char in ["☑", "☐", "\uf703", "\uf702"]): + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, np.asarray(box_targets, dtype=np_dtype))) + else: + self.data.append(( + img_path, + dict(boxes=np.asarray(box_targets, dtype=np_dtype), labels=list(text_targets)), + )) + + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -386,8 +453,8 @@

    Source code for doctr.datasets.funsd

           
         
       
    -
    - + + diff --git a/v0.6.0/_modules/doctr/datasets/generator/tensorflow.html b/v0.6.0/_modules/doctr/datasets/generator/tensorflow.html index 9f562582d9..a3e619f720 100644 --- a/v0.6.0/_modules/doctr/datasets/generator/tensorflow.html +++ b/v0.6.0/_modules/doctr/datasets/generator/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.datasets.generator.tensorflow - docTR documentation @@ -395,7 +395,7 @@

    Source code for doctr.datasets.generator.tensorflow

    - + diff --git a/v0.6.0/_modules/doctr/datasets/ic03.html b/v0.6.0/_modules/doctr/datasets/ic03.html index 3d221d07de..60e54a8a4b 100644 --- a/v0.6.0/_modules/doctr/datasets/ic03.html +++ b/v0.6.0/_modules/doctr/datasets/ic03.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic03 - docTR documentation @@ -468,7 +468,7 @@

    Source code for doctr.datasets.ic03

         
       
    - + diff --git a/v0.6.0/_modules/doctr/datasets/ic13.html b/v0.6.0/_modules/doctr/datasets/ic13.html index 8137e08e9f..219c98dcd1 100644 --- a/v0.6.0/_modules/doctr/datasets/ic13.html +++ b/v0.6.0/_modules/doctr/datasets/ic13.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic13 - docTR documentation @@ -440,7 +440,7 @@

    Source code for doctr.datasets.ic13

         
       
    - + diff --git a/v0.6.0/_modules/doctr/datasets/iiit5k.html b/v0.6.0/_modules/doctr/datasets/iiit5k.html index 1fc8ecfb27..b49c80fe18 100644 --- a/v0.6.0/_modules/doctr/datasets/iiit5k.html +++ b/v0.6.0/_modules/doctr/datasets/iiit5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiit5k - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.datasets.iiit5k

         
       
    - + diff --git a/v0.6.0/_modules/doctr/datasets/iiithws.html b/v0.6.0/_modules/doctr/datasets/iiithws.html index 07f5b13685..f7220afbc7 100644 --- a/v0.6.0/_modules/doctr/datasets/iiithws.html +++ b/v0.6.0/_modules/doctr/datasets/iiithws.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiithws - docTR documentation @@ -407,7 +407,7 @@

    Source code for doctr.datasets.iiithws

         
       
    - + diff --git a/v0.6.0/_modules/doctr/datasets/imgur5k.html b/v0.6.0/_modules/doctr/datasets/imgur5k.html index 68d433ca62..51c6545db4 100644 --- a/v0.6.0/_modules/doctr/datasets/imgur5k.html +++ b/v0.6.0/_modules/doctr/datasets/imgur5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.imgur5k - docTR documentation @@ -488,7 +488,7 @@

    Source code for doctr.datasets.imgur5k

         
       
    - + diff --git a/v0.6.0/_modules/doctr/datasets/loader.html b/v0.6.0/_modules/doctr/datasets/loader.html index d32e6da298..ed80350ef0 100644 --- a/v0.6.0/_modules/doctr/datasets/loader.html +++ b/v0.6.0/_modules/doctr/datasets/loader.html @@ -13,7 +13,7 @@ - + doctr.datasets.loader - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.loader

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import math
    -import tensorflow as tf
    -import numpy as np
    -from typing import Optional
    +from typing import Callable, Optional
     
    -from .multithreading import multithread_exec
    +import numpy as np
    +import tensorflow as tf
     
     __all__ = ["DataLoader"]
     
    @@ -293,12 +314,13 @@ 

    Source code for doctr.datasets.loader

         """Collate multiple elements into batches
     
         Args:
    +    ----
             samples: list of N tuples containing M elements
     
         Returns:
    +    -------
             Tuple of M sequences contianing N elements each
         """
    -
         batch_data = zip(*samples)
     
         tf_data = tuple(tf.stack(elt, axis=0) for elt in batch_data)
    @@ -307,23 +329,23 @@ 

    Source code for doctr.datasets.loader

     
     
     
    -[docs] +[docs] class DataLoader: """Implements a dataset wrapper for fast data loading - Example:: - >>> from doctr.datasets import FUNSD, 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) + >>> 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) Args: + ---- dataset: the dataset shuffle: whether the samples should be shuffled before passing it to the iterator batch_size: number of elements in each batch drop_last: if `True`, drops the last batch if it isn't full - workers: number of workers to use for data loading + collate_fn: function to merge samples into a batch """ def __init__( @@ -332,17 +354,22 @@

    Source code for doctr.datasets.loader

             shuffle: bool = True,
             batch_size: int = 1,
             drop_last: bool = False,
    -        workers: Optional[int] = None,
    +        collate_fn: Optional[Callable] = None,
         ) -> None:
             self.dataset = dataset
             self.shuffle = shuffle
             self.batch_size = batch_size
             nb = len(self.dataset) / batch_size
             self.num_batches = math.floor(nb) if drop_last else math.ceil(nb)
    -        self.collate_fn = self.dataset.collate_fn if hasattr(self.dataset, 'collate_fn') else default_collate
    -        self.workers = workers
    +        if collate_fn is None:
    +            self.collate_fn = self.dataset.collate_fn if hasattr(self.dataset, "collate_fn") else default_collate
    +        else:
    +            self.collate_fn = collate_fn
             self.reset()
     
    +    def __len__(self) -> int:
    +        return self.num_batches
    +
         def reset(self) -> None:
             # Updates indices after each epoch
             self._num_yielded = 0
    @@ -358,9 +385,9 @@ 

    Source code for doctr.datasets.loader

             if self._num_yielded < self.num_batches:
                 # Get next indices
                 idx = self._num_yielded * self.batch_size
    -            indices = self.indices[idx: min(len(self.dataset), idx + self.batch_size)]
    +            indices = self.indices[idx : min(len(self.dataset), idx + self.batch_size)]
     
    -            samples = multithread_exec(self.dataset.__getitem__, indices, threads=self.workers)
    +            samples = list(map(self.dataset.__getitem__, indices))
     
                 batch_data = self.collate_fn(samples)
     
    @@ -401,8 +428,8 @@ 

    Source code for doctr.datasets.loader

           
         
       
    -
    - +
    + diff --git a/v0.6.0/_modules/doctr/datasets/mjsynth.html b/v0.6.0/_modules/doctr/datasets/mjsynth.html index 77bb01d523..df34e49cf9 100644 --- a/v0.6.0/_modules/doctr/datasets/mjsynth.html +++ b/v0.6.0/_modules/doctr/datasets/mjsynth.html @@ -13,7 +13,7 @@ - + doctr.datasets.mjsynth - docTR documentation @@ -438,7 +438,7 @@

    Source code for doctr.datasets.mjsynth

         
       
    - + diff --git a/v0.6.0/_modules/doctr/datasets/ocr.html b/v0.6.0/_modules/doctr/datasets/ocr.html index 11297d5952..ce1ed8b0d4 100644 --- a/v0.6.0/_modules/doctr/datasets/ocr.html +++ b/v0.6.0/_modules/doctr/datasets/ocr.html @@ -13,7 +13,7 @@ - + doctr.datasets.ocr - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.ocr

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import json
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple
     
    -from .datasets import AbstractDataset
    -from doctr.utils.geometry import fit_rbbox
    +import numpy as np
     
    +from .datasets import AbstractDataset
     
    -__all__ = ['OCRDataset']
    +__all__ = ["OCRDataset"]
     
     
     
    -[docs] +[docs] class OCRDataset(AbstractDataset): """Implements an OCR dataset + >>> from doctr.datasets import OCRDataset + >>> train_set = OCRDataset(img_folder="/path/to/images", + >>> label_file="/path/to/labels.json") + >>> img, target = train_set[0] + Args: + ---- img_folder: local path to image folder (all jpg at the root) label_file: local path to the label file - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) - **kwargs: keyword arguments from `VisionDataset`. + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + **kwargs: keyword arguments from `AbstractDataset`. """ def __init__( self, img_folder: str, label_file: str, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, **kwargs: Any, ) -> None: - - self.sample_transforms = sample_transforms - self.root = img_folder + super().__init__(img_folder, **kwargs) # List images self.data: List[Tuple[str, Dict[str, Any]]] = [] - with open(label_file, 'rb') as f: + np_dtype = np.float32 + with open(label_file, "rb") as f: data = json.load(f) - for file_dic in data: + for img_name, annotations in data.items(): # Get image path - img_name = Path(os.path.basename(file_dic["raw-archive-filepath"])).stem + '.jpg' + img_name = Path(img_name) # File existence check if not os.path.exists(os.path.join(self.root, img_name)): raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_name)}") # handle empty images - if (len(file_dic["coordinates"]) == 0 or - (len(file_dic["coordinates"]) == 1 and file_dic["coordinates"][0] == "N/A")): - self.data.append((img_name, dict(boxes=np.zeros((0, 4), dtype=np.float32), labels=[]))) + if len(annotations["typed_words"]) == 0: + self.data.append((img_name, dict(boxes=np.zeros((0, 4), dtype=np_dtype), labels=[]))) continue - is_valid: List[bool] = [] - box_targets: List[List[float]] = [] - for box in file_dic["coordinates"]: - if rotated_bbox: - x, y, w, h, alpha = fit_rbbox(np.asarray(box, dtype=np.float32)) - box = [x, y, w, h, alpha] - is_valid.append(w > 0 and h > 0) - else: - xs, ys = zip(*box) - box = [min(xs), min(ys), max(xs), max(ys)] - is_valid.append(box[0] < box[2] and box[1] < box[3]) - if is_valid[-1]: - box_targets.append(box) + # Unpack the straight boxes (xmin, ymin, xmax, ymax) + geoms = [list(map(float, obj["geometry"][:4])) for obj in annotations["typed_words"]] + if use_polygons: + # (x, y) coordinates of top left, top right, bottom right, bottom left corners + geoms = [ + [geom[:2], [geom[2], geom[1]], geom[2:], [geom[0], geom[3]]] # type: ignore[list-item] + for geom in geoms + ] + + text_targets = [obj["value"] for obj in annotations["typed_words"]] - text_targets = [word for word, _valid in zip(file_dic["string"], is_valid) if _valid] - self.data.append((img_name, dict(boxes=np.asarray(box_targets, dtype=np.float32), labels=text_targets)))
    + self.data.append((img_name, dict(boxes=np.asarray(geoms, dtype=np_dtype), labels=text_targets)))
    @@ -383,8 +402,8 @@

    Source code for doctr.datasets.ocr

           
         
       
    - - + + diff --git a/v0.6.0/_modules/doctr/datasets/recognition.html b/v0.6.0/_modules/doctr/datasets/recognition.html index 512c70c308..1754789364 100644 --- a/v0.6.0/_modules/doctr/datasets/recognition.html +++ b/v0.6.0/_modules/doctr/datasets/recognition.html @@ -13,7 +13,7 @@ - + doctr.datasets.recognition - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.datasets.recognition

         
       
    - + diff --git a/v0.6.0/_modules/doctr/datasets/sroie.html b/v0.6.0/_modules/doctr/datasets/sroie.html index 66fd4ca3e0..04cf10bda2 100644 --- a/v0.6.0/_modules/doctr/datasets/sroie.html +++ b/v0.6.0/_modules/doctr/datasets/sroie.html @@ -13,7 +13,7 @@ - + doctr.datasets.sroie - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.sroie

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import os
     import csv
    -import numpy as np
    +import os
     from pathlib import Path
    -from typing import List, Dict, Any, Tuple, Optional, Callable
    +from typing import Any, Dict, List, Tuple, Union
    +
    +import numpy as np
    +from tqdm import tqdm
     
     from .datasets import VisionDataset
    +from .utils import convert_target_to_relative, crop_bboxes_from_image
     
    -__all__ = ['SROIE']
    +__all__ = ["SROIE"]
     
     
     
    -[docs] +[docs] class SROIE(VisionDataset): """SROIE dataset from `"ICDAR2019 Competition on Scanned Receipt OCR and Information Extraction" <https://arxiv.org/pdf/2103.10213.pdf>`_. - Example:: - >>> from doctr.datasets import SROIE - >>> train_set = SROIE(train=True, download=True) - >>> img, target = train_set[0] + .. image:: https://doctr-static.mindee.com/models?id=v0.5.0/sroie-grid.png&src=0 + :align: center + + >>> from doctr.datasets import SROIE + >>> train_set = SROIE(train=True, download=True) + >>> img, target = train_set[0] Args: + ---- train: whether the subset should be the training one - sample_transforms: composable transformations that will be applied to each image - rotated_bbox: whether polygons should be considered as rotated bounding box (instead of straight ones) + use_polygons: whether polygons should be considered as rotated bounding box (instead of straight ones) + recognition_task: whether the dataset should be used for recognition task + detection_task: whether the dataset should be used for detection task **kwargs: keyword arguments from `VisionDataset`. """ - TRAIN = ('https://github.com/mindee/doctr/releases/download/v0.1.1/sroie2019_train_task1.zip', - 'd4fa9e60abb03500d83299c845b9c87fd9c9430d1aeac96b83c5d0bb0ab27f6f') - TEST = ('https://github.com/mindee/doctr/releases/download/v0.1.1/sroie2019_test.zip', - '41b3c746a20226fddc80d86d4b2a903d43b5be4f521dd1bbe759dbf8844745e2') + TRAIN = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/sroie2019_train_task1.zip&src=0", + "d4fa9e60abb03500d83299c845b9c87fd9c9430d1aeac96b83c5d0bb0ab27f6f", + "sroie2019_train_task1.zip", + ) + TEST = ( + "https://doctr-static.mindee.com/models?id=v0.1.1/sroie2019_test.zip&src=0", + "41b3c746a20226fddc80d86d4b2a903d43b5be4f521dd1bbe759dbf8844745e2", + "sroie2019_test.zip", + ) def __init__( self, train: bool = True, - sample_transforms: Optional[Callable[[Any], Any]] = None, - rotated_bbox: bool = False, + use_polygons: bool = False, + recognition_task: bool = False, + detection_task: bool = False, **kwargs: Any, ) -> None: + url, sha256, name = self.TRAIN if train else self.TEST + super().__init__( + url, + name, + sha256, + True, + pre_transforms=convert_target_to_relative if not recognition_task else None, + **kwargs, + ) + if recognition_task and detection_task: + raise ValueError( + "`recognition_task` and `detection_task` cannot be set to True simultaneously. " + + "To get the whole dataset with boxes and labels leave both parameters to False." + ) - url, sha256 = self.TRAIN if train else self.TEST - super().__init__(url, None, sha256, True, **kwargs) - self.sample_transforms = sample_transforms self.train = train - if rotated_bbox: - raise NotImplementedError + tmp_root = os.path.join(self.root, "images") + self.data: List[Tuple[Union[str, np.ndarray], Union[str, Dict[str, Any], np.ndarray]]] = [] + np_dtype = np.float32 - # # List images - self.root = os.path.join(self._root, 'images') - self.data: List[Tuple[str, Dict[str, Any]]] = [] - for img_path in os.listdir(self.root): + for img_path in tqdm(iterable=os.listdir(tmp_root), desc="Unpacking SROIE", total=len(os.listdir(tmp_root))): # File existence check - if not os.path.exists(os.path.join(self.root, img_path)): - raise FileNotFoundError(f"unable to locate {os.path.join(self.root, img_path)}") + if not os.path.exists(os.path.join(tmp_root, img_path)): + raise FileNotFoundError(f"unable to locate {os.path.join(tmp_root, img_path)}") + stem = Path(img_path).stem - _targets = [] - with open(os.path.join(self._root, 'annotations', f"{stem}.txt"), encoding='latin') as f: - for row in csv.reader(f, delimiter=','): - # Safeguard for blank lines - if len(row) > 0: - # Label may contain commas - label = ",".join(row[8:]) - # Reduce 8 coords to 4 - p1_x, p1_y, p2_x, p2_y, p3_x, p3_y, p4_x, p4_y = map(int, row[:8]) - left, right = min(p1_x, p2_x, p3_x, p4_x), max(p1_x, p2_x, p3_x, p4_x) - top, bot = min(p1_y, p2_y, p3_y, p4_y), max(p1_y, p2_y, p3_y, p4_y) - if len(label) > 0: - _targets.append((label, [left, top, right, bot])) - - text_targets, box_targets = zip(*_targets) - - self.data.append((img_path, dict(boxes=np.asarray(box_targets, dtype=np.float32), labels=text_targets))) + with open(os.path.join(self.root, "annotations", f"{stem}.txt"), encoding="latin") as f: + _rows = [row for row in list(csv.reader(f, delimiter=",")) if len(row) > 0] + + labels = [",".join(row[8:]) for row in _rows] + # reorder coordinates (8 -> (4,2) -> + # (x, y) coordinates of top left, top right, bottom right, bottom left corners) and filter empty lines + coords: np.ndarray = np.stack( + [np.array(list(map(int, row[:8])), dtype=np_dtype).reshape((4, 2)) for row in _rows], axis=0 + ) + + if not use_polygons: + # xmin, ymin, xmax, ymax + coords = np.concatenate((coords.min(axis=1), coords.max(axis=1)), axis=1) + + if recognition_task: + crops = crop_bboxes_from_image(img_path=os.path.join(tmp_root, img_path), geoms=coords) + for crop, label in zip(crops, labels): + if crop.shape[0] > 0 and crop.shape[1] > 0 and len(label) > 0: + self.data.append((crop, label)) + elif detection_task: + self.data.append((img_path, coords)) + else: + self.data.append((img_path, dict(boxes=coords, labels=labels))) + + self.root = tmp_root def extra_repr(self) -> str: return f"train={self.train}"
    @@ -390,8 +444,8 @@

    Source code for doctr.datasets.sroie

           
         
       
    -
    - + + diff --git a/v0.6.0/_modules/doctr/datasets/svhn.html b/v0.6.0/_modules/doctr/datasets/svhn.html index 48e4e4d210..60e02b1b3b 100644 --- a/v0.6.0/_modules/doctr/datasets/svhn.html +++ b/v0.6.0/_modules/doctr/datasets/svhn.html @@ -13,7 +13,7 @@ - + doctr.datasets.svhn - docTR documentation @@ -473,7 +473,7 @@

    Source code for doctr.datasets.svhn

         
       
    - + diff --git a/v0.6.0/_modules/doctr/datasets/svt.html b/v0.6.0/_modules/doctr/datasets/svt.html index 4144dc6b9b..a997fcbb50 100644 --- a/v0.6.0/_modules/doctr/datasets/svt.html +++ b/v0.6.0/_modules/doctr/datasets/svt.html @@ -13,7 +13,7 @@ - + doctr.datasets.svt - docTR documentation @@ -459,7 +459,7 @@

    Source code for doctr.datasets.svt

         
       
    - + diff --git a/v0.6.0/_modules/doctr/datasets/synthtext.html b/v0.6.0/_modules/doctr/datasets/synthtext.html index 3b9de506a7..c776e1d673 100644 --- a/v0.6.0/_modules/doctr/datasets/synthtext.html +++ b/v0.6.0/_modules/doctr/datasets/synthtext.html @@ -13,7 +13,7 @@ - + doctr.datasets.synthtext - docTR documentation @@ -470,7 +470,7 @@

    Source code for doctr.datasets.synthtext

         
       
    - + diff --git a/v0.6.0/_modules/doctr/datasets/utils.html b/v0.6.0/_modules/doctr/datasets/utils.html index 2259698c0f..bde9304597 100644 --- a/v0.6.0/_modules/doctr/datasets/utils.html +++ b/v0.6.0/_modules/doctr/datasets/utils.html @@ -13,7 +13,7 @@ - + doctr.datasets.utils - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.datasets.utils

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import string
     import unicodedata
    +from collections.abc import Sequence
    +from functools import partial
    +from pathlib import Path
    +from typing import Any, Dict, List, Optional, Tuple, TypeVar, Union
    +from typing import Sequence as SequenceType
    +
     import numpy as np
    -from typing import List, Optional, Any
    +from PIL import Image
    +
    +from doctr.io.image import get_img_shape
    +from doctr.utils.geometry import convert_to_relative_coords, extract_crops, extract_rcrops
     
     from .vocabs import VOCABS
     
    -__all__ = ['translate', 'encode_sequence', 'decode_sequence', 'encode_sequences']
    +__all__ = ["translate", "encode_string", "decode_sequence", "encode_sequences", "pre_transform_multiclass"]
    +
    +ImageTensor = TypeVar("ImageTensor")
     
     
     def translate(
         input_string: str,
         vocab_name: str,
    -    unknown_char: str = '■',
    +    unknown_char: str = "■",
     ) -> str:
         """Translate a string input in a given vocabulary
     
         Args:
    +    ----
             input_string: input string to translate
             vocab_name: vocabulary to use (french, latin, ...)
             unknown_char: unknown character for non-translatable characters
     
         Returns:
    -        A string translated in a given vocab"""
    -
    +    -------
    +        A string translated in a given vocab
    +    """
         if VOCABS.get(vocab_name) is None:
             raise KeyError("output vocabulary must be in vocabs dictionnary")
     
    -    translated = ''
    +    translated = ""
         for char in input_string:
             if char not in VOCABS[vocab_name]:
                 # we need to translate char into a vocab char
    @@ -315,51 +350,63 @@ 

    Source code for doctr.datasets.utils

                     # remove whitespaces
                     continue
                 # normalize character if it is not in vocab
    -            char = unicodedata.normalize('NFD', char).encode('ascii', 'ignore').decode('ascii')
    -            if char == '' or char not in VOCABS[vocab_name]:
    +            char = unicodedata.normalize("NFD", char).encode("ascii", "ignore").decode("ascii")
    +            if char == "" or char not in VOCABS[vocab_name]:
                     # if normalization fails or char still not in vocab, return unknown character)
                     char = unknown_char
             translated += char
         return translated
     
     
    -def encode_sequence(
    +def encode_string(
         input_string: str,
         vocab: str,
     ) -> List[int]:
         """Given a predefined mapping, encode the string to a sequence of numbers
     
         Args:
    +    ----
             input_string: string to encode
             vocab: vocabulary (string), the encoding is given by the indexing of the character sequence
     
         Returns:
    -        A list encoding the input_string"""
    -
    -    return list(map(vocab.index, input_string))  # type: ignore[arg-type]
    +    -------
    +        A list encoding the input_string
    +    """
    +    try:
    +        return list(map(vocab.index, input_string))
    +    except ValueError:
    +        raise ValueError(
    +            f"some characters cannot be found in 'vocab'. \
    +                         Please check the input string {input_string} and the vocabulary {vocab}"
    +        )
     
     
     def decode_sequence(
    -    input_array: np.array,
    +    input_seq: Union[np.ndarray, SequenceType[int]],
         mapping: str,
     ) -> str:
         """Given a predefined mapping, decode the sequence of numbers to a string
     
         Args:
    -        input_array: array to decode
    +    ----
    +        input_seq: array to decode
             mapping: vocabulary (string), the encoding is given by the indexing of the character sequence
     
         Returns:
    -        A string, decoded from input_array"""
    -
    -    if not input_array.dtype == np.int_ or input_array.max() >= len(mapping):
    +    -------
    +        A string, decoded from input_seq
    +    """
    +    if not isinstance(input_seq, (Sequence, np.ndarray)):
    +        raise TypeError("Invalid sequence type")
    +    if isinstance(input_seq, np.ndarray) and (input_seq.dtype != np.int_ or input_seq.max() >= len(mapping)):
             raise AssertionError("Input must be an array of int, with max less than mapping size")
    -    decoded = ''.join(mapping[idx] for idx in input_array)
    -    return decoded
    +
    +    return "".join(map(mapping.__getitem__, input_seq))
     
     
     
    -[docs] +[docs] def encode_sequences( sequences: List[str], vocab: str, @@ -367,48 +414,53 @@

    Source code for doctr.datasets.utils

         eos: int = -1,
         sos: Optional[int] = None,
         pad: Optional[int] = None,
    -    **kwargs: Any,
    +    dynamic_seq_length: bool = False,
     ) -> np.ndarray:
         """Encode character sequences using a given vocab as mapping
     
         Args:
    +    ----
             sequences: the list of character sequences of size N
             vocab: the ordered vocab to use for encoding
             target_size: maximum length of the encoded data
             eos: encoding of End Of String
             sos: optional encoding of Start Of String
             pad: optional encoding for padding. In case of padding, all sequences are followed by 1 EOS then PAD
    +        dynamic_seq_length: if `target_size` is specified, uses it as upper bound and enables dynamic sequence size
     
         Returns:
    +    -------
             the padded encoded data as a tensor
         """
    -
         if 0 <= eos < len(vocab):
             raise ValueError("argument 'eos' needs to be outside of vocab possible indices")
     
    -    if not isinstance(target_size, int):
    -        target_size = max(len(w) for w in sequences)
    -        if sos:
    -            target_size += 1
    -        if pad:
    -            target_size += 1
    +    if not isinstance(target_size, int) or dynamic_seq_length:
    +        # Maximum string length + EOS
    +        max_length = max(len(w) for w in sequences) + 1
    +        if isinstance(sos, int):
    +            max_length += 1
    +        if isinstance(pad, int):
    +            max_length += 1
    +        target_size = max_length if not isinstance(target_size, int) else min(max_length, target_size)
     
         # Pad all sequences
    -    if pad:  # pad with padding symbol
    +    if isinstance(pad, int):  # pad with padding symbol
             if 0 <= pad < len(vocab):
                 raise ValueError("argument 'pad' needs to be outside of vocab possible indices")
             # In that case, add EOS at the end of the word before padding
    -        encoded_data = np.full([len(sequences), target_size], pad, dtype=np.int32)
    +        default_symbol = pad
         else:  # pad with eos symbol
    -        encoded_data = np.full([len(sequences), target_size], eos, dtype=np.int32)
    +        default_symbol = eos
    +    encoded_data: np.ndarray = np.full([len(sequences), target_size], default_symbol, dtype=np.int32)
     
    -    for idx, seq in enumerate(sequences):
    -        encoded_seq = encode_sequence(seq, vocab)
    -        if pad:  # add eos at the end of the sequence
    -            encoded_seq.append(eos)
    -        encoded_data[idx, :min(len(encoded_seq), target_size)] = encoded_seq[:min(len(encoded_seq), target_size)]
    +    # Encode the strings
    +    for idx, seq in enumerate(map(partial(encode_string, vocab=vocab), sequences)):
    +        if isinstance(pad, int):  # add eos at the end of the sequence
    +            seq.append(eos)
    +        encoded_data[idx, : min(len(seq), target_size)] = seq[: min(len(seq), target_size)]
     
    -    if sos:  # place eos symbol at the beginning of each sequence
    +    if isinstance(sos, int):  # place sos symbol at the beginning of each sequence
             if 0 <= sos < len(vocab):
                 raise ValueError("argument 'sos' needs to be outside of vocab possible indices")
             encoded_data = np.roll(encoded_data, 1)
    @@ -416,6 +468,59 @@ 

    Source code for doctr.datasets.utils

     
         return encoded_data
    + + +def convert_target_to_relative( + img: ImageTensor, target: Union[np.ndarray, Dict[str, Any]] +) -> Tuple[ImageTensor, Union[Dict[str, Any], np.ndarray]]: + if isinstance(target, np.ndarray): + target = convert_to_relative_coords(target, get_img_shape(img)) + else: + target["boxes"] = convert_to_relative_coords(target["boxes"], get_img_shape(img)) + return img, target + + +def crop_bboxes_from_image(img_path: Union[str, Path], geoms: np.ndarray) -> List[np.ndarray]: + """Crop a set of bounding boxes from an image + + Args: + ---- + img_path: path to the image + geoms: a array of polygons of shape (N, 4, 2) or of straight boxes of shape (N, 4) + + Returns: + ------- + a list of cropped images + """ + with Image.open(img_path) as pil_img: + img: np.ndarray = np.array(pil_img.convert("RGB")) + # Polygon + if geoms.ndim == 3 and geoms.shape[1:] == (4, 2): + return extract_rcrops(img, geoms.astype(dtype=int)) + if geoms.ndim == 2 and geoms.shape[1] == 4: + return extract_crops(img, geoms.astype(dtype=int)) + raise ValueError("Invalid geometry format") + + +def pre_transform_multiclass(img, target: Tuple[np.ndarray, List]) -> Tuple[np.ndarray, Dict[str, List]]: + """Converts multiclass target to relative coordinates. + + Args: + ---- + img: Image + target: tuple of target polygons and their classes names + + Returns: + ------- + Image and dictionary of boxes, with class names as keys + """ + boxes = convert_to_relative_coords(target[0], get_img_shape(img)) + boxes_classes = target[1] + boxes_dict: Dict = {k: [] for k in sorted(set(boxes_classes))} + for k, poly in zip(boxes_classes, boxes): + boxes_dict[k].append(poly) + boxes_dict = {k: np.stack(v, axis=0) for k, v in boxes_dict.items()} + return img, boxes_dict
    @@ -448,8 +553,8 @@

    Source code for doctr.datasets.utils

           
         
       
    - - + + diff --git a/v0.6.0/_modules/doctr/datasets/wildreceipt.html b/v0.6.0/_modules/doctr/datasets/wildreceipt.html index c543ee7cac..12c6aebd14 100644 --- a/v0.6.0/_modules/doctr/datasets/wildreceipt.html +++ b/v0.6.0/_modules/doctr/datasets/wildreceipt.html @@ -13,7 +13,7 @@ - + doctr.datasets.wildreceipt - docTR documentation @@ -454,7 +454,7 @@

    Source code for doctr.datasets.wildreceipt

         
       
    - + diff --git a/v0.6.0/_modules/doctr/documents/elements.html b/v0.6.0/_modules/doctr/documents/elements.html deleted file mode 100644 index 10c1e142d2..0000000000 --- a/v0.6.0/_modules/doctr/documents/elements.html +++ /dev/null @@ -1,577 +0,0 @@ - - - - - - - - - - - - doctr.documents.elements - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.elements

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import matplotlib.pyplot as plt
    -from typing import Tuple, Dict, List, Any, Optional, Union
    -
    -from doctr.utils.geometry import resolve_enclosing_bbox, resolve_enclosing_rbbox
    -from doctr.utils.visualization import visualize_page
    -from doctr.utils.common_types import BoundingBox, RotatedBbox
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['Element', 'Word', 'Artefact', 'Line', 'Block', 'Page', 'Document']
    -
    -
    -class Element(NestedObject):
    -    """Implements an abstract document element with exporting and text rendering capabilities"""
    -
    -    _exported_keys: List[str] = []
    -
    -    def __init__(self, **kwargs: Any) -> None:
    -        self._children_names: List[str] = []
    -        for k, v in kwargs.items():
    -            setattr(self, k, v)
    -            self._children_names.append(k)
    -
    -    def export(self) -> Dict[str, Any]:
    -        """Exports the object into a nested dict format"""
    -
    -        export_dict = {k: getattr(self, k) for k in self._exported_keys}
    -        for children_name in self._children_names:
    -            export_dict[children_name] = [c.export() for c in getattr(self, children_name)]
    -
    -        return export_dict
    -
    -    def render(self) -> str:
    -        raise NotImplementedError
    -
    -
    -
    -[docs] -class Word(Element): - """Implements a word element - - Args: - value: the text string of the word - confidence: the confidence associated with the text prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size - """ - - _exported_keys: List[str] = ["value", "confidence", "geometry"] - - def __init__(self, value: str, confidence: float, geometry: Union[BoundingBox, RotatedBbox]) -> None: - super().__init__() - self.value = value - self.confidence = confidence - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return self.value - - def extra_repr(self) -> str: - return f"value='{self.value}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Artefact(Element): - """Implements a non-textual element - - Args: - artefact_type: the type of artefact - confidence: the confidence of the type prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. - """ - - _exported_keys: List[str] = ["geometry", "type", "confidence"] - - def __init__(self, artefact_type: str, confidence: float, geometry: BoundingBox) -> None: - super().__init__() - self.geometry = geometry - self.type = artefact_type - self.confidence = confidence - - def render(self) -> str: - """Renders the full text of the element""" - return f"[{self.type.upper()}]" - - def extra_repr(self) -> str: - return f"type='{self.type}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Line(Element): - """Implements a line element as a collection of words - - Args: - words: list of word elements - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all words in it. - """ - - _exported_keys: List[str] = ["geometry"] - words: List[Word] = [] - - def __init__( - self, - words: List[Word], - geometry: Optional[Union[BoundingBox, RotatedBbox]] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - # Check whether this is a rotated or straight box - box_resolution_fn = resolve_enclosing_rbbox if len(words[0].geometry) == 5 else resolve_enclosing_bbox - geometry = box_resolution_fn([w.geometry for w in words]) # type: ignore[operator, misc] - - super().__init__(words=words) - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return " ".join(w.render() for w in self.words)
    - - - -
    -[docs] -class Block(Element): - """Implements a block element as a collection of lines and artefacts - - Args: - lines: list of line elements - artefacts: list of artefacts - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all lines and artefacts in it. - """ - - _exported_keys: List[str] = ["geometry"] - lines: List[Line] = [] - artefacts: List[Artefact] = [] - - def __init__( - self, - lines: List[Line] = [], - artefacts: List[Artefact] = [], - geometry: Optional[Union[BoundingBox, RotatedBbox]] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - line_boxes = [word.geometry for line in lines for word in line.words] - artefact_boxes = [artefact.geometry for artefact in artefacts] - box_resolution_fn = resolve_enclosing_rbbox if len(lines[0].geometry) == 5 else resolve_enclosing_bbox - geometry = box_resolution_fn(line_boxes + artefact_boxes) # type: ignore[operator, arg-type] - - super().__init__(lines=lines, artefacts=artefacts) - self.geometry = geometry - - def render(self, line_break: str = '\n') -> str: - """Renders the full text of the element""" - return line_break.join(line.render() for line in self.lines)
    - - - -
    -[docs] -class Page(Element): - """Implements a page element as a collection of blocks - - Args: - blocks: list of block elements - page_idx: the index of the page in the input raw document - dimensions: the page size in pixels in format (width, height) - orientation: a dictionary with the value of the rotation angle in degress and confidence of the prediction - language: a dictionary with the language value and confidence of the prediction - """ - - _exported_keys: List[str] = ["page_idx", "dimensions", "orientation", "language"] - blocks: List[Block] = [] - - def __init__( - self, - blocks: List[Block], - page_idx: int, - dimensions: Tuple[int, int], - orientation: Optional[Dict[str, Any]] = None, - language: Optional[Dict[str, Any]] = None, - ) -> None: - super().__init__(blocks=blocks) - self.page_idx = page_idx - self.dimensions = dimensions - self.orientation = orientation if isinstance(orientation, dict) else dict(value=None, confidence=None) - self.language = language if isinstance(language, dict) else dict(value=None, confidence=None) - - def render(self, block_break: str = '\n\n') -> str: - """Renders the full text of the element""" - return block_break.join(b.render() for b in self.blocks) - - def extra_repr(self) -> str: - return f"dimensions={self.dimensions}" - -
    -[docs] - def show( - self, page: np.ndarray, interactive: bool = True, **kwargs - ) -> None: - """Overlay the result on a given image - - Args: - page: image encoded as a numpy array in uint8 - interactive: whether the display should be interactive - """ - visualize_page(self.export(), page, interactive=interactive) - plt.show(**kwargs)
    -
    - - - -
    -[docs] -class Document(Element): - """Implements a document element as a collection of pages - - Args: - pages: list of page elements - """ - - pages: List[Page] = [] - - def __init__( - self, - pages: List[Page], - ) -> None: - super().__init__(pages=pages) - - def render(self, page_break: str = '\n\n\n\n') -> str: - """Renders the full text of the element""" - return page_break.join(p.render() for p in self.pages) - -
    -[docs] - def show(self, pages: List[np.ndarray], **kwargs) -> None: - """Overlay the result on a given image - - Args: - pages: list of images encoded as numpy arrays in uint8 - """ - for img, result in zip(pages, self.pages): - result.show(img, **kwargs)
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.6.0/_modules/doctr/documents/reader.html b/v0.6.0/_modules/doctr/documents/reader.html deleted file mode 100644 index cdcd814b6c..0000000000 --- a/v0.6.0/_modules/doctr/documents/reader.html +++ /dev/null @@ -1,612 +0,0 @@ - - - - - - - - - - - - doctr.documents.reader - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.reader

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import cv2
    -from pathlib import Path
    -import fitz
    -from weasyprint import HTML
    -from typing import List, Tuple, Optional, Any, Union, Sequence, Dict
    -
    -__all__ = ['read_pdf', 'read_img', 'read_html', 'DocumentFile', 'PDF']
    -
    -
    -AbstractPath = Union[str, Path]
    -AbstractFile = Union[AbstractPath, bytes]
    -Bbox = Tuple[float, float, float, float]
    -
    -
    -
    -[docs] -def read_img( - file: AbstractFile, - output_size: Optional[Tuple[int, int]] = None, - rgb_output: bool = True, -) -> np.ndarray: - """Read an image file into numpy format - - Example:: - >>> from doctr.documents import read_img - >>> page = read_img("path/to/your/doc.jpg") - - Args: - file: the path to the image file - output_size: the expected output size of each page in format H x W - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - Returns: - the page decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)): - if not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - img = cv2.imread(str(file), cv2.IMREAD_COLOR) - elif isinstance(file, bytes): - file = np.frombuffer(file, np.uint8) - img = cv2.imdecode(file, cv2.IMREAD_COLOR) - else: - raise TypeError("unsupported object type for argument 'file'") - - # Validity check - if img is None: - raise ValueError("unable to read file.") - # Resizing - if isinstance(output_size, tuple): - img = cv2.resize(img, output_size[::-1], interpolation=cv2.INTER_LINEAR) - # Switch the channel order - if rgb_output: - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - return img
    - - - -
    -[docs] -def read_pdf(file: AbstractFile, **kwargs: Any) -> fitz.Document: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_pdf - >>> doc = read_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)) and not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - - fitz_args: Dict[str, AbstractFile] = {} - - if isinstance(file, (str, Path)): - fitz_args['filename'] = file - elif isinstance(file, bytes): - fitz_args['stream'] = file - else: - raise TypeError("unsupported object type for argument 'file'") - - # Read pages with fitz and convert them to numpy ndarrays - return fitz.open(**fitz_args, filetype="pdf", **kwargs)
    - - - -def convert_page_to_numpy( - page: fitz.fitz.Page, - output_size: Optional[Tuple[int, int]] = None, - bgr_output: bool = False, - default_scales: Tuple[float, float] = (2, 2), -) -> np.ndarray: - """Convert a fitz page to a numpy-formatted image - - Args: - page: the page of a file read with PyMuPDF - output_size: the expected output size of each page in format H x W. Default goes to 840 x 595 for A4 pdf, - if you want to increase the resolution while preserving the original A4 aspect ratio can pass (1024, 726) - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - default_scales: spatial scaling to be applied when output_size is not specified where (1, 1) - corresponds to 72 dpi rendering. - - Returns: - the rendered image in numpy format - """ - - # If no output size is specified, keep the origin one - if output_size is not None: - scales = (output_size[1] / page.MediaBox[2], output_size[0] / page.MediaBox[3]) - else: - # Default 72 DPI (scales of (1, 1)) is unnecessarily low - scales = default_scales - - transform_matrix = fitz.Matrix(*scales) - - # Generate the pixel map using the transformation matrix - pixmap = page.getPixmap(matrix=transform_matrix) - # Decode it into a numpy - img = np.frombuffer(pixmap.samples, dtype=np.uint8).reshape(pixmap.height, pixmap.width, 3) - - # Switch the channel order - if bgr_output: - img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) - - return img - - -
    -[docs] -def read_html(url: str, **kwargs: Any) -> bytes: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_html - >>> doc = read_html("https://www.yoursite.com") - - Args: - url: URL of the target web page - Returns: - decoded PDF file as a bytes stream - """ - - return HTML(url, **kwargs).write_pdf()
    - - - -
    -[docs] -class PDF: - """PDF document template - - Args: - doc: input PDF document - """ - def __init__(self, doc: fitz.Document) -> None: - self.doc = doc - -
    -[docs] - def as_images(self, **kwargs) -> List[np.ndarray]: - """Convert all document pages to images - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images() - - Args: - kwargs: keyword arguments of `convert_page_to_numpy` - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - return [convert_page_to_numpy(page, **kwargs) for page in self.doc]
    - - - def get_page_words(self, idx, **kwargs) -> List[Tuple[Bbox, str]]: - """Get the annotations for all words of a given page""" - - # xmin, ymin, xmax, ymax, value, block_idx, line_idx, word_idx - return [(info[:4], info[4]) for info in self.doc[idx].getTextWords(**kwargs)] - -
    -[docs] - def get_words(self, **kwargs) -> List[List[Tuple[Bbox, str]]]: - """Get the annotations for all words in the document - - Example:: - >>> from doctr.documents import DocumentFile - >>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words() - - Args: - kwargs: keyword arguments of `fitz.Page.getTextWords` - Returns: - the list of pages annotations, represented as a list of tuple (bounding box, value) - """ - return [self.get_page_words(idx, **kwargs) for idx in range(len(self.doc))]
    - - - def get_page_artefacts(self, idx) -> List[Tuple[float, float, float, float]]: - return [tuple(self.doc[idx].getImageBbox(artefact)) # type: ignore[misc] - for artefact in self.doc[idx].get_images(full=True)] - -
    -[docs] - def get_artefacts(self) -> List[List[Tuple[float, float, float, float]]]: - """Get the artefacts for the entire document - - Example:: - >>> from doctr.documents import DocumentFile - >>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts() - - Returns: - the list of pages artefacts, represented as a list of bounding boxes - """ - - return [self.get_page_artefacts(idx) for idx in range(len(self.doc))]
    -
    - - - -
    -[docs] -class DocumentFile: - """Read a document from multiple extensions""" - -
    -[docs] - @classmethod - def from_pdf(cls, file: AbstractFile, **kwargs) -> PDF: - """Read a PDF file - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file or a binary stream - Returns: - a PDF document - """ - - doc = read_pdf(file, **kwargs) - - return PDF(doc)
    - - -
    -[docs] - @classmethod - def from_url(cls, url: str, **kwargs) -> PDF: - """Interpret a web page as a PDF document - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_url("https://www.yoursite.com") - - Args: - url: the URL of the target web page - Returns: - a PDF document - """ - pdf_stream = read_html(url) - return cls.from_pdf(pdf_stream, **kwargs)
    - - -
    -[docs] - @classmethod - def from_images(cls, files: Union[Sequence[AbstractFile], AbstractFile], **kwargs) -> List[np.ndarray]: - """Read an image file (or a collection of image files) and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"]) - - Args: - files: the path to the image file or a binary stream, or a collection of those - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - if isinstance(files, (str, Path, bytes)): - files = [files] - - return [read_img(file, **kwargs) for file in files]
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.6.0/_modules/doctr/io/elements.html b/v0.6.0/_modules/doctr/io/elements.html index 753a47455c..e049d6ce30 100644 --- a/v0.6.0/_modules/doctr/io/elements.html +++ b/v0.6.0/_modules/doctr/io/elements.html @@ -13,7 +13,7 @@ - + doctr.io.elements - docTR documentation @@ -1008,7 +1008,7 @@

    Source code for doctr.io.elements

         
       
    - + diff --git a/v0.6.0/_modules/doctr/io/html.html b/v0.6.0/_modules/doctr/io/html.html index 7ad5b97031..be73631500 100644 --- a/v0.6.0/_modules/doctr/io/html.html +++ b/v0.6.0/_modules/doctr/io/html.html @@ -13,7 +13,7 @@ - + doctr.io.html - docTR documentation @@ -360,7 +360,7 @@

    Source code for doctr.io.html

         
       
    - + diff --git a/v0.6.0/_modules/doctr/io/image/base.html b/v0.6.0/_modules/doctr/io/image/base.html index 336b4bff0e..a50c95d595 100644 --- a/v0.6.0/_modules/doctr/io/image/base.html +++ b/v0.6.0/_modules/doctr/io/image/base.html @@ -13,7 +13,7 @@ - + doctr.io.image.base - docTR documentation @@ -388,7 +388,7 @@

    Source code for doctr.io.image.base

         
       
    - + diff --git a/v0.6.0/_modules/doctr/io/image/tensorflow.html b/v0.6.0/_modules/doctr/io/image/tensorflow.html index f1846820a3..3b9e731756 100644 --- a/v0.6.0/_modules/doctr/io/image/tensorflow.html +++ b/v0.6.0/_modules/doctr/io/image/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.io.image.tensorflow - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.io.image.tensorflow

         
       
    - + diff --git a/v0.6.0/_modules/doctr/io/pdf.html b/v0.6.0/_modules/doctr/io/pdf.html index e3abf6960b..e5b94811c3 100644 --- a/v0.6.0/_modules/doctr/io/pdf.html +++ b/v0.6.0/_modules/doctr/io/pdf.html @@ -13,7 +13,7 @@ - + doctr.io.pdf - docTR documentation @@ -377,7 +377,7 @@

    Source code for doctr.io.pdf

         
       
    - + diff --git a/v0.6.0/_modules/doctr/io/reader.html b/v0.6.0/_modules/doctr/io/reader.html index c1ddc26edd..d36e5bb553 100644 --- a/v0.6.0/_modules/doctr/io/reader.html +++ b/v0.6.0/_modules/doctr/io/reader.html @@ -13,7 +13,7 @@ - + doctr.io.reader - docTR documentation @@ -426,7 +426,7 @@

    Source code for doctr.io.reader

         
       
    - + diff --git a/v0.6.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html b/v0.6.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html index 9f074805c1..61a010d548 100644 --- a/v0.6.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html +++ b/v0.6.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.magc_resnet.tensorflow - docTR documentation @@ -531,7 +531,7 @@

    Source code for doctr.models.classification.magc_resnet.tensorflow

    - + diff --git a/v0.6.0/_modules/doctr/models/classification/mobilenet/tensorflow.html b/v0.6.0/_modules/doctr/models/classification/mobilenet/tensorflow.html index 6a63851276..7c448394ad 100644 --- a/v0.6.0/_modules/doctr/models/classification/mobilenet/tensorflow.html +++ b/v0.6.0/_modules/doctr/models/classification/mobilenet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.mobilenet.tensorflow - docTR documentation @@ -793,7 +793,7 @@

    Source code for doctr.models.classification.mobilenet.tensorflow

    - + diff --git a/v0.6.0/_modules/doctr/models/classification/resnet/tensorflow.html b/v0.6.0/_modules/doctr/models/classification/resnet/tensorflow.html index 095d377f31..aed4343741 100644 --- a/v0.6.0/_modules/doctr/models/classification/resnet/tensorflow.html +++ b/v0.6.0/_modules/doctr/models/classification/resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.resnet.tensorflow - docTR documentation @@ -749,7 +749,7 @@

    Source code for doctr.models.classification.resnet.tensorflow

    - + diff --git a/v0.6.0/_modules/doctr/models/classification/textnet/tensorflow.html b/v0.6.0/_modules/doctr/models/classification/textnet/tensorflow.html index ad254ebbfb..c5567d7d67 100644 --- a/v0.6.0/_modules/doctr/models/classification/textnet/tensorflow.html +++ b/v0.6.0/_modules/doctr/models/classification/textnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.textnet.tensorflow - docTR documentation @@ -611,7 +611,7 @@

    Source code for doctr.models.classification.textnet.tensorflow

    - + diff --git a/v0.6.0/_modules/doctr/models/classification/vgg/tensorflow.html b/v0.6.0/_modules/doctr/models/classification/vgg/tensorflow.html index 01ae452624..788111ae87 100644 --- a/v0.6.0/_modules/doctr/models/classification/vgg/tensorflow.html +++ b/v0.6.0/_modules/doctr/models/classification/vgg/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vgg.tensorflow - docTR documentation @@ -451,7 +451,7 @@

    Source code for doctr.models.classification.vgg.tensorflow

    - + diff --git a/v0.6.0/_modules/doctr/models/classification/vit/tensorflow.html b/v0.6.0/_modules/doctr/models/classification/vit/tensorflow.html index 1333cf6045..971ba5abe9 100644 --- a/v0.6.0/_modules/doctr/models/classification/vit/tensorflow.html +++ b/v0.6.0/_modules/doctr/models/classification/vit/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vit.tensorflow - docTR documentation @@ -533,7 +533,7 @@

    Source code for doctr.models.classification.vit.tensorflow

    - + diff --git a/v0.6.0/_modules/doctr/models/classification/zoo.html b/v0.6.0/_modules/doctr/models/classification/zoo.html index f7796a7522..3eb2a3ec4e 100644 --- a/v0.6.0/_modules/doctr/models/classification/zoo.html +++ b/v0.6.0/_modules/doctr/models/classification/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.classification.zoo - docTR documentation @@ -447,7 +447,7 @@

    Source code for doctr.models.classification.zoo

    <
    - + diff --git a/v0.6.0/_modules/doctr/models/detection/differentiable_binarization.html b/v0.6.0/_modules/doctr/models/detection/differentiable_binarization.html deleted file mode 100644 index 38e9b36ec2..0000000000 --- a/v0.6.0/_modules/doctr/models/detection/differentiable_binarization.html +++ /dev/null @@ -1,879 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.differentiable_binarization - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.differentiable_binarization

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -import cv2
    -from copy import deepcopy
    -import numpy as np
    -from shapely.geometry import Polygon
    -import pyclipper
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers
    -from typing import Union, List, Tuple, Optional, Any, Dict
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..utils import IntermediateLayerGetter, load_pretrained_params, conv_sequence
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['DBPostProcessor', 'DBNet', 'db_resnet50']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'db_resnet50': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'backbone': 'ResNet50',
    -        'fpn_layers': ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"],
    -        'fpn_channels': 128,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'DBPostProcessor',
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.2.0/db_resnet50-adcafc63.zip',
    -    },
    -}
    -
    -
    -class DBPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for DBNet adapted from the implementation of `xuannianz
    -    <https://github.com/xuannianz/DifferentiableBinarization>`_.
    -
    -    Args:
    -        unclip ratio: ratio used to unshrink polygons
    -        min_size_box: minimal length (pix) to keep a box
    -        max_candidates: maximum boxes to consider in a single page
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        unclip_ratio: Union[float, int] = 1.5,
    -        max_candidates: int = 1000,
    -        box_thresh: float = 0.1,
    -        bin_thresh: float = 0.3,
    -    ) -> None:
    -
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -        self.unclip_ratio = unclip_ratio
    -        self.max_candidates = max_candidates
    -
    -    def polygon_to_box(
    -        self,
    -        points: np.ndarray,
    -    ) -> Optional[Tuple[int, int, int, int]]:
    -        """Expand a polygon (points) by a factor unclip_ratio, and returns a 4-points box
    -
    -        Args:
    -            points: The first parameter.
    -
    -        Returns:
    -            a box in absolute coordinates (x, y, w, h)
    -        """
    -        poly = Polygon(points)
    -        distance = poly.area * self.unclip_ratio / poly.length  # compute distance to expand polygon
    -        offset = pyclipper.PyclipperOffset()
    -        offset.AddPath(points, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        _points = offset.Execute(distance)
    -        # Take biggest stack of points
    -        idx = 0
    -        if len(_points) > 1:
    -            max_size = 0
    -            for _idx, p in enumerate(_points):
    -                if len(p) > max_size:
    -                    idx = _idx
    -                    max_size = len(p)
    -            # We ensure that _points can be correctly casted to a ndarray
    -            _points = [_points[idx]]
    -        expanded_points = np.asarray(_points)  # expand polygon
    -        if len(expanded_points) < 1:
    -            return None
    -        x, y, w, h = cv2.boundingRect(expanded_points)  # compute a 4-points box from expanded polygon
    -        return x, y, w, h
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map
    -
    -        Args:
    -            pred: Pred map from differentiable binarization output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        # get contours from connected components on the bitmap
    -        contours, _ = cv2.findContours(bitmap.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    -        for contour in contours[:self.max_candidates]:
    -            # Check whether smallest enclosing bounding box is not too small
    -            if np.any(contour[:, 0].max(axis=0) - contour[:, 0].min(axis=0) < min_size_box):
    -                continue
    -            x, y, w, h = cv2.boundingRect(contour)
    -            points = np.array([[x, y], [x, y + h], [x + w, y + h], [x + w, y]])
    -            # Compute objectness
    -            score = self.box_score(pred, points)
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            _box = self.polygon_to_box(points)
    -
    -            if _box is None or _box[2] < min_size_box or _box[3] < min_size_box:  # remove to small boxes
    -                continue
    -            x, y, w, h = _box
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -class FeaturePyramidNetwork(layers.Layer, NestedObject):
    -    """Feature Pyramid Network as described in `"Feature Pyramid Networks for Object Detection"
    -    <https://arxiv.org/pdf/1612.03144.pdf>`_.
    -
    -    Args:
    -        channels: number of channel to output
    -    """
    -
    -    def __init__(
    -        self,
    -        channels: int,
    -    ) -> None:
    -        super().__init__()
    -        self.channels = channels
    -        self.upsample = layers.UpSampling2D(size=(2, 2), interpolation='nearest')
    -        self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer='he_normal') for _ in range(4)]
    -        self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2 ** idx) for idx in range(4)]
    -
    -    @staticmethod
    -    def build_upsampling(
    -        channels: int,
    -        dilation_factor: int = 1,
    -    ) -> layers.Layer:
    -        """Module which performs a 3x3 convolution followed by up-sampling
    -
    -        Args:
    -            channels: number of output channels
    -            dilation_factor (int): dilation factor to scale the convolution output before concatenation
    -
    -        Returns:
    -            a keras.layers.Layer object, wrapping these operations in a sequential module
    -
    -        """
    -
    -        _layers = conv_sequence(channels, 'relu', True, kernel_size=3)
    -
    -        if dilation_factor > 1:
    -            _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation='nearest'))
    -
    -        module = keras.Sequential(_layers)
    -
    -        return module
    -
    -    def extra_repr(self) -> str:
    -        return f"channels={self.channels}"
    -
    -    def call(
    -        self,
    -        x: List[tf.Tensor],
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # Channel mapping
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.inner_blocks, x)]
    -        # Upsample & sum
    -        for idx in range(len(results) - 1, -1):
    -            results[idx] += self.upsample(results[idx + 1])
    -        # Conv & upsample
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.layer_blocks, results)]
    -
    -        return layers.concatenate(results)
    -
    -
    -class DBNet(DetectionModel, NestedObject):
    -    """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization"
    -    <https://arxiv.org/pdf/1911.08947.pdf>`_.
    -
    -    Args:
    -        feature extractor: the backbone serving as feature extractor
    -        fpn_channels: number of channels each extracted feature maps is mapped to
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'fpn', 'probability_head', 'threshold_head', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: IntermediateLayerGetter,
    -        fpn_channels: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(cfg=cfg)
    -
    -        self.shrink_ratio = 0.4
    -        self.thresh_min = 0.3
    -        self.thresh_max = 0.7
    -        self.min_size_box = 3
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.fpn = FeaturePyramidNetwork(channels=fpn_channels)
    -        # Initialize kernels
    -        _inputs = [layers.Input(shape=in_shape[1:]) for in_shape in self.feat_extractor.output_shape]
    -        output_shape = tuple(self.fpn(_inputs).shape)
    -
    -        self.probability_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -        self.threshold_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -
    -        self.postprocessor = DBPostProcessor()
    -
    -    @staticmethod
    -    def compute_distance(
    -        xs: np.array,
    -        ys: np.array,
    -        a: np.array,
    -        b: np.array,
    -        eps: float = 1e-7,
    -    ) -> float:
    -        """Compute the distance for each point of the map (xs, ys) to the (a, b) segment
    -
    -        Args:
    -            xs : map of x coordinates (height, width)
    -            ys : map of y coordinates (height, width)
    -            a: first point defining the [ab] segment
    -            b: second point defining the [ab] segment
    -
    -        Returns:
    -            The computed distance
    -
    -        """
    -        square_dist_1 = np.square(xs - a[0]) + np.square(ys - a[1])
    -        square_dist_2 = np.square(xs - b[0]) + np.square(ys - b[1])
    -        square_dist = np.square(a[0] - b[0]) + np.square(a[1] - b[1])
    -        cosin = (square_dist - square_dist_1 - square_dist_2) / (2 * np.sqrt(square_dist_1 * square_dist_2) + eps)
    -        square_sin = 1 - np.square(cosin)
    -        square_sin = np.nan_to_num(square_sin)
    -        result = np.sqrt(square_dist_1 * square_dist_2 * square_sin / square_dist)
    -        result[cosin < 0] = np.sqrt(np.fmin(square_dist_1, square_dist_2))[cosin < 0]
    -        return result
    -
    -    def draw_thresh_map(
    -        self,
    -        polygon: np.array,
    -        canvas: np.array,
    -        mask: np.array,
    -    ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    -        """Draw a polygon treshold map on a canvas, as described in the DB paper
    -
    -        Args:
    -            polygon : array of coord., to draw the boundary of the polygon
    -            canvas : threshold map to fill with polygons
    -            mask : mask for training on threshold polygons
    -        """
    -        if polygon.ndim != 2 or polygon.shape[1] != 2:
    -            raise AttributeError("polygon should be a 2 dimensional array of coords")
    -
    -        # Augment polygon by shrink_ratio
    -        polygon_shape = Polygon(polygon)
    -        distance = polygon_shape.area * (1 - np.power(self.shrink_ratio, 2)) / polygon_shape.length
    -        subject = [tuple(coor) for coor in polygon]  # Get coord as list of tuples
    -        padding = pyclipper.PyclipperOffset()
    -        padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        padded_polygon = np.array(padding.Execute(distance)[0])
    -
    -        # Fill the mask with 1 on the new padded polygon
    -        cv2.fillPoly(mask, [padded_polygon.astype(np.int32)], 1.0)
    -
    -        # Get min/max to recover polygon after distance computation
    -        xmin = padded_polygon[:, 0].min()
    -        xmax = padded_polygon[:, 0].max()
    -        ymin = padded_polygon[:, 1].min()
    -        ymax = padded_polygon[:, 1].max()
    -        width = xmax - xmin + 1
    -        height = ymax - ymin + 1
    -        # Get absolute polygon for distance computation
    -        polygon[:, 0] = polygon[:, 0] - xmin
    -        polygon[:, 1] = polygon[:, 1] - ymin
    -        # Get absolute padded polygon
    -        xs = np.broadcast_to(np.linspace(0, width - 1, num=width).reshape(1, width), (height, width))
    -        ys = np.broadcast_to(np.linspace(0, height - 1, num=height).reshape(height, 1), (height, width))
    -
    -        # Compute distance map to fill the padded polygon
    -        distance_map = np.zeros((polygon.shape[0], height, width), dtype=np.float32)
    -        for i in range(polygon.shape[0]):
    -            j = (i + 1) % polygon.shape[0]
    -            absolute_distance = self.compute_distance(xs, ys, polygon[i], polygon[j])
    -            distance_map[i] = np.clip(absolute_distance / distance, 0, 1)
    -        distance_map = np.min(distance_map, axis=0)
    -
    -        # Clip the padded polygon inside the canvas
    -        xmin_valid = min(max(0, xmin), canvas.shape[1] - 1)
    -        xmax_valid = min(max(0, xmax), canvas.shape[1] - 1)
    -        ymin_valid = min(max(0, ymin), canvas.shape[0] - 1)
    -        ymax_valid = min(max(0, ymax), canvas.shape[0] - 1)
    -
    -        # Fill the canvas with the distances computed inside the valid padded polygon
    -        canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1] = np.fmax(
    -            1 - distance_map[
    -                ymin_valid - ymin:ymax_valid - ymin + 1,
    -                xmin_valid - xmin:xmax_valid - xmin + 1
    -            ],
    -            canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1]
    -        )
    -
    -        return polygon, canvas, mask
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor, tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.uint8)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -        thresh_target = np.zeros(output_shape, dtype=np.uint8)
    -        thresh_mask = np.ones(output_shape, dtype=np.uint8)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            polys = np.stack([
    -                abs_boxes[:, [0, 1]],
    -                abs_boxes[:, [0, 3]],
    -                abs_boxes[:, [2, 3]],
    -                abs_boxes[:, [2, 1]],
    -            ], axis=1)
    -
    -            for box, box_size, poly, is_ambiguous in zip(abs_boxes, boxes_size, polys, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -
    -                # Negative shrink for gt, as described in paper
    -                polygon = Polygon(poly)
    -                distance = polygon.area * (1 - np.power(self.shrink_ratio, 2)) / polygon.length
    -                subject = [tuple(coor) for coor in poly]
    -                padding = pyclipper.PyclipperOffset()
    -                padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -                shrinked = padding.Execute(-distance)
    -
    -                # Draw polygon on gt if it is valid
    -                if len(shrinked) == 0:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                shrinked = np.array(shrinked[0]).reshape(-1, 2)
    -                if shrinked.shape[0] <= 2 or not Polygon(shrinked).is_valid:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                cv2.fillPoly(seg_target[idx], [shrinked.astype(np.int32)], 1)
    -
    -                # Draw on both thresh map and thresh mask
    -                poly, thresh_target[idx], thresh_mask[idx] = self.draw_thresh_map(poly, thresh_target[idx],
    -                                                                                  thresh_mask[idx])
    -
    -        thresh_target = thresh_target.astype(np.float32) * (self.thresh_max - self.thresh_min) + self.thresh_min
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -        thresh_target = tf.convert_to_tensor(thresh_target, dtype=tf.float32)
    -        thresh_mask = tf.convert_to_tensor(thresh_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask, thresh_target, thresh_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        thresh_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts, masks, thresh_gts, thresh_masks from a list of boxes
    -        and a list of masks for each image. From there it computes the loss with the model output
    -
    -        Args:
    -            out_map: output feature map of the model of shape (N, H, W, C)
    -            thresh_map: threshold map of shape (N, H, W, C)
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -
    -        prob_map = tf.math.sigmoid(tf.squeeze(out_map, axis=[-1]))
    -        thresh_map = tf.math.sigmoid(tf.squeeze(thresh_map, axis=[-1]))
    -
    -        seg_target, seg_mask, thresh_target, thresh_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute balanced BCE loss for proba_map
    -        bce_scale = 5.
    -        bce_loss = tf.keras.losses.binary_crossentropy(seg_target[..., None], out_map, from_logits=True)[seg_mask]
    -
    -        neg_target = 1 - seg_target[seg_mask]
    -        positive_count = tf.math.reduce_sum(seg_target[seg_mask])
    -        negative_count = tf.math.reduce_min([tf.math.reduce_sum(neg_target), 3. * positive_count])
    -        negative_loss = bce_loss * neg_target
    -        negative_loss, _ = tf.nn.top_k(negative_loss, tf.cast(negative_count, tf.int32))
    -        sum_losses = tf.math.reduce_sum(bce_loss * seg_target[seg_mask]) + tf.math.reduce_sum(negative_loss)
    -        balanced_bce_loss = sum_losses / (positive_count + negative_count + 1e-6)
    -
    -        # Compute dice loss for approxbin_map
    -        bin_map = 1 / (1 + tf.exp(-50. * (prob_map[seg_mask] - thresh_map[seg_mask])))
    -
    -        bce_min = tf.math.reduce_min(bce_loss)
    -        weights = (bce_loss - bce_min) / (tf.math.reduce_max(bce_loss) - bce_min) + 1.
    -        inter = tf.math.reduce_sum(bin_map * seg_target[seg_mask] * weights)
    -        union = tf.math.reduce_sum(bin_map) + tf.math.reduce_sum(seg_target[seg_mask]) + 1e-8
    -        dice_loss = 1 - 2.0 * inter / union
    -
    -        # Compute l1 loss for thresh_map
    -        l1_scale = 10.
    -        if tf.reduce_any(thresh_mask):
    -            l1_loss = tf.math.reduce_mean(tf.math.abs(thresh_map[thresh_mask] - thresh_target[thresh_mask]))
    -        else:
    -            l1_loss = tf.constant(0.)
    -
    -        return l1_scale * l1_loss + bce_scale * balanced_bce_loss + dice_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        feat_maps = self.feat_extractor(x, **kwargs)
    -        feat_concat = self.fpn(feat_maps, **kwargs)
    -        logits = self.probability_head(feat_concat, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            thresh_map = self.threshold_head(feat_concat, **kwargs)
    -            loss = self.compute_loss(logits, thresh_map, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _db_resnet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> DBNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['fpn_channels'] = kwargs.get('fpn_channels', _cfg['fpn_channels'])
    -
    -    # Feature extractor
    -    resnet = tf.keras.applications.__dict__[_cfg['backbone']](
    -        include_top=False,
    -        weights=None,
    -        input_shape=_cfg['input_shape'],
    -        pooling=None,
    -    )
    -
    -    feat_extractor = IntermediateLayerGetter(
    -        resnet,
    -        _cfg['fpn_layers'],
    -    )
    -
    -    kwargs['fpn_channels'] = _cfg['fpn_channels']
    -
    -    # Build the model
    -    model = DBNet(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def db_resnet50(pretrained: bool = False, **kwargs: Any) -> DBNet: - """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" - <https://arxiv.org/pdf/1911.08947.pdf>`_, using a ResNet-50 backbone. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _db_resnet('db_resnet50', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.6.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html b/v0.6.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html index 9145c7c3fd..66cef8663d 100644 --- a/v0.6.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html +++ b/v0.6.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.differentiable_binarization.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     # Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
     
     from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers
    -from typing import List, Tuple, Optional, Any, Dict
    +from typing import Any, Dict, List, Optional, Tuple
     
    +import numpy as np
    +import tensorflow as tf
    +from tensorflow.keras import Model, Sequential, layers, losses
    +from tensorflow.keras.applications import ResNet50
    +
    +from doctr.file_utils import CLASS_NAME
    +from doctr.models.utils import (
    +    IntermediateLayerGetter,
    +    _bf16_to_float32,
    +    _build_model,
    +    conv_sequence,
    +    load_pretrained_params,
    +)
     from doctr.utils.repr import NestedObject
    -from doctr.models.utils import IntermediateLayerGetter, load_pretrained_params, conv_sequence
    +
    +from ...classification import mobilenet_v3_large
     from .base import DBPostProcessor, _DBNet
     
    -__all__ = ['DBNet', 'db_resnet50']
    +__all__ = ["DBNet", "db_resnet50", "db_mobilenet_v3_large"]
     
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'db_resnet50': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'backbone': 'ResNet50',
    -        'fpn_layers': ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"],
    -        'fpn_channels': 128,
    -        'input_shape': (1024, 1024, 3),
    -        'rotated_bbox': False,
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.2.0/db_resnet50-adcafc63.zip',
    +    "db_resnet50": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/db_resnet50-649fa22b.weights.h5&src=0",
    +    },
    +    "db_mobilenet_v3_large": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/db_mobilenet_v3_large-ee2e1dbe.weights.h5&src=0",
         },
     }
     
    @@ -313,6 +348,7 @@ 

    Source code for doctr.models.detection.differentiable_binarization.tensorflo <https://arxiv.org/pdf/1612.03144.pdf>`_. Args: + ---- channels: number of channel to output """ @@ -322,9 +358,9 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo ) -> None: super().__init__() self.channels = channels - self.upsample = layers.UpSampling2D(size=(2, 2), interpolation='nearest') - self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer='he_normal') for _ in range(4)] - self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2 ** idx) for idx in range(4)] + self.upsample = layers.UpSampling2D(size=(2, 2), interpolation="nearest") + self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer="he_normal") for _ in range(4)] + self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2**idx) for idx in range(4)] @staticmethod def build_upsampling( @@ -334,20 +370,21 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo """Module which performs a 3x3 convolution followed by up-sampling Args: + ---- channels: number of output channels dilation_factor (int): dilation factor to scale the convolution output before concatenation Returns: + ------- a keras.layers.Layer object, wrapping these operations in a sequential module """ - - _layers = conv_sequence(channels, 'relu', True, kernel_size=3) + _layers = conv_sequence(channels, "relu", True, kernel_size=3) if dilation_factor > 1: - _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation='nearest')) + _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation="nearest")) - module = keras.Sequential(_layers) + module = Sequential(_layers) return module @@ -359,7 +396,6 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo x: List[tf.Tensor], **kwargs: Any, ) -> tf.Tensor: - # Channel mapping results = [block(fmap, **kwargs) for block, fmap in zip(self.inner_blocks, x)] # Upsample & sum @@ -371,200 +407,324 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo return layers.concatenate(results) -class DBNet(_DBNet, keras.Model, NestedObject): +class DBNet(_DBNet, Model, NestedObject): """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" <https://arxiv.org/pdf/1911.08947.pdf>`_. Args: + ---- feature extractor: the backbone serving as feature extractor fpn_channels: number of channels each extracted feature maps is mapped to + bin_thresh: threshold for binarization + box_thresh: minimal objectness score to consider a box + assume_straight_pages: if True, fit straight bounding boxes only + exportable: onnx exportable returns only logits + cfg: the configuration dict of the model + class_names: list of class names """ - _children_names: List[str] = ['feat_extractor', 'fpn', 'probability_head', 'threshold_head', 'postprocessor'] + _children_names: List[str] = ["feat_extractor", "fpn", "probability_head", "threshold_head", "postprocessor"] def __init__( self, feature_extractor: IntermediateLayerGetter, - fpn_channels: int = 128, - rotated_bbox: bool = False, + fpn_channels: int = 128, # to be set to 256 to represent the author's initial idea + bin_thresh: float = 0.3, + box_thresh: float = 0.1, + assume_straight_pages: bool = True, + exportable: bool = False, cfg: Optional[Dict[str, Any]] = None, + class_names: List[str] = [CLASS_NAME], ) -> None: - super().__init__() + self.class_names = class_names + num_classes: int = len(self.class_names) self.cfg = cfg self.feat_extractor = feature_extractor - self.rotated_bbox = rotated_bbox + self.exportable = exportable + self.assume_straight_pages = assume_straight_pages self.fpn = FeaturePyramidNetwork(channels=fpn_channels) # Initialize kernels _inputs = [layers.Input(shape=in_shape[1:]) for in_shape in self.feat_extractor.output_shape] output_shape = tuple(self.fpn(_inputs).shape) - self.probability_head = keras.Sequential( - [ - *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]), - layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'), - layers.BatchNormalization(), - layers.Activation('relu'), - layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'), - ] + self.probability_head = Sequential([ + *conv_sequence(64, "relu", True, kernel_size=3, input_shape=output_shape[1:]), + layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer="he_normal"), + layers.BatchNormalization(), + layers.Activation("relu"), + layers.Conv2DTranspose(num_classes, 2, strides=2, kernel_initializer="he_normal"), + ]) + self.threshold_head = Sequential([ + *conv_sequence(64, "relu", True, kernel_size=3, input_shape=output_shape[1:]), + layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer="he_normal"), + layers.BatchNormalization(), + layers.Activation("relu"), + layers.Conv2DTranspose(num_classes, 2, strides=2, kernel_initializer="he_normal"), + ]) + + self.postprocessor = DBPostProcessor( + assume_straight_pages=assume_straight_pages, bin_thresh=bin_thresh, box_thresh=box_thresh ) - self.threshold_head = keras.Sequential( - [ - *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]), - layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'), - layers.BatchNormalization(), - layers.Activation('relu'), - layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'), - ] - ) - - self.postprocessor = DBPostProcessor(rotated_bbox=rotated_bbox) def compute_loss( self, out_map: tf.Tensor, thresh_map: tf.Tensor, - target: List[Dict[str, Any]] + target: List[Dict[str, np.ndarray]], + gamma: float = 2.0, + alpha: float = 0.5, + eps: float = 1e-8, ) -> tf.Tensor: """Compute a batch of gts, masks, thresh_gts, thresh_masks from a list of boxes and a list of masks for each image. From there it computes the loss with the model output Args: + ---- out_map: output feature map of the model of shape (N, H, W, C) thresh_map: threshold map of shape (N, H, W, C) target: list of dictionary where each dict has a `boxes` and a `flags` entry + gamma: modulating factor in the focal loss formula + alpha: balancing factor in the focal loss formula + eps: epsilon factor in dice loss Returns: + ------- A loss tensor """ + if gamma < 0: + raise ValueError("Value of gamma should be greater than or equal to zero.") - prob_map = tf.math.sigmoid(tf.squeeze(out_map, axis=[-1])) - thresh_map = tf.math.sigmoid(tf.squeeze(thresh_map, axis=[-1])) + prob_map = tf.math.sigmoid(out_map) + thresh_map = tf.math.sigmoid(thresh_map) - seg_target, seg_mask, thresh_target, thresh_mask = self.compute_target(target, out_map.shape[:3]) - seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32) + seg_target, seg_mask, thresh_target, thresh_mask = self.build_target(target, out_map.shape[1:], True) + seg_target = tf.convert_to_tensor(seg_target, dtype=out_map.dtype) seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool) - thresh_target = tf.convert_to_tensor(thresh_target, dtype=tf.float32) + seg_mask = tf.cast(seg_mask, tf.float32) + thresh_target = tf.convert_to_tensor(thresh_target, dtype=out_map.dtype) thresh_mask = tf.convert_to_tensor(thresh_mask, dtype=tf.bool) - # Compute balanced BCE loss for proba_map - bce_scale = 5. - bce_loss = tf.keras.losses.binary_crossentropy(seg_target[..., None], out_map, from_logits=True)[seg_mask] - - neg_target = 1 - seg_target[seg_mask] - positive_count = tf.math.reduce_sum(seg_target[seg_mask]) - negative_count = tf.math.reduce_min([tf.math.reduce_sum(neg_target), 3. * positive_count]) - negative_loss = bce_loss * neg_target - negative_loss, _ = tf.nn.top_k(negative_loss, tf.cast(negative_count, tf.int32)) - sum_losses = tf.math.reduce_sum(bce_loss * seg_target[seg_mask]) + tf.math.reduce_sum(negative_loss) - balanced_bce_loss = sum_losses / (positive_count + negative_count + 1e-6) - - # Compute dice loss for approxbin_map - bin_map = 1 / (1 + tf.exp(-50. * (prob_map[seg_mask] - thresh_map[seg_mask]))) - - bce_min = tf.math.reduce_min(bce_loss) - weights = (bce_loss - bce_min) / (tf.math.reduce_max(bce_loss) - bce_min) + 1. - inter = tf.math.reduce_sum(bin_map * seg_target[seg_mask] * weights) - union = tf.math.reduce_sum(bin_map) + tf.math.reduce_sum(seg_target[seg_mask]) + 1e-8 - dice_loss = 1 - 2.0 * inter / union + # Focal loss + focal_scale = 10.0 + bce_loss = losses.binary_crossentropy(seg_target[..., None], out_map[..., None], from_logits=True) + + # Convert logits to prob, compute gamma factor + p_t = (seg_target * prob_map) + ((1 - seg_target) * (1 - prob_map)) + alpha_t = seg_target * alpha + (1 - seg_target) * (1 - alpha) + # Unreduced loss + focal_loss = alpha_t * (1 - p_t) ** gamma * bce_loss + # Class reduced + focal_loss = tf.reduce_sum(seg_mask * focal_loss, (0, 1, 2, 3)) / tf.reduce_sum(seg_mask, (0, 1, 2, 3)) + + # Compute dice loss for each class or for approx binary_map + if len(self.class_names) > 1: + dice_map = tf.nn.softmax(out_map, axis=-1) + else: + # compute binary map instead + dice_map = 1.0 / (1.0 + tf.exp(-50 * (prob_map - thresh_map))) + # Class-reduced dice loss + inter = tf.reduce_sum(seg_mask * dice_map * seg_target, axis=[0, 1, 2]) + cardinality = tf.reduce_sum(seg_mask * (dice_map + seg_target), axis=[0, 1, 2]) + dice_loss = tf.reduce_mean(1 - 2 * inter / (cardinality + eps)) # Compute l1 loss for thresh_map - l1_scale = 10. if tf.reduce_any(thresh_mask): - l1_loss = tf.math.reduce_mean(tf.math.abs(thresh_map[thresh_mask] - thresh_target[thresh_mask])) + thresh_mask = tf.cast(thresh_mask, tf.float32) + l1_loss = tf.reduce_sum(tf.abs(thresh_map - thresh_target) * thresh_mask) / ( + tf.reduce_sum(thresh_mask) + eps + ) else: - l1_loss = tf.constant(0.) + l1_loss = tf.constant(0.0) - return l1_scale * l1_loss + bce_scale * balanced_bce_loss + dice_loss + return l1_loss + focal_scale * focal_loss + dice_loss def call( self, x: tf.Tensor, - target: Optional[List[Dict[str, Any]]] = None, + target: Optional[List[Dict[str, np.ndarray]]] = None, return_model_output: bool = False, - return_boxes: bool = False, + return_preds: bool = False, **kwargs: Any, ) -> Dict[str, Any]: - feat_maps = self.feat_extractor(x, **kwargs) feat_concat = self.fpn(feat_maps, **kwargs) logits = self.probability_head(feat_concat, **kwargs) out: Dict[str, tf.Tensor] = {} - if return_model_output or target is None or return_boxes: - prob_map = tf.math.sigmoid(logits) + if self.exportable: + out["logits"] = logits + return out + + if return_model_output or target is None or return_preds: + prob_map = _bf16_to_float32(tf.math.sigmoid(logits)) if return_model_output: out["out_map"] = prob_map - if target is None or return_boxes: - # Post-process boxes - out["preds"] = self.postprocessor(tf.squeeze(prob_map, axis=-1).numpy()) + if target is None or return_preds: + # Post-process boxes (keep only text predictions) + out["preds"] = [dict(zip(self.class_names, preds)) for preds in self.postprocessor(prob_map.numpy())] if target is not None: thresh_map = self.threshold_head(feat_concat, **kwargs) loss = self.compute_loss(logits, thresh_map, target) - out['loss'] = loss + out["loss"] = loss return out -def _db_resnet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> DBNet: +def _db_resnet( + arch: str, + pretrained: bool, + backbone_fn, + fpn_layers: List[str], + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> DBNet: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['fpn_channels'] = kwargs.get('fpn_channels', _cfg['fpn_channels']) - _cfg['rotated_bbox'] = kwargs.get('rotated_bbox', _cfg['rotated_bbox']) + _cfg["input_shape"] = input_shape or _cfg["input_shape"] + if not kwargs.get("class_names", None): + kwargs["class_names"] = _cfg.get("class_names", [CLASS_NAME]) + else: + kwargs["class_names"] = sorted(kwargs["class_names"]) # Feature extractor - resnet = tf.keras.applications.__dict__[_cfg['backbone']]( - include_top=False, - weights=None, - input_shape=_cfg['input_shape'], - pooling=None, + feat_extractor = IntermediateLayerGetter( + backbone_fn( + weights="imagenet" if pretrained_backbone else None, + include_top=False, + pooling=None, + input_shape=_cfg["input_shape"], + ), + fpn_layers, ) + # Build the model + model = DBNet(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) + + # Load pretrained parameters + if pretrained: + # The given class_names differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, + _cfg["url"], + skip_mismatch=kwargs["class_names"] != default_cfgs[arch].get("class_names", [CLASS_NAME]), + ) + + return model + + +def _db_mobilenet( + arch: str, + pretrained: bool, + backbone_fn, + fpn_layers: List[str], + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> DBNet: + pretrained_backbone = pretrained_backbone and not pretrained + + # Patch the config + _cfg = deepcopy(default_cfgs[arch]) + _cfg["input_shape"] = input_shape or _cfg["input_shape"] + if not kwargs.get("class_names", None): + kwargs["class_names"] = default_cfgs[arch].get("class_names", [CLASS_NAME]) + else: + kwargs["class_names"] = sorted(kwargs["class_names"]) + + # Feature extractor feat_extractor = IntermediateLayerGetter( - resnet, - _cfg['fpn_layers'], + backbone_fn( + input_shape=_cfg["input_shape"], + include_top=False, + pretrained=pretrained_backbone, + ), + fpn_layers, ) - kwargs['fpn_channels'] = _cfg['fpn_channels'] - kwargs['rotated_bbox'] = _cfg['rotated_bbox'] - # Build the model model = DBNet(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) # Load pretrained parameters if pretrained: - load_pretrained_params(model, _cfg['url']) + # The given class_names differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, + _cfg["url"], + skip_mismatch=kwargs["class_names"] != default_cfgs[arch].get("class_names", [CLASS_NAME]), + ) return model
    -[docs] +[docs] def db_resnet50(pretrained: bool = False, **kwargs: Any) -> DBNet: """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" <https://arxiv.org/pdf/1911.08947.pdf>`_, using a ResNet-50 backbone. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import db_resnet50 + >>> model = db_resnet50(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the DBNet architecture Returns: + ------- text detection architecture """ + return _db_resnet( + "db_resnet50", + pretrained, + ResNet50, + ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"], + **kwargs, + )
    + + + +
    +[docs] +def db_mobilenet_v3_large(pretrained: bool = False, **kwargs: Any) -> DBNet: + """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" + <https://arxiv.org/pdf/1911.08947.pdf>`_, using a mobilenet v3 large backbone. + + >>> import tensorflow as tf + >>> from doctr.models import db_mobilenet_v3_large + >>> model = db_mobilenet_v3_large(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) - return _db_resnet('db_resnet50', pretrained, **kwargs)
    + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the DBNet architecture + + Returns: + ------- + text detection architecture + """ + return _db_mobilenet( + "db_mobilenet_v3_large", + pretrained, + mobilenet_v3_large, + ["inverted_2", "inverted_5", "inverted_11", "final_block"], + **kwargs, + )

    @@ -598,8 +758,8 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo - - + + diff --git a/v0.6.0/_modules/doctr/models/detection/fast/tensorflow.html b/v0.6.0/_modules/doctr/models/detection/fast/tensorflow.html index 5b84d2dea1..65e1a77af8 100644 --- a/v0.6.0/_modules/doctr/models/detection/fast/tensorflow.html +++ b/v0.6.0/_modules/doctr/models/detection/fast/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.fast.tensorflow - docTR documentation @@ -769,7 +769,7 @@

    Source code for doctr.models.detection.fast.tensorflow

    - + diff --git a/v0.6.0/_modules/doctr/models/detection/linknet.html b/v0.6.0/_modules/doctr/models/detection/linknet.html deleted file mode 100644 index 129cfdce8b..0000000000 --- a/v0.6.0/_modules/doctr/models/detection/linknet.html +++ /dev/null @@ -1,644 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.linknet - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.linknet

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -import numpy as np
    -import cv2
    -from tensorflow.keras import layers, Sequential
    -from typing import Dict, Any, Tuple, Optional, List
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..backbones import ResnetStage
    -from ..utils import conv_sequence, load_pretrained_params
    -from ...utils.repr import NestedObject
    -
    -__all__ = ['LinkNet', 'linknet', 'LinkNetPostProcessor']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'linknet': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'out_chan': 1,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'LinkNetPostProcessor',
    -        'url': None,
    -    },
    -}
    -
    -
    -class LinkNetPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for LinkNet model.
    -
    -    Args:
    -        min_size_box: minimal length (pix) to keep a box
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        min_size_box: int = 3,
    -        bin_thresh: float = 0.15,
    -        box_thresh: float = 0.1,
    -    ) -> None:
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map: find connected components then filter boxes
    -
    -        Args:
    -            pred: Pred map from differentiable linknet output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        label_num, labelimage = cv2.connectedComponents(bitmap.astype(np.uint8), connectivity=4)
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        for label in range(1, label_num + 1):
    -            points = np.array(np.where(labelimage == label)[::-1]).T
    -            if points.shape[0] < 4:  # remove polygons with 3 points or less
    -                continue
    -            score = self.box_score(pred, points.reshape(-1, 2))
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            x, y, w, h = cv2.boundingRect(points)
    -            if min(w, h) < min_size_box:  # filter too small boxes
    -                continue
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -def decoder_block(in_chan: int, out_chan: int) -> Sequential:
    -    """Creates a LinkNet decoder block"""
    -
    -    return Sequential([
    -        *conv_sequence(in_chan // 4, 'relu', True, kernel_size=1),
    -        layers.Conv2DTranspose(
    -            filters=in_chan // 4,
    -            kernel_size=3,
    -            strides=2,
    -            padding="same",
    -            use_bias=False,
    -            kernel_initializer='he_normal'
    -        ),
    -        layers.BatchNormalization(),
    -        layers.Activation('relu'),
    -        *conv_sequence(out_chan, 'relu', True, kernel_size=1),
    -    ])
    -
    -
    -class LinkNetFPN(layers.Layer, NestedObject):
    -    """LinkNet Encoder-Decoder module
    -
    -    """
    -
    -    def __init__(
    -        self,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.encoder_1 = ResnetStage(num_blocks=2, output_channels=64, downsample=True)
    -        self.encoder_2 = ResnetStage(num_blocks=2, output_channels=128, downsample=True)
    -        self.encoder_3 = ResnetStage(num_blocks=2, output_channels=256, downsample=True)
    -        self.encoder_4 = ResnetStage(num_blocks=2, output_channels=512, downsample=True)
    -        self.decoder_1 = decoder_block(in_chan=64, out_chan=64)
    -        self.decoder_2 = decoder_block(in_chan=128, out_chan=64)
    -        self.decoder_3 = decoder_block(in_chan=256, out_chan=128)
    -        self.decoder_4 = decoder_block(in_chan=512, out_chan=256)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor
    -    ) -> tf.Tensor:
    -        x_1 = self.encoder_1(x)
    -        x_2 = self.encoder_2(x_1)
    -        x_3 = self.encoder_3(x_2)
    -        x_4 = self.encoder_4(x_3)
    -        y_4 = self.decoder_4(x_4)
    -        y_3 = self.decoder_3(y_4 + x_3)
    -        y_2 = self.decoder_2(y_3 + x_2)
    -        y_1 = self.decoder_1(y_2 + x_1)
    -        return y_1
    -
    -
    -class LinkNet(DetectionModel, NestedObject):
    -    """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation"
    -    <https://arxiv.org/pdf/1707.03718.pdf>`_.
    -
    -    Args:
    -        out_chan: number of channels for the output
    -    """
    -
    -    _children_names: List[str] = ['stem', 'fpn', 'classifier', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        out_chan: int = 1,
    -        input_shape: Tuple[int, int, int] = (512, 512, 3),
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(cfg=cfg)
    -
    -        self.stem = Sequential([
    -            *conv_sequence(64, 'relu', True, strides=2, kernel_size=7, input_shape=input_shape),
    -            layers.MaxPool2D(pool_size=(3, 3), strides=2, padding='same'),
    -        ])
    -
    -        self.fpn = LinkNetFPN()
    -
    -        self.classifier = Sequential([
    -            layers.Conv2DTranspose(
    -                filters=32,
    -                kernel_size=3,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -            layers.BatchNormalization(),
    -            layers.Activation('relu'),
    -            *conv_sequence(32, 'relu', True, strides=1, kernel_size=3),
    -            layers.Conv2DTranspose(
    -                filters=out_chan,
    -                kernel_size=2,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -        ])
    -
    -        self.min_size_box = 3
    -
    -        self.postprocessor = LinkNetPostProcessor()
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.bool)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            for box, box_size, is_ambiguous in zip(abs_boxes, boxes_size, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Fill polygon with 1
    -                seg_target[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = True
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts and masks from a list of boxes and a list of masks for each image
    -        Then, it computes the loss function with proba_map, gts and masks
    -
    -        Args:
    -            out_map: output feature map of the model of shape N x H x W x 1
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -        seg_target, seg_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute BCE loss
    -        return tf.math.reduce_mean(tf.keras.losses.binary_crossentropy(
    -            seg_target[seg_mask],
    -            tf.squeeze(out_map, axis=[-1])[seg_mask],
    -            from_logits=True
    -        ))
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        logits = self.stem(x)
    -        logits = self.fpn(logits)
    -        logits = self.classifier(logits)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            loss = self.compute_loss(logits, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _linknet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> LinkNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['out_chan'] = kwargs.get('out_chan', _cfg['out_chan'])
    -
    -    kwargs['out_chan'] = _cfg['out_chan']
    -    kwargs['input_shape'] = _cfg['input_shape']
    -    # Build the model
    -    model = LinkNet(cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def linknet(pretrained: bool = False, **kwargs: Any) -> LinkNet: - """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" - <https://arxiv.org/pdf/1707.03718.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import linknet - >>> model = linknet(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _linknet('linknet', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.6.0/_modules/doctr/models/detection/linknet/tensorflow.html b/v0.6.0/_modules/doctr/models/detection/linknet/tensorflow.html index cd4f446673..ce995f99d4 100644 --- a/v0.6.0/_modules/doctr/models/detection/linknet/tensorflow.html +++ b/v0.6.0/_modules/doctr/models/detection/linknet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.linknet.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.detection.linknet.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     # Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
     
     from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers, Sequential
    -from typing import Dict, Any, Tuple, Optional, List
    +from typing import Any, Dict, List, Optional, Tuple
     
    +import numpy as np
    +import tensorflow as tf
    +from tensorflow.keras import Model, Sequential, layers, losses
    +
    +from doctr.file_utils import CLASS_NAME
    +from doctr.models.classification import resnet18, resnet34, resnet50
    +from doctr.models.utils import (
    +    IntermediateLayerGetter,
    +    _bf16_to_float32,
    +    _build_model,
    +    conv_sequence,
    +    load_pretrained_params,
    +)
     from doctr.utils.repr import NestedObject
    -from doctr.models.backbones import ResnetStage
    -from doctr.models.utils import conv_sequence, load_pretrained_params
    -from .base import LinkNetPostProcessor, _LinkNet
     
    -__all__ = ['LinkNet', 'linknet16']
    +from .base import LinkNetPostProcessor, _LinkNet
     
    +__all__ = ["LinkNet", "linknet_resnet18", "linknet_resnet34", "linknet_resnet50"]
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'linknet16': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'num_classes': 1,
    -        'input_shape': (1024, 1024, 3),
    -        'rotated_bbox': False,
    -        'url': None,
    +    "linknet_resnet18": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/linknet_resnet18-615a82c5.weights.h5&src=0",
    +    },
    +    "linknet_resnet34": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/linknet_resnet34-9d772be5.weights.h5&src=0",
    +    },
    +    "linknet_resnet50": {
    +        "mean": (0.798, 0.785, 0.772),
    +        "std": (0.264, 0.2749, 0.287),
    +        "input_shape": (1024, 1024, 3),
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/linknet_resnet50-6bf6c8b5.weights.h5&src=0",
         },
     }
     
     
    -def decoder_block(in_chan: int, out_chan: int) -> Sequential:
    +def decoder_block(in_chan: int, out_chan: int, stride: int, **kwargs: Any) -> Sequential:
         """Creates a LinkNet decoder block"""
    -
         return Sequential([
    -        *conv_sequence(in_chan // 4, 'relu', True, kernel_size=1),
    +        *conv_sequence(in_chan // 4, "relu", True, kernel_size=1, **kwargs),
             layers.Conv2DTranspose(
                 filters=in_chan // 4,
                 kernel_size=3,
    -            strides=2,
    +            strides=stride,
                 padding="same",
                 use_bias=False,
    -            kernel_initializer='he_normal'
    +            kernel_initializer="he_normal",
             ),
             layers.BatchNormalization(),
    -        layers.Activation('relu'),
    -        *conv_sequence(out_chan, 'relu', True, kernel_size=1),
    +        layers.Activation("relu"),
    +        *conv_sequence(out_chan, "relu", True, kernel_size=1),
         ])
     
     
    -class LinkNetFPN(layers.Layer, NestedObject):
    -    """LinkNet Encoder-Decoder module"""
    +class LinkNetFPN(Model, NestedObject):
    +    """LinkNet Decoder module"""
     
         def __init__(
             self,
    +        out_chans: int,
    +        in_shapes: List[Tuple[int, ...]],
         ) -> None:
    -
             super().__init__()
    -        self.encoder_1 = ResnetStage(num_blocks=2, output_channels=64, downsample=True)
    -        self.encoder_2 = ResnetStage(num_blocks=2, output_channels=128, downsample=True)
    -        self.encoder_3 = ResnetStage(num_blocks=2, output_channels=256, downsample=True)
    -        self.encoder_4 = ResnetStage(num_blocks=2, output_channels=512, downsample=True)
    -        self.decoder_1 = decoder_block(in_chan=64, out_chan=64)
    -        self.decoder_2 = decoder_block(in_chan=128, out_chan=64)
    -        self.decoder_3 = decoder_block(in_chan=256, out_chan=128)
    -        self.decoder_4 = decoder_block(in_chan=512, out_chan=256)
    +        self.out_chans = out_chans
    +        strides = [2] * (len(in_shapes) - 1) + [1]
    +        i_chans = [s[-1] for s in in_shapes[::-1]]
    +        o_chans = i_chans[1:] + [out_chans]
    +        self.decoders = [
    +            decoder_block(in_chan, out_chan, s, input_shape=in_shape)
    +            for in_chan, out_chan, s, in_shape in zip(i_chans, o_chans, strides, in_shapes[::-1])
    +        ]
    +
    +    def call(self, x: List[tf.Tensor], **kwargs: Any) -> tf.Tensor:
    +        out = 0
    +        for decoder, fmap in zip(self.decoders, x[::-1]):
    +            out = decoder(out + fmap, **kwargs)
    +        return out
     
    -    def call(
    -        self,
    -        x: tf.Tensor
    -    ) -> tf.Tensor:
    -        x_1 = self.encoder_1(x)
    -        x_2 = self.encoder_2(x_1)
    -        x_3 = self.encoder_3(x_2)
    -        x_4 = self.encoder_4(x_3)
    -        y_4 = self.decoder_4(x_4)
    -        y_3 = self.decoder_3(y_4 + x_3)
    -        y_2 = self.decoder_2(y_3 + x_2)
    -        y_1 = self.decoder_1(y_2 + x_1)
    -        return y_1
    -
    -
    -class LinkNet(_LinkNet, keras.Model):
    +    def extra_repr(self) -> str:
    +        return f"out_chans={self.out_chans}"
    +
    +
    +class LinkNet(_LinkNet, Model):
         """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation"
         <https://arxiv.org/pdf/1707.03718.pdf>`_.
     
         Args:
    -        num_classes: number of channels for the output
    +    ----
    +        feature extractor: the backbone serving as feature extractor
    +        fpn_channels: number of channels each extracted feature maps is mapped to
    +        bin_thresh: threshold for binarization of the output feature map
    +        box_thresh: minimal objectness score to consider a box
    +        assume_straight_pages: if True, fit straight bounding boxes only
    +        exportable: onnx exportable returns only logits
    +        cfg: the configuration dict of the model
    +        class_names: list of class names
         """
     
    -    _children_names: List[str] = ['stem', 'fpn', 'classifier', 'postprocessor']
    +    _children_names: List[str] = ["feat_extractor", "fpn", "classifier", "postprocessor"]
     
         def __init__(
             self,
    -        num_classes: int = 1,
    -        input_shape: Tuple[int, int, int] = (512, 512, 3),
    -        rotated_bbox: bool = False,
    +        feat_extractor: IntermediateLayerGetter,
    +        fpn_channels: int = 64,
    +        bin_thresh: float = 0.1,
    +        box_thresh: float = 0.1,
    +        assume_straight_pages: bool = True,
    +        exportable: bool = False,
             cfg: Optional[Dict[str, Any]] = None,
    +        class_names: List[str] = [CLASS_NAME],
         ) -> None:
             super().__init__(cfg=cfg)
     
    -        self.rotated_bbox = rotated_bbox
    +        self.class_names = class_names
    +        num_classes: int = len(self.class_names)
     
    -        self.stem = Sequential([
    -            *conv_sequence(64, 'relu', True, strides=2, kernel_size=7, input_shape=input_shape),
    -            layers.MaxPool2D(pool_size=(3, 3), strides=2, padding='same'),
    -        ])
    +        self.exportable = exportable
    +        self.assume_straight_pages = assume_straight_pages
    +
    +        self.feat_extractor = feat_extractor
     
    -        self.fpn = LinkNetFPN()
    +        self.fpn = LinkNetFPN(fpn_channels, [_shape[1:] for _shape in self.feat_extractor.output_shape])
    +        self.fpn.build(self.feat_extractor.output_shape)
     
             self.classifier = Sequential([
                 layers.Conv2DTranspose(
    @@ -393,154 +442,246 @@ 

    Source code for doctr.models.detection.linknet.tensorflow

    strides=2, padding="same", use_bias=False, - kernel_initializer='he_normal' + kernel_initializer="he_normal", + input_shape=self.fpn.decoders[-1].output_shape[1:], ), layers.BatchNormalization(), - layers.Activation('relu'), - *conv_sequence(32, 'relu', True, strides=1, kernel_size=3), + layers.Activation("relu"), + *conv_sequence(32, "relu", True, kernel_size=3, strides=1), layers.Conv2DTranspose( filters=num_classes, kernel_size=2, strides=2, padding="same", - use_bias=False, - kernel_initializer='he_normal' + use_bias=True, + kernel_initializer="he_normal", ), ]) - self.postprocessor = LinkNetPostProcessor(rotated_bbox=rotated_bbox) + self.postprocessor = LinkNetPostProcessor( + assume_straight_pages=assume_straight_pages, bin_thresh=bin_thresh, box_thresh=box_thresh + ) def compute_loss( self, out_map: tf.Tensor, - target: List[Dict[str, Any]], - focal_loss: bool = False, - alpha: float = .5, - gamma: float = 2., - edge_factor: float = 2., + target: List[Dict[str, np.ndarray]], + gamma: float = 2.0, + alpha: float = 0.5, + eps: float = 1e-8, ) -> tf.Tensor: """Compute linknet loss, BCE with boosted box edges or focal loss. Focal loss implementation based on <https://github.com/tensorflow/addons/>`_. Args: + ---- out_map: output feature map of the model of shape N x H x W x 1 target: list of dictionary where each dict has a `boxes` and a `flags` entry - focal_loss: if True, use focal loss instead of BCE - edge_factor: boost factor for box edges (in case of BCE) + gamma: modulating factor in the focal loss formula alpha: balancing factor in the focal loss formula - gammma: modulating factor in the focal loss formula + eps: epsilon factor in dice loss Returns: + ------- A loss tensor """ - seg_target, seg_mask, edge_mask = self.compute_target(target, out_map.shape[:3]) - seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32) - edge_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool) + seg_target, seg_mask = self.build_target(target, out_map.shape[1:], True) + seg_target = tf.convert_to_tensor(seg_target, dtype=out_map.dtype) seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool) - - # Get the cross_entropy for each entry - bce = tf.keras.losses.binary_crossentropy( - seg_target[seg_mask], - tf.squeeze(out_map, axis=[-1])[seg_mask], - from_logits=True) - - if focal_loss: - if gamma and gamma < 0: - raise ValueError("Value of gamma should be greater than or equal to zero.") - - # Convert logits to prob, compute gamma factor - pred_prob = tf.sigmoid(tf.squeeze(out_map, axis=[-1])[seg_mask]) - p_t = (seg_target[seg_mask] * pred_prob) + ((1 - seg_target[seg_mask]) * (1 - pred_prob)) - modulating_factor = tf.pow((1.0 - p_t), gamma) - - # Compute alpha factor - alpha_factor = seg_target[seg_mask] * alpha + (1 - seg_target[seg_mask]) * (1 - alpha) - - # compute the final loss - loss = tf.reduce_mean(alpha_factor * modulating_factor * bce) - - else: - # Compute BCE loss with highlighted edges - loss = tf.math.multiply( - 1 + (edge_factor - 1) * tf.cast(edge_mask, tf.float32), - bce - ) - loss = tf.reduce_mean(loss) - - return loss + seg_mask = tf.cast(seg_mask, tf.float32) + + bce_loss = losses.binary_crossentropy(seg_target[..., None], out_map[..., None], from_logits=True) + proba_map = tf.sigmoid(out_map) + + # Focal loss + if gamma < 0: + raise ValueError("Value of gamma should be greater than or equal to zero.") + # Convert logits to prob, compute gamma factor + p_t = (seg_target * proba_map) + ((1 - seg_target) * (1 - proba_map)) + alpha_t = seg_target * alpha + (1 - seg_target) * (1 - alpha) + # Unreduced loss + focal_loss = alpha_t * (1 - p_t) ** gamma * bce_loss + # Class reduced + focal_loss = tf.reduce_sum(seg_mask * focal_loss, (0, 1, 2, 3)) / tf.reduce_sum(seg_mask, (0, 1, 2, 3)) + + # Compute dice loss for each class + dice_map = tf.nn.softmax(out_map, axis=-1) if len(self.class_names) > 1 else proba_map + # Class-reduced dice loss + inter = tf.reduce_sum(seg_mask * dice_map * seg_target, axis=[0, 1, 2]) + cardinality = tf.reduce_sum(seg_mask * (dice_map + seg_target), axis=[0, 1, 2]) + dice_loss = tf.reduce_mean(1 - 2 * inter / (cardinality + eps)) + + return focal_loss + dice_loss def call( self, x: tf.Tensor, - target: Optional[List[Dict[str, Any]]] = None, + target: Optional[List[Dict[str, np.ndarray]]] = None, return_model_output: bool = False, - return_boxes: bool = False, - focal_loss: bool = True, + return_preds: bool = False, **kwargs: Any, ) -> Dict[str, Any]: - - logits = self.stem(x) - logits = self.fpn(logits) - logits = self.classifier(logits) + feat_maps = self.feat_extractor(x, **kwargs) + logits = self.fpn(feat_maps, **kwargs) + logits = self.classifier(logits, **kwargs) out: Dict[str, tf.Tensor] = {} - if return_model_output or target is None or return_boxes: - prob_map = tf.math.sigmoid(logits) + if self.exportable: + out["logits"] = logits + return out + + if return_model_output or target is None or return_preds: + prob_map = _bf16_to_float32(tf.math.sigmoid(logits)) + if return_model_output: out["out_map"] = prob_map - if target is None or return_boxes: + if target is None or return_preds: # Post-process boxes - out["preds"] = self.postprocessor(tf.squeeze(prob_map, axis=-1).numpy()) + out["preds"] = [dict(zip(self.class_names, preds)) for preds in self.postprocessor(prob_map.numpy())] if target is not None: - loss = self.compute_loss(logits, target, focal_loss) - out['loss'] = loss + loss = self.compute_loss(logits, target) + out["loss"] = loss return out -def _linknet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> LinkNet: +def _linknet( + arch: str, + pretrained: bool, + backbone_fn, + fpn_layers: List[str], + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> LinkNet: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['num_classes'] = kwargs.get('num_classes', _cfg['num_classes']) - _cfg['rotated_bbox'] = kwargs.get('rotated_bbox', _cfg['rotated_bbox']) + _cfg["input_shape"] = input_shape or default_cfgs[arch]["input_shape"] + if not kwargs.get("class_names", None): + kwargs["class_names"] = _cfg.get("class_names", [CLASS_NAME]) + else: + kwargs["class_names"] = sorted(kwargs["class_names"]) + + # Feature extractor + feat_extractor = IntermediateLayerGetter( + backbone_fn( + pretrained=pretrained_backbone, + include_top=False, + input_shape=_cfg["input_shape"], + ), + fpn_layers, + ) - kwargs['num_classes'] = _cfg['num_classes'] - kwargs['input_shape'] = _cfg['input_shape'] - kwargs['rotated_bbox'] = _cfg['rotated_bbox'] # Build the model - model = LinkNet(cfg=_cfg, **kwargs) + model = LinkNet(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) + # Load pretrained parameters if pretrained: - load_pretrained_params(model, _cfg['url']) + # The given class_names differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, + _cfg["url"], + skip_mismatch=kwargs["class_names"] != default_cfgs[arch].get("class_names", [CLASS_NAME]), + ) return model -
    -[docs] -def linknet16(pretrained: bool = False, **kwargs: Any) -> LinkNet: +
    +[docs] +def linknet_resnet18(pretrained: bool = False, **kwargs: Any) -> LinkNet: + """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" + <https://arxiv.org/pdf/1707.03718.pdf>`_. + + >>> import tensorflow as tf + >>> from doctr.models import linknet_resnet18 + >>> model = linknet_resnet18(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the LinkNet architecture + + Returns: + ------- + text detection architecture + """ + return _linknet( + "linknet_resnet18", + pretrained, + resnet18, + ["resnet_block_1", "resnet_block_3", "resnet_block_5", "resnet_block_7"], + **kwargs, + )
    + + + +
    +[docs] +def linknet_resnet34(pretrained: bool = False, **kwargs: Any) -> LinkNet: """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" <https://arxiv.org/pdf/1707.03718.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import linknet16 - >>> model = linknet16(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import linknet_resnet34 + >>> model = linknet_resnet34(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the LinkNet architecture Returns: + ------- text detection architecture """ + return _linknet( + "linknet_resnet34", + pretrained, + resnet34, + ["resnet_block_2", "resnet_block_6", "resnet_block_12", "resnet_block_15"], + **kwargs, + )
    + + + +
    +[docs] +def linknet_resnet50(pretrained: bool = False, **kwargs: Any) -> LinkNet: + """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" + <https://arxiv.org/pdf/1707.03718.pdf>`_. + + >>> import tensorflow as tf + >>> from doctr.models import linknet_resnet50 + >>> model = linknet_resnet50(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text detection dataset + **kwargs: keyword arguments of the LinkNet architecture - return _linknet('linknet16', pretrained, **kwargs)
    + Returns: + ------- + text detection architecture + """ + return _linknet( + "linknet_resnet50", + pretrained, + resnet50, + ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"], + **kwargs, + )
    @@ -574,8 +715,8 @@

    Source code for doctr.models.detection.linknet.tensorflow

    - +
    + diff --git a/v0.6.0/_modules/doctr/models/detection/zoo.html b/v0.6.0/_modules/doctr/models/detection/zoo.html index d3128b8d14..3651c4e2d3 100644 --- a/v0.6.0/_modules/doctr/models/detection/zoo.html +++ b/v0.6.0/_modules/doctr/models/detection/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.detection.zoo - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.detection.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -from typing import Any
    +from typing import Any, List
     
     from doctr.file_utils import is_tf_available, is_torch_available
    -from .core import DetectionPredictor
    -from ..preprocessor import PreProcessor
    -from .. import detection
     
    +from .. import detection
    +from ..detection.fast import reparameterize
    +from ..preprocessor import PreProcessor
    +from .predictor import DetectionPredictor
     
     __all__ = ["detection_predictor"]
     
    +ARCHS: List[str]
    +
     
     if is_tf_available():
    -    ARCHS = ['db_resnet50', 'linknet16']
    +    ARCHS = [
    +        "db_resnet50",
    +        "db_mobilenet_v3_large",
    +        "linknet_resnet18",
    +        "linknet_resnet34",
    +        "linknet_resnet50",
    +        "fast_tiny",
    +        "fast_small",
    +        "fast_base",
    +    ]
     elif is_torch_available():
    -    ARCHS = ['db_resnet34', 'db_resnet50', 'db_mobilenet_v3', 'linknet16']
    +    ARCHS = [
    +        "db_resnet34",
    +        "db_resnet50",
    +        "db_mobilenet_v3_large",
    +        "linknet_resnet18",
    +        "linknet_resnet34",
    +        "linknet_resnet50",
    +        "fast_tiny",
    +        "fast_small",
    +        "fast_base",
    +    ]
    +
     
    +def _predictor(arch: Any, pretrained: bool, assume_straight_pages: bool = True, **kwargs: Any) -> DetectionPredictor:
    +    if isinstance(arch, str):
    +        if arch not in ARCHS:
    +            raise ValueError(f"unknown architecture '{arch}'")
     
    -def _predictor(arch: str, pretrained: bool, **kwargs: Any) -> DetectionPredictor:
    +        _model = detection.__dict__[arch](
    +            pretrained=pretrained,
    +            pretrained_backbone=kwargs.get("pretrained_backbone", True),
    +            assume_straight_pages=assume_straight_pages,
    +        )
    +        # Reparameterize FAST models by default to lower inference latency and memory usage
    +        if isinstance(_model, detection.FAST):
    +            _model = reparameterize(_model)
    +    else:
    +        if not isinstance(arch, (detection.DBNet, detection.LinkNet, detection.FAST)):
    +            raise ValueError(f"unknown architecture: {type(arch)}")
     
    -    if arch not in ARCHS:
    -        raise ValueError(f"unknown architecture '{arch}'")
    +        _model = arch
    +        _model.assume_straight_pages = assume_straight_pages
    +        _model.postprocessor.assume_straight_pages = assume_straight_pages
     
    -    # Detection
    -    _model = detection.__dict__[arch](pretrained=pretrained)
    -    kwargs['mean'] = kwargs.get('mean', _model.cfg['mean'])
    -    kwargs['std'] = kwargs.get('std', _model.cfg['std'])
    -    kwargs['batch_size'] = kwargs.get('batch_size', 1)
    +    kwargs.pop("pretrained_backbone", None)
    +
    +    kwargs["mean"] = kwargs.get("mean", _model.cfg["mean"])
    +    kwargs["std"] = kwargs.get("std", _model.cfg["std"])
    +    kwargs["batch_size"] = kwargs.get("batch_size", 2)
         predictor = DetectionPredictor(
    -        PreProcessor(_model.cfg['input_shape'][:2], **kwargs),
    -        _model
    +        PreProcessor(_model.cfg["input_shape"][:-1] if is_tf_available() else _model.cfg["input_shape"][1:], **kwargs),
    +        _model,
         )
         return predictor
     
     
     
    -[docs] -def detection_predictor(arch: str = 'db_resnet50', pretrained: bool = False, **kwargs: Any) -> DetectionPredictor: +[docs] +def detection_predictor( + arch: Any = "fast_base", + pretrained: bool = False, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + batch_size: int = 2, + **kwargs: Any, +) -> DetectionPredictor: """Text detection architecture. - Example:: - >>> import numpy as np - >>> from doctr.models import detection_predictor - >>> model = detection_predictor(pretrained=True) - >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) - >>> out = model([input_page]) + >>> import numpy as np + >>> from doctr.models import detection_predictor + >>> model = detection_predictor(arch='db_resnet50', pretrained=True) + >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) Args: - arch: name of the architecture to use ('db_resnet50') + ---- + arch: name of the architecture or model itself to use (e.g. 'db_resnet50') pretrained: If True, returns a model pre-trained on our text detection dataset + assume_straight_pages: If True, fit straight boxes to the page + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right + batch_size: number of samples the model processes in parallel + **kwargs: optional keyword arguments passed to the architecture Returns: + ------- Detection predictor """ - - return _predictor(arch, pretrained, **kwargs)
    + return _predictor( + arch=arch, + pretrained=pretrained, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + batch_size=batch_size, + **kwargs, + )
    @@ -367,8 +449,8 @@

    Source code for doctr.models.detection.zoo

           
         
       
    - - + + diff --git a/v0.6.0/_modules/doctr/models/export.html b/v0.6.0/_modules/doctr/models/export.html deleted file mode 100644 index f25a81aa21..0000000000 --- a/v0.6.0/_modules/doctr/models/export.html +++ /dev/null @@ -1,411 +0,0 @@ - - - - - - - - - - - - doctr.models.export - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.export

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import logging
    -import numpy as np
    -import tensorflow as tf
    -from tensorflow.keras import Model
    -from typing import Tuple
    -
    -logging.getLogger("tensorflow").setLevel(logging.DEBUG)
    -
    -
    -__all__ = ['convert_to_tflite', 'convert_to_fp16', 'quantize_model']
    -
    -
    -
    -[docs] -def convert_to_tflite(tf_model: Model) -> bytes: - """Converts a model to TFLite format - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_tflite, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_tflite(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - return converter.convert()
    - - - -
    -[docs] -def convert_to_fp16(tf_model: Model) -> bytes: - """Converts a model to half precision - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_fp16, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_fp16(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the serialized FP16 model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - converter.target_spec.supported_types = [tf.float16] - return converter.convert()
    - - - -
    -[docs] -def quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) -> bytes: - """Quantize a Tensorflow model - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import quantize_model, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = quantize_model(model, (224, 224, 3)) - - Args: - tf_model: a keras model - input_shape: shape of the expected input tensor (excluding batch dimension) with channel last order - - Returns: - bytes: the serialized quantized model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - - # Float fallback for operators that do not have an integer implementation - def representative_dataset(): - for _ in range(100): - data = np.random.rand(1, *input_shape) - yield [data.astype(np.float32)] - - converter.representative_dataset = representative_dataset - converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] - converter.inference_input_type = tf.int8 - converter.inference_output_type = tf.int8 - - return converter.convert()
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.6.0/_modules/doctr/models/factory/hub.html b/v0.6.0/_modules/doctr/models/factory/hub.html index 8274a809f5..756b2c7a17 100644 --- a/v0.6.0/_modules/doctr/models/factory/hub.html +++ b/v0.6.0/_modules/doctr/models/factory/hub.html @@ -13,7 +13,7 @@ - + doctr.models.factory.hub - docTR documentation @@ -568,7 +568,7 @@

    Source code for doctr.models.factory.hub

         
       
    - + diff --git a/v0.6.0/_modules/doctr/models/recognition/crnn.html b/v0.6.0/_modules/doctr/models/recognition/crnn.html deleted file mode 100644 index daa2393439..0000000000 --- a/v0.6.0/_modules/doctr/models/recognition/crnn.html +++ /dev/null @@ -1,565 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.crnn - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.crnn

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import layers
    -from tensorflow.keras.models import Sequential
    -from typing import Tuple, Dict, Any, Optional, List
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel, RecognitionPostProcessor
    -
    -__all__ = ['CRNN', 'crnn_vgg16_bn', 'crnn_resnet31', 'CTCPostProcessor']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'crnn_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/crnn_vgg16_bn-748c855f.zip',
    -    },
    -    'crnn_resnet31': {
    -        'mean': (0.694, 0.695, 0.693),
    -        'std': (0.299, 0.296, 0.301),
    -        'backbone': 'resnet31', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.1/crnn_resnet31-69ab71db.zip',
    -    },
    -}
    -
    -
    -class CTCPostProcessor(RecognitionPostProcessor):
    -    """
    -    Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor
    -    ) -> List[Tuple[str, float]]:
    -        """
    -        Performs decoding of raw output with CTC and decoding of CTC predictions
    -        with label_to_idx mapping dictionnary
    -
    -        Args:
    -            logits: raw output of the model, shape BATCH_SIZE X SEQ_LEN X NUM_CLASSES + 1
    -
    -        Returns:
    -            A list of decoded words of length BATCH_SIZE
    -
    -        """
    -        # Decode CTC
    -        _decoded, _log_prob = tf.nn.ctc_beam_search_decoder(
    -            tf.transpose(logits, perm=[1, 0, 2]),
    -            tf.fill(logits.shape[0], logits.shape[1]),
    -            beam_width=1, top_paths=1,
    -        )
    -        out_idxs = tf.sparse.to_dense(_decoded[0], default_value=len(self.vocab))
    -        probs = tf.math.exp(tf.squeeze(_log_prob, axis=1))
    -
    -        # Map it to characters
    -        _decoded_strings_pred = tf.strings.reduce_join(
    -            inputs=tf.nn.embedding_lookup(self._embedding, out_idxs),
    -            axis=-1
    -        )
    -        _decoded_strings_pred = tf.strings.split(_decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -class CRNN(RecognitionModel):
    -    """Implements a CRNN architecture as described in `"An End-to-End Trainable Neural Network for Image-based
    -    Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of units in the LSTM layers
    -        cfg: configuration dictionary
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: tf.keras.Model,
    -        vocab: str,
    -        rnn_units: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(vocab=vocab, cfg=cfg)
    -        self.feat_extractor = feature_extractor
    -
    -        # Initialize kernels
    -        h, w, c = self.feat_extractor.output_shape[1:]
    -        self.max_length = w
    -
    -        self.decoder = Sequential(
    -            [
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Dense(units=len(vocab) + 1)
    -            ]
    -        )
    -        self.decoder.build(input_shape=(None, w, h * c))
    -
    -        self.postprocessor = CTCPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        target: List[str],
    -    ) -> tf.Tensor:
    -        """Compute CTC loss for the model.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        gt, seq_len = self.compute_target(target)
    -        batch_len = model_output.shape[0]
    -        input_length = model_output.shape[1] * tf.ones(shape=(batch_len))
    -        ctc_loss = tf.nn.ctc_loss(
    -            gt, model_output, seq_len, input_length, logits_time_major=False, blank_index=len(self.vocab)
    -        )
    -        return ctc_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        # B x H x W x C --> B x W x H x C
    -        transposed_feat = tf.transpose(features, perm=[0, 2, 1, 3])
    -        w, h, c = transposed_feat.get_shape().as_list()[1:]
    -        # B x W x H x C --> B x W x H * C
    -        features_seq = tf.reshape(transposed_feat, shape=(-1, w, h * c))
    -        logits = self.decoder(features_seq, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = logits
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(logits)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(logits, target)
    -
    -        return out
    -
    -
    -def _crnn(arch: str, pretrained: bool, input_shape: Optional[Tuple[int, int, int]] = None, **kwargs: Any) -> CRNN:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[_cfg['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -
    -    # Build the model
    -    model = CRNN(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a VGG-16 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_vgg16_bn - >>> model = crnn_vgg16_bn(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_vgg16_bn', pretrained, **kwargs)
    - - - -def crnn_resnet31(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a resnet31 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_resnet31 - >>> model = crnn_resnet31(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_resnet31', pretrained, **kwargs) -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.6.0/_modules/doctr/models/recognition/crnn/tensorflow.html b/v0.6.0/_modules/doctr/models/recognition/crnn/tensorflow.html index 41cc93dd23..bc64da9a1b 100644 --- a/v0.6.0/_modules/doctr/models/recognition/crnn/tensorflow.html +++ b/v0.6.0/_modules/doctr/models/recognition/crnn/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.crnn.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple, Union
    +
     import tensorflow as tf
     from tensorflow.keras import layers
    -from tensorflow.keras.models import Sequential, Model
    -from typing import Tuple, Dict, Any, Optional, List
    +from tensorflow.keras.models import Model, Sequential
    +
    +from doctr.datasets import VOCABS
     
    -from ... import backbones
    -from ...utils import load_pretrained_params
    +from ...classification import mobilenet_v3_large_r, mobilenet_v3_small_r, vgg16_bn_r
    +from ...utils.tensorflow import _bf16_to_float32, _build_model, load_pretrained_params
     from ..core import RecognitionModel, RecognitionPostProcessor
     
    -__all__ = ['CRNN', 'crnn_vgg16_bn', 'crnn_resnet31', 'CTCPostProcessor']
    +__all__ = ["CRNN", "crnn_vgg16_bn", "crnn_mobilenet_v3_small", "crnn_mobilenet_v3_large"]
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'crnn_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/crnn_vgg16_bn-748c855f.zip',
    +    "crnn_vgg16_bn": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["legacy_french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/crnn_vgg16_bn-9c188f45.weights.h5&src=0",
         },
    -    'crnn_resnet31': {
    -        'mean': (0.694, 0.695, 0.693),
    -        'std': (0.299, 0.296, 0.301),
    -        'backbone': 'resnet31', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.1/crnn_resnet31-69ab71db.zip',
    +    "crnn_mobilenet_v3_small": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/crnn_mobilenet_v3_small-54850265.weights.h5&src=0",
    +    },
    +    "crnn_mobilenet_v3_large": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/crnn_mobilenet_v3_large-c64045e5.weights.h5&src=0",
         },
     }
     
     
     class CTCPostProcessor(RecognitionPostProcessor):
    -    """
    -    Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
    +    """Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
     
         Args:
    +    ----
             vocab: string containing the ordered sequence of supported characters
             ignore_case: if True, ignore case of letters
             ignore_accents: if True, ignore accents of letters
    @@ -325,37 +353,57 @@ 

    Source code for doctr.models.recognition.crnn.tensorflow

    def __call__( self, - logits: tf.Tensor - ) -> List[Tuple[str, float]]: - """ - Performs decoding of raw output with CTC and decoding of CTC predictions + logits: tf.Tensor, + beam_width: int = 1, + top_paths: int = 1, + ) -> Union[List[Tuple[str, float]], List[Tuple[List[str], List[float]]]]: + """Performs decoding of raw output with CTC and decoding of CTC predictions with label_to_idx mapping dictionnary Args: + ---- logits: raw output of the model, shape BATCH_SIZE X SEQ_LEN X NUM_CLASSES + 1 + beam_width: An int scalar >= 0 (beam search beam width). + top_paths: An int scalar >= 0, <= beam_width (controls output size). Returns: + ------- A list of decoded words of length BATCH_SIZE + """ # Decode CTC _decoded, _log_prob = tf.nn.ctc_beam_search_decoder( tf.transpose(logits, perm=[1, 0, 2]), - tf.fill(logits.shape[0], logits.shape[1]), - beam_width=1, top_paths=1, + tf.fill(tf.shape(logits)[:1], tf.shape(logits)[1]), + beam_width=beam_width, + top_paths=top_paths, ) - out_idxs = tf.sparse.to_dense(_decoded[0], default_value=len(self.vocab)) - probs = tf.math.exp(tf.squeeze(_log_prob, axis=1)) + + _decoded = tf.sparse.concat( + 1, + [tf.sparse.expand_dims(dec, axis=1) for dec in _decoded], + expand_nonconcat_dims=True, + ) # dim : batchsize x beamwidth x actual_max_len_predictions + out_idxs = tf.sparse.to_dense(_decoded, default_value=len(self.vocab)) # Map it to characters _decoded_strings_pred = tf.strings.reduce_join( inputs=tf.nn.embedding_lookup(tf.constant(self._embedding, dtype=tf.string), out_idxs), - axis=-1 + axis=-1, ) _decoded_strings_pred = tf.strings.split(_decoded_strings_pred, "<eos>") - decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0] - word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] - + decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value="not valid")[ + :, :, 0 + ] # dim : batch_size x beam_width + + if top_paths == 1: + probs = tf.math.exp(tf.squeeze(_log_prob, axis=1)) # dim : batchsize + decoded_strings_pred = tf.squeeze(decoded_strings_pred, axis=1) + word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] + else: + probs = tf.math.exp(_log_prob) # dim : batchsize x beamwidth + word_values = [[word.decode() for word in words] for words in decoded_strings_pred.numpy().tolist()] return list(zip(word_values, probs.numpy().tolist())) @@ -364,19 +412,26 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. Args: + ---- feature_extractor: the backbone serving as feature extractor vocab: vocabulary used for encoding rnn_units: number of units in the LSTM layers + exportable: onnx exportable returns only logits + beam_width: beam width for beam search decoding + top_paths: number of top paths for beam search decoding cfg: configuration dictionary """ - _children_names: List[str] = ['feat_extractor', 'decoder', 'postprocessor'] + _children_names: List[str] = ["feat_extractor", "decoder", "postprocessor"] def __init__( self, - feature_extractor: tf.keras.Model, + feature_extractor: Model, vocab: str, rnn_units: int = 128, + exportable: bool = False, + beam_width: int = 1, + top_paths: int = 1, cfg: Optional[Dict[str, Any]] = None, ) -> None: # Initialize kernels @@ -386,19 +441,21 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    self.vocab = vocab self.max_length = w self.cfg = cfg + self.exportable = exportable self.feat_extractor = feature_extractor - self.decoder = Sequential( - [ - layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), - layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), - layers.Dense(units=len(vocab) + 1) - ] - ) + self.decoder = Sequential([ + layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), + layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)), + layers.Dense(units=len(vocab) + 1), + ]) self.decoder.build(input_shape=(None, w, h * c)) self.postprocessor = CTCPostProcessor(vocab=vocab) + self.beam_width = beam_width + self.top_paths = top_paths + def compute_loss( self, model_output: tf.Tensor, @@ -407,16 +464,17 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    """Compute CTC loss for the model. Args: - gt: the encoded tensor with gt labels + ---- model_output: predicted logits of the model - seq_len: lengths of each gt word inside the batch + target: lengths of each gt word inside the batch Returns: + ------- The loss of the model on the batch """ - gt, seq_len = self.compute_target(target) + gt, seq_len = self.build_target(target) batch_len = model_output.shape[0] - input_length = model_output.shape[1] * tf.ones(shape=(batch_len)) + input_length = tf.fill((batch_len,), model_output.shape[1]) ctc_loss = tf.nn.ctc_loss( gt, model_output, seq_len, input_length, logits_time_major=False, blank_index=len(self.vocab) ) @@ -428,8 +486,12 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    target: Optional[List[str]] = None, return_model_output: bool = False, return_preds: bool = False, + beam_width: int = 1, + top_paths: int = 1, **kwargs: Any, ) -> Dict[str, Any]: + if kwargs.get("training", False) and target is None: + raise ValueError("Need to provide labels during training") features = self.feat_extractor(x, **kwargs) # B x H x W x C --> B x W x H x C @@ -437,91 +499,132 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    w, h, c = transposed_feat.get_shape().as_list()[1:] # B x W x H x C --> B x W x H * C features_seq = tf.reshape(transposed_feat, shape=(-1, w, h * c)) - logits = self.decoder(features_seq, **kwargs) + logits = _bf16_to_float32(self.decoder(features_seq, **kwargs)) out: Dict[str, tf.Tensor] = {} + if self.exportable: + out["logits"] = logits + return out + if return_model_output: out["out_map"] = logits if target is None or return_preds: # Post-process boxes - out["preds"] = self.postprocessor(logits) + out["preds"] = self.postprocessor(logits, beam_width=beam_width, top_paths=top_paths) if target is not None: - out['loss'] = self.compute_loss(logits, target) + out["loss"] = self.compute_loss(logits, target) return out -def _crnn(arch: str, pretrained: bool, input_shape: Optional[Tuple[int, int, int]] = None, **kwargs: Any) -> CRNN: +def _crnn( + arch: str, + pretrained: bool, + backbone_fn, + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> CRNN: + pretrained_backbone = pretrained_backbone and not pretrained + + kwargs["vocab"] = kwargs.get("vocab", default_cfgs[arch]["vocab"]) - # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab']) - _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units']) + _cfg["vocab"] = kwargs["vocab"] + _cfg["input_shape"] = input_shape or default_cfgs[arch]["input_shape"] - # Feature extractor - feat_extractor = backbones.__dict__[_cfg['backbone']]( - input_shape=_cfg['input_shape'], + feat_extractor = backbone_fn( + input_shape=_cfg["input_shape"], include_top=False, + pretrained=pretrained_backbone, ) - kwargs['vocab'] = _cfg['vocab'] - kwargs['rnn_units'] = _cfg['rnn_units'] - # Build the model model = CRNN(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) # Load pretrained parameters if pretrained: - load_pretrained_params(model, _cfg['url']) + # The given vocab differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params(model, _cfg["url"], skip_mismatch=kwargs["vocab"] != default_cfgs[arch]["vocab"]) return model
    -[docs] +[docs] def crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> CRNN: """CRNN with a VGG-16 backbone as described in `"An End-to-End Trainable Neural Network for Image-based Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_vgg16_bn - >>> model = crnn_vgg16_bn(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import crnn_vgg16_bn + >>> model = crnn_vgg16_bn(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the CRNN architecture Returns: + ------- text recognition architecture """ + return _crnn("crnn_vgg16_bn", pretrained, vgg16_bn_r, **kwargs)
    + + + +
    +[docs] +def crnn_mobilenet_v3_small(pretrained: bool = False, **kwargs: Any) -> CRNN: + """CRNN with a MobileNet V3 Small backbone as described in `"An End-to-End Trainable Neural Network for Image-based + Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. + + >>> import tensorflow as tf + >>> from doctr.models import crnn_mobilenet_v3_small + >>> model = crnn_mobilenet_v3_small(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + + Args: + ---- + pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the CRNN architecture - return _crnn('crnn_vgg16_bn', pretrained, **kwargs)
    + Returns: + ------- + text recognition architecture + """ + return _crnn("crnn_mobilenet_v3_small", pretrained, mobilenet_v3_small_r, **kwargs)
    -def crnn_resnet31(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a resnet31 backbone as described in `"An End-to-End Trainable Neural Network for Image-based +
    +[docs] +def crnn_mobilenet_v3_large(pretrained: bool = False, **kwargs: Any) -> CRNN: + """CRNN with a MobileNet V3 Large backbone as described in `"An End-to-End Trainable Neural Network for Image-based Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_resnet31 - >>> model = crnn_resnet31(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import crnn_mobilenet_v3_large + >>> model = crnn_mobilenet_v3_large(pretrained=True) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the CRNN architecture Returns: + ------- text recognition architecture """ + return _crnn("crnn_mobilenet_v3_large", pretrained, mobilenet_v3_large_r, **kwargs)
    - return _crnn('crnn_resnet31', pretrained, **kwargs)
    @@ -554,8 +657,8 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    - +
    + diff --git a/v0.6.0/_modules/doctr/models/recognition/master/tensorflow.html b/v0.6.0/_modules/doctr/models/recognition/master/tensorflow.html index 2dc5a27717..aa6aa69325 100644 --- a/v0.6.0/_modules/doctr/models/recognition/master/tensorflow.html +++ b/v0.6.0/_modules/doctr/models/recognition/master/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.master.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.master.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -import tensorflow as tf
    -from tensorflow.keras import layers, Sequential, Model
    -from typing import Tuple, List, Dict, Any, Optional
     from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple
     
    -from ..core import RecognitionPostProcessor
    -from ...backbones.resnet import ResnetStage
    -from ...utils import conv_sequence, load_pretrained_params
    -from ..transformer import Decoder, positional_encoding, create_look_ahead_mask, create_padding_mask
    -from ....datasets import VOCABS
    -from .base import _MASTER, _MASTERPostProcessor
    +import tensorflow as tf
    +from tensorflow.keras import Model, layers
    +
    +from doctr.datasets import VOCABS
    +from doctr.models.classification import magc_resnet31
    +from doctr.models.modules.transformer import Decoder, PositionalEncoding
     
    +from ...utils.tensorflow import _bf16_to_float32, _build_model, load_pretrained_params
    +from .base import _MASTER, _MASTERPostProcessor
     
    -__all__ = ['MASTER', 'master', 'MASTERPostProcessor']
    +__all__ = ["MASTER", "master"]
     
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'master': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'input_shape': (48, 160, 3),
    -        'vocab': VOCABS['french'],
    -        'url': None,
    +    "master": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/master-d7fdaeff.weights.h5&src=0",
         },
     }
     
     
    -class MAGC(layers.Layer):
    -
    -    """Implements the Multi-Aspect Global Context Attention, as described in
    -    <https://arxiv.org/pdf/1910.02562.pdf>`_.
    -
    -    Args:
    -        inplanes: input channels
    -        headers: number of headers to split channels
    -        att_scale: if True, re-scale attention to counteract the variance distibutions
    -        **kwargs
    -    """
    -
    -    def __init__(
    -        self,
    -        inplanes: int,
    -        headers: int = 1,
    -        att_scale: bool = False,
    -        **kwargs
    -    ) -> None:
    -        super().__init__(**kwargs)
    -
    -        self.headers = headers  # h
    -        self.inplanes = inplanes  # C
    -        self.att_scale = att_scale
    -
    -        self.single_header_inplanes = int(inplanes / headers)  # C / h
    -
    -        self.conv_mask = tf.keras.layers.Conv2D(
    -            filters=1,
    -            kernel_size=1,
    -            kernel_initializer=tf.initializers.he_normal()
    -        )
    -
    -        self.transform = tf.keras.Sequential(
    -            [
    -                tf.keras.layers.Conv2D(
    -                    filters=self.inplanes,
    -                    kernel_size=1,
    -                    kernel_initializer=tf.initializers.he_normal()
    -                ),
    -                tf.keras.layers.LayerNormalization([1, 2, 3]),
    -                tf.keras.layers.ReLU(),
    -                tf.keras.layers.Conv2D(
    -                    filters=self.inplanes,
    -                    kernel_size=1,
    -                    kernel_initializer=tf.initializers.he_normal()
    -                ),
    -            ],
    -            name='transform'
    -        )
    -
    -    @tf.function
    -    def context_modeling(self, inputs: tf.Tensor) -> tf.Tensor:
    -        b, h, w, c = (tf.shape(inputs)[i] for i in range(4))
    -
    -        # B, H, W, C -->> B*h, H, W, C/h
    -        x = tf.reshape(inputs, shape=(b, h, w, self.headers, self.single_header_inplanes))
    -        x = tf.transpose(x, perm=(0, 3, 1, 2, 4))
    -        x = tf.reshape(x, shape=(b * self.headers, h, w, self.single_header_inplanes))
    -
    -        # Compute shorcut
    -        shortcut = x
    -        # B*h, 1, H*W, C/h
    -        shortcut = tf.reshape(shortcut, shape=(b * self.headers, 1, h * w, self.single_header_inplanes))
    -        # B*h, 1, C/h, H*W
    -        shortcut = tf.transpose(shortcut, perm=[0, 1, 3, 2])
    -
    -        # Compute context mask
    -        # B*h, H, W, 1,
    -        context_mask = self.conv_mask(x)
    -        # B*h, 1, H*W, 1
    -        context_mask = tf.reshape(context_mask, shape=(b * self.headers, 1, h * w, 1))
    -        # scale variance
    -        if self.att_scale and self.headers > 1:
    -            context_mask = context_mask / tf.sqrt(self.single_header_inplanes)
    -        # B*h, 1, H*W, 1
    -        context_mask = tf.keras.activations.softmax(context_mask, axis=2)
    -
    -        # Compute context
    -        # B*h, 1, C/h, 1
    -        context = tf.matmul(shortcut, context_mask)
    -        context = tf.reshape(context, shape=(b, 1, c, 1))
    -        # B, 1, 1, C
    -        context = tf.transpose(context, perm=(0, 1, 3, 2))
    -        # Set shape to resolve shape when calling this module in the Sequential MAGCResnet
    -        batch, chan = inputs.get_shape().as_list()[0], inputs.get_shape().as_list()[-1]
    -        context.set_shape([batch, 1, 1, chan])
    -        return context
    -
    -    def call(self, inputs: tf.Tensor, **kwargs) -> tf.Tensor:
    -        # Context modeling: B, H, W, C  ->  B, 1, 1, C
    -        context = self.context_modeling(inputs)
    -        # Transform: B, 1, 1, C  ->  B, 1, 1, C
    -        transformed = self.transform(context)
    -        return inputs + transformed
    -
    -
    -class MAGCResnet(Sequential):
    -
    -    """Implements the modified resnet with MAGC layers, as described in paper.
    -
    -    Args:
    -        headers: number of header to split channels in MAGC layers
    -        input_shape: shape of the model input (without batch dim)
    -    """
    -
    -    def __init__(
    -        self,
    -        headers: int = 1,
    -        input_shape: Tuple[int, int, int] = (48, 160, 3),
    -    ) -> None:
    -        _layers = [
    -            # conv_1x
    -            *conv_sequence(out_channels=64, activation='relu', bn=True, kernel_size=3, input_shape=input_shape),
    -            *conv_sequence(out_channels=128, activation='relu', bn=True, kernel_size=3),
    -            layers.MaxPooling2D((2, 2), (2, 2)),
    -            # conv_2x
    -            ResnetStage(num_blocks=1, output_channels=256),
    -            MAGC(inplanes=256, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=256, activation='relu', bn=True, kernel_size=3),
    -            layers.MaxPooling2D((2, 2), (2, 2)),
    -            # conv_3x
    -            ResnetStage(num_blocks=2, output_channels=512),
    -            MAGC(inplanes=512, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=512, activation='relu', bn=True, kernel_size=3),
    -            layers.MaxPooling2D((2, 1), (2, 1)),
    -            # conv_4x
    -            ResnetStage(num_blocks=5, output_channels=512),
    -            MAGC(inplanes=512, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=512, activation='relu', bn=True, kernel_size=3),
    -            # conv_5x
    -            ResnetStage(num_blocks=3, output_channels=512),
    -            MAGC(inplanes=512, headers=headers, att_scale=True),
    -            *conv_sequence(out_channels=512, activation='relu', bn=True, kernel_size=3),
    -        ]
    -        super().__init__(_layers)
    -
    -
     class MASTER(_MASTER, Model):
    -
         """Implements MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_.
         Implementation based on the official TF implementation: <https://github.com/jiangxiluning/MASTER-TF>`_.
     
         Args:
    +    ----
    +        feature_extractor: the backbone serving as feature extractor
             vocab: vocabulary, (without EOS, SOS, PAD)
             d_model: d parameter for the transformer decoder
    -        headers: headers for the MAGC module
             dff: depth of the pointwise feed-forward layer
             num_heads: number of heads for the mutli-head attention module
             num_layers: number of decoder layers to stack
             max_length: maximum length of character sequence handled by the model
    -        input_size: size of the image inputs
    +        dropout: dropout probability of the decoder
    +        input_shape: size of the image inputs
    +        exportable: onnx exportable returns only logits
    +        cfg: dictionary containing information about the model
         """
     
         def __init__(
             self,
    +        feature_extractor: Model,
             vocab: str,
             d_model: int = 512,
    -        headers: int = 1,
             dff: int = 2048,
    -        num_heads: int = 8,
    +        num_heads: int = 8,  # number of heads in the transformer decoder
             num_layers: int = 3,
             max_length: int = 50,
    -        input_shape: Tuple[int, int, int] = (48, 160, 3),
    +        dropout: float = 0.2,
    +        input_shape: Tuple[int, int, int] = (32, 128, 3),  # different from the paper
    +        exportable: bool = False,
             cfg: Optional[Dict[str, Any]] = None,
         ) -> None:
             super().__init__()
     
    -        self.vocab = vocab
    +        self.exportable = exportable
             self.max_length = max_length
    +        self.d_model = d_model
    +        self.vocab = vocab
             self.cfg = cfg
             self.vocab_size = len(vocab)
     
    -        self.feature_extractor = MAGCResnet(headers=headers, input_shape=input_shape)
    -        self.seq_embedding = layers.Embedding(self.vocab_size + 3, d_model)  # 3 more classes: EOS/PAD/SOS
    +        self.feat_extractor = feature_extractor
    +        self.positional_encoding = PositionalEncoding(self.d_model, dropout, max_len=input_shape[0] * input_shape[1])
     
             self.decoder = Decoder(
                 num_layers=num_layers,
    -            d_model=d_model,
    +            d_model=self.d_model,
                 num_heads=num_heads,
    +            vocab_size=self.vocab_size + 3,  # EOS, SOS, PAD
                 dff=dff,
    -            vocab_size=self.vocab_size,
    -            maximum_position_encoding=max_length,
    +            dropout=dropout,
    +            maximum_position_encoding=self.max_length,
             )
    -        self.feature_pe = positional_encoding(input_shape[0] * input_shape[1], d_model)
    -        self.linear = layers.Dense(self.vocab_size + 3, kernel_initializer=tf.initializers.he_uniform())
     
    +        self.linear = layers.Dense(self.vocab_size + 3, kernel_initializer=tf.initializers.he_uniform())
             self.postprocessor = MASTERPostProcessor(vocab=self.vocab)
     
         @tf.function
    -    def make_mask(self, target: tf.Tensor) -> tf.Tensor:
    -        look_ahead_mask = create_look_ahead_mask(tf.shape(target)[1])
    -        target_padding_mask = create_padding_mask(target, self.vocab_size + 2)  # Pad symbol
    -        combined_mask = tf.maximum(target_padding_mask, look_ahead_mask)
    -        return combined_mask
    +    def make_source_and_target_mask(self, source: tf.Tensor, target: tf.Tensor) -> Tuple[tf.Tensor, tf.Tensor]:
    +        # [1, 1, 1, ..., 0, 0, 0] -> 0 is masked
    +        # (N, 1, 1, max_length)
    +        target_pad_mask = tf.cast(tf.math.not_equal(target, self.vocab_size + 2), dtype=tf.uint8)
    +        target_pad_mask = target_pad_mask[:, tf.newaxis, tf.newaxis, :]
    +        target_length = target.shape[1]
    +        # sub mask filled diagonal with 1 = see 0 = masked (max_length, max_length)
    +        target_sub_mask = tf.linalg.band_part(tf.ones((target_length, target_length)), -1, 0)
    +        # source mask filled with ones (max_length, positional_encoded_seq_len)
    +        source_mask = tf.ones((target_length, source.shape[1]))
    +        # combine the two masks into one boolean mask where False is masked (N, 1, max_length, max_length)
    +        target_mask = tf.math.logical_and(
    +            tf.cast(target_sub_mask, dtype=tf.bool), tf.cast(target_pad_mask, dtype=tf.bool)
    +        )
    +        return source_mask, target_mask
     
    +    @staticmethod
         def compute_loss(
    -        self,
             model_output: tf.Tensor,
             gt: tf.Tensor,
             seq_len: List[int],
    @@ -512,11 +413,13 @@ 

    Source code for doctr.models.recognition.master.tensorflow

    Sequences are masked after the EOS character. Args: + ---- gt: the encoded tensor with gt labels model_output: predicted logits of the model seq_len: lengths of each gt word inside the batch Returns: + ------- The loss of the model on the batch """ # Input length : number of timesteps @@ -532,7 +435,7 @@

    Source code for doctr.models.recognition.master.tensorflow

    mask_values = tf.zeros_like(cce) mask_2d = tf.sequence_mask(seq_len, input_len - 1) # delete the last mask timestep as well masked_loss = tf.where(mask_2d, cce, mask_values) - ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32)) + ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, model_output.dtype)) return tf.expand_dims(ce_loss, axis=1) @@ -547,94 +450,103 @@

    Source code for doctr.models.recognition.master.tensorflow

    """Call function for training Args: + ---- x: images target: list of str labels return_model_output: if True, return logits return_preds: if True, decode logits + **kwargs: keyword arguments passed to the decoder - Return: + Returns: + ------- A dictionnary containing eventually loss, logits and predictions. """ - # Encode - feature = self.feature_extractor(x, **kwargs) - b, h, w, c = (tf.shape(feature)[i] for i in range(4)) + feature = self.feat_extractor(x, **kwargs) + b, h, w, c = feature.get_shape() + # (N, H, W, C) --> (N, H * W, C) feature = tf.reshape(feature, shape=(b, h * w, c)) - encoded = feature + self.feature_pe[:, :h * w, :] + # add positional encoding to features + encoded = self.positional_encoding(feature, **kwargs) out: Dict[str, tf.Tensor] = {} + if kwargs.get("training", False) and target is None: + raise ValueError("Need to provide labels during training") + if target is not None: # Compute target: tensor of gts and sequence lengths - gt, seq_len = self.compute_target(target) - - if kwargs.get('training', False): - if target is None: - raise AssertionError("In training mode, you need to pass a value to 'target'") - tgt_mask = self.make_mask(gt) + gt, seq_len = self.build_target(target) + # Compute decoder masks + source_mask, target_mask = self.make_source_and_target_mask(encoded, gt) # Compute logits - output = self.decoder(gt, encoded, tgt_mask, None, **kwargs) + output = self.decoder(gt, encoded, source_mask, target_mask, **kwargs) logits = self.linear(output, **kwargs) - else: - # When not training, we want to compute logits in with the decoder, although - # we have access to gts (we need gts to compute the loss, but not in the decoder) logits = self.decode(encoded, **kwargs) + logits = _bf16_to_float32(logits) + + if self.exportable: + out["logits"] = logits + return out + if target is not None: - out['loss'] = self.compute_loss(logits, gt, seq_len) + out["loss"] = self.compute_loss(logits, gt, seq_len) if return_model_output: - out['out_map'] = logits + out["out_map"] = logits if return_preds: - predictions = self.postprocessor(logits) - out['preds'] = predictions + out["preds"] = self.postprocessor(logits) return out + @tf.function def decode(self, encoded: tf.Tensor, **kwargs: Any) -> tf.Tensor: """Decode function for prediction Args: + ---- encoded: encoded features + **kwargs: keyword arguments passed to the decoder - Return: + Returns: + ------- A Tuple of tf.Tensor: predictions, logits """ - b = tf.shape(encoded)[0] - max_len = tf.constant(self.max_length, dtype=tf.int32) + b = encoded.shape[0] + start_symbol = tf.constant(self.vocab_size + 1, dtype=tf.int32) # SOS padding_symbol = tf.constant(self.vocab_size + 2, dtype=tf.int32) # PAD - ys = tf.fill(dims=(b, max_len - 1), value=padding_symbol) + ys = tf.fill(dims=(b, self.max_length - 1), value=padding_symbol) start_vector = tf.fill(dims=(b, 1), value=start_symbol) ys = tf.concat([start_vector, ys], axis=-1) - logits = tf.zeros(shape=(b, max_len - 1, self.vocab_size + 3), dtype=tf.float32) # 3 symbols - # max_len = len + 2 (sos + eos) + # Final dimension include EOS/SOS/PAD for i in range(self.max_length - 1): - ys_mask = self.make_mask(ys) - output = self.decoder(ys, encoded, ys_mask, None, **kwargs) + source_mask, target_mask = self.make_source_and_target_mask(encoded, ys) + output = self.decoder(ys, encoded, source_mask, target_mask, **kwargs) logits = self.linear(output, **kwargs) prob = tf.nn.softmax(logits, axis=-1) - next_word = tf.argmax(prob, axis=-1, output_type=ys.dtype) - # ys.shape = B, T - i_mesh, j_mesh = tf.meshgrid(tf.range(b), tf.range(max_len), indexing='ij') + next_token = tf.argmax(prob, axis=-1, output_type=ys.dtype) + # update ys with the next token and ignore the first token (SOS) + i_mesh, j_mesh = tf.meshgrid(tf.range(b), tf.range(self.max_length), indexing="ij") indices = tf.stack([i_mesh[:, i + 1], j_mesh[:, i + 1]], axis=1) - ys = tf.tensor_scatter_nd_update(ys, indices, next_word[:, i + 1]) + ys = tf.tensor_scatter_nd_update(ys, indices, next_token[:, i]) - # final_logits of shape (N, max_length - 1, vocab_size + 1) (whithout sos) + # Shape (N, max_length, vocab_size + 1) return logits class MASTERPostProcessor(_MASTERPostProcessor): """Post processor for MASTER architectures + Args: + ---- vocab: string containing the ordered sequence of supported characters - ignore_case: if True, ignore case of letters - ignore_accents: if True, ignore accents of letters """ def __call__( @@ -649,51 +561,66 @@

    Source code for doctr.models.recognition.master.tensorflow

    probs = tf.math.reduce_min(probs, axis=1) # decode raw output of the model with tf_label_to_idx - out_idxs = tf.cast(out_idxs, dtype='int32') + out_idxs = tf.cast(out_idxs, dtype="int32") embedding = tf.constant(self._embedding, dtype=tf.string) decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(embedding, out_idxs), axis=-1) decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>") - decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0] + decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value="not valid")[:, 0] word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] - return list(zip(word_values, probs.numpy().tolist())) + return list(zip(word_values, probs.numpy().clip(0, 1).tolist())) -def _master(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> MASTER: +def _master(arch: str, pretrained: bool, backbone_fn, pretrained_backbone: bool = True, **kwargs: Any) -> MASTER: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab']) + _cfg["input_shape"] = kwargs.get("input_shape", _cfg["input_shape"]) + _cfg["vocab"] = kwargs.get("vocab", _cfg["vocab"]) - kwargs['vocab'] = _cfg['vocab'] + kwargs["vocab"] = _cfg["vocab"] + kwargs["input_shape"] = _cfg["input_shape"] # Build the model - model = MASTER(cfg=_cfg, **kwargs) + model = MASTER( + backbone_fn(pretrained=pretrained_backbone, input_shape=_cfg["input_shape"], include_top=False), + cfg=_cfg, + **kwargs, + ) + _build_model(model) + # Load pretrained parameters if pretrained: - load_pretrained_params(model, default_cfgs[arch]['url']) + # The given vocab differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, default_cfgs[arch]["url"], skip_mismatch=kwargs["vocab"] != default_cfgs[arch]["vocab"] + ) return model
    -[docs] +[docs] def master(pretrained: bool = False, **kwargs: Any) -> MASTER: """MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_. - Example:: - >>> import tensorflow as tf - >>> from doctr.models import master - >>> model = master(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 48, 160, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + + >>> import tensorflow as tf + >>> from doctr.models import master + >>> model = master(pretrained=False) + >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) + Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keywoard arguments passed to the MASTER architecture + Returns: + ------- text recognition architecture """ - - return _master('master', pretrained, **kwargs)
    + return _master("master", pretrained, magc_resnet31, **kwargs)
    @@ -727,8 +654,8 @@

    Source code for doctr.models.recognition.master.tensorflow

    - +
    + diff --git a/v0.6.0/_modules/doctr/models/recognition/parseq/tensorflow.html b/v0.6.0/_modules/doctr/models/recognition/parseq/tensorflow.html index 0819737dfc..b181acef53 100644 --- a/v0.6.0/_modules/doctr/models/recognition/parseq/tensorflow.html +++ b/v0.6.0/_modules/doctr/models/recognition/parseq/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.parseq.tensorflow - docTR documentation @@ -845,7 +845,7 @@

    Source code for doctr.models.recognition.parseq.tensorflow

    - + diff --git a/v0.6.0/_modules/doctr/models/recognition/sar.html b/v0.6.0/_modules/doctr/models/recognition/sar.html deleted file mode 100644 index 2482e9f156..0000000000 --- a/v0.6.0/_modules/doctr/models/recognition/sar.html +++ /dev/null @@ -1,712 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.sar - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.sar

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import Sequential, layers
    -from typing import Tuple, Dict, List, Any, Optional
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel
    -from .core import RecognitionPostProcessor
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['SAR', 'SARPostProcessor', 'sar_vgg16_bn', 'sar_resnet31']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'sar_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1-models/sar_vgg16bn-0d7e2c26.zip',
    -    },
    -    'sar_resnet31': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'resnet31', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/sar_resnet31-ea202587.zip',
    -    },
    -}
    -
    -
    -class AttentionModule(layers.Layer, NestedObject):
    -    """Implements attention module of the SAR model
    -
    -    Args:
    -        attention_units: number of hidden attention units
    -
    -    """
    -    def __init__(
    -        self,
    -        attention_units: int
    -    ) -> None:
    -
    -        super().__init__()
    -        self.hidden_state_projector = layers.Conv2D(
    -            attention_units, 1, strides=1, use_bias=False, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.features_projector = layers.Conv2D(
    -            attention_units, 3, strides=1, use_bias=True, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.attention_projector = layers.Conv2D(
    -            1, 1, strides=1, use_bias=False, padding="same", kernel_initializer='he_normal',
    -        )
    -        self.flatten = layers.Flatten()
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        hidden_state: tf.Tensor,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        [H, W] = features.get_shape().as_list()[1:3]
    -        # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units)
    -        hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs)
    -        # shape (N, H, W, vgg_units) -> (N, H, W, attention_units)
    -        features_projection = self.features_projector(features, **kwargs)
    -        projection = tf.math.tanh(hidden_state_projection + features_projection)
    -        # shape (N, H, W, attention_units) -> (N, H, W, 1)
    -        attention = self.attention_projector(projection, **kwargs)
    -        # shape (N, H, W, 1) -> (N, H * W)
    -        attention = self.flatten(attention)
    -        attention = tf.nn.softmax(attention)
    -        # shape (N, H * W) -> (N, H, W, 1)
    -        attention_map = tf.reshape(attention, [-1, H, W, 1])
    -        glimpse = tf.math.multiply(features, attention_map)
    -        # shape (N, H * W) -> (N, 1)
    -        glimpse = tf.reduce_sum(glimpse, axis=[1, 2])
    -        return glimpse
    -
    -
    -class SARDecoder(layers.Layer, NestedObject):
    -    """Implements decoder module of the SAR model
    -
    -    Args:
    -        rnn_units: number of hidden units in recurrent cells
    -        max_length: maximum length of a sequence
    -        vocab_size: number of classes in the model alphabet
    -        embedding_units: number of hidden embedding units
    -        attention_units: number of hidden attention units
    -        num_decoder_layers: number of LSTM layers to stack
    -
    -    """
    -    def __init__(
    -        self,
    -        rnn_units: int,
    -        max_length: int,
    -        vocab_size: int,
    -        embedding_units: int,
    -        attention_units: int,
    -        num_decoder_layers: int = 2,
    -        input_shape: Optional[List[Tuple[Optional[int]]]] = None,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.vocab_size = vocab_size
    -        self.lstm_decoder = layers.StackedRNNCells(
    -            [layers.LSTMCell(rnn_units, dtype=tf.float32, implementation=1) for _ in range(num_decoder_layers)]
    -        )
    -        self.embed = layers.Dense(embedding_units, use_bias=False, input_shape=(None, self.vocab_size + 1))
    -        self.attention_module = AttentionModule(attention_units)
    -        self.output_dense = layers.Dense(vocab_size + 1, use_bias=True, input_shape=(None, 2 * rnn_units))
    -        self.max_length = max_length
    -
    -        # Initialize kernels
    -        if input_shape is not None:
    -            self.attention_module.call(layers.Input(input_shape[0][1:]), layers.Input((1, 1, rnn_units)))
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        holistic: tf.Tensor,
    -        gt: Optional[tf.Tensor] = None,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # initialize states (each of shape (N, rnn_units))
    -        states = self.lstm_decoder.get_initial_state(
    -            inputs=None, batch_size=features.shape[0], dtype=tf.float32
    -        )
    -        # run first step of lstm
    -        # holistic: shape (N, rnn_units)
    -        _, states = self.lstm_decoder(holistic, states, **kwargs)
    -        # Initialize with the index of virtual START symbol (placed after <eos>)
    -        symbol = tf.fill(features.shape[0], self.vocab_size + 1)
    -        logits_list = []
    -        if kwargs.get('training') and gt is None:
    -            raise ValueError('Need to provide labels during training for teacher forcing')
    -        for t in range(self.max_length + 1):  # keep 1 step for <eos>
    -            # one-hot symbol with depth vocab_size + 1
    -            # embeded_symbol: shape (N, embedding_units)
    -            embeded_symbol = self.embed(tf.one_hot(symbol, depth=self.vocab_size + 1), **kwargs)
    -            logits, states = self.lstm_decoder(embeded_symbol, states, **kwargs)
    -            glimpse = self.attention_module(
    -                features, tf.expand_dims(tf.expand_dims(logits, axis=1), axis=1), **kwargs,
    -            )
    -            # logits: shape (N, rnn_units), glimpse: shape (N, 1)
    -            logits = tf.concat([logits, glimpse], axis=-1)
    -            # shape (N, rnn_units + 1) -> (N, vocab_size + 1)
    -            logits = self.output_dense(logits, **kwargs)
    -            # update symbol with predicted logits for t+1 step
    -            if kwargs.get('training'):
    -                symbol = gt[:, t]
    -            else:
    -                symbol = tf.argmax(logits, axis=-1)
    -            logits_list.append(logits)
    -        outputs = tf.stack(logits_list, axis=1)  # shape (N, max_length + 1, vocab_size + 1)
    -
    -        return outputs
    -
    -
    -class SAR(RecognitionModel):
    -    """Implements a SAR architecture as described in `"Show, Attend and Read:A Simple and Strong Baseline for
    -    Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of hidden units in both encoder and decoder LSTM
    -        embedding_units: number of embedding units
    -        attention_units: number of hidden units in attention module
    -        max_length: maximum word length handled by the model
    -        num_decoders: number of LSTM to stack in decoder layer
    -
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'encoder', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor,
    -        vocab: str,
    -        rnn_units: int = 512,
    -        embedding_units: int = 512,
    -        attention_units: int = 512,
    -        max_length: int = 30,
    -        num_decoders: int = 2,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(vocab=vocab, cfg=cfg)
    -
    -        self.max_length = max_length + 1  # Add 1 timestep for EOS after the longest word
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.encoder = Sequential(
    -            [
    -                layers.LSTM(units=rnn_units, return_sequences=True),
    -                layers.LSTM(units=rnn_units, return_sequences=False)
    -            ]
    -        )
    -        # Initialize the kernels (watch out for reduce_max)
    -        self.encoder.build(input_shape=(None,) + self.feat_extractor.output_shape[2:])
    -
    -        self.decoder = SARDecoder(
    -            rnn_units, max_length, len(vocab), embedding_units, attention_units, num_decoders,
    -            input_shape=[self.feat_extractor.output_shape, self.encoder.output_shape]
    -        )
    -
    -        self.postprocessor = SARPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        gt: tf.Tensor,
    -        seq_len: tf.Tensor,
    -    ) -> tf.Tensor:
    -        """Compute categorical cross-entropy loss for the model.
    -        Sequences are masked after the EOS character.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        # Input length : number of timesteps
    -        input_len = tf.shape(model_output)[1]
    -        # Add one for additional <eos> token
    -        seq_len = seq_len + 1
    -        # One-hot gt labels
    -        oh_gt = tf.one_hot(gt, depth=model_output.shape[2])
    -        # Compute loss
    -        cce = tf.nn.softmax_cross_entropy_with_logits(oh_gt, model_output)
    -        # Compute mask
    -        mask_values = tf.zeros_like(cce)
    -        mask_2d = tf.sequence_mask(seq_len, input_len)
    -        masked_loss = tf.where(mask_2d, cce, mask_values)
    -        ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32))
    -        return tf.expand_dims(ce_loss, axis=1)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        pooled_features = tf.reduce_max(features, axis=1)  # vertical max pooling
    -        encoded = self.encoder(pooled_features, **kwargs)
    -        if target is not None:
    -            gt, seq_len = self.compute_target(target)
    -        decoded_features = self.decoder(features, encoded, gt=None if target is None else gt, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = decoded_features
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(decoded_features)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(decoded_features, gt, seq_len)
    -
    -        return out
    -
    -
    -class SARPostProcessor(RecognitionPostProcessor):
    -    """Post processor for SAR architectures
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor,
    -    ) -> List[Tuple[str, float]]:
    -        # compute pred with argmax for attention models
    -        out_idxs = tf.math.argmax(logits, axis=2)
    -        # N x L
    -        probs = tf.gather(tf.nn.softmax(logits, axis=-1), out_idxs, axis=-1, batch_dims=2)
    -        # Take the minimum confidence of the sequence
    -        probs = tf.math.reduce_min(probs, axis=1)
    -
    -        # decode raw output of the model with tf_label_to_idx
    -        out_idxs = tf.cast(out_idxs, dtype='int32')
    -        decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(self._embedding, out_idxs), axis=-1)
    -        decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -def _sar(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> SAR:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -    _cfg['embedding_units'] = kwargs.get('embedding_units', _cfg['rnn_units'])
    -    _cfg['attention_units'] = kwargs.get('attention_units', _cfg['rnn_units'])
    -    _cfg['max_length'] = kwargs.get('max_length', _cfg['max_length'])
    -    _cfg['num_decoders'] = kwargs.get('num_decoders', _cfg['num_decoders'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[default_cfgs[arch]['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -    kwargs['embedding_units'] = _cfg['embedding_units']
    -    kwargs['attention_units'] = _cfg['attention_units']
    -    kwargs['max_length'] = _cfg['max_length']
    -    kwargs['num_decoders'] = _cfg['num_decoders']
    -
    -    # Build the model
    -    model = SAR(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, default_cfgs[arch]['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a VGG16 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import sar_vgg16_bn - >>> model = sar_vgg16_bn(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_vgg16_bn', pretrained, **kwargs)
    - - - -
    -[docs] -def sar_resnet31(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a resnet-31 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example: - >>> import tensorflow as tf - >>> from doctr.models import sar_resnet31 - >>> model = sar_resnet31(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_resnet31', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.6.0/_modules/doctr/models/recognition/sar/tensorflow.html b/v0.6.0/_modules/doctr/models/recognition/sar/tensorflow.html index e514e4f0c4..4a591e6451 100644 --- a/v0.6.0/_modules/doctr/models/recognition/sar/tensorflow.html +++ b/v0.6.0/_modules/doctr/models/recognition/sar/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.sar.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.sar.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple
    +
     import tensorflow as tf
    -from tensorflow.keras import Sequential, layers, Model
    -from typing import Tuple, Dict, List, Any, Optional
    +from tensorflow.keras import Model, Sequential, layers
     
    -from ... import backbones
    -from ...utils import load_pretrained_params
    -from ..core import RecognitionModel, RecognitionPostProcessor
    +from doctr.datasets import VOCABS
     from doctr.utils.repr import NestedObject
     
    -__all__ = ['SAR', 'SARPostProcessor', 'sar_vgg16_bn', 'sar_resnet31']
    +from ...classification import resnet31
    +from ...utils.tensorflow import _bf16_to_float32, _build_model, load_pretrained_params
    +from ..core import RecognitionModel, RecognitionPostProcessor
    +
    +__all__ = ["SAR", "sar_resnet31"]
     
     default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'sar_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1-models/sar_vgg16bn-0d7e2c26.zip',
    -    },
    -    'sar_resnet31': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'resnet31', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/sar_resnet31-ea202587.zip',
    +    "sar_resnet31": {
    +        "mean": (0.694, 0.695, 0.693),
    +        "std": (0.299, 0.296, 0.301),
    +        "input_shape": (32, 128, 3),
    +        "vocab": VOCABS["french"],
    +        "url": "https://doctr-static.mindee.com/models?id=v0.9.0/sar_resnet31-5a58806c.weights.h5&src=0",
         },
     }
     
     
    +class SAREncoder(layers.Layer, NestedObject):
    +    """Implements encoder module of the SAR model
    +
    +    Args:
    +    ----
    +        rnn_units: number of hidden rnn units
    +        dropout_prob: dropout probability
    +    """
    +
    +    def __init__(self, rnn_units: int, dropout_prob: float = 0.0) -> None:
    +        super().__init__()
    +        self.rnn = Sequential([
    +            layers.LSTM(units=rnn_units, return_sequences=True, recurrent_dropout=dropout_prob),
    +            layers.LSTM(units=rnn_units, return_sequences=False, recurrent_dropout=dropout_prob),
    +        ])
    +
    +    def call(
    +        self,
    +        x: tf.Tensor,
    +        **kwargs: Any,
    +    ) -> tf.Tensor:
    +        # (N, C)
    +        return self.rnn(x, **kwargs)
    +
    +
     class AttentionModule(layers.Layer, NestedObject):
         """Implements attention module of the SAR model
     
         Args:
    +    ----
             attention_units: number of hidden attention units
     
         """
    -    def __init__(
    -        self,
    -        attention_units: int
    -    ) -> None:
     
    +    def __init__(self, attention_units: int) -> None:
             super().__init__()
             self.hidden_state_projector = layers.Conv2D(
    -            attention_units, 1, strides=1, use_bias=False, padding='same', kernel_initializer='he_normal',
    +            attention_units,
    +            1,
    +            strides=1,
    +            use_bias=False,
    +            padding="same",
    +            kernel_initializer="he_normal",
             )
             self.features_projector = layers.Conv2D(
    -            attention_units, 3, strides=1, use_bias=True, padding='same', kernel_initializer='he_normal',
    +            attention_units,
    +            3,
    +            strides=1,
    +            use_bias=True,
    +            padding="same",
    +            kernel_initializer="he_normal",
             )
             self.attention_projector = layers.Conv2D(
    -            1, 1, strides=1, use_bias=False, padding="same", kernel_initializer='he_normal',
    +            1,
    +            1,
    +            strides=1,
    +            use_bias=False,
    +            padding="same",
    +            kernel_initializer="he_normal",
             )
             self.flatten = layers.Flatten()
     
    @@ -343,12 +395,12 @@ 

    Source code for doctr.models.recognition.sar.tensorflow

    hidden_state: tf.Tensor, **kwargs: Any, ) -> tf.Tensor: - [H, W] = features.get_shape().as_list()[1:3] - # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units) - hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs) # shape (N, H, W, vgg_units) -> (N, H, W, attention_units) features_projection = self.features_projector(features, **kwargs) + # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units) + hidden_state = tf.expand_dims(tf.expand_dims(hidden_state, axis=1), axis=1) + hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs) projection = tf.math.tanh(hidden_state_projection + features_projection) # shape (N, H, W, attention_units) -> (N, H, W, 1) attention = self.attention_projector(projection, **kwargs) @@ -358,23 +410,25 @@

    Source code for doctr.models.recognition.sar.tensorflow

    # shape (N, H * W) -> (N, H, W, 1) attention_map = tf.reshape(attention, [-1, H, W, 1]) glimpse = tf.math.multiply(features, attention_map) - # shape (N, H * W) -> (N, 1) - glimpse = tf.reduce_sum(glimpse, axis=[1, 2]) - return glimpse + # shape (N, H * W) -> (N, C) + return tf.reduce_sum(glimpse, axis=[1, 2]) class SARDecoder(layers.Layer, NestedObject): """Implements decoder module of the SAR model Args: + ---- rnn_units: number of hidden units in recurrent cells max_length: maximum length of a sequence vocab_size: number of classes in the model alphabet embedding_units: number of hidden embedding units attention_units: number of hidden attention units - num_decoder_layers: number of LSTM layers to stack + num_decoder_cells: number of LSTMCell layers to stack + dropout_prob: dropout probability """ + def __init__( self, rnn_units: int, @@ -382,23 +436,22 @@

    Source code for doctr.models.recognition.sar.tensorflow

    vocab_size: int, embedding_units: int, attention_units: int, - num_decoder_layers: int = 2, - input_shape: Optional[List[Tuple[Optional[int]]]] = None, + num_decoder_cells: int = 2, + dropout_prob: float = 0.0, ) -> None: - super().__init__() self.vocab_size = vocab_size - self.lstm_decoder = layers.StackedRNNCells( - [layers.LSTMCell(rnn_units, dtype=tf.float32, implementation=1) for _ in range(num_decoder_layers)] - ) - self.embed = layers.Dense(embedding_units, use_bias=False, input_shape=(None, self.vocab_size + 1)) - self.attention_module = AttentionModule(attention_units) - self.output_dense = layers.Dense(vocab_size + 1, use_bias=True, input_shape=(None, 2 * rnn_units)) self.max_length = max_length - # Initialize kernels - if input_shape is not None: - self.attention_module.call(layers.Input(input_shape[0][1:]), layers.Input((1, 1, rnn_units))) + self.embed = layers.Dense(embedding_units, use_bias=False) + self.embed_tgt = layers.Embedding(embedding_units, self.vocab_size + 1) + + self.lstm_cells = layers.StackedRNNCells([ + layers.LSTMCell(rnn_units, implementation=1) for _ in range(num_decoder_cells) + ]) + self.attention_module = AttentionModule(attention_units) + self.output_dense = layers.Dense(self.vocab_size + 1, use_bias=True) + self.dropout = layers.Dropout(dropout_prob) def call( self, @@ -407,40 +460,47 @@

    Source code for doctr.models.recognition.sar.tensorflow

    gt: Optional[tf.Tensor] = None, **kwargs: Any, ) -> tf.Tensor: - - # initialize states (each of shape (N, rnn_units)) - states = self.lstm_decoder.get_initial_state( - inputs=None, batch_size=features.shape[0], dtype=tf.float32 - ) - # run first step of lstm - # holistic: shape (N, rnn_units) - _, states = self.lstm_decoder(holistic, states, **kwargs) - # Initialize with the index of virtual START symbol (placed after <eos>) - symbol = tf.fill(features.shape[0], self.vocab_size + 1) - logits_list = [] - if kwargs.get('training') and gt is None: - raise ValueError('Need to provide labels during training for teacher forcing') - for t in range(self.max_length + 1): # keep 1 step for <eos> - # one-hot symbol with depth vocab_size + 1 - # embeded_symbol: shape (N, embedding_units) - embeded_symbol = self.embed(tf.one_hot(symbol, depth=self.vocab_size + 1), **kwargs) - logits, states = self.lstm_decoder(embeded_symbol, states, **kwargs) - glimpse = self.attention_module( - features, tf.expand_dims(tf.expand_dims(logits, axis=1), axis=1), **kwargs, - ) - # logits: shape (N, rnn_units), glimpse: shape (N, 1) - logits = tf.concat([logits, glimpse], axis=-1) - # shape (N, rnn_units + 1) -> (N, vocab_size + 1) - logits = self.output_dense(logits, **kwargs) - # update symbol with predicted logits for t+1 step - if kwargs.get('training'): - symbol = gt[:, t] # type: ignore[index] + if gt is not None: + gt_embedding = self.embed_tgt(gt, **kwargs) + + logits_list: List[tf.Tensor] = [] + + for t in range(self.max_length + 1): # 32 + if t == 0: + # step to init the first states of the LSTMCell + states = self.lstm_cells.get_initial_state( + inputs=None, batch_size=features.shape[0], dtype=features.dtype + ) + prev_symbol = holistic + elif t == 1: + # step to init a 'blank' sequence of length vocab_size + 1 filled with zeros + # (N, vocab_size + 1) --> (N, embedding_units) + prev_symbol = tf.zeros([features.shape[0], self.vocab_size + 1], dtype=features.dtype) + prev_symbol = self.embed(prev_symbol, **kwargs) else: - symbol = tf.argmax(logits, axis=-1) - logits_list.append(logits) - outputs = tf.stack(logits_list, axis=1) # shape (N, max_length + 1, vocab_size + 1) - - return outputs + if gt is not None and kwargs.get("training", False): + # (N, embedding_units) -2 because of <bos> and <eos> (same) + prev_symbol = self.embed(gt_embedding[:, t - 2], **kwargs) + else: + # -1 to start at timestep where prev_symbol was initialized + index = tf.argmax(logits_list[t - 1], axis=-1) + # update prev_symbol with ones at the index of the previous logit vector + prev_symbol = self.embed(self.embed_tgt(index, **kwargs), **kwargs) + + # (N, C), (N, C) take the last hidden state and cell state from current timestep + _, states = self.lstm_cells(prev_symbol, states, **kwargs) + # states = (hidden_state, cell_state) + hidden_state = states[0][0] + # (N, H, W, C), (N, C) --> (N, C) + glimpse = self.attention_module(features, hidden_state, **kwargs) + # (N, C), (N, C) --> (N, 2 * C) + logits = tf.concat([hidden_state, glimpse], axis=1) + logits = self.dropout(logits, **kwargs) + # (N, vocab_size + 1) + logits_list.append(self.output_dense(logits, **kwargs)) + + # (max_length + 1, N, vocab_size + 1) --> (N, max_length + 1, vocab_size + 1) + return tf.transpose(tf.stack(logits_list[1:]), (1, 0, 2)) class SAR(Model, RecognitionModel): @@ -448,17 +508,20 @@

    Source code for doctr.models.recognition.sar.tensorflow

    Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. Args: + ---- feature_extractor: the backbone serving as feature extractor vocab: vocabulary used for encoding rnn_units: number of hidden units in both encoder and decoder LSTM embedding_units: number of embedding units attention_units: number of hidden units in attention module max_length: maximum word length handled by the model - num_decoders: number of LSTM to stack in decoder layer - + num_decoder_cells: number of LSTMCell layers to stack + dropout_prob: dropout probability for the encoder and decoder + exportable: onnx exportable returns only logits + cfg: dictionary containing information about the model """ - _children_names: List[str] = ['feat_extractor', 'encoder', 'decoder', 'postprocessor'] + _children_names: List[str] = ["feat_extractor", "encoder", "decoder", "postprocessor"] def __init__( self, @@ -468,36 +531,34 @@

    Source code for doctr.models.recognition.sar.tensorflow

    embedding_units: int = 512, attention_units: int = 512, max_length: int = 30, - num_decoders: int = 2, + num_decoder_cells: int = 2, + dropout_prob: float = 0.0, + exportable: bool = False, cfg: Optional[Dict[str, Any]] = None, ) -> None: - super().__init__() self.vocab = vocab + self.exportable = exportable self.cfg = cfg - self.max_length = max_length + 1 # Add 1 timestep for EOS after the longest word self.feat_extractor = feature_extractor - self.encoder = Sequential( - [ - layers.LSTM(units=rnn_units, return_sequences=True), - layers.LSTM(units=rnn_units, return_sequences=False) - ] - ) - # Initialize the kernels (watch out for reduce_max) - self.encoder.build(input_shape=(None,) + self.feat_extractor.output_shape[2:]) - + self.encoder = SAREncoder(rnn_units, dropout_prob) self.decoder = SARDecoder( - rnn_units, max_length, len(vocab), embedding_units, attention_units, num_decoders, - input_shape=[self.feat_extractor.output_shape, self.encoder.output_shape] + rnn_units, + self.max_length, + len(vocab), + embedding_units, + attention_units, + num_decoder_cells, + dropout_prob, ) self.postprocessor = SARPostProcessor(vocab=vocab) + @staticmethod def compute_loss( - self, model_output: tf.Tensor, gt: tf.Tensor, seq_len: tf.Tensor, @@ -506,11 +567,13 @@

    Source code for doctr.models.recognition.sar.tensorflow

    Sequences are masked after the EOS character. Args: + ---- gt: the encoded tensor with gt labels model_output: predicted logits of the model seq_len: lengths of each gt word inside the batch Returns: + ------- The loss of the model on the batch """ # Input length : number of timesteps @@ -525,7 +588,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    mask_values = tf.zeros_like(cce) mask_2d = tf.sequence_mask(seq_len, input_len) masked_loss = tf.where(mask_2d, cce, mask_values) - ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32)) + ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, model_output.dtype)) return tf.expand_dims(ce_loss, axis=1) def call( @@ -536,16 +599,28 @@

    Source code for doctr.models.recognition.sar.tensorflow

    return_preds: bool = False, **kwargs: Any, ) -> Dict[str, Any]: - features = self.feat_extractor(x, **kwargs) - pooled_features = tf.reduce_max(features, axis=1) # vertical max pooling + # vertical max pooling --> (N, C, W) + pooled_features = tf.reduce_max(features, axis=1) + # holistic (N, C) encoded = self.encoder(pooled_features, **kwargs) + if target is not None: - gt, seq_len = self.compute_target(target) + gt, seq_len = self.build_target(target) seq_len = tf.cast(seq_len, tf.int32) - decoded_features = self.decoder(features, encoded, gt=None if target is None else gt, **kwargs) + + if kwargs.get("training", False) and target is None: + raise ValueError("Need to provide labels during training for teacher forcing") + + decoded_features = _bf16_to_float32( + self.decoder(features, encoded, gt=None if target is None else gt, **kwargs) + ) out: Dict[str, tf.Tensor] = {} + if self.exportable: + out["logits"] = decoded_features + return out + if return_model_output: out["out_map"] = decoded_features @@ -554,7 +629,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    out["preds"] = self.postprocessor(decoded_features) if target is not None: - out['loss'] = self.compute_loss(decoded_features, gt, seq_len) + out["loss"] = self.compute_loss(decoded_features, gt, seq_len) return out @@ -563,9 +638,8 @@

    Source code for doctr.models.recognition.sar.tensorflow

    """Post processor for SAR architectures Args: + ---- vocab: string containing the ordered sequence of supported characters - ignore_case: if True, ignore case of letters - ignore_accents: if True, ignore accents of letters """ def __call__( @@ -580,95 +654,75 @@

    Source code for doctr.models.recognition.sar.tensorflow

    probs = tf.math.reduce_min(probs, axis=1) # decode raw output of the model with tf_label_to_idx - out_idxs = tf.cast(out_idxs, dtype='int32') + out_idxs = tf.cast(out_idxs, dtype="int32") embedding = tf.constant(self._embedding, dtype=tf.string) decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(embedding, out_idxs), axis=-1) decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>") - decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0] + decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value="not valid")[:, 0] word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()] - return list(zip(word_values, probs.numpy().tolist())) + return list(zip(word_values, probs.numpy().clip(0, 1).tolist())) -def _sar(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> SAR: +def _sar( + arch: str, + pretrained: bool, + backbone_fn, + pretrained_backbone: bool = True, + input_shape: Optional[Tuple[int, int, int]] = None, + **kwargs: Any, +) -> SAR: + pretrained_backbone = pretrained_backbone and not pretrained # Patch the config _cfg = deepcopy(default_cfgs[arch]) - _cfg['input_shape'] = input_shape or _cfg['input_shape'] - _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab']) - _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units']) - _cfg['embedding_units'] = kwargs.get('embedding_units', _cfg['rnn_units']) - _cfg['attention_units'] = kwargs.get('attention_units', _cfg['rnn_units']) - _cfg['max_length'] = kwargs.get('max_length', _cfg['max_length']) - _cfg['num_decoders'] = kwargs.get('num_decoders', _cfg['num_decoders']) + _cfg["input_shape"] = input_shape or _cfg["input_shape"] + _cfg["vocab"] = kwargs.get("vocab", _cfg["vocab"]) # Feature extractor - feat_extractor = backbones.__dict__[default_cfgs[arch]['backbone']]( - input_shape=_cfg['input_shape'], + feat_extractor = backbone_fn( + pretrained=pretrained_backbone, + input_shape=_cfg["input_shape"], include_top=False, ) - kwargs['vocab'] = _cfg['vocab'] - kwargs['rnn_units'] = _cfg['rnn_units'] - kwargs['embedding_units'] = _cfg['embedding_units'] - kwargs['attention_units'] = _cfg['attention_units'] - kwargs['max_length'] = _cfg['max_length'] - kwargs['num_decoders'] = _cfg['num_decoders'] + kwargs["vocab"] = _cfg["vocab"] # Build the model model = SAR(feat_extractor, cfg=_cfg, **kwargs) + _build_model(model) # Load pretrained parameters if pretrained: - load_pretrained_params(model, default_cfgs[arch]['url']) + # The given vocab differs from the pretrained model => skip the mismatching layers for fine tuning + load_pretrained_params( + model, default_cfgs[arch]["url"], skip_mismatch=kwargs["vocab"] != default_cfgs[arch]["vocab"] + ) return model -
    -[docs] -def sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a VGG16 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import sar_vgg16_bn - >>> model = sar_vgg16_bn(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_vgg16_bn', pretrained, **kwargs)
    - - -
    -[docs] +[docs] def sar_resnet31(pretrained: bool = False, **kwargs: Any) -> SAR: """SAR with a resnet-31 feature extractor as described in `"Show, Attend and Read:A Simple and Strong Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - Example: - >>> import tensorflow as tf - >>> from doctr.models import sar_resnet31 - >>> model = sar_resnet31(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) + >>> import tensorflow as tf + >>> from doctr.models import sar_resnet31 + >>> model = sar_resnet31(pretrained=False) + >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) + >>> out = model(input_tensor) Args: + ---- pretrained (bool): If True, returns a model pre-trained on our text recognition dataset + **kwargs: keyword arguments of the SAR architecture Returns: + ------- text recognition architecture """ - - return _sar('sar_resnet31', pretrained, **kwargs)
    + return _sar("sar_resnet31", pretrained, resnet31, **kwargs)
    @@ -702,8 +756,8 @@

    Source code for doctr.models.recognition.sar.tensorflow

    - +
    + diff --git a/v0.6.0/_modules/doctr/models/recognition/vitstr/tensorflow.html b/v0.6.0/_modules/doctr/models/recognition/vitstr/tensorflow.html index 6e101893bf..c594d40a56 100644 --- a/v0.6.0/_modules/doctr/models/recognition/vitstr/tensorflow.html +++ b/v0.6.0/_modules/doctr/models/recognition/vitstr/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.vitstr.tensorflow - docTR documentation @@ -621,7 +621,7 @@

    Source code for doctr.models.recognition.vitstr.tensorflow

    - + diff --git a/v0.6.0/_modules/doctr/models/recognition/zoo.html b/v0.6.0/_modules/doctr/models/recognition/zoo.html index bf0ae6af6e..f664304019 100644 --- a/v0.6.0/_modules/doctr/models/recognition/zoo.html +++ b/v0.6.0/_modules/doctr/models/recognition/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.zoo - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.models.recognition.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -from typing import Any
    +from typing import Any, List
     
    -from doctr.file_utils import is_tf_available, is_torch_available
    -from .core import RecognitionPredictor
    -from ..preprocessor import PreProcessor
    -from .. import recognition
    +from doctr.file_utils import is_tf_available
    +from doctr.models.preprocessor import PreProcessor
     
    +from .. import recognition
    +from .predictor import RecognitionPredictor
     
     __all__ = ["recognition_predictor"]
     
     
    -if is_tf_available():
    -    ARCHS = ['crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31', 'master']
    -elif is_torch_available():
    -    ARCHS = ['crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31']
    +ARCHS: List[str] = [
    +    "crnn_vgg16_bn",
    +    "crnn_mobilenet_v3_small",
    +    "crnn_mobilenet_v3_large",
    +    "sar_resnet31",
    +    "master",
    +    "vitstr_small",
    +    "vitstr_base",
    +    "parseq",
    +]
    +
     
    +def _predictor(arch: Any, pretrained: bool, **kwargs: Any) -> RecognitionPredictor:
    +    if isinstance(arch, str):
    +        if arch not in ARCHS:
    +            raise ValueError(f"unknown architecture '{arch}'")
     
    -def _predictor(arch: str, pretrained: bool, **kwargs: Any) -> RecognitionPredictor:
    +        _model = recognition.__dict__[arch](
    +            pretrained=pretrained, pretrained_backbone=kwargs.get("pretrained_backbone", True)
    +        )
    +    else:
    +        if not isinstance(
    +            arch, (recognition.CRNN, recognition.SAR, recognition.MASTER, recognition.ViTSTR, recognition.PARSeq)
    +        ):
    +            raise ValueError(f"unknown architecture: {type(arch)}")
    +        _model = arch
     
    -    if arch not in ARCHS:
    -        raise ValueError(f"unknown architecture '{arch}'")
    +    kwargs.pop("pretrained_backbone", None)
     
    -    _model = recognition.__dict__[arch](pretrained=pretrained)
    -    kwargs['mean'] = kwargs.get('mean', _model.cfg['mean'])
    -    kwargs['std'] = kwargs.get('std', _model.cfg['std'])
    -    kwargs['batch_size'] = kwargs.get('batch_size', 32)
    -    predictor = RecognitionPredictor(
    -        PreProcessor(_model.cfg['input_shape'][:2], preserve_aspect_ratio=True, **kwargs),
    -        _model
    -    )
    +    kwargs["mean"] = kwargs.get("mean", _model.cfg["mean"])
    +    kwargs["std"] = kwargs.get("std", _model.cfg["std"])
    +    kwargs["batch_size"] = kwargs.get("batch_size", 128)
    +    input_shape = _model.cfg["input_shape"][:2] if is_tf_available() else _model.cfg["input_shape"][-2:]
    +    predictor = RecognitionPredictor(PreProcessor(input_shape, preserve_aspect_ratio=True, **kwargs), _model)
     
         return predictor
     
     
     
    -[docs] -def recognition_predictor(arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) -> RecognitionPredictor: +[docs] +def recognition_predictor( + arch: Any = "crnn_vgg16_bn", + pretrained: bool = False, + symmetric_pad: bool = False, + batch_size: int = 128, + **kwargs: Any, +) -> RecognitionPredictor: """Text recognition architecture. Example:: @@ -326,14 +369,18 @@

    Source code for doctr.models.recognition.zoo

            >>> out = model([input_page])
     
         Args:
    -        arch: name of the architecture to use ('crnn_vgg16_bn', 'crnn_resnet31', 'sar_vgg16_bn', 'sar_resnet31')
    +    ----
    +        arch: name of the architecture or model itself to use (e.g. 'crnn_vgg16_bn')
             pretrained: If True, returns a model pre-trained on our text recognition dataset
    +        symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right
    +        batch_size: number of samples the model processes in parallel
    +        **kwargs: optional parameters to be passed to the architecture
     
         Returns:
    +    -------
             Recognition predictor
         """
    -
    -    return _predictor(arch, pretrained, **kwargs)
    + return _predictor(arch=arch, pretrained=pretrained, symmetric_pad=symmetric_pad, batch_size=batch_size, **kwargs)
    @@ -367,8 +414,8 @@

    Source code for doctr.models.recognition.zoo

       
    -
    - +
    + diff --git a/v0.6.0/_modules/doctr/models/zoo.html b/v0.6.0/_modules/doctr/models/zoo.html index dec6857019..d459671648 100644 --- a/v0.6.0/_modules/doctr/models/zoo.html +++ b/v0.6.0/_modules/doctr/models/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.zoo - docTR documentation @@ -225,15 +225,42 @@

    Source code for doctr.models.zoo

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     from typing import Any
    -from .core import OCRPredictor
    +
     from .detection.zoo import detection_predictor
    +from .kie_predictor import KIEPredictor
    +from .predictor import OCRPredictor
     from .recognition.zoo import recognition_predictor
     
    +__all__ = ["ocr_predictor", "kie_predictor"]
     
    -__all__ = ["ocr_predictor"]
    -
    -
    -def _predictor(det_arch: str, reco_arch: str, pretrained: bool, det_bs=2, reco_bs=128) -> OCRPredictor:
     
    +def _predictor(
    +    det_arch: Any,
    +    reco_arch: Any,
    +    pretrained: bool,
    +    pretrained_backbone: bool = True,
    +    assume_straight_pages: bool = True,
    +    preserve_aspect_ratio: bool = True,
    +    symmetric_pad: bool = True,
    +    det_bs: int = 2,
    +    reco_bs: int = 128,
    +    detect_orientation: bool = False,
    +    straighten_pages: bool = False,
    +    detect_language: bool = False,
    +    **kwargs,
    +) -> OCRPredictor:
         # Detection
    -    det_predictor = detection_predictor(det_arch, pretrained=pretrained, batch_size=det_bs)
    +    det_predictor = detection_predictor(
    +        det_arch,
    +        pretrained=pretrained,
    +        pretrained_backbone=pretrained_backbone,
    +        batch_size=det_bs,
    +        assume_straight_pages=assume_straight_pages,
    +        preserve_aspect_ratio=preserve_aspect_ratio,
    +        symmetric_pad=symmetric_pad,
    +    )
     
         # Recognition
    -    reco_predictor = recognition_predictor(reco_arch, pretrained=pretrained, batch_size=reco_bs)
    +    reco_predictor = recognition_predictor(
    +        reco_arch,
    +        pretrained=pretrained,
    +        pretrained_backbone=pretrained_backbone,
    +        batch_size=reco_bs,
    +    )
     
    -    return OCRPredictor(det_predictor, reco_predictor)
    +    return OCRPredictor(
    +        det_predictor,
    +        reco_predictor,
    +        assume_straight_pages=assume_straight_pages,
    +        preserve_aspect_ratio=preserve_aspect_ratio,
    +        symmetric_pad=symmetric_pad,
    +        detect_orientation=detect_orientation,
    +        straighten_pages=straighten_pages,
    +        detect_language=detect_language,
    +        **kwargs,
    +    )
     
     
     
    -[docs] +[docs] def ocr_predictor( - det_arch: str = 'db_resnet50', - reco_arch: str = 'crnn_vgg16_bn', + det_arch: Any = "fast_base", + reco_arch: Any = "crnn_vgg16_bn", pretrained: bool = False, - **kwargs: Any + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + export_as_straight_boxes: bool = False, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs: Any, ) -> OCRPredictor: """End-to-end OCR architecture using one model for localization, and another for text recognition. - Example:: - >>> import numpy as np - >>> from doctr.models import ocr_predictor - >>> model = ocr_predictor(pretrained=True) - >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) - >>> out = model([input_page]) + >>> 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(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) Args: - arch: name of the architecture to use ('db_sar_vgg', 'db_sar_resnet', 'db_crnn_vgg', 'db_crnn_resnet') + ---- + det_arch: name of the detection architecture or the model itself to use + (e.g. 'db_resnet50', 'db_mobilenet_v3_large') + reco_arch: name of the recognition architecture or the model itself to use + (e.g. 'crnn_vgg16_bn', 'sar_resnet31') pretrained: If True, returns a model pre-trained on our OCR dataset + pretrained_backbone: If True, returns a model with a pretrained backbone + assume_straight_pages: if True, speeds up the inference by assuming you only pass straight pages + without rotated textual elements. + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it. + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right. + export_as_straight_boxes: when assume_straight_pages is set to False, export final predictions + (potentially rotated) as straight bounding boxes. + detect_orientation: if True, the estimated general page orientation will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + straighten_pages: if True, estimates the page general orientation + based on the segmentation map median line orientation. + Then, rotates page before passing it again to the deep learning detection module. + Doing so will improve performances for documents with page-uniform rotations. + detect_language: if True, the language prediction will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + kwargs: keyword args of `OCRPredictor` Returns: + ------- OCR predictor """ + return _predictor( + det_arch, + reco_arch, + pretrained, + pretrained_backbone=pretrained_backbone, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + export_as_straight_boxes=export_as_straight_boxes, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + )
    + + - return _predictor(det_arch, reco_arch, pretrained, **kwargs)
    +def _kie_predictor( + det_arch: Any, + reco_arch: Any, + pretrained: bool, + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + det_bs: int = 2, + reco_bs: int = 128, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs, +) -> KIEPredictor: + # Detection + det_predictor = detection_predictor( + det_arch, + pretrained=pretrained, + pretrained_backbone=pretrained_backbone, + batch_size=det_bs, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + ) + + # Recognition + reco_predictor = recognition_predictor( + reco_arch, + pretrained=pretrained, + pretrained_backbone=pretrained_backbone, + batch_size=reco_bs, + ) + + return KIEPredictor( + det_predictor, + reco_predictor, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + ) + + +
    +[docs] +def kie_predictor( + det_arch: Any = "fast_base", + reco_arch: Any = "crnn_vgg16_bn", + pretrained: bool = False, + pretrained_backbone: bool = True, + assume_straight_pages: bool = True, + preserve_aspect_ratio: bool = True, + symmetric_pad: bool = True, + export_as_straight_boxes: bool = False, + detect_orientation: bool = False, + straighten_pages: bool = False, + detect_language: bool = False, + **kwargs: Any, +) -> KIEPredictor: + """End-to-end KIE architecture using one model for localization, and another for text recognition. + + >>> 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(600, 800, 3)).astype(np.uint8) + >>> out = model([input_page]) + + Args: + ---- + det_arch: name of the detection architecture or the model itself to use + (e.g. 'db_resnet50', 'db_mobilenet_v3_large') + reco_arch: name of the recognition architecture or the model itself to use + (e.g. 'crnn_vgg16_bn', 'sar_resnet31') + pretrained: If True, returns a model pre-trained on our OCR dataset + pretrained_backbone: If True, returns a model with a pretrained backbone + assume_straight_pages: if True, speeds up the inference by assuming you only pass straight pages + without rotated textual elements. + preserve_aspect_ratio: If True, pad the input document image to preserve the aspect ratio before + running the detection model on it. + symmetric_pad: if True, pad the image symmetrically instead of padding at the bottom-right. + export_as_straight_boxes: when assume_straight_pages is set to False, export final predictions + (potentially rotated) as straight bounding boxes. + detect_orientation: if True, the estimated general page orientation will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + straighten_pages: if True, estimates the page general orientation + based on the segmentation map median line orientation. + Then, rotates page before passing it again to the deep learning detection module. + Doing so will improve performances for documents with page-uniform rotations. + detect_language: if True, the language prediction will be added to the predictions for each + page. Doing so will slightly deteriorate the overall latency. + kwargs: keyword args of `OCRPredictor` + + Returns: + ------- + KIE predictor + """ + return _kie_predictor( + det_arch, + reco_arch, + pretrained, + pretrained_backbone=pretrained_backbone, + assume_straight_pages=assume_straight_pages, + preserve_aspect_ratio=preserve_aspect_ratio, + symmetric_pad=symmetric_pad, + export_as_straight_boxes=export_as_straight_boxes, + detect_orientation=detect_orientation, + straighten_pages=straighten_pages, + detect_language=detect_language, + **kwargs, + )
    @@ -353,8 +575,8 @@

    Source code for doctr.models.zoo

           
         
       
    - - + + diff --git a/v0.6.0/_modules/doctr/transforms/modules.html b/v0.6.0/_modules/doctr/transforms/modules.html deleted file mode 100644 index ba8269e7ef..0000000000 --- a/v0.6.0/_modules/doctr/transforms/modules.html +++ /dev/null @@ -1,734 +0,0 @@ - - - - - - - - - - - - doctr.transforms.modules - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.transforms.modules

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import random
    -import tensorflow as tf
    -from typing import List, Any, Tuple, Callable
    -
    -from doctr.utils.repr import NestedObject
    -from . import functional as F
    -
    -
    -__all__ = ['Compose', 'Resize', 'Normalize', 'LambdaTransformation', 'ToGray', 'ColorInversion',
    -           'RandomBrightness', 'RandomContrast', 'RandomSaturation', 'RandomHue', 'RandomGamma', 'RandomJpegQuality',
    -           'OneOf', 'RandomApply']
    -
    -
    -
    -[docs] -class Compose(NestedObject): - """Implements a wrapper that will apply transformations sequentially - - Example:: - >>> from doctr.transforms import Compose, Resize - >>> import tensorflow as tf - >>> transfos = Compose([Resize((32, 32))]) - >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformation modules - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, x: Any) -> Any: - for t in self.transforms: - x = t(x) - - return x
    - - - -
    -[docs] -class Resize(NestedObject): - """Resizes a tensor to a target size - - Example:: - >>> from doctr.transforms import Resize - >>> import tensorflow as tf - >>> transfo = Resize((32, 32)) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - output_size: expected output size - method: interpolation method - preserve_aspect_ratio: if `True`, preserve aspect ratio and pad the rest with zeros - symmetric_pad: if `True` while preserving aspect ratio, the padding will be done symmetrically - """ - def __init__( - self, - output_size: Tuple[int, int], - method: str = 'bilinear', - preserve_aspect_ratio: bool = False, - symmetric_pad: bool = False, - ) -> None: - self.output_size = output_size - self.method = method - self.preserve_aspect_ratio = preserve_aspect_ratio - self.symmetric_pad = symmetric_pad - - def extra_repr(self) -> str: - _repr = f"output_size={self.output_size}, method='{self.method}'" - if self.preserve_aspect_ratio: - _repr += f", preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}" - return _repr - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img = tf.image.resize(img, self.output_size, self.method, self.preserve_aspect_ratio) - if self.preserve_aspect_ratio: - # pad width - if not self.symmetric_pad: - offset = (0, 0) - elif self.output_size[0] == img.shape[0]: - offset = (0, int((self.output_size[1] - img.shape[1]) / 2)) - else: - offset = (int((self.output_size[0] - img.shape[0]) / 2), 0) - img = tf.image.pad_to_bounding_box(img, *offset, *self.output_size) - return img
    - - - -
    -[docs] -class Normalize(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - mean: average value per channel - std: standard deviation per channel - """ - def __init__(self, mean: Tuple[float, float, float], std: Tuple[float, float, float]) -> None: - self.mean = tf.constant(mean, dtype=tf.float32) - self.std = tf.constant(std, dtype=tf.float32) - - def extra_repr(self) -> str: - return f"mean={self.mean.numpy().tolist()}, std={self.std.numpy().tolist()}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img -= self.mean - img /= self.std - return img
    - - - -
    -[docs] -class LambdaTransformation(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import LambdaTransformation - >>> import tensorflow as tf - >>> transfo = LambdaTransformation(lambda x: x/ 255.) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - fn: the function to be applied to the input tensor - """ - def __init__(self, fn: Callable[[tf.Tensor], tf.Tensor]) -> None: - self.fn = fn - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return self.fn(img)
    - - - -
    -[docs] -class ToGray(NestedObject): - """Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ToGray() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - """ - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.rgb_to_grayscale(img)
    - - - -
    -[docs] -class ColorInversion(NestedObject): - """Applies the following tranformation to a tensor (image or batch of images): - convert to grayscale, colorize (shift 0-values randomly), and then invert colors - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ColorInversion(min_val=0.6) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_val: range [min_val, 1] to colorize RGB pixels - """ - def __init__(self, min_val: float = 0.6) -> None: - self.min_val = min_val - - def extra_repr(self) -> str: - return f"min_val={self.min_val}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return F.invert_colors(img, self.min_val)
    - - - -
    -[docs] -class RandomBrightness(NestedObject): - """Randomly adjust brightness of a tensor (batch of images or image) by adding a delta - to all pixels - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Brightness() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - p: probability to apply transformation - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_brightness(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomContrast(NestedObject): - """Randomly adjust contrast of a tensor (batch of images or image) by adjusting - each pixel: (img - mean) * contrast_factor + mean. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Contrast() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1) - """ - def __init__(self, delta: float = .3) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_contrast(img, lower=1 - self.delta, upper=1 / (1 - self.delta))
    - - - -
    -[docs] -class RandomSaturation(NestedObject): - """Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and - increasing saturation by a factor. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Saturation() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1) - """ - def __init__(self, delta: float = .5) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_saturation(img, lower=1 - self.delta, upper=1 + self.delta)
    - - - -
    -[docs] -class RandomHue(NestedObject): - """Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Hue() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_hue(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomGamma(NestedObject): - """randomly performs gamma correction for a tensor (batch of images or image) - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Gamma() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_gamma: non-negative real number, lower bound for gamma param - max_gamma: non-negative real number, upper bound for gamma - min_gain: lower bound for constant multiplier - max_gain: upper bound for constant multiplier - """ - def __init__( - self, - min_gamma: float = 0.5, - max_gamma: float = 1.5, - min_gain: float = 0.8, - max_gain: float = 1.2, - ) -> None: - self.min_gamma = min_gamma - self.max_gamma = max_gamma - self.min_gain = min_gain - self.max_gain = max_gain - - def extra_repr(self) -> str: - return f"""gamma_range=({self.min_gamma}, {self.max_gamma}), - gain_range=({self.min_gain}, {self.max_gain})""" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - gamma = random.uniform(self.min_gamma, self.max_gamma) - gain = random.uniform(self.min_gain, self.max_gain) - return tf.image.adjust_gamma(img, gamma=gamma, gain=gain)
    - - - -
    -[docs] -class RandomJpegQuality(NestedObject): - """Randomly adjust jpeg quality of a 3 dimensional RGB image - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = JpegQuality() - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - min_quality: int between [0, 100] - max_quality: int between [0, 100] - """ - def __init__(self, min_quality: int = 60, max_quality: int = 100) -> None: - self.min_quality = min_quality - self.max_quality = max_quality - - def extra_repr(self) -> str: - return f"min_quality={self.min_quality}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_jpeg_quality( - img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality - )
    - - - -
    -[docs] -class OneOf(NestedObject): - """Randomly apply one of the input transformations - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = OneOf([JpegQuality(), Gamma()]) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformations, one only will be picked - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - # Pick transformation - transfo = self.transforms[int(random.random() * len(self.transforms))] - # Apply - return transfo(img)
    - - - -
    -[docs] -class RandomApply(NestedObject): - """Apply with a probability p the input transformation - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = RandomApply(Gamma(), p=.5) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transform: transformation to apply - p: probability to apply - """ - def __init__(self, transform: NestedObject, p: float = .5) -> None: - self.transform = transform - self.p = p - - def extra_repr(self) -> str: - return f"transform={self.transform}, p={self.p}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - if random.random() < self.p: - return self.transform(img) - return img
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.6.0/_modules/doctr/transforms/modules/base.html b/v0.6.0/_modules/doctr/transforms/modules/base.html index c42079a8fd..4596df3848 100644 --- a/v0.6.0/_modules/doctr/transforms/modules/base.html +++ b/v0.6.0/_modules/doctr/transforms/modules/base.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.base - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.transforms.modules.base

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    +import math
     import random
    -from typing import List, Any, Callable
    +from typing import Any, Callable, List, Optional, Tuple, Union
    +
    +import numpy as np
     
     from doctr.utils.repr import NestedObject
    +
     from .. import functional as F
     
    +__all__ = ["SampleCompose", "ImageTransform", "ColorInversion", "OneOf", "RandomApply", "RandomRotate", "RandomCrop"]
    +
    +
    +class SampleCompose(NestedObject):
    +    """Implements a wrapper that will apply transformations sequentially on both image and target
    +
    +    .. tabs::
    +
    +        .. tab:: TensorFlow
    +
    +            .. code:: python
    +
    +                >>> import numpy as np
    +                >>> import tensorflow as tf
    +                >>> from doctr.transforms import SampleCompose, ImageTransform, ColorInversion, RandomRotate
    +                >>> transfo = SampleCompose([ImageTransform(ColorInversion((32, 32))), RandomRotate(30)])
    +                >>> out, out_boxes = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1), np.zeros((2, 4)))
    +
    +        .. tab:: PyTorch
    +
    +            .. code:: python
    +
    +                >>> import numpy as np
    +                >>> import torch
    +                >>> from doctr.transforms import SampleCompose, ImageTransform, ColorInversion, RandomRotate
    +                >>> transfos = SampleCompose([ImageTransform(ColorInversion((32, 32))), RandomRotate(30)])
    +                >>> out, out_boxes = transfos(torch.rand(8, 64, 64, 3), np.zeros((2, 4)))
    +
    +    Args:
    +    ----
    +        transforms: list of transformation modules
    +    """
    +
    +    _children_names: List[str] = ["sample_transforms"]
    +
    +    def __init__(self, transforms: List[Callable[[Any, Any], Tuple[Any, Any]]]) -> None:
    +        self.sample_transforms = transforms
    +
    +    def __call__(self, x: Any, target: Any) -> Tuple[Any, Any]:
    +        for t in self.sample_transforms:
    +            x, target = t(x, target)
    +
    +        return x, target
    +
    +
    +class ImageTransform(NestedObject):
    +    """Implements a transform wrapper to turn an image-only transformation into an image+target transform
    +
    +    .. tabs::
    +
    +        .. tab:: TensorFlow
    +
    +            .. code:: python
    +
    +                >>> import tensorflow as tf
    +                >>> from doctr.transforms import ImageTransform, ColorInversion
    +                >>> transfo = ImageTransform(ColorInversion((32, 32)))
    +                >>> out, _ = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1), None)
    +
    +        .. tab:: PyTorch
    +
    +            .. code:: python
    +
    +                >>> import torch
    +                >>> from doctr.transforms import ImageTransform, ColorInversion
    +                >>> transfo = ImageTransform(ColorInversion((32, 32)))
    +                >>> out, _ = transfo(torch.rand(8, 64, 64, 3), None)
    +
    +    Args:
    +    ----
    +        transform: the image transformation module to wrap
    +    """
    +
    +    _children_names: List[str] = ["img_transform"]
    +
    +    def __init__(self, transform: Callable[[Any], Any]) -> None:
    +        self.img_transform = transform
     
    -__all__ = ['ColorInversion', 'OneOf', 'RandomApply']
    +    def __call__(self, img: Any, target: Any) -> Tuple[Any, Any]:
    +        img = self.img_transform(img)
    +        return img, target
     
     
     
    -[docs] +[docs] class ColorInversion(NestedObject): """Applies the following tranformation to a tensor (image or batch of images): convert to grayscale, colorize (shift 0-values randomly), and then invert colors - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ColorInversion(min_val=0.6) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + .. tabs:: + + .. tab:: TensorFlow + + .. code:: python + + >>> import tensorflow as tf + >>> from doctr.transforms import ColorInversion + >>> transfo = ColorInversion(min_val=0.6) + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + + .. tab:: PyTorch + + .. code:: python + + >>> import torch + >>> from doctr.transforms import ColorInversion + >>> transfo = ColorInversion(min_val=0.6) + >>> out = transfo(torch.rand(8, 64, 64, 3)) Args: + ---- min_val: range [min_val, 1] to colorize RGB pixels """ + def __init__(self, min_val: float = 0.5) -> None: self.min_val = min_val @@ -316,59 +437,178 @@

    Source code for doctr.transforms.modules.base

    -[docs] +[docs] class OneOf(NestedObject): """Randomly apply one of the input transformations - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = OneOf([JpegQuality(), Gamma()]) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + .. tabs:: + + .. tab:: TensorFlow + + .. code:: python + + >>> import tensorflow as tf + >>> from doctr.transforms import OneOf + >>> transfo = OneOf([JpegQuality(), Gamma()]) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + .. tab:: PyTorch + + .. code:: python + + >>> import torch + >>> from doctr.transforms import OneOf + >>> transfo = OneOf([JpegQuality(), Gamma()]) + >>> out = transfo(torch.rand(1, 64, 64, 3)) Args: + ---- transforms: list of transformations, one only will be picked """ - _children_names: List[str] = ['transforms'] + _children_names: List[str] = ["transforms"] def __init__(self, transforms: List[Callable[[Any], Any]]) -> None: self.transforms = transforms - def __call__(self, img: Any) -> Any: + def __call__(self, img: Any, target: Optional[np.ndarray] = None) -> Union[Any, Tuple[Any, np.ndarray]]: # Pick transformation transfo = self.transforms[int(random.random() * len(self.transforms))] # Apply - return transfo(img)
    + return transfo(img) if target is None else transfo(img, target) # type: ignore[call-arg]
    -[docs] +[docs] class RandomApply(NestedObject): """Apply with a probability p the input transformation - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = RandomApply(Gamma(), p=.5) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + .. tabs:: + + .. tab:: TensorFlow + + .. code:: python + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomApply + >>> transfo = RandomApply(Gamma(), p=.5) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + .. tab:: PyTorch + + .. code:: python + + >>> import torch + >>> from doctr.transforms import RandomApply + >>> transfo = RandomApply(Gamma(), p=.5) + >>> out = transfo(torch.rand(1, 64, 64, 3)) Args: + ---- transform: transformation to apply p: probability to apply """ - def __init__(self, transform: Callable[[Any], Any], p: float = .5) -> None: + + def __init__(self, transform: Callable[[Any], Any], p: float = 0.5) -> None: self.transform = transform self.p = p def extra_repr(self) -> str: return f"transform={self.transform}, p={self.p}" - def __call__(self, img: Any) -> Any: + def __call__(self, img: Any, target: Optional[np.ndarray] = None) -> Union[Any, Tuple[Any, np.ndarray]]: if random.random() < self.p: - return self.transform(img) - return img
    + return self.transform(img) if target is None else self.transform(img, target) # type: ignore[call-arg] + return img if target is None else (img, target)
    + + + +
    +[docs] +class RandomRotate(NestedObject): + """Randomly rotate a tensor image and its boxes + + .. image:: https://doctr-static.mindee.com/models?id=v0.4.0/rotation_illustration.png&src=0 + :align: center + + Args: + ---- + max_angle: maximum angle for rotation, in degrees. Angles will be uniformly picked in + [-max_angle, max_angle] + expand: whether the image should be padded before the rotation + """ + + def __init__(self, max_angle: float = 5.0, expand: bool = False) -> None: + self.max_angle = max_angle + self.expand = expand + + def extra_repr(self) -> str: + return f"max_angle={self.max_angle}, expand={self.expand}" + + def __call__(self, img: Any, target: np.ndarray) -> Tuple[Any, np.ndarray]: + angle = random.uniform(-self.max_angle, self.max_angle) + r_img, r_polys = F.rotate_sample(img, target, angle, self.expand) + # Removes deleted boxes + is_kept = (r_polys.max(1) > r_polys.min(1)).sum(1) == 2 + return r_img, r_polys[is_kept]
    + + + +
    +[docs] +class RandomCrop(NestedObject): + """Randomly crop a tensor image and its boxes + + Args: + ---- + scale: tuple of floats, relative (min_area, max_area) of the crop + ratio: tuple of float, relative (min_ratio, max_ratio) where ratio = h/w + """ + + def __init__(self, scale: Tuple[float, float] = (0.08, 1.0), ratio: Tuple[float, float] = (0.75, 1.33)) -> None: + self.scale = scale + self.ratio = ratio + + def extra_repr(self) -> str: + return f"scale={self.scale}, ratio={self.ratio}" + + def __call__(self, img: Any, target: np.ndarray) -> Tuple[Any, np.ndarray]: + scale = random.uniform(self.scale[0], self.scale[1]) + ratio = random.uniform(self.ratio[0], self.ratio[1]) + + height, width = img.shape[:2] + + # Calculate crop size + crop_area = scale * width * height + aspect_ratio = ratio * (width / height) + crop_width = int(round(math.sqrt(crop_area * aspect_ratio))) + crop_height = int(round(math.sqrt(crop_area / aspect_ratio))) + + # Ensure crop size does not exceed image dimensions + crop_width = min(crop_width, width) + crop_height = min(crop_height, height) + + # Randomly select crop position + x = random.randint(0, width - crop_width) + y = random.randint(0, height - crop_height) + + # relative crop box + crop_box = (x / width, y / height, (x + crop_width) / width, (y + crop_height) / height) + if target.shape[1:] == (4, 2): + min_xy = np.min(target, axis=1) + max_xy = np.max(target, axis=1) + _target = np.concatenate((min_xy, max_xy), axis=1) + else: + _target = target + + # Crop image and targets + croped_img, crop_boxes = F.crop_detection(img, _target, crop_box) + # hard fallback if no box is kept + if crop_boxes.shape[0] == 0: + return img, target + # clip boxes + return croped_img, np.clip(crop_boxes, 0, 1)
    @@ -402,8 +642,8 @@

    Source code for doctr.transforms.modules.base

    - - + + diff --git a/v0.6.0/_modules/doctr/transforms/modules/tensorflow.html b/v0.6.0/_modules/doctr/transforms/modules/tensorflow.html index 1d192a876b..acbbe96225 100644 --- a/v0.6.0/_modules/doctr/transforms/modules/tensorflow.html +++ b/v0.6.0/_modules/doctr/transforms/modules/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.tensorflow - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.transforms.modules.tensorflow

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
     import random
    +from typing import Any, Callable, Iterable, List, Optional, Tuple, Union
    +
    +import numpy as np
     import tensorflow as tf
    -from typing import List, Any, Tuple, Callable
     
     from doctr.utils.repr import NestedObject
     
    -
    -__all__ = ['Compose', 'Resize', 'Normalize', 'LambdaTransformation', 'ToGray', 'RandomBrightness',
    -           'RandomContrast', 'RandomSaturation', 'RandomHue', 'RandomGamma', 'RandomJpegQuality']
    +from ..functional.tensorflow import _gaussian_filter, random_shadow
    +
    +__all__ = [
    +    "Compose",
    +    "Resize",
    +    "Normalize",
    +    "LambdaTransformation",
    +    "ToGray",
    +    "RandomBrightness",
    +    "RandomContrast",
    +    "RandomSaturation",
    +    "RandomHue",
    +    "RandomGamma",
    +    "RandomJpegQuality",
    +    "GaussianBlur",
    +    "ChannelShuffle",
    +    "GaussianNoise",
    +    "RandomHorizontalFlip",
    +    "RandomShadow",
    +    "RandomResize",
    +]
     
     
     
    -[docs] +[docs] class Compose(NestedObject): """Implements a wrapper that will apply transformations sequentially - Example:: - >>> from doctr.transforms import Compose, Resize - >>> import tensorflow as tf - >>> transfos = Compose([Resize((32, 32))]) - >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import Compose, Resize + >>> transfos = Compose([Resize((32, 32))]) + >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) Args: + ---- transforms: list of transformation modules """ - _children_names: List[str] = ['transforms'] + _children_names: List[str] = ["transforms"] def __init__(self, transforms: List[Callable[[Any], Any]]) -> None: self.transforms = transforms @@ -319,26 +361,27 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class Resize(NestedObject): """Resizes a tensor to a target size - Example:: - >>> from doctr.transforms import Resize - >>> import tensorflow as tf - >>> transfo = Resize((32, 32)) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import Resize + >>> transfo = Resize((32, 32)) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) Args: + ---- output_size: expected output size method: interpolation method preserve_aspect_ratio: if `True`, preserve aspect ratio and pad the rest with zeros symmetric_pad: if `True` while preserving aspect ratio, the padding will be done symmetrically """ + def __init__( self, - output_size: Tuple[int, int], - method: str = 'bilinear', + output_size: Union[int, Tuple[int, int]], + method: str = "bilinear", preserve_aspect_ratio: bool = False, symmetric_pad: bool = False, ) -> None: @@ -346,6 +389,14 @@

    Source code for doctr.transforms.modules.tensorflow

    self.method = method self.preserve_aspect_ratio = preserve_aspect_ratio self.symmetric_pad = symmetric_pad + self.antialias = True + + if isinstance(self.output_size, int): + self.wanted_size = (self.output_size, self.output_size) + elif isinstance(self.output_size, (tuple, list)): + self.wanted_size = self.output_size + else: + raise AssertionError("Output size should be either a list, a tuple or an int") def extra_repr(self) -> str: _repr = f"output_size={self.output_size}, method='{self.method}'" @@ -353,64 +404,106 @@

    Source code for doctr.transforms.modules.tensorflow

    _repr += f", preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}" return _repr - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img = tf.image.resize(img, self.output_size, self.method, self.preserve_aspect_ratio) + def __call__( + self, + img: tf.Tensor, + target: Optional[np.ndarray] = None, + ) -> Union[tf.Tensor, Tuple[tf.Tensor, np.ndarray]]: + input_dtype = img.dtype + self.output_size = ( + (self.output_size, self.output_size) if isinstance(self.output_size, int) else self.output_size + ) + + img = tf.image.resize(img, self.wanted_size, self.method, self.preserve_aspect_ratio, self.antialias) + # It will produce an un-padded resized image, with a side shorter than wanted if we preserve aspect ratio + raw_shape = img.shape[:2] + if self.symmetric_pad: + half_pad = (int((self.output_size[0] - img.shape[0]) / 2), 0) if self.preserve_aspect_ratio: - # pad width - if not self.symmetric_pad: - offset = (0, 0) - elif self.output_size[0] == img.shape[0]: - offset = (0, int((self.output_size[1] - img.shape[1]) / 2)) - else: - offset = (int((self.output_size[0] - img.shape[0]) / 2), 0) - img = tf.image.pad_to_bounding_box(img, *offset, *self.output_size) - return img
    + if isinstance(self.output_size, (tuple, list)): + # In that case we need to pad because we want to enforce both width and height + if not self.symmetric_pad: + half_pad = (0, 0) + elif self.output_size[0] == img.shape[0]: + half_pad = (0, int((self.output_size[1] - img.shape[1]) / 2)) + # Pad image + img = tf.image.pad_to_bounding_box(img, *half_pad, *self.output_size) + + # In case boxes are provided, resize boxes if needed (for detection task if preserve aspect ratio) + if target is not None: + if self.symmetric_pad: + offset = half_pad[0] / img.shape[0], half_pad[1] / img.shape[1] + + if self.preserve_aspect_ratio: + # Get absolute coords + if target.shape[1:] == (4,): + if isinstance(self.output_size, (tuple, list)) and self.symmetric_pad: + target[:, [0, 2]] = offset[1] + target[:, [0, 2]] * raw_shape[1] / img.shape[1] + target[:, [1, 3]] = offset[0] + target[:, [1, 3]] * raw_shape[0] / img.shape[0] + else: + target[:, [0, 2]] *= raw_shape[1] / img.shape[1] + target[:, [1, 3]] *= raw_shape[0] / img.shape[0] + elif target.shape[1:] == (4, 2): + if isinstance(self.output_size, (tuple, list)) and self.symmetric_pad: + target[..., 0] = offset[1] + target[..., 0] * raw_shape[1] / img.shape[1] + target[..., 1] = offset[0] + target[..., 1] * raw_shape[0] / img.shape[0] + else: + target[..., 0] *= raw_shape[1] / img.shape[1] + target[..., 1] *= raw_shape[0] / img.shape[0] + else: + raise AssertionError("Boxes should be in the format (n_boxes, 4, 2) or (n_boxes, 4)") + + return tf.cast(img, dtype=input_dtype), np.clip(target, 0, 1) + + return tf.cast(img, dtype=input_dtype)
    -[docs] +[docs] class Normalize(NestedObject): """Normalize a tensor to a Gaussian distribution for each channel - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import Normalize + >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- mean: average value per channel std: standard deviation per channel """ + def __init__(self, mean: Tuple[float, float, float], std: Tuple[float, float, float]) -> None: - self.mean = tf.constant(mean, dtype=tf.float32) - self.std = tf.constant(std, dtype=tf.float32) + self.mean = tf.constant(mean) + self.std = tf.constant(std) def extra_repr(self) -> str: return f"mean={self.mean.numpy().tolist()}, std={self.std.numpy().tolist()}" def __call__(self, img: tf.Tensor) -> tf.Tensor: - img -= self.mean - img /= self.std + img -= tf.cast(self.mean, dtype=img.dtype) + img /= tf.cast(self.std, dtype=img.dtype) return img
    -[docs] +[docs] class LambdaTransformation(NestedObject): """Normalize a tensor to a Gaussian distribution for each channel - Example:: - >>> from doctr.transforms import LambdaTransformation - >>> import tensorflow as tf - >>> transfo = LambdaTransformation(lambda x: x/ 255.) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import LambdaTransformation + >>> transfo = LambdaTransformation(lambda x: x/ 255.) + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- fn: the function to be applied to the input tensor """ + def __init__(self, fn: Callable[[tf.Tensor], tf.Tensor]) -> None: self.fn = fn @@ -420,37 +513,42 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class ToGray(NestedObject): """Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ToGray() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import ToGray + >>> transfo = ToGray() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) """ + + def __init__(self, num_output_channels: int = 1): + self.num_output_channels = num_output_channels + def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.rgb_to_grayscale(img)
    + img = tf.image.rgb_to_grayscale(img) + return img if self.num_output_channels == 1 else tf.repeat(img, self.num_output_channels, axis=-1)
    -[docs] +[docs] class RandomBrightness(NestedObject): """Randomly adjust brightness of a tensor (batch of images or image) by adding a delta to all pixels - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Brightness() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomBrightness + >>> transfo = RandomBrightness() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] p: probability to apply transformation """ + def __init__(self, max_delta: float = 0.3) -> None: self.max_delta = max_delta @@ -463,21 +561,22 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomContrast(NestedObject): """Randomly adjust contrast of a tensor (batch of images or image) by adjusting each pixel: (img - mean) * contrast_factor + mean. - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Contrast() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomContrast + >>> transfo = RandomContrast() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1) """ - def __init__(self, delta: float = .3) -> None: + + def __init__(self, delta: float = 0.3) -> None: self.delta = delta def extra_repr(self) -> str: @@ -489,21 +588,22 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomSaturation(NestedObject): """Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and increasing saturation by a factor. - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Saturation() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomSaturation + >>> transfo = RandomSaturation() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1) """ - def __init__(self, delta: float = .5) -> None: + + def __init__(self, delta: float = 0.5) -> None: self.delta = delta def extra_repr(self) -> str: @@ -515,19 +615,20 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomHue(NestedObject): """Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Hue() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomHue + >>> transfo = RandomHue() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] """ + def __init__(self, max_delta: float = 0.3) -> None: self.max_delta = max_delta @@ -540,22 +641,23 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomGamma(NestedObject): """randomly performs gamma correction for a tensor (batch of images or image) - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Gamma() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomGamma + >>> transfo = RandomGamma() + >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) Args: + ---- min_gamma: non-negative real number, lower bound for gamma param max_gamma: non-negative real number, upper bound for gamma min_gain: lower bound for constant multiplier max_gain: upper bound for constant multiplier """ + def __init__( self, min_gamma: float = 0.5, @@ -580,20 +682,21 @@

    Source code for doctr.transforms.modules.tensorflow

    -[docs] +[docs] class RandomJpegQuality(NestedObject): """Randomly adjust jpeg quality of a 3 dimensional RGB image - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = JpegQuality() - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + >>> import tensorflow as tf + >>> from doctr.transforms import RandomJpegQuality + >>> transfo = RandomJpegQuality() + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) Args: + ---- min_quality: int between [0, 100] max_quality: int between [0, 100] """ + def __init__(self, min_quality: int = 60, max_quality: int = 100) -> None: self.min_quality = min_quality self.max_quality = max_quality @@ -602,10 +705,224 @@

    Source code for doctr.transforms.modules.tensorflow

    return f"min_quality={self.min_quality}" def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_jpeg_quality( - img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality + return tf.image.random_jpeg_quality(img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality)
    + + + +
    +[docs] +class GaussianBlur(NestedObject): + """Randomly adjust jpeg quality of a 3 dimensional RGB image + + >>> import tensorflow as tf + >>> from doctr.transforms import GaussianBlur + >>> transfo = GaussianBlur(3, (.1, 5)) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + kernel_shape: size of the blurring kernel + std: min and max value of the standard deviation + """ + + def __init__(self, kernel_shape: Union[int, Iterable[int]], std: Tuple[float, float]) -> None: + self.kernel_shape = kernel_shape + self.std = std + + def extra_repr(self) -> str: + return f"kernel_shape={self.kernel_shape}, std={self.std}" + + def __call__(self, img: tf.Tensor) -> tf.Tensor: + return tf.squeeze( + _gaussian_filter( + img[tf.newaxis, ...], + kernel_size=self.kernel_shape, + sigma=random.uniform(self.std[0], self.std[1]), + mode="REFLECT", + ), + axis=0, )
    + + +
    +[docs] +class ChannelShuffle(NestedObject): + """Randomly shuffle channel order of a given image""" + + def __init__(self): + pass + + def __call__(self, img: tf.Tensor) -> tf.Tensor: + return tf.transpose(tf.random.shuffle(tf.transpose(img, perm=[2, 0, 1])), perm=[1, 2, 0])
    + + + +
    +[docs] +class GaussianNoise(NestedObject): + """Adds Gaussian Noise to the input tensor + + >>> import tensorflow as tf + >>> from doctr.transforms import GaussianNoise + >>> transfo = GaussianNoise(0., 1.) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + mean : mean of the gaussian distribution + std : std of the gaussian distribution + """ + + def __init__(self, mean: float = 0.0, std: float = 1.0) -> None: + super().__init__() + self.std = std + self.mean = mean + + def __call__(self, x: tf.Tensor) -> tf.Tensor: + # Reshape the distribution + noise = self.mean + 2 * self.std * tf.random.uniform(x.shape) - self.std + if x.dtype == tf.uint8: + return tf.cast( + tf.clip_by_value(tf.math.round(tf.cast(x, dtype=tf.float32) + 255 * noise), 0, 255), dtype=tf.uint8 + ) + else: + return tf.cast(tf.clip_by_value(x + noise, 0, 1), dtype=x.dtype) + + def extra_repr(self) -> str: + return f"mean={self.mean}, std={self.std}"
    + + + +
    +[docs] +class RandomHorizontalFlip(NestedObject): + """Adds random horizontal flip to the input tensor/np.ndarray + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomHorizontalFlip + >>> transfo = RandomHorizontalFlip(p=0.5) + >>> image = tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1) + >>> target = np.array([[0.1, 0.1, 0.4, 0.5] ], dtype= np.float32) + >>> out = transfo(image, target) + + Args: + ---- + p : probability of Horizontal Flip + """ + + def __init__(self, p: float) -> None: + super().__init__() + self.p = p + + def __call__(self, img: Union[tf.Tensor, np.ndarray], target: np.ndarray) -> Tuple[tf.Tensor, np.ndarray]: + if np.random.rand(1) <= self.p: + _img = tf.image.flip_left_right(img) + _target = target.copy() + # Changing the relative bbox coordinates + if target.shape[1:] == (4,): + _target[:, ::2] = 1 - target[:, [2, 0]] + else: + _target[..., 0] = 1 - target[..., 0] + return _img, _target + return img, target
    + + + +
    +[docs] +class RandomShadow(NestedObject): + """Adds random shade to the input image + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomShadow + >>> transfo = RandomShadow(0., 1.) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + opacity_range : minimum and maximum opacity of the shade + """ + + def __init__(self, opacity_range: Optional[Tuple[float, float]] = None) -> None: + super().__init__() + self.opacity_range = opacity_range if isinstance(opacity_range, tuple) else (0.2, 0.8) + + def __call__(self, x: tf.Tensor) -> tf.Tensor: + # Reshape the distribution + if x.dtype == tf.uint8: + return tf.cast( + tf.clip_by_value( + tf.math.round(255 * random_shadow(tf.cast(x, dtype=tf.float32) / 255, self.opacity_range)), + 0, + 255, + ), + dtype=tf.uint8, + ) + else: + return tf.clip_by_value(random_shadow(x, self.opacity_range), 0, 1) + + def extra_repr(self) -> str: + return f"opacity_range={self.opacity_range}"
    + + + +
    +[docs] +class RandomResize(NestedObject): + """Randomly resize the input image and align corresponding targets + + >>> import tensorflow as tf + >>> from doctr.transforms import RandomResize + >>> transfo = RandomResize((0.3, 0.9), preserve_aspect_ratio=True, symmetric_pad=True, p=0.5) + >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) + + Args: + ---- + scale_range: range of the resizing factor for width and height (independently) + preserve_aspect_ratio: whether to preserve the aspect ratio of the image, + given a float value, the aspect ratio will be preserved with this probability + symmetric_pad: whether to symmetrically pad the image, + given a float value, the symmetric padding will be applied with this probability + p: probability to apply the transformation + """ + + def __init__( + self, + scale_range: Tuple[float, float] = (0.3, 0.9), + preserve_aspect_ratio: Union[bool, float] = False, + symmetric_pad: Union[bool, float] = False, + p: float = 0.5, + ): + super().__init__() + self.scale_range = scale_range + self.preserve_aspect_ratio = preserve_aspect_ratio + self.symmetric_pad = symmetric_pad + self.p = p + self._resize = Resize + + def __call__(self, img: tf.Tensor, target: np.ndarray) -> Tuple[tf.Tensor, np.ndarray]: + if np.random.rand(1) <= self.p: + scale_h = random.uniform(*self.scale_range) + scale_w = random.uniform(*self.scale_range) + new_size = (int(img.shape[-3] * scale_h), int(img.shape[-2] * scale_w)) + + _img, _target = self._resize( + new_size, + preserve_aspect_ratio=self.preserve_aspect_ratio + if isinstance(self.preserve_aspect_ratio, bool) + else bool(np.random.rand(1) <= self.symmetric_pad), + symmetric_pad=self.symmetric_pad + if isinstance(self.symmetric_pad, bool) + else bool(np.random.rand(1) <= self.symmetric_pad), + )(img, target) + + return _img, _target + return img, target + + def extra_repr(self) -> str: + return f"scale_range={self.scale_range}, preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}, p={self.p}" # noqa: E501
    +
    @@ -638,8 +955,8 @@

    Source code for doctr.transforms.modules.tensorflow

    - +
    + diff --git a/v0.6.0/_modules/doctr/utils/metrics.html b/v0.6.0/_modules/doctr/utils/metrics.html index 460c64a385..8a37d5949a 100644 --- a/v0.6.0/_modules/doctr/utils/metrics.html +++ b/v0.6.0/_modules/doctr/utils/metrics.html @@ -13,7 +13,7 @@ - + doctr.utils.metrics - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.utils.metrics

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
    +
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +from typing import Dict, List, Optional, Tuple
     
     import numpy as np
    -import cv2
    -from typing import List, Tuple, Dict, Optional
    -from unidecode import unidecode
    +from anyascii import anyascii
     from scipy.optimize import linear_sum_assignment
    -from doctr.utils.geometry import rbbox_to_polygon
    +from shapely.geometry import Polygon
     
    -__all__ = ['TextMatch', 'box_iou', 'box_ioa', 'mask_iou', 'rbox_to_mask',
    -           'nms', 'LocalizationConfusion', 'OCRMetric']
    +__all__ = [
    +    "TextMatch",
    +    "box_iou",
    +    "polygon_iou",
    +    "nms",
    +    "LocalizationConfusion",
    +    "OCRMetric",
    +    "DetectionMetric",
    +]
     
     
     def string_match(word1: str, word2: str) -> Tuple[bool, bool, bool, bool]:
    -    """Perform string comparison with multiple levels of tolerance
    +    """Performs string comparison with multiple levels of tolerance
     
         Args:
    +    ----
             word1: a string
             word2: another string
     
         Returns:
    +    -------
             a tuple with booleans specifying respectively whether the raw strings, their lower-case counterparts, their
    -            unidecode counterparts and their lower-case unidecode counterparts match
    +            anyascii counterparts and their lower-case anyascii counterparts match
         """
    -    raw_match = (word1 == word2)
    -    caseless_match = (word1.lower() == word2.lower())
    -    unidecode_match = (unidecode(word1) == unidecode(word2))
    +    raw_match = word1 == word2
    +    caseless_match = word1.lower() == word2.lower()
    +    anyascii_match = anyascii(word1) == anyascii(word2)
     
         # Warning: the order is important here otherwise the pair ("EUR", "€") cannot be matched
    -    unicase_match = (unidecode(word1).lower() == unidecode(word2).lower())
    +    unicase_match = anyascii(word1).lower() == anyascii(word2).lower()
     
    -    return raw_match, caseless_match, unidecode_match, unicase_match
    +    return raw_match, caseless_match, anyascii_match, unicase_match
     
     
     
    -[docs] +[docs] class TextMatch: - """Implements text match metric (word-level accuracy) for recognition task. + r"""Implements text match metric (word-level accuracy) for recognition task. The raw aggregated metric is computed as follows: .. math:: - \\forall X, Y \\in \\mathcal{W}^N, - TextMatch(X, Y) = \\frac{1}{N} \\sum\\limits_{i=1}^N f_{Y_i}(X_i) + \forall X, Y \in \mathcal{W}^N, + TextMatch(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N f_{Y_i}(X_i) with the indicator function :math:`f_{a}` defined as: .. math:: - \\forall a, x \\in \\mathcal{W}, - f_a(x) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } x = a \\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{W}` is the set of all possible character sequences, + \forall a, x \in \mathcal{W}, + f_a(x) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } x = a \\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{W}` is the set of all possible character sequences, :math:`N` is a strictly positive integer. - Example:: - >>> from doctr.utils import TextMatch - >>> metric = TextMatch() - >>> metric.update(['Hello', 'world'], ['hello', 'world']) - >>> metric.summary() + >>> from doctr.utils import TextMatch + >>> metric = TextMatch() + >>> metric.update(['Hello', 'world'], ['hello', 'world']) + >>> metric.summary() """ def __init__(self) -> None: self.reset() +
    +[docs] def update( self, gt: List[str], @@ -354,29 +386,32 @@

    Source code for doctr.utils.metrics

             """Update the state of the metric with new predictions
     
             Args:
    +        ----
                 gt: list of groung-truth character sequences
    -            pred: list of predicted character sequences"""
    -
    +            pred: list of predicted character sequences
    +        """
             if len(gt) != len(pred):
                 raise AssertionError("prediction size does not match with ground-truth labels size")
     
             for gt_word, pred_word in zip(gt, pred):
    -            _raw, _caseless, _unidecode, _unicase = string_match(gt_word, pred_word)
    +            _raw, _caseless, _anyascii, _unicase = string_match(gt_word, pred_word)
                 self.raw += int(_raw)
                 self.caseless += int(_caseless)
    -            self.unidecode += int(_unidecode)
    +            self.anyascii += int(_anyascii)
                 self.unicase += int(_unicase)
     
    -        self.total += len(gt)
    +        self.total += len(gt)
    +
    -[docs] +[docs] def summary(self) -> Dict[str, float]: """Computes the aggregated metrics - Returns: - a dictionary with the exact match score for the raw data, its lower-case counterpart, its unidecode - counterpart and its lower-case unidecode counterpart + Returns + ------- + a dictionary with the exact match score for the raw data, its lower-case counterpart, its anyascii + counterpart and its lower-case anyascii counterpart """ if self.total == 0: raise AssertionError("you need to update the metric before getting the summary") @@ -384,7 +419,7 @@

    Source code for doctr.utils.metrics

             return dict(
                 raw=self.raw / self.total,
                 caseless=self.caseless / self.total,
    -            unidecode=self.unidecode / self.total,
    +            anyascii=self.anyascii / self.total,
                 unicase=self.unicase / self.total,
             )
    @@ -392,23 +427,25 @@

    Source code for doctr.utils.metrics

         def reset(self) -> None:
             self.raw = 0
             self.caseless = 0
    -        self.unidecode = 0
    +        self.anyascii = 0
             self.unicase = 0
             self.total = 0
    def box_iou(boxes_1: np.ndarray, boxes_2: np.ndarray) -> np.ndarray: - """Compute the IoU between two sets of bounding boxes + """Computes the IoU between two sets of bounding boxes Args: + ---- boxes_1: bounding boxes of shape (N, 4) in format (xmin, ymin, xmax, ymax) boxes_2: bounding boxes of shape (M, 4) in format (xmin, ymin, xmax, ymax) + Returns: + ------- the IoU matrix of shape (N, M) """ - - iou_mat = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32) + iou_mat: np.ndarray = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32) if boxes_1.shape[0] > 0 and boxes_2.shape[0] > 0: l1, t1, r1, b1 = np.split(boxes_1, 4, axis=1) @@ -419,107 +456,54 @@

    Source code for doctr.utils.metrics

             right = np.minimum(r1, r2.T)
             bot = np.minimum(b1, b2.T)
     
    -        intersection = np.clip(right - left, 0, np.Inf) * np.clip(bot - top, 0, np.Inf)
    +        intersection = np.clip(right - left, 0, np.inf) * np.clip(bot - top, 0, np.inf)
             union = (r1 - l1) * (b1 - t1) + ((r2 - l2) * (b2 - t2)).T - intersection
             iou_mat = intersection / union
     
         return iou_mat
     
     
    -def box_ioa(boxes_1: np.ndarray, boxes_2: np.ndarray) -> np.ndarray:
    -    """Compute the IoA (intersection over area) between two sets of bounding boxes:
    -    ioa(i, j) = inter(i, j) / area(i)
    -
    -    Args:
    -        boxes_1: bounding boxes of shape (N, 4) in format (xmin, ymin, xmax, ymax)
    -        boxes_2: bounding boxes of shape (M, 4) in format (xmin, ymin, xmax, ymax)
    -    Returns:
    -        the IoA matrix of shape (N, M)
    -    """
    -
    -    ioa_mat = np.zeros((boxes_1.shape[0], boxes_2.shape[0]), dtype=np.float32)
    -
    -    if boxes_1.shape[0] > 0 and boxes_2.shape[0] > 0:
    -        l1, t1, r1, b1 = np.split(boxes_1, 4, axis=1)
    -        l2, t2, r2, b2 = np.split(boxes_2, 4, axis=1)
    -
    -        left = np.maximum(l1, l2.T)
    -        top = np.maximum(t1, t2.T)
    -        right = np.minimum(r1, r2.T)
    -        bot = np.minimum(b1, b2.T)
    -
    -        intersection = np.clip(right - left, 0, np.Inf) * np.clip(bot - top, 0, np.Inf)
    -        area = (r1 - l1) * (b1 - t1)
    -        ioa_mat = intersection / area
    -
    -    return ioa_mat
    -
    -
    -def mask_iou(masks_1: np.ndarray, masks_2: np.ndarray) -> np.ndarray:
    -    """Compute the IoU between two sets of boolean masks
    +def polygon_iou(polys_1: np.ndarray, polys_2: np.ndarray) -> np.ndarray:
    +    """Computes the IoU between two sets of rotated bounding boxes
     
         Args:
    -        masks_1: boolean masks of shape (N, H, W)
    -        masks_2: boolean masks of shape (M, H, W)
    +    ----
    +        polys_1: rotated bounding boxes of shape (N, 4, 2)
    +        polys_2: rotated bounding boxes of shape (M, 4, 2)
    +        mask_shape: spatial shape of the intermediate masks
    +        use_broadcasting: if set to True, leverage broadcasting speedup by consuming more memory
     
         Returns:
    +    -------
             the IoU matrix of shape (N, M)
         """
    +    if polys_1.ndim != 3 or polys_2.ndim != 3:
    +        raise AssertionError("expects boxes to be in format (N, 4, 2)")
     
    -    if masks_1.shape[1:] != masks_2.shape[1:]:
    -        raise AssertionError("both boolean masks should have the same spatial shape")
    +    iou_mat = np.zeros((polys_1.shape[0], polys_2.shape[0]), dtype=np.float32)
     
    -    iou_mat = np.zeros((masks_1.shape[0], masks_2.shape[0]), dtype=np.float32)
    +    shapely_polys_1 = [Polygon(poly) for poly in polys_1]
    +    shapely_polys_2 = [Polygon(poly) for poly in polys_2]
     
    -    if masks_1.shape[0] > 0 and masks_2.shape[0] > 0:
    -        intersection = np.logical_and(masks_1[:, None, ...], masks_2[None, ...])
    -        union = np.logical_or(masks_1[:, None, ...], masks_2[None, ...])
    -        axes = tuple(range(2, masks_1.ndim + 1))
    -        iou_mat = intersection.sum(axis=axes) / union.sum(axis=axes)
    +    for i, poly1 in enumerate(shapely_polys_1):
    +        for j, poly2 in enumerate(shapely_polys_2):
    +            intersection_area = poly1.intersection(poly2).area
    +            union_area = poly1.area + poly2.area - intersection_area
    +            iou_mat[i, j] = intersection_area / union_area
     
         return iou_mat
     
     
    -def rbox_to_mask(boxes: np.ndarray, shape: Tuple[int, int]) -> np.ndarray:
    -    """Convert boxes to masks
    -
    -    Args:
    -        boxes: rotated bounding boxes of shape (N, 5) in format (x, y, w, h, alpha)
    -        shape: spatial shapes of the output masks
    -
    -    Returns:
    -        the boolean masks of shape (N, H, W)
    -    """
    -
    -    masks = np.zeros((boxes.shape[0], *shape), dtype=np.uint8)
    -
    -    if boxes.shape[0] > 0:
    -        # Get absolute coordinates
    -        if boxes.dtype != np.int:
    -            abs_boxes = boxes.copy()
    -            abs_boxes[:, [0, 2]] = abs_boxes[:, [0, 2]] * shape[1]
    -            abs_boxes[:, [1, 3]] = abs_boxes[:, [1, 3]] * shape[0]
    -            abs_boxes = abs_boxes.round().astype(np.int)
    -        else:
    -            abs_boxes = boxes
    -            abs_boxes[:, 2:] = abs_boxes[:, 2:] + 1
    -
    -        # TODO: optimize slicing to improve vectorization
    -        for idx, _box in enumerate(abs_boxes):
    -            box = rbbox_to_polygon(_box)
    -            cv2.fillPoly(masks[idx], [np.array(box, np.int32)], 1)
    -
    -    return masks.astype(bool)
    -
    -
    -def nms(boxes: np.ndarray, thresh: float = .5) -> List[int]:
    +def nms(boxes: np.ndarray, thresh: float = 0.5) -> List[int]:
         """Perform non-max suppression, borrowed from <https://github.com/rbgirshick/fast-rcnn>`_.
     
         Args:
    +    ----
             boxes: np array of straight boxes: (*, 5), (xmin, ymin, xmax, ymax, score)
             thresh: iou threshold to perform box suppression.
     
         Returns:
    +    -------
             A list of box indexes to keep
         """
         x1 = boxes[:, 0]
    @@ -551,66 +535,71 @@ 

    Source code for doctr.utils.metrics

     
     
     
    -[docs] +[docs] class LocalizationConfusion: - """Implements common confusion metrics and mean IoU for localization evaluation. + r"""Implements common confusion metrics and mean IoU for localization evaluation. The aggregated metrics are computed as follows: .. math:: - \\forall Y \\in \\mathcal{B}^N, \\forall X \\in \\mathcal{B}^M, \\\\ - Recall(X, Y) = \\frac{1}{N} \\sum\\limits_{i=1}^N g_{X}(Y_i) \\\\ - Precision(X, Y) = \\frac{1}{M} \\sum\\limits_{i=1}^N g_{X}(Y_i) \\\\ - meanIoU(X, Y) = \\frac{1}{M} \\sum\\limits_{i=1}^M \\max\\limits_{j \\in [1, N]} IoU(X_i, Y_j) + \forall Y \in \mathcal{B}^N, \forall X \in \mathcal{B}^M, \\ + Recall(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N g_{X}(Y_i) \\ + Precision(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M g_{X}(Y_i) \\ + meanIoU(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(X_i, Y_j) with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and :math:`y`, and the function :math:`g_{X}` defined as: .. math:: - \\forall y \\in \\mathcal{B}, - g_X(y) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } y\\mbox{ has been assigned to any }(X_i)_i\\mbox{ with an }IoU \\geq 0.5 \\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{B}` is the set of possible bounding boxes, + \forall y \in \mathcal{B}, + g_X(y) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } y\mbox{ has been assigned to any }(X_i)_i\mbox{ with an }IoU \geq 0.5 \\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. - Example:: - >>> import numpy as np - >>> from doctr.utils import LocalizationConfusion - >>> metric = LocalizationConfusion(iou_thresh=0.5) - >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]])) - >>> metric.summary() + >>> import numpy as np + >>> from doctr.utils import LocalizationConfusion + >>> metric = LocalizationConfusion(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]])) + >>> metric.summary() Args: + ---- iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format """ def __init__( self, iou_thresh: float = 0.5, - rotated_bbox: bool = False, - mask_shape: Tuple[int, int] = (1024, 1024), + use_polygons: bool = False, ) -> None: self.iou_thresh = iou_thresh - self.rotated_bbox = rotated_bbox - self.mask_shape = mask_shape + self.use_polygons = use_polygons self.reset() +
    +[docs] def update(self, gts: np.ndarray, preds: np.ndarray) -> None: + """Updates the metric + Args: + ---- + gts: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones + preds: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones + """ if preds.shape[0] > 0: # Compute IoU - if self.rotated_bbox: - mask_gts = rbox_to_mask(gts, shape=self.mask_shape) - mask_preds = rbox_to_mask(preds, shape=self.mask_shape) - iou_mat = mask_iou(mask_gts, mask_preds) + if self.use_polygons: + iou_mat = polygon_iou(gts, preds) else: iou_mat = box_iou(gts, preds) - self.tot_iou += float(iou_mat.max(axis=1).sum()) + self.tot_iou += float(iou_mat.max(axis=0).sum()) # Assign pairs gt_indices, pred_indices = linear_sum_assignment(-iou_mat) @@ -618,17 +607,18 @@

    Source code for doctr.utils.metrics

     
             # Update counts
             self.num_gts += gts.shape[0]
    -        self.num_preds += preds.shape[0]
    +        self.num_preds += preds.shape[0]
    +
    -[docs] +[docs] def summary(self) -> Tuple[Optional[float], Optional[float], Optional[float]]: """Computes the aggregated metrics - Returns: + Returns + ------- a tuple with the recall, precision and meanIoU scores """ - # Recall recall = self.matches / self.num_gts if self.num_gts > 0 else None @@ -636,7 +626,7 @@

    Source code for doctr.utils.metrics

             precision = self.matches / self.num_preds if self.num_preds > 0 else None
     
             # mean IoU
    -        mean_iou = self.tot_iou / self.num_preds if self.num_preds > 0 else None
    +        mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None
     
             return recall, precision, mean_iou
    @@ -645,64 +635,65 @@

    Source code for doctr.utils.metrics

             self.num_gts = 0
             self.num_preds = 0
             self.matches = 0
    -        self.tot_iou = 0.
    + self.tot_iou = 0.0
    -[docs] +[docs] class OCRMetric: - """Implements end-to-end OCR metric. + r"""Implements an end-to-end OCR metric. The aggregated metrics are computed as follows: .. math:: - \\forall (B, L) \\in \\mathcal{B}^N \\times \\mathcal{L}^N, - \\forall (\\hat{B}, \\hat{L}) \\in \\mathcal{B}^M \\times \\mathcal{L}^M, \\\\ - Recall(B, \\hat{B}, L, \\hat{L}) = \\frac{1}{N} \\sum\\limits_{i=1}^N h_{B,L}(\\hat{B}_i, \\hat{L}_i) \\\\ - Precision(B, \\hat{B}, L, \\hat{L}) = \\frac{1}{M} \\sum\\limits_{i=1}^N h_{B,L}(\\hat{B}_i, \\hat{L}_i) \\\\ - meanIoU(B, \\hat{B}) = \\frac{1}{M} \\sum\\limits_{i=1}^M \\max\\limits_{j \\in [1, N]} IoU(\\hat{B}_i, B_j) + \forall (B, L) \in \mathcal{B}^N \times \mathcal{L}^N, + \forall (\hat{B}, \hat{L}) \in \mathcal{B}^M \times \mathcal{L}^M, \\ + Recall(B, \hat{B}, L, \hat{L}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ + Precision(B, \hat{B}, L, \hat{L}) = \frac{1}{M} \sum\limits_{i=1}^M h_{B,L}(\hat{B}_i, \hat{L}_i) \\ + meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j) with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and :math:`y`, and the function :math:`h_{B, L}` defined as: .. math:: - \\forall (b, l) \\in \\mathcal{B} \\times \\mathcal{L}, - h_{B,L}(b, l) = \\left\\{ - \\begin{array}{ll} - 1 & \\mbox{if } b\\mbox{ has been assigned to a given }B_j\\mbox{ with an } \\\\ - & IoU \\geq 0.5 \\mbox{ and that for this assignment, } l = L_j\\\\ - 0 & \\mbox{otherwise.} - \\end{array} - \\right. - - where :math:`\\mathcal{B}` is the set of possible bounding boxes, - :math:`\\mathcal{L}` is the set of possible character sequences, + \forall (b, l) \in \mathcal{B} \times \mathcal{L}, + h_{B,L}(b, l) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ + & IoU \geq 0.5 \mbox{ and that for this assignment, } l = L_j\\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, + :math:`\mathcal{L}` is the set of possible character sequences, :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. - Example:: - >>> import numpy as np - >>> from doctr.utils import OCRMetric - >>> metric = OCRMetric(iou_thresh=0.5) - >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), - ['hello'], ['hello', 'world']) - >>> metric.summary() + >>> import numpy as np + >>> from doctr.utils import OCRMetric + >>> metric = OCRMetric(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), + >>> ['hello'], ['hello', 'world']) + >>> metric.summary() Args: + ---- iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format """ def __init__( self, iou_thresh: float = 0.5, - rotated_bbox: bool = False, - mask_shape: Tuple[int, int] = (1024, 1024), + use_polygons: bool = False, ) -> None: self.iou_thresh = iou_thresh - self.rotated_bbox = rotated_bbox - self.mask_shape = mask_shape + self.use_polygons = use_polygons self.reset() +
    +[docs] def update( self, gt_boxes: np.ndarray, @@ -710,50 +701,58 @@

    Source code for doctr.utils.metrics

             gt_labels: List[str],
             pred_labels: List[str],
         ) -> None:
    +        """Updates the metric
     
    +        Args:
    +        ----
    +            gt_boxes: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones
    +            pred_boxes: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones
    +            gt_labels: a list of N string labels
    +            pred_labels: a list of M string labels
    +        """
             if gt_boxes.shape[0] != len(gt_labels) or pred_boxes.shape[0] != len(pred_labels):
    -            raise AssertionError("there should be the same number of boxes and string both for the ground truth "
    -                                 "and the predictions")
    +            raise AssertionError(
    +                "there should be the same number of boxes and string both for the ground truth and the predictions"
    +            )
     
             # Compute IoU
             if pred_boxes.shape[0] > 0:
    -            if self.rotated_bbox:
    -                mask_gts = rbox_to_mask(gt_boxes, shape=self.mask_shape)
    -                mask_preds = rbox_to_mask(pred_boxes, shape=self.mask_shape)
    -                iou_mat = mask_iou(mask_gts, mask_preds)
    +            if self.use_polygons:
    +                iou_mat = polygon_iou(gt_boxes, pred_boxes)
                 else:
                     iou_mat = box_iou(gt_boxes, pred_boxes)
     
    -            self.tot_iou += float(iou_mat.max(axis=1).sum())
    +            self.tot_iou += float(iou_mat.max(axis=0).sum())
     
                 # Assign pairs
                 gt_indices, pred_indices = linear_sum_assignment(-iou_mat)
                 is_kept = iou_mat[gt_indices, pred_indices] >= self.iou_thresh
                 # String comparison
                 for gt_idx, pred_idx in zip(gt_indices[is_kept], pred_indices[is_kept]):
    -                _raw, _caseless, _unidecode, _unicase = string_match(gt_labels[gt_idx], pred_labels[pred_idx])
    +                _raw, _caseless, _anyascii, _unicase = string_match(gt_labels[gt_idx], pred_labels[pred_idx])
                     self.raw_matches += int(_raw)
                     self.caseless_matches += int(_caseless)
    -                self.unidecode_matches += int(_unidecode)
    +                self.anyascii_matches += int(_anyascii)
                     self.unicase_matches += int(_unicase)
     
             self.num_gts += gt_boxes.shape[0]
    -        self.num_preds += pred_boxes.shape[0]
    +        self.num_preds += pred_boxes.shape[0]
    +
    -[docs] +[docs] def summary(self) -> Tuple[Dict[str, Optional[float]], Dict[str, Optional[float]], Optional[float]]: """Computes the aggregated metrics - Returns: - a tuple with the recall & precision for each string comparison flexibility and the mean IoU + Returns + ------- + a tuple with the recall & precision for each string comparison and the mean IoU """ - # Recall recall = dict( raw=self.raw_matches / self.num_gts if self.num_gts > 0 else None, caseless=self.caseless_matches / self.num_gts if self.num_gts > 0 else None, - unidecode=self.unidecode_matches / self.num_gts if self.num_gts > 0 else None, + anyascii=self.anyascii_matches / self.num_gts if self.num_gts > 0 else None, unicase=self.unicase_matches / self.num_gts if self.num_gts > 0 else None, ) @@ -761,12 +760,12 @@

    Source code for doctr.utils.metrics

             precision = dict(
                 raw=self.raw_matches / self.num_preds if self.num_preds > 0 else None,
                 caseless=self.caseless_matches / self.num_preds if self.num_preds > 0 else None,
    -            unidecode=self.unidecode_matches / self.num_preds if self.num_preds > 0 else None,
    +            anyascii=self.anyascii_matches / self.num_preds if self.num_preds > 0 else None,
                 unicase=self.unicase_matches / self.num_preds if self.num_preds > 0 else None,
             )
     
             # mean IoU (overall detected boxes)
    -        mean_iou = self.tot_iou / self.num_preds if self.num_preds > 0 else None
    +        mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None
     
             return recall, precision, mean_iou
    @@ -774,12 +773,136 @@

    Source code for doctr.utils.metrics

         def reset(self) -> None:
             self.num_gts = 0
             self.num_preds = 0
    -        self.tot_iou = 0.
    +        self.tot_iou = 0.0
             self.raw_matches = 0
             self.caseless_matches = 0
    -        self.unidecode_matches = 0
    +        self.anyascii_matches = 0
             self.unicase_matches = 0
    + + +
    +[docs] +class DetectionMetric: + r"""Implements an object detection metric. + + The aggregated metrics are computed as follows: + + .. math:: + \forall (B, C) \in \mathcal{B}^N \times \mathcal{C}^N, + \forall (\hat{B}, \hat{C}) \in \mathcal{B}^M \times \mathcal{C}^M, \\ + Recall(B, \hat{B}, C, \hat{C}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,C}(\hat{B}_i, \hat{C}_i) \\ + Precision(B, \hat{B}, C, \hat{C}) = \frac{1}{M} \sum\limits_{i=1}^M h_{B,C}(\hat{B}_i, \hat{C}_i) \\ + meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j) + + with the function :math:`IoU(x, y)` being the Intersection over Union between bounding boxes :math:`x` and + :math:`y`, and the function :math:`h_{B, C}` defined as: + + .. math:: + \forall (b, c) \in \mathcal{B} \times \mathcal{C}, + h_{B,C}(b, c) = \left\{ + \begin{array}{ll} + 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ + & IoU \geq 0.5 \mbox{ and that for this assignment, } c = C_j\\ + 0 & \mbox{otherwise.} + \end{array} + \right. + + where :math:`\mathcal{B}` is the set of possible bounding boxes, + :math:`\mathcal{C}` is the set of possible class indices, + :math:`N` (number of ground truths) and :math:`M` (number of predictions) are strictly positive integers. + + >>> import numpy as np + >>> from doctr.utils import DetectionMetric + >>> metric = DetectionMetric(iou_thresh=0.5) + >>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]), + >>> np.zeros(1, dtype=np.int64), np.array([0, 1], dtype=np.int64)) + >>> metric.summary() + + Args: + ---- + iou_thresh: minimum IoU to consider a pair of prediction and ground truth as a match + use_polygons: if set to True, predictions and targets will be expected to have rotated format + """ + + def __init__( + self, + iou_thresh: float = 0.5, + use_polygons: bool = False, + ) -> None: + self.iou_thresh = iou_thresh + self.use_polygons = use_polygons + self.reset() + +
    +[docs] + def update( + self, + gt_boxes: np.ndarray, + pred_boxes: np.ndarray, + gt_labels: np.ndarray, + pred_labels: np.ndarray, + ) -> None: + """Updates the metric + + Args: + ---- + gt_boxes: a set of relative bounding boxes either of shape (N, 4) or (N, 5) if they are rotated ones + pred_boxes: a set of relative bounding boxes either of shape (M, 4) or (M, 5) if they are rotated ones + gt_labels: an array of class indices of shape (N,) + pred_labels: an array of class indices of shape (M,) + """ + if gt_boxes.shape[0] != gt_labels.shape[0] or pred_boxes.shape[0] != pred_labels.shape[0]: + raise AssertionError( + "there should be the same number of boxes and string both for the ground truth and the predictions" + ) + + # Compute IoU + if pred_boxes.shape[0] > 0: + if self.use_polygons: + iou_mat = polygon_iou(gt_boxes, pred_boxes) + else: + iou_mat = box_iou(gt_boxes, pred_boxes) + + self.tot_iou += float(iou_mat.max(axis=0).sum()) + + # Assign pairs + gt_indices, pred_indices = linear_sum_assignment(-iou_mat) + is_kept = iou_mat[gt_indices, pred_indices] >= self.iou_thresh + # Category comparison + self.num_matches += int((gt_labels[gt_indices[is_kept]] == pred_labels[pred_indices[is_kept]]).sum()) + + self.num_gts += gt_boxes.shape[0] + self.num_preds += pred_boxes.shape[0]
    + + +
    +[docs] + def summary(self) -> Tuple[Optional[float], Optional[float], Optional[float]]: + """Computes the aggregated metrics + + Returns + ------- + a tuple with the recall & precision for each class prediction and the mean IoU + """ + # Recall + recall = self.num_matches / self.num_gts if self.num_gts > 0 else None + + # Precision + precision = self.num_matches / self.num_preds if self.num_preds > 0 else None + + # mean IoU (overall detected boxes) + mean_iou = round(self.tot_iou / self.num_preds, 2) if self.num_preds > 0 else None + + return recall, precision, mean_iou
    + + + def reset(self) -> None: + self.num_gts = 0 + self.num_preds = 0 + self.tot_iou = 0.0 + self.num_matches = 0
    +
    @@ -812,8 +935,8 @@

    Source code for doctr.utils.metrics

           
         
       
    - - + + diff --git a/v0.6.0/_modules/doctr/utils/visualization.html b/v0.6.0/_modules/doctr/utils/visualization.html index 8e7dcca811..c818be6d7b 100644 --- a/v0.6.0/_modules/doctr/utils/visualization.html +++ b/v0.6.0/_modules/doctr/utils/visualization.html @@ -13,7 +13,7 @@ - + doctr.utils.visualization - docTR documentation @@ -225,20 +225,42 @@

    Source code for doctr.utils.visualization

    -# Copyright (C) 2021, Mindee.
    +# Copyright (C) 2021-2024, Mindee.
     
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    +# This program is licensed under the Apache License 2.0.
    +# See LICENSE or go to <https://opensource.org/licenses/Apache-2.0> for full license details.
    +import colorsys
    +from copy import deepcopy
    +from typing import Any, Dict, List, Optional, Tuple, Union
     
    -import matplotlib.pyplot as plt
    -from matplotlib.figure import Figure
    +import cv2
     import matplotlib.patches as patches
    -import mplcursors
    -from PIL import ImageFont, ImageDraw, Image
    +import matplotlib.pyplot as plt
     import numpy as np
    -import cv2
    -from typing import Tuple, List, Dict, Any, Union
    +from matplotlib.figure import Figure
     
    -from .common_types import BoundingBox, RotatedBbox
    +from .common_types import BoundingBox, Polygon4P
     
    -__all__ = ['visualize_page', 'synthetize_page']
    +__all__ = ["visualize_page", "visualize_kie_page", "draw_boxes"]
     
     
    -def create_rect_patch(
    -    geometry: Union[BoundingBox, RotatedBbox],
    -    label: str,
    +def rect_patch(
    +    geometry: BoundingBox,
         page_dimensions: Tuple[int, int],
    -    color: Tuple[int, int, int],
    +    label: Optional[str] = None,
    +    color: Tuple[float, float, float] = (0, 0, 0),
         alpha: float = 0.3,
         linewidth: int = 2,
         fill: bool = True,
    -) -> patches.Patch:
    -    """Create a matplotlib patch (rectangle) bounding the element
    +    preserve_aspect_ratio: bool = False,
    +) -> patches.Rectangle:
    +    """Create a matplotlib rectangular patch for the element
     
         Args:
    +    ----
             geometry: bounding box of the element
    +        page_dimensions: dimensions of the Page in format (height, width)
             label: label to display when hovered
    -        page_dimensions: dimensions of the Page
             color: color to draw box
             alpha: opacity parameter to fill the boxes, 0 = transparent
             linewidth: line width
    +        fill: whether the patch should be filled
    +        preserve_aspect_ratio: pass True if you passed True to the predictor
     
         Returns:
    +    -------
             a rectangular Patch
         """
    +    if len(geometry) != 2 or any(not isinstance(elt, tuple) or len(elt) != 2 for elt in geometry):
    +        raise ValueError("invalid geometry format")
    +
    +    # Unpack
         height, width = page_dimensions
    -    if len(geometry) == 5:
    -        x, y, w, h, a = geometry  # type: ignore[misc]
    -        x, w = x * width, w * width
    -        y, h = y * height, h * height
    -        points = cv2.boxPoints(((x, y), (w, h), a))
    -        return patches.Polygon(
    -            points,
    -            fill=fill,
    -            linewidth=linewidth,
    -            edgecolor=(*color, alpha),
    -            facecolor=(*color, alpha),
    -            label=label
    -        )
    -    else:
    -        (xmin, ymin), (xmax, ymax) = geometry  # type: ignore[misc]
    -        xmin, xmax = xmin * width, xmax * width
    -        ymin, ymax = ymin * height, ymax * height
    -        return patches.Rectangle(
    -            (xmin, ymin),
    -            xmax - xmin,
    -            ymax - ymin,
    -            fill=fill,
    -            linewidth=linewidth,
    -            edgecolor=(*color, alpha),
    -            facecolor=(*color, alpha),
    -            label=label
    -        )
    +    (xmin, ymin), (xmax, ymax) = geometry
    +    # Switch to absolute coords
    +    if preserve_aspect_ratio:
    +        width = height = max(height, width)
    +    xmin, w = xmin * width, (xmax - xmin) * width
    +    ymin, h = ymin * height, (ymax - ymin) * height
    +
    +    return patches.Rectangle(
    +        (xmin, ymin),
    +        w,
    +        h,
    +        fill=fill,
    +        linewidth=linewidth,
    +        edgecolor=(*color, alpha),
    +        facecolor=(*color, alpha),
    +        label=label,
    +    )
    +
    +
    +def polygon_patch(
    +    geometry: np.ndarray,
    +    page_dimensions: Tuple[int, int],
    +    label: Optional[str] = None,
    +    color: Tuple[float, float, float] = (0, 0, 0),
    +    alpha: float = 0.3,
    +    linewidth: int = 2,
    +    fill: bool = True,
    +    preserve_aspect_ratio: bool = False,
    +) -> patches.Polygon:
    +    """Create a matplotlib polygon patch for the element
    +
    +    Args:
    +    ----
    +        geometry: bounding box of the element
    +        page_dimensions: dimensions of the Page in format (height, width)
    +        label: label to display when hovered
    +        color: color to draw box
    +        alpha: opacity parameter to fill the boxes, 0 = transparent
    +        linewidth: line width
    +        fill: whether the patch should be filled
    +        preserve_aspect_ratio: pass True if you passed True to the predictor
    +
    +    Returns:
    +    -------
    +        a polygon Patch
    +    """
    +    if not geometry.shape == (4, 2):
    +        raise ValueError("invalid geometry format")
    +
    +    # Unpack
    +    height, width = page_dimensions
    +    geometry[:, 0] = geometry[:, 0] * (max(width, height) if preserve_aspect_ratio else width)
    +    geometry[:, 1] = geometry[:, 1] * (max(width, height) if preserve_aspect_ratio else height)
    +
    +    return patches.Polygon(
    +        geometry,
    +        fill=fill,
    +        linewidth=linewidth,
    +        edgecolor=(*color, alpha),
    +        facecolor=(*color, alpha),
    +        label=label,
    +    )
    +
    +
    +def create_obj_patch(
    +    geometry: Union[BoundingBox, Polygon4P, np.ndarray],
    +    page_dimensions: Tuple[int, int],
    +    **kwargs: Any,
    +) -> patches.Patch:
    +    """Create a matplotlib patch for the element
    +
    +    Args:
    +    ----
    +        geometry: bounding box (straight or rotated) of the element
    +        page_dimensions: dimensions of the page in format (height, width)
    +        **kwargs: keyword arguments for the patch
    +
    +    Returns:
    +    -------
    +        a matplotlib Patch
    +    """
    +    if isinstance(geometry, tuple):
    +        if len(geometry) == 2:  # straight word BB (2 pts)
    +            return rect_patch(geometry, page_dimensions, **kwargs)
    +        elif len(geometry) == 4:  # rotated word BB (4 pts)
    +            return polygon_patch(np.asarray(geometry), page_dimensions, **kwargs)
    +    elif isinstance(geometry, np.ndarray) and geometry.shape == (4, 2):  # rotated line
    +        return polygon_patch(geometry, page_dimensions, **kwargs)
    +    raise ValueError("invalid geometry format")
    +
    +
    +def get_colors(num_colors: int) -> List[Tuple[float, float, float]]:
    +    """Generate num_colors color for matplotlib
    +
    +    Args:
    +    ----
    +        num_colors: number of colors to generate
    +
    +    Returns:
    +    -------
    +        colors: list of generated colors
    +    """
    +    colors = []
    +    for i in np.arange(0.0, 360.0, 360.0 / num_colors):
    +        hue = i / 360.0
    +        lightness = (50 + np.random.rand() * 10) / 100.0
    +        saturation = (90 + np.random.rand() * 10) / 100.0
    +        colors.append(colorsys.hls_to_rgb(hue, lightness, saturation))
    +    return colors
     
     
     
    -[docs] +[docs] def visualize_page( page: Dict[str, Any], image: np.ndarray, @@ -359,18 +472,18 @@

    Source code for doctr.utils.visualization

     ) -> Figure:
         """Visualize a full page with predicted blocks, lines and words
     
    -    Example::
    -        >>> import numpy as np
    -        >>> import matplotlib.pyplot as plt
    -        >>> from doctr.utils.visualization import visualize_page
    -        >>> from doctr.models import ocr_db_crnn
    -        >>> model = ocr_db_crnn(pretrained=True)
    -        >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    -        >>> out = model([[input_page]])
    -        >>> visualize_page(out[0].pages[0].export(), input_page)
    -        >>> plt.show()
    +    >>> import numpy as np
    +    >>> import matplotlib.pyplot as plt
    +    >>> from doctr.utils.visualization import visualize_page
    +    >>> from doctr.models import ocr_db_crnn
    +    >>> model = ocr_db_crnn(pretrained=True)
    +    >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    +    >>> out = model([[input_page]])
    +    >>> visualize_page(out[0].pages[0].export(), input_page)
    +    >>> plt.show()
     
         Args:
    +    ----
             page: the exported Page of a Document
             image: np array of the page, needs to have the same shape than page['dimensions']
             words_only: whether only words should be displayed
    @@ -378,6 +491,11 @@ 

    Source code for doctr.utils.visualization

             scale: figsize of the largest windows side
             interactive: whether the plot should be interactive
             add_labels: for static plot, adds text labels on top of bounding box
    +        **kwargs: keyword arguments for the polygon patch
    +
    +    Returns:
    +    -------
    +        the matplotlib figure
         """
         # Get proper scale and aspect ratio
         h, w = image.shape[:2]
    @@ -386,128 +504,189 @@ 

    Source code for doctr.utils.visualization

         # Display the image
         ax.imshow(image)
         # hide both axis
    -    ax.axis('off')
    +    ax.axis("off")
     
         if interactive:
             artists: List[patches.Patch] = []  # instantiate an empty list of patches (to be drawn on the page)
     
    -    for block in page['blocks']:
    +    for block in page["blocks"]:
             if not words_only:
    -            rect = create_rect_patch(block['geometry'], 'block', page['dimensions'], (0, 1, 0), linewidth=1, **kwargs)
    +            rect = create_obj_patch(
    +                block["geometry"], page["dimensions"], label="block", color=(0, 1, 0), linewidth=1, **kwargs
    +            )
                 # add patch on figure
                 ax.add_patch(rect)
                 if interactive:
                     # add patch to cursor's artists
                     artists.append(rect)
     
    -        for line in block['lines']:
    +        for line in block["lines"]:
                 if not words_only:
    -                rect = create_rect_patch(line['geometry'], 'line', page['dimensions'], (1, 0, 0), linewidth=1, **kwargs)
    +                rect = create_obj_patch(
    +                    line["geometry"], page["dimensions"], label="line", color=(1, 0, 0), linewidth=1, **kwargs
    +                )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
     
    -            for word in line['words']:
    -                rect = create_rect_patch(word['geometry'], f"{word['value']} (confidence: {word['confidence']:.2%})",
    -                                         page['dimensions'], (0, 0, 1), **kwargs)
    +            for word in line["words"]:
    +                rect = create_obj_patch(
    +                    word["geometry"],
    +                    page["dimensions"],
    +                    label=f"{word['value']} (confidence: {word['confidence']:.2%})",
    +                    color=(0, 0, 1),
    +                    **kwargs,
    +                )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
                     elif add_labels:
    -                    if len(word['geometry']) == 5:
    +                    if len(word["geometry"]) == 5:
                             text_loc = (
    -                            int(page['dimensions'][1] * (word['geometry'][0] - word['geometry'][2] / 2)),
    -                            int(page['dimensions'][0] * (word['geometry'][1] - word['geometry'][3] / 2))
    +                            int(page["dimensions"][1] * (word["geometry"][0] - word["geometry"][2] / 2)),
    +                            int(page["dimensions"][0] * (word["geometry"][1] - word["geometry"][3] / 2)),
                             )
                         else:
                             text_loc = (
    -                            int(page['dimensions'][1] * word['geometry'][0][0]),
    -                            int(page['dimensions'][0] * word['geometry'][0][1])
    +                            int(page["dimensions"][1] * word["geometry"][0][0]),
    +                            int(page["dimensions"][0] * word["geometry"][0][1]),
    +                        )
    +
    +                    if len(word["geometry"]) == 2:
    +                        # We draw only if boxes are in straight format
    +                        ax.text(
    +                            *text_loc,
    +                            word["value"],
    +                            size=10,
    +                            alpha=0.5,
    +                            color=(0, 0, 1),
                             )
    -                    ax.text(
    -                        *text_loc,
    -                        word['value'],
    -                        size=10,
    -                        alpha=0.5,
    -                        color=(0, 0, 1),
    -                    )
     
             if display_artefacts:
    -            for artefact in block['artefacts']:
    -                rect = create_rect_patch(
    -                    artefact['geometry'],
    -                    'artefact',
    -                    page['dimensions'],
    -                    (0.5, 0.5, 0.5),  # type: ignore[arg-type]
    +            for artefact in block["artefacts"]:
    +                rect = create_obj_patch(
    +                    artefact["geometry"],
    +                    page["dimensions"],
    +                    label="artefact",
    +                    color=(0.5, 0.5, 0.5),
                         linewidth=1,
    -                    **kwargs
    +                    **kwargs,
                     )
                     ax.add_patch(rect)
                     if interactive:
                         artists.append(rect)
     
         if interactive:
    +        import mplcursors
    +
             # Create mlp Cursor to hover patches in artists
             mplcursors.Cursor(artists, hover=2).connect("add", lambda sel: sel.annotation.set_text(sel.artist.get_label()))
    -    fig.tight_layout(pad=0.)
    +    fig.tight_layout(pad=0.0)
     
         return fig
    -def synthetize_page( +def visualize_kie_page( page: Dict[str, Any], - draw_proba: bool = False, - font_size: int = 13, -) -> np.ndarray: - """Draw a the content of the element page (OCR response) on a blank page. + image: np.ndarray, + words_only: bool = False, + display_artefacts: bool = True, + scale: float = 10, + interactive: bool = True, + add_labels: bool = True, + **kwargs: Any, +) -> Figure: + """Visualize a full page with predicted blocks, lines and words + + >>> import numpy as np + >>> import matplotlib.pyplot as plt + >>> from doctr.utils.visualization import visualize_page + >>> from doctr.models import ocr_db_crnn + >>> model = ocr_db_crnn(pretrained=True) + >>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8) + >>> out = model([[input_page]]) + >>> visualize_kie_page(out[0].pages[0].export(), input_page) + >>> plt.show() Args: - page: exported Page object to represent - draw_proba: if True, draw words in colors to represent confidence. Blue: p=1, red: p=0 - font_size: size of the font, default font = 13 + ---- + page: the exported Page of a Document + image: np array of the page, needs to have the same shape than page['dimensions'] + words_only: whether only words should be displayed + display_artefacts: whether artefacts should be displayed + scale: figsize of the largest windows side + interactive: whether the plot should be interactive + add_labels: for static plot, adds text labels on top of bounding box + **kwargs: keyword arguments for the polygon patch - Return: - A np array (drawn page) + Returns: + ------- + the matplotlib figure """ - # Draw template - h, w = page["dimensions"] - response = 255 * np.ones((h, w, 3), dtype=np.int32) + # Get proper scale and aspect ratio + h, w = image.shape[:2] + size = (scale * w / h, scale) if h > w else (scale, h / w * scale) + fig, ax = plt.subplots(figsize=size) + # Display the image + ax.imshow(image) + # hide both axis + ax.axis("off") - # Draw each word - for block in page["blocks"]: - for line in block["lines"]: - for word in line["words"]: - # Get aboslute word geometry - (xmin, ymin), (xmax, ymax) = word["geometry"] - xmin, xmax = int(w * xmin), int(w * xmax) - ymin, ymax = int(h * ymin), int(h * ymax) - - # White drawing context adapted to font size, 0.75 factor to convert pts --> pix - h_box, w_box = ymax - ymin, xmax - xmin - h_font, w_font = font_size, int(font_size * w_box / (h_box * 0.75)) - img = Image.new('RGB', (w_font, h_font), color=(255, 255, 255)) - d = ImageDraw.Draw(img) - - # Draw in black the value of the word - d.text((0, 0), word["value"], font=ImageFont.load_default(), fill=(0, 0, 0)) - - # Resize back to box size - img = img.resize((w_box, h_box), Image.NEAREST) - - # Colorize if draw_proba - if draw_proba: - p = int(255 * word["confidence"]) - mask = np.where(np.array(img) == 0, 1, 0) - proba = np.array([255 - p, 0, p]) - color = mask * proba[np.newaxis, np.newaxis, :] - white_mask = 255 * (1 - mask) - img = color + white_mask - - # Write to response page - response[ymin:ymax, xmin:xmax, :] = np.array(img) - - return response + if interactive: + artists: List[patches.Patch] = [] # instantiate an empty list of patches (to be drawn on the page) + + colors = {k: color for color, k in zip(get_colors(len(page["predictions"])), page["predictions"])} + for key, value in page["predictions"].items(): + for prediction in value: + if not words_only: + rect = create_obj_patch( + prediction["geometry"], + page["dimensions"], + label=f"{key} \n {prediction['value']} (confidence: {prediction['confidence']:.2%}", + color=colors[key], + linewidth=1, + **kwargs, + ) + # add patch on figure + ax.add_patch(rect) + if interactive: + # add patch to cursor's artists + artists.append(rect) + + if interactive: + import mplcursors + + # Create mlp Cursor to hover patches in artists + mplcursors.Cursor(artists, hover=2).connect("add", lambda sel: sel.annotation.set_text(sel.artist.get_label())) + fig.tight_layout(pad=0.0) + + return fig + + +def draw_boxes(boxes: np.ndarray, image: np.ndarray, color: Optional[Tuple[int, int, int]] = None, **kwargs) -> None: + """Draw an array of relative straight boxes on an image + + Args: + ---- + boxes: array of relative boxes, of shape (*, 4) + image: np array, float32 or uint8 + color: color to use for bounding box edges + **kwargs: keyword arguments from `matplotlib.pyplot.plot` + """ + h, w = image.shape[:2] + # Convert boxes to absolute coords + _boxes = deepcopy(boxes) + _boxes[:, [0, 2]] *= w + _boxes[:, [1, 3]] *= h + _boxes = _boxes.astype(np.int32) + for box in _boxes.tolist(): + xmin, ymin, xmax, ymax = box + image = cv2.rectangle( + image, (xmin, ymin), (xmax, ymax), color=color if isinstance(color, tuple) else (0, 0, 255), thickness=2 + ) + plt.imshow(image) + plt.plot(**kwargs)
    @@ -540,8 +719,8 @@

    Source code for doctr.utils.visualization

           
         
       
    - - + + diff --git a/v0.6.0/_modules/index.html b/v0.6.0/_modules/index.html index e86abcd4d4..5793c44f20 100644 --- a/v0.6.0/_modules/index.html +++ b/v0.6.0/_modules/index.html @@ -13,7 +13,7 @@ - + Overview: module code - docTR documentation @@ -225,20 +225,42 @@ - - + + diff --git a/v0.6.0/_sources/changelog.rst.txt b/v0.6.0/_sources/changelog.rst.txt index 430097d6c8..35befe7b96 100644 --- a/v0.6.0/_sources/changelog.rst.txt +++ b/v0.6.0/_sources/changelog.rst.txt @@ -1,6 +1,54 @@ Changelog ========= +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 `_ diff --git a/v0.6.0/_sources/datasets.rst.txt b/v0.6.0/_sources/datasets.rst.txt deleted file mode 100644 index 354122f1e5..0000000000 --- a/v0.6.0/_sources/datasets.rst.txt +++ /dev/null @@ -1,68 +0,0 @@ -doctr.datasets -============== - -.. currentmodule:: doctr.datasets - -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. - - -.. _datasets: - -Available Datasets ------------------- -The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL. - -.. autoclass:: doctr.datasets.datasets.VisionDataset - - -Here are all datasets that are available through DocTR: - -.. autoclass:: FUNSD -.. autoclass:: SROIE -.. autoclass:: CORD -.. autoclass:: OCRDataset - - -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. - -.. autoclass:: doctr.datasets.loader.DataLoader - - -.. _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 - * - digits - - 10 - - 0123456789 - * - ascii_letters - - 52 - - abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ - * - punctuation - - 32 - - !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ - * - currency - - 5 - - £€¥¢฿ - * - latin - - 96 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~° - * - french - - 154 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿ - -.. autofunction:: encode_sequences diff --git a/v0.6.0/_sources/documents.rst.txt b/v0.6.0/_sources/documents.rst.txt deleted file mode 100644 index 655730073e..0000000000 --- a/v0.6.0/_sources/documents.rst.txt +++ /dev/null @@ -1,87 +0,0 @@ -doctr.documents -=============== - - -.. currentmodule:: doctr.documents - -The documents module enables users to easily access content from documents and export analysis -results to structured formats. - - -Document structure ------------------- - -Structural organization of the documents. - -Word -^^^^ -A Word is an uninterrupted sequence of characters. - -.. autoclass:: Word - -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 - -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 - - -Document -^^^^^^^^ - -A Document is a collection of Pages. - -.. autoclass:: Document - - .. automethod:: show - - -File reading ------------- - -High-performance file reading and conversion to processable structured data. - -.. autofunction:: read_pdf - -.. autofunction:: read_img - -.. autofunction:: read_html - - -.. autoclass:: DocumentFile - - .. automethod:: from_pdf - - .. automethod:: from_url - - .. automethod:: from_images - -.. autoclass:: PDF - - .. automethod:: as_images - - .. automethod:: get_words - - .. automethod:: get_artefacts diff --git a/v0.6.0/_sources/getting_started/installing.rst.txt b/v0.6.0/_sources/getting_started/installing.rst.txt index e764e734a7..39e79aa3dd 100644 --- a/v0.6.0/_sources/getting_started/installing.rst.txt +++ b/v0.6.0/_sources/getting_started/installing.rst.txt @@ -3,7 +3,7 @@ Installation ************ -This library requires `Python `_ 3.9 or higher. +This library requires `Python `_ 3.10 or higher. Prerequisites diff --git a/v0.6.0/_sources/index.rst.txt b/v0.6.0/_sources/index.rst.txt index fc3ff89fdf..53251db142 100644 --- a/v0.6.0/_sources/index.rst.txt +++ b/v0.6.0/_sources/index.rst.txt @@ -1,7 +1,8 @@ -DocTR: Document Text Recognition -================================ +******************************** +docTR: Document Text Recognition +******************************** -State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 (PyTorch now in beta) +State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 & PyTorch .. image:: https://github.com/mindee/doctr/releases/download/v0.2.0/ocr.png :align: center @@ -9,38 +10,29 @@ State-of-the-art Optical Character Recognition made seamless & accessible to any DocTR provides an easy and powerful way to extract valuable information from your documents: -* |:receipt:| **for automation**: seemlessly process documents for Natural Language Understanding tasks: we provide OCR predictors to parse textual information (localize and identify each word) 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. -Welcome to the documentation of `DocTR `_! - - Main Features ------------- * |:robot:| Robust 2-stage (detection + recognition) OCR predictors with pretrained parameters * |:zap:| User-friendly, 3 lines of code to load a document and extract text with a predictor -* |:rocket:| State-of-the-art performances on public document datasets, comparable with GoogleVision/AWS Textract +* |: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, small dependencies -* |:tools:| Daily maintained -* |:factory:| Easy integration - +* |:bird:| Light package, minimal dependencies +* |:tools:| Actively maintained by Mindee +* |:factory:| Easy integration (available templates for browser demo & API deployment) -Getting Started ---------------- .. toctree:: :maxdepth: 2 + :caption: Getting started + :hidden: - installing - - -Build & train your predictor -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* Compose your own end-to-end OCR predictor: mix and match detection & recognition predictors (all-pretrained) -* Fine-tune or train from scratch any detection or recognition model to specialize on your data + getting_started/installing + notebooks Model zoo @@ -48,36 +40,83 @@ Model zoo Text detection models """"""""""""""""""""" - * `DBNet `_ (Differentiable Binarization) - * `LinkNet `_ +* 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 `_ (Show, Attend and Read) - * `CRNN `_ (Convolutional Recurrent Neural Network) - * `MASTER `_ (Multi-Aspect Non-local Network for Scene Text Recognition) +* 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" `_ 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 `_. +* 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" `_. .. toctree:: :maxdepth: 2 - :caption: Notes + :caption: Using docTR + :hidden: - changelog + 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 + + +.. toctree:: + :maxdepth: 2 + :caption: Community + :hidden: + + community/resources .. toctree:: :maxdepth: 2 :caption: Package Reference + :hidden: - datasets - documents - models - transforms - utils + 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/v0.6.0/_sources/installing.rst.txt b/v0.6.0/_sources/installing.rst.txt deleted file mode 100644 index 5c8779dc1c..0000000000 --- a/v0.6.0/_sources/installing.rst.txt +++ /dev/null @@ -1,46 +0,0 @@ - -************ -Installation -************ - -This library requires Python 3.6 or higher. - - -Prerequisites -============= - -Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so: - -* TensorFlow: `installation page `_. -* PyTorch: `installation page `_. - -If you are running another OS than Linux, you will need a few extra dependencies. - -For MacOS users, you can install them as follows: - -.. code:: shell - - brew install cairo pango gdk-pixbuf libffi - -For Windows users, those dependencies are included in GTK. You can find the latest installer over `here `_. - - -Via Python Package -================== - -Install the last stable release of the package using pip: - -.. code:: bash - - pip install python-doctr - - -Via Git -======= - -Install the library in developper mode: - -.. code:: bash - - git clone https://github.com/mindee/doctr.git - pip install -e doctr/. diff --git a/v0.6.0/_sources/models.rst.txt b/v0.6.0/_sources/models.rst.txt deleted file mode 100644 index 9830c6c153..0000000000 --- a/v0.6.0/_sources/models.rst.txt +++ /dev/null @@ -1,215 +0,0 @@ -doctr.models -============ - -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. - -.. currentmodule:: doctr.models - -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 TensorFlow model. -* Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable. - - -Text Detection --------------- -Localizing text elements in images - -+---------------------------------------------------+----------------------------+----------------------------+---------+ -| | FUNSD | CORD | | -+==================+=================+==============+============+===============+============+===============+=========+ -| **Architecture** | **Input shape** | **# params** | **Recall** | **Precision** | **Recall** | **Precision** | **FPS** | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ -| db_resnet50 | (1024, 1024, 3) | 25.2 M | 82.14 | 87.64 | 92.49 | 89.66 | 2.1 | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ - -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 combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for detection -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for detection is the following: - -1. resize each input image to the target size (bilinear interpolation by default) with potential deformation. -2. batch images together -3. normalize the batch using the training data statistics - - -Detection models -^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - -.. autofunction:: doctr.models.detection.db_resnet50 -.. autofunction:: doctr.models.detection.linknet16 - -Detection predictors -^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information. - -.. autofunction:: doctr.models.detection.detection_predictor - - -Text Recognition ----------------- -Identifying strings in images - -.. list-table:: Text recognition model zoo - :widths: 20 20 15 10 10 10 - :header-rows: 1 - - * - Architecture - - Input shape - - # params - - FUNSD - - CORD - - FPS - * - crnn_vgg16_bn - - (32, 128, 3) - - 15.8M - - 86.02 - - 91.3 - - 12.8 - * - sar_vgg16_bn - - (32, 128, 3) - - 21.5M - - 86.2 - - 91.7 - - 3.3 - * - sar_resnet31 - - (32, 128, 3) - - 53.1M - - **86.3** - - **92.1** - - 2.7 - -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 metrics being used are available in :ref:`metrics`. - -All these recognition models are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for recognition -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for recognition is the following: - -1. resize each input image to the target size (bilinear interpolation by default) without deformation. -2. pad the image to the target size (with zeros by default) -3. batch images together -4. normalize the batch using the training data statistics - -Recognition models -^^^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - - -.. autofunction:: doctr.models.recognition.crnn_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_resnet31 -.. autofunction:: doctr.models.recognition.master - - -Recognition predictors -^^^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage. - -.. autofunction:: doctr.models.recognition.recognition_predictor - - -End-to-End OCR --------------- -Predictors that localize and identify text elements in images - -+-----------------------------+--------------------------------------+--------------------------------------+ -| | FUNSD | CORD | -+=============================+============+===============+=========+============+===============+=========+ -| **Architecture** | **Recall** | **Precision** | **FPS** | **Recall** | **Precision** | **FPS** | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + crnn_vgg16_bn | 70.08 | 74.77 | 0.85 | 82.19 | **79.67** | 1.6 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_vgg16_bn | N/A | N/A | 0.49 | N/A | N/A | 1.0 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_resnet31 | N/A | N/A | 0.27 | N/A | N/A | 0.83 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision text detection | 59.50 | 62.50 | | 75.30 | 70.00 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision doc. text detection | 64.00 | 53.30 | | 68.90 | 61.10 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| AWS textract | **78.10** | **83.00** | | **87.50** | 66.00 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ - -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`. - -All recognition models of predictors are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Results on private ocr datasets - -+------------------------------------+----------------------------+----------------------------+----------------------------+ -| | Receipts | Invoices | IDs | -+====================================+============+===============+============+===============+============+===============+ -| **Architecture** | **Recall** | **Precision** | **Recall** | **Precision** | **Recall** | **Precision** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| db_resnet50 + crnn_vgg16_bn (ours) | **78.90** | **81.01** | 65.68 | **69.86** | **49.48** | **50.46** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| Gvision doc. text detection | 68.91 | 59.89 | 63.20 | 52.85 | 43.70 | 29.21 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| AWS textract | 75.77 | 77.70 | **70.47** | 69.13 | 46.39 | 43.32 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ - - -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. - -.. autofunction:: doctr.models.zoo.ocr_predictor - - -Model export ------------- -Utility functions to make the most of document analysis models. - -.. currentmodule:: doctr.models.export - -Model compression -^^^^^^^^^^^^^^^^^ - -.. autofunction:: convert_to_tflite - -.. autofunction:: convert_to_fp16 - -.. autofunction:: quantize_model - -Using SavedModel -^^^^^^^^^^^^^^^^ - -Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -`SavedModel `_ format as follows: - - - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> _ = model(input_t, training=False) - >>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/') - -And loaded just as easily: - - - >>> import tensorflow as tf - >>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/') diff --git a/v0.6.0/_sources/transforms.rst.txt b/v0.6.0/_sources/transforms.rst.txt deleted file mode 100644 index 0230fe75f5..0000000000 --- a/v0.6.0/_sources/transforms.rst.txt +++ /dev/null @@ -1,32 +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: - -.. autoclass:: Resize -.. autoclass:: Normalize -.. autoclass:: LambdaTransformation -.. autoclass:: ToGray -.. autoclass:: ColorInversion -.. autoclass:: RandomBrightness -.. autoclass:: RandomContrast -.. autoclass:: RandomSaturation -.. autoclass:: RandomHue -.. autoclass:: RandomGamma -.. autoclass:: RandomJpegQuality - - -Composing transformations ---------------------------------------------- -It is common to require several transformations to be performed consecutively. - -.. autoclass:: Compose -.. autoclass:: OneOf -.. autoclass:: RandomApply diff --git a/v0.6.0/_sources/utils.rst.txt b/v0.6.0/_sources/utils.rst.txt deleted file mode 100644 index 69c1abe0eb..0000000000 --- a/v0.6.0/_sources/utils.rst.txt +++ /dev/null @@ -1,36 +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 - - -.. _metrics: - -Task evaluation ---------------- -Implementations of task-specific metrics to easily assess your model performances. - -.. currentmodule:: doctr.utils.metrics - -.. autoclass:: TextMatch - - .. automethod:: summary - -.. autoclass:: LocalizationConfusion - - .. automethod:: summary - -.. autoclass:: OCRMetric - - .. automethod:: summary diff --git a/v0.6.0/_static/basic.css b/v0.6.0/_static/basic.css index f316efcb47..7ebbd6d07b 100644 --- a/v0.6.0/_static/basic.css +++ b/v0.6.0/_static/basic.css @@ -1,12 +1,5 @@ /* - * basic.css - * ~~~~~~~~~ - * * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ /* -- main layout ----------------------------------------------------------- */ @@ -115,15 +108,11 @@ img { /* -- search page ----------------------------------------------------------- */ ul.search { - margin: 10px 0 0 20px; - padding: 0; + margin-top: 10px; } ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; + padding: 5px 0; } ul.search li a { diff --git a/v0.6.0/_static/doctools.js b/v0.6.0/_static/doctools.js index 4d67807d17..0398ebb9f0 100644 --- a/v0.6.0/_static/doctools.js +++ b/v0.6.0/_static/doctools.js @@ -1,12 +1,5 @@ /* - * doctools.js - * ~~~~~~~~~~~ - * * Base JavaScript utilities for all Sphinx HTML documentation. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; diff --git a/v0.6.0/_static/documentation_options.js b/v0.6.0/_static/documentation_options.js index a7b5cbe04a..4f656fdbea 100644 --- a/v0.6.0/_static/documentation_options.js +++ b/v0.6.0/_static/documentation_options.js @@ -1,5 +1,5 @@ const DOCUMENTATION_OPTIONS = { - VERSION: '0.3.0a0-git', + VERSION: '0.10.1a0-git', LANGUAGE: 'en', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/v0.6.0/_static/language_data.js b/v0.6.0/_static/language_data.js index 367b8ed81b..c7fe6c6faf 100644 --- a/v0.6.0/_static/language_data.js +++ b/v0.6.0/_static/language_data.js @@ -1,13 +1,6 @@ /* - * language_data.js - * ~~~~~~~~~~~~~~~~ - * * This script contains the language-specific data used by searchtools.js, * namely the list of stopwords, stemmer, scorer and splitter. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; diff --git a/v0.6.0/_static/searchtools.js b/v0.6.0/_static/searchtools.js index b08d58c9b9..2c774d17af 100644 --- a/v0.6.0/_static/searchtools.js +++ b/v0.6.0/_static/searchtools.js @@ -1,12 +1,5 @@ /* - * searchtools.js - * ~~~~~~~~~~~~~~~~ - * * Sphinx JavaScript utilities for the full-text search. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; @@ -20,7 +13,7 @@ if (typeof Scorer === "undefined") { // and returns the new score. /* score: result => { - const [docname, title, anchor, descr, score, filename] = result + const [docname, title, anchor, descr, score, filename, kind] = result return score }, */ @@ -47,6 +40,14 @@ if (typeof Scorer === "undefined") { }; } +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + const _removeChildren = (element) => { while (element && element.lastChild) element.removeChild(element.lastChild); }; @@ -64,9 +65,13 @@ const _displayItem = (item, searchTerms, highlightTerms) => { const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; const contentRoot = document.documentElement.dataset.content_root; - const [docName, title, anchor, descr, score, _filename] = item; + const [docName, title, anchor, descr, score, _filename, kind] = item; let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); let requestUrl; let linkUrl; if (docBuilder === "dirhtml") { @@ -115,8 +120,10 @@ const _finishSearch = (resultCount) => { "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." ); else - Search.status.innerText = _( - "Search finished, found ${resultCount} page(s) matching the search query." + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, ).replace('${resultCount}', resultCount); }; const _displayNextItem = ( @@ -138,7 +145,7 @@ const _displayNextItem = ( else _finishSearch(resultCount); }; // Helper function used by query() to order search results. -// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. // Order the results by score (in opposite order of appearance, since the // `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. const _orderResultsByScoreThenName = (a, b) => { @@ -248,6 +255,7 @@ const Search = { searchSummary.classList.add("search-summary"); searchSummary.innerText = ""; const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); searchList.classList.add("search"); const out = document.getElementById("search-results"); @@ -318,7 +326,7 @@ const Search = { const indexEntries = Search._index.indexentries; // Collect multiple result groups to be sorted separately and then ordered. - // Each is an array of [docname, title, anchor, descr, score, filename]. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. const normalResults = []; const nonMainIndexResults = []; @@ -337,6 +345,7 @@ const Search = { null, score + boost, filenames[file], + SearchResultKind.title, ]); } } @@ -354,6 +363,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.index, ]; if (isMain) { normalResults.push(result); @@ -475,6 +485,7 @@ const Search = { descr, score, filenames[match[0]], + SearchResultKind.object, ]); }; Object.keys(objects).forEach((prefix) => @@ -585,6 +596,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.text, ]); } return results; diff --git a/v0.6.0/changelog.html b/v0.6.0/changelog.html index eafac3a877..fc45a50384 100644 --- a/v0.6.0/changelog.html +++ b/v0.6.0/changelog.html @@ -12,9 +12,9 @@ gtag('js', new Date()); gtag('config', 'G-40DVRMX8T4'); - + - + Changelog - docTR documentation @@ -226,20 +226,42 @@ + diff --git a/v0.6.0/community/resources.html b/v0.6.0/community/resources.html index 2564037893..9a1988258c 100644 --- a/v0.6.0/community/resources.html +++ b/v0.6.0/community/resources.html @@ -14,7 +14,7 @@ - + Community resources - docTR documentation @@ -389,7 +389,7 @@

    Community resources - + diff --git a/v0.6.0/contributing/code_of_conduct.html b/v0.6.0/contributing/code_of_conduct.html index 5ea4a1f99d..03422dbb4d 100644 --- a/v0.6.0/contributing/code_of_conduct.html +++ b/v0.6.0/contributing/code_of_conduct.html @@ -14,7 +14,7 @@ - + Contributor Covenant Code of Conduct - docTR documentation @@ -504,7 +504,7 @@

    Attribution - + diff --git a/v0.6.0/contributing/contributing.html b/v0.6.0/contributing/contributing.html index e5a85682c6..05e2b3641b 100644 --- a/v0.6.0/contributing/contributing.html +++ b/v0.6.0/contributing/contributing.html @@ -14,7 +14,7 @@ - + Contributing to docTR - docTR documentation @@ -481,7 +481,7 @@

    Let’s connect - + diff --git a/v0.6.0/datasets.html b/v0.6.0/datasets.html deleted file mode 100644 index 193e576c57..0000000000 --- a/v0.6.0/datasets.html +++ /dev/null @@ -1,578 +0,0 @@ - - - - - - - - - - - - - doctr.datasets - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.datasets

    -

    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.

    -
    -

    Available Datasets

    -

    The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL.

    -
    -
    -class doctr.datasets.datasets.VisionDataset(url: str, file_name: str | None = None, file_hash: str | None = None, extract_archive: bool = False, download: bool = False, overwrite: bool = False)[source]
    -
    - -

    Here are all datasets that are available through DocTR:

    -
    -
    -class doctr.datasets.FUNSD(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    FUNSD dataset from “FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents”.

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD
    ->>> train_set = FUNSD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.SROIE(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    SROIE dataset from “ICDAR2019 Competition on Scanned Receipt OCR and Information Extraction”.

    -
    -
    Example::
    >>> from doctr.datasets import SROIE
    ->>> train_set = SROIE(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.CORD(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    CORD dataset from “CORD: A Consolidated Receipt Dataset forPost-OCR Parsing”.

    -
    -
    Example::
    >>> from doctr.datasets import CORD
    ->>> train_set = CORD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.OCRDataset(img_folder: str, label_file: str, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    Implements an OCR dataset

    -
    -
    Parameters:
    -
      -
    • img_folder – local path to image folder (all jpg at the root)

    • -
    • label_file – local path to the label file

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -

    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.

    -
    -
    -class doctr.datasets.loader.DataLoader(dataset, shuffle: bool = True, batch_size: int = 1, drop_last: bool = False, workers: int | None = None)[source]
    -

    Implements a dataset wrapper for fast data loading

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD, 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)
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • dataset – the dataset

    • -
    • shuffle – whether the samples should be shuffled before passing it to the iterator

    • -
    • batch_size – number of elements in each batch

    • -
    • drop_last – if True, drops the last batch if it isn’t full

    • -
    • workers – number of workers to use for data loading

    • -
    -
    -
    -
    - -
    -
    -

    Supported Vocabs

    -

    Since textual content has to be encoded properly for models to interpret them efficiently, DocTR supports multiple sets -of vocabs.

    -
    - - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    DocTR Vocabs

    Name

    size

    characters

    digits

    10

    0123456789

    ascii_letters

    52

    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

    punctuation

    32

    !”#$%&'()*+,-./:;<=>?@[\]^_`{|}~

    currency

    5

    £€¥¢฿

    latin

    96

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°

    french

    154

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿

    -
    -
    -
    -doctr.datasets.encode_sequences(sequences: List[str], vocab: str, target_size: int | None = None, eos: int = -1, sos: int | None = None, pad: int | None = None, **kwargs: Any) ndarray[source]
    -

    Encode character sequences using a given vocab as mapping

    -
    -
    Parameters:
    -
      -
    • sequences – the list of character sequences of size N

    • -
    • vocab – the ordered vocab to use for encoding

    • -
    • target_size – maximum length of the encoded data

    • -
    • eos – encoding of End Of String

    • -
    • sos – optional encoding of Start Of String

    • -
    • pad – optional encoding for padding. In case of padding, all sequences are followed by 1 EOS then PAD

    • -
    -
    -
    Returns:
    -

    the padded encoded data as a tensor

    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.6.0/documents.html b/v0.6.0/documents.html deleted file mode 100644 index 98cbb2c5ef..0000000000 --- a/v0.6.0/documents.html +++ /dev/null @@ -1,772 +0,0 @@ - - - - - - - - - - - - - doctr.documents - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.documents

    -

    The documents module enables users to easily access content from documents and export analysis -results to structured formats.

    -
    -

    Document structure

    -

    Structural organization of the documents.

    -
    -

    Word

    -

    A Word is an uninterrupted sequence of characters.

    -
    -
    -class doctr.documents.Word(value: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float])[source]
    -

    Implements a word element

    -
    -
    Parameters:
    -
      -
    • value – the text string of the word

    • -
    • confidence – the confidence associated with the text prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to

    • -
    • size (the page's)

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Line(words: List[Word], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float] | None = None)[source]
    -

    Implements a line element as a collection of words

    -
    -
    Parameters:
    -
      -
    • words – list of word elements

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all words in it.

    • -
    -
    -
    -
    - -
    -
    -

    Artefact

    -

    An Artefact is a non-textual element (e.g. QR code, picture, chart, signature, logo, etc.).

    -
    -
    -class doctr.documents.Artefact(artefact_type: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]])[source]
    -

    Implements a non-textual element

    -
    -
    Parameters:
    -
      -
    • artefact_type – the type of artefact

    • -
    • confidence – the confidence of the type prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size.

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Block(lines: List[Line] = [], artefacts: List[Artefact] = [], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float] | None = None)[source]
    -

    Implements a block element as a collection of lines and artefacts

    -
    -
    Parameters:
    -
      -
    • lines – list of line elements

    • -
    • artefacts – list of artefacts

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all lines and artefacts in it.

    • -
    -
    -
    -
    - -
    -
    -

    Page

    -

    A Page is a collection of Blocks that were on the same physical page.

    -
    -
    -class doctr.documents.Page(blocks: List[Block], page_idx: int, dimensions: Tuple[int, int], orientation: Dict[str, Any] | None = None, language: Dict[str, Any] | None = None)[source]
    -

    Implements a page element as a collection of blocks

    -
    -
    Parameters:
    -
      -
    • blocks – list of block elements

    • -
    • page_idx – the index of the page in the input raw document

    • -
    • dimensions – the page size in pixels in format (width, height)

    • -
    • orientation – a dictionary with the value of the rotation angle in degress and confidence of the prediction

    • -
    • language – a dictionary with the language value and confidence of the prediction

    • -
    -
    -
    -
    -
    -show(page: ndarray, interactive: bool = True, **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -
      -
    • page – image encoded as a numpy array in uint8

    • -
    • interactive – whether the display should be interactive

    • -
    -
    -
    -
    - -
    - -
    -
    -

    Document

    -

    A Document is a collection of Pages.

    -
    -
    -class doctr.documents.Document(pages: List[Page])[source]
    -

    Implements a document element as a collection of pages

    -
    -
    Parameters:
    -

    pages – list of page elements

    -
    -
    -
    -
    -show(pages: List[ndarray], **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -

    pages – list of images encoded as numpy arrays in uint8

    -
    -
    -
    - -
    - -
    -
    -
    -

    File reading

    -

    High-performance file reading and conversion to processable structured data.

    -
    -
    -doctr.documents.read_pdf(file: str | Path | bytes, **kwargs: Any) Document[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_pdf
    ->>> doc = read_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_img(file: str | Path | bytes, output_size: Tuple[int, int] | None = None, rgb_output: bool = True) ndarray[source]
    -

    Read an image file into numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_img
    ->>> page = read_img("path/to/your/doc.jpg")
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • file – the path to the image file

    • -
    • output_size – the expected output size of each page in format H x W

    • -
    • rgb_output – whether the output ndarray channel order should be RGB instead of BGR.

    • -
    -
    -
    Returns:
    -

    the page decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_html(url: str, **kwargs: Any) bytes[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_html
    ->>> doc = read_html("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – URL of the target web page

    -
    -
    Returns:
    -

    decoded PDF file as a bytes stream

    -
    -
    -
    - -
    -
    -class doctr.documents.DocumentFile[source]
    -

    Read a document from multiple extensions

    -
    -
    -classmethod from_pdf(file: str | Path | bytes, **kwargs) PDF[source]
    -

    Read a PDF file

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file or a binary stream

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_url(url: str, **kwargs) PDF[source]
    -

    Interpret a web page as a PDF document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_url("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – the URL of the target web page

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_images(files: Sequence[str | Path | bytes] | str | Path | bytes, **kwargs) List[ndarray][source]
    -

    Read an image file (or a collection of image files) and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"])
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    files – the path to the image file or a binary stream, or a collection of those

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    - -
    -
    -class doctr.documents.PDF(doc: Document)[source]
    -

    PDF document template

    -
    -
    Parameters:
    -

    doc – input PDF document

    -
    -
    -
    -
    -as_images(**kwargs) List[ndarray][source]
    -

    Convert all document pages to images

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of convert_page_to_numpy

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -get_words(**kwargs) List[List[Tuple[Tuple[float, float, float, float], str]]][source]
    -

    Get the annotations for all words in the document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of fitz.Page.getTextWords

    -
    -
    Returns:
    -

    the list of pages annotations, represented as a list of tuple (bounding box, value)

    -
    -
    -
    - -
    -
    -get_artefacts() List[List[Tuple[float, float, float, float]]][source]
    -

    Get the artefacts for the entire document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts()
    -
    -
    -
    -
    -
    -
    Returns:
    -

    the list of pages artefacts, represented as a list of bounding boxes

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.6.0/genindex.html b/v0.6.0/genindex.html index a19b433943..21520455b4 100644 --- a/v0.6.0/genindex.html +++ b/v0.6.0/genindex.html @@ -13,7 +13,7 @@ - Index - docTR documentation + Index - docTR documentation @@ -224,20 +224,42 @@

    +
    +

    U

    + + +
    +
    +

    V

    @@ -561,7 +711,13 @@

    V

    W

    +
    @@ -599,8 +755,8 @@

    W

    - - + + diff --git a/v0.6.0/getting_started/installing.html b/v0.6.0/getting_started/installing.html index a488e9a030..af3b58193e 100644 --- a/v0.6.0/getting_started/installing.html +++ b/v0.6.0/getting_started/installing.html @@ -14,7 +14,7 @@ - + Installation - docTR documentation @@ -305,7 +305,7 @@

    Installation

    -

    This library requires Python 3.9 or higher.

    +

    This library requires Python 3.10 or higher.

    Prerequisites

    Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so:

    @@ -435,7 +435,7 @@

    Via Git - + diff --git a/v0.6.0/index.html b/v0.6.0/index.html index 4c6a28c66a..3a06afc6d9 100644 --- a/v0.6.0/index.html +++ b/v0.6.0/index.html @@ -12,9 +12,9 @@ gtag('js', new Date()); gtag('config', 'G-40DVRMX8T4'); - + - + docTR documentation @@ -226,20 +226,42 @@
    -

    DocTR: Document Text Recognition

    -

    State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 (PyTorch now in beta)

    +

    docTR: Document Text Recognition

    +

    State-of-the-art Optical Character Recognition made seamless & accessible to anyone, powered by TensorFlow 2 & PyTorch

    https://github.com/mindee/doctr/releases/download/v0.2.0/ocr.png

    DocTR provides an easy and powerful way to extract valuable information from your documents:

      -
    • 🧾 for automation: seemlessly process documents for Natural Language Understanding tasks: we provide OCR predictors to parse textual information (localize and identify each word) from your documents.

    • +
    • 🧾 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.

    • 👩‍🔬 for research: quickly compare your own architectures speed & performances with state-of-art models on public datasets.

    -

    Welcome to the documentation of DocTR!

    Main Features

    • 🤖 Robust 2-stage (detection + recognition) OCR predictors with pretrained parameters

    • ⚡ User-friendly, 3 lines of code to load a document and extract text with a predictor

    • -
    • 🚀 State-of-the-art performances on public document datasets, comparable with GoogleVision/AWS Textract

    • +
    • 🚀 State-of-the-art performance on public document datasets, comparable with GoogleVision/AWS Textract

    • ⚡ Optimized for inference speed on both CPU & GPU

    • -
    • 🐦 Light package, small dependencies

    • -
    • 🛠️ Daily maintained

    • -
    • 🏭 Easy integration

    • +
    • 🐦 Light package, minimal dependencies

    • +
    • 🛠️ Actively maintained by Mindee

    • +
    • 🏭 Easy integration (available templates for browser demo & API deployment)

    -
    -
    -

    Getting Started

    -
    -

    Build & train your predictor

    -
      -
    • Compose your own end-to-end OCR predictor: mix and match detection & recognition predictors (all-pretrained)

    • -
    • Fine-tune or train from scratch any detection or recognition model to specialize on your data

    • -
    -

    Model zoo

    Text detection models

    -
    -

    Text recognition models

    -
    -

    Supported datasets

    -
    -
    +
    +
    +
    +
    +
    @@ -406,7 +381,7 @@

    Supported datasets - +
    Next @@ -446,10 +421,8 @@

    Supported datasets + diff --git a/v0.6.0/installing.html b/v0.6.0/installing.html deleted file mode 100644 index b61c60134b..0000000000 --- a/v0.6.0/installing.html +++ /dev/null @@ -1,395 +0,0 @@ - - - - - - - - - - - - - Installation - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    Installation

    -

    This library requires Python 3.6 or higher.

    -
    -

    Prerequisites

    -

    Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so:

    - -

    If you are running another OS than Linux, you will need a few extra dependencies.

    -

    For MacOS users, you can install them as follows:

    -
    brew install cairo pango gdk-pixbuf libffi
    -
    -
    -

    For Windows users, those dependencies are included in GTK. You can find the latest installer over here.

    -
    -
    -

    Via Python Package

    -

    Install the last stable release of the package using pip:

    -
    pip install python-doctr
    -
    -
    -
    -
    -

    Via Git

    -

    Install the library in developper mode:

    -
    git clone https://github.com/mindee/doctr.git
    -pip install -e doctr/.
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.6.0/models.html b/v0.6.0/models.html deleted file mode 100644 index b5cd44c9fa..0000000000 --- a/v0.6.0/models.html +++ /dev/null @@ -1,1002 +0,0 @@ - - - - - - - - - - - - - doctr.models - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.models

    -

    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 TensorFlow model.

    • -
    • Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable.

    • -
    -
    -

    Text Detection

    -

    Localizing text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Input shape

    # params

    Recall

    Precision

    Recall

    Precision

    FPS

    db_resnet50

    (1024, 1024, 3)

    25.2 M

    82.14

    87.64

    92.49

    89.66

    2.1

    -
    -

    All text detection models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for detection

    -

    In DocTR, the pre-processing scheme for detection is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) with potential deformation.

    2. -
    3. batch images together

    4. -
    5. normalize the batch using the training data statistics

    6. -
    -
    -
    -

    Detection models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.detection.db_resnet50(pretrained: bool = False, **kwargs: Any) DBNet[source]
    -

    DBNet as described in “Real-time Scene Text Detection with Differentiable Binarization”, using a ResNet-50 backbone.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -doctr.models.detection.linknet16(pretrained: bool = False, **kwargs: Any) LinkNet[source]
    -

    LinkNet as described in “LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import linknet16
    ->>> model = linknet16(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -

    Detection predictors

    -

    Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information.

    -
    -
    -doctr.models.detection.detection_predictor(arch: str = 'db_resnet50', pretrained: bool = False, **kwargs: Any) DetectionPredictor[source]
    -

    Text detection architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import detection_predictor
    ->>> model = detection_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_resnet50’)

    • -
    • pretrained – If True, returns a model pre-trained on our text detection dataset

    • -
    -
    -
    Returns:
    -

    Detection predictor

    -
    -
    -
    - -
    -
    -
    -

    Text Recognition

    -

    Identifying strings in images

    -
    - - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Text recognition model zoo

    Architecture

    Input shape

    # params

    FUNSD

    CORD

    FPS

    crnn_vgg16_bn

    (32, 128, 3)

    15.8M

    86.02

    91.3

    12.8

    sar_vgg16_bn

    (32, 128, 3)

    21.5M

    86.2

    91.7

    3.3

    sar_resnet31

    (32, 128, 3)

    53.1M

    86.3

    92.1

    2.7

    -
    -

    All text recognition models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All these recognition models are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for recognition

    -

    In DocTR, the pre-processing scheme for recognition is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) without deformation.

    2. -
    3. pad the image to the target size (with zeros by default)

    4. -
    5. batch images together

    6. -
    7. normalize the batch using the training data statistics

    8. -
    -
    -
    -

    Recognition models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.recognition.crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) CRNN[source]
    -

    CRNN with a VGG-16 backbone as described in “An End-to-End Trainable Neural Network for Image-based -Sequence Recognition and Its Application to Scene Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import crnn_vgg16_bn
    ->>> model = crnn_vgg16_bn(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a VGG16 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_vgg16_bn
    ->>> model = sar_vgg16_bn(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_resnet31(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a resnet-31 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -

    Example

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_resnet31
    ->>> model = sar_resnet31(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.master(pretrained: bool = False, **kwargs: Any) MASTER[source]
    -

    MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_. -Example:

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import master
    ->>> model = master(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 48, 160, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -

    Recognition predictors

    -

    Combining the right components around a given architecture for easier usage.

    -
    -
    -doctr.models.recognition.recognition_predictor(arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) RecognitionPredictor[source]
    -

    Text recognition architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import recognition_predictor
    ->>> model = recognition_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(32, 128, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘crnn_vgg16_bn’, ‘crnn_resnet31’, ‘sar_vgg16_bn’, ‘sar_resnet31’)

    • -
    • pretrained – If True, returns a model pre-trained on our text recognition dataset

    • -
    -
    -
    Returns:
    -

    Recognition predictor

    -
    -
    -
    - -
    -
    -
    -

    End-to-End OCR

    -

    Predictors that localize and identify text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Recall

    Precision

    FPS

    Recall

    Precision

    FPS

    db_resnet50 + crnn_vgg16_bn

    70.08

    74.77

    0.85

    82.19

    79.67

    1.6

    db_resnet50 + sar_vgg16_bn

    N/A

    N/A

    0.49

    N/A

    N/A

    1.0

    db_resnet50 + sar_resnet31

    N/A

    N/A

    0.27

    N/A

    N/A

    0.83

    Gvision text detection

    59.50

    62.50

    75.30

    70.00

    Gvision doc. text detection

    64.00

    53.30

    68.90

    61.10

    AWS textract

    78.10

    83.00

    87.50

    66.00

    -
    -

    All OCR models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All recognition models of predictors are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -

    Results on private ocr datasets

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Receipts

    Invoices

    IDs

    Architecture

    Recall

    Precision

    Recall

    Precision

    Recall

    Precision

    db_resnet50 + crnn_vgg16_bn (ours)

    78.90

    81.01

    65.68

    69.86

    49.48

    50.46

    Gvision doc. text detection

    68.91

    59.89

    63.20

    52.85

    43.70

    29.21

    AWS textract

    75.77

    77.70

    70.47

    69.13

    46.39

    43.32

    -
    -
    -

    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.

    -
    -
    -doctr.models.zoo.ocr_predictor(det_arch: str = 'db_resnet50', reco_arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) OCRPredictor[source]
    -

    End-to-end OCR architecture using one model for localization, and another for text recognition.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import ocr_predictor
    ->>> model = ocr_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_sar_vgg’, ‘db_sar_resnet’, ‘db_crnn_vgg’, ‘db_crnn_resnet’)

    • -
    • pretrained – If True, returns a model pre-trained on our OCR dataset

    • -
    -
    -
    Returns:
    -

    OCR predictor

    -
    -
    -
    - -
    -
    -
    -

    Model export

    -

    Utility functions to make the most of document analysis models.

    -
    -

    Model compression

    -
    -
    -doctr.models.export.convert_to_tflite(tf_model: Model) bytes[source]
    -

    Converts a model to TFLite format

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_tflite, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_tflite(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.convert_to_fp16(tf_model: Model) bytes[source]
    -

    Converts a model to half precision

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_fp16, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_fp16(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the serialized FP16 model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) bytes[source]
    -

    Quantize a Tensorflow model

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import quantize_model, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = quantize_model(model, (224, 224, 3))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • tf_model – a keras model

    • -
    • input_shape – shape of the expected input tensor (excluding batch dimension) with channel last order

    • -
    -
    -
    Returns:
    -

    the serialized quantized model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -

    Using SavedModel

    -

    Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -SavedModel format as follows:

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> _ = model(input_t, training=False)
    ->>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/')
    -
    -
    -

    And loaded just as easily:

    -
    >>> import tensorflow as tf
    ->>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/')
    -
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.6.0/modules/contrib.html b/v0.6.0/modules/contrib.html index e99f6b3f74..7fb86b8b38 100644 --- a/v0.6.0/modules/contrib.html +++ b/v0.6.0/modules/contrib.html @@ -14,7 +14,7 @@ - + doctr.contrib - docTR documentation @@ -380,7 +380,7 @@

    Supported contribution modules - + diff --git a/v0.6.0/modules/datasets.html b/v0.6.0/modules/datasets.html index 456e10b172..380a986793 100644 --- a/v0.6.0/modules/datasets.html +++ b/v0.6.0/modules/datasets.html @@ -14,7 +14,7 @@ - + doctr.datasets - docTR documentation @@ -1081,7 +1081,7 @@

    Returns:

    - + diff --git a/v0.6.0/modules/io.html b/v0.6.0/modules/io.html index 01eadaa4b8..24c41954be 100644 --- a/v0.6.0/modules/io.html +++ b/v0.6.0/modules/io.html @@ -14,7 +14,7 @@ - + doctr.io - docTR documentation @@ -760,7 +760,7 @@

    Returns: - + diff --git a/v0.6.0/modules/models.html b/v0.6.0/modules/models.html index c465cc0586..91b8810a6a 100644 --- a/v0.6.0/modules/models.html +++ b/v0.6.0/modules/models.html @@ -14,7 +14,7 @@ - + doctr.models - docTR documentation @@ -1612,7 +1612,7 @@

    Args: - + diff --git a/v0.6.0/modules/transforms.html b/v0.6.0/modules/transforms.html index 30f7a2631a..c5ead3f3ce 100644 --- a/v0.6.0/modules/transforms.html +++ b/v0.6.0/modules/transforms.html @@ -14,7 +14,7 @@ - + doctr.transforms - docTR documentation @@ -835,7 +835,7 @@

    Args:< - + diff --git a/v0.6.0/modules/utils.html b/v0.6.0/modules/utils.html index 888a32c321..b7f6fc570b 100644 --- a/v0.6.0/modules/utils.html +++ b/v0.6.0/modules/utils.html @@ -14,7 +14,7 @@ - + doctr.utils - docTR documentation @@ -715,7 +715,7 @@

    Args: - + diff --git a/v0.6.0/notebooks.html b/v0.6.0/notebooks.html index f97771aebb..d36539f59e 100644 --- a/v0.6.0/notebooks.html +++ b/v0.6.0/notebooks.html @@ -14,7 +14,7 @@ - + docTR Notebooks - docTR documentation @@ -391,7 +391,7 @@

    docTR Notebooks - + diff --git a/v0.6.0/objects.inv b/v0.6.0/objects.inv index a22d2ce821026792e7b2f75cbac9aecf06cc0d89..c1700f291b7b6bc7252a625eb1acde3a63b67eca 100644 GIT binary patch delta 4090 zcmVE-{flJb$&E$#&Z~7KZnH3ZBzB-Bk-HmK|4yO&>?j;A&50 zIZ5wyAQF;Tp$HZSIj&W|M!#M^Nn<2H5+ngqwX#Iv|L(z?S&7#up!*R3hb(vqotp}EyrnZK7Dx@Y4_&W z<#ST(MrrRB4^xK50}AjqiKdRQ<-^_8hjGfUpKnJBAIIrvQ$L~~<0|^?>iY8G!{Wo$ z{W$C2a28IB2mv89!V>6`hku4()&hny#PkFkTMI2h+Zqm5PzIxp$YgCzSGO#oB)}nB z<>-%+yhMz@DHGt-NkK&o$-!=X=}GG zZ<&mOle5NJU`u8y18{!USR?F#a}zL%iu3QS)x#Rfbw=#&$*{DzfPbm5P2O+IjC$N$ zc&y1n*doUmhA5lSDSXXdj*3(J-*Xyi+l!m6e^S^Y;~+CGd$wRr+hS*GJ?vc@ZEYDC zEt`3UBh*|%Z4Gc)n|atJjkVR9NL2=4QD1WHagPQX?b(7X!lvcenp{a+{HG`mNmrsM zEzC={bzIZL(n^G9a(~$|%?$E!V@{AK?_(T%w=PW!dWoOcle2__Ndt5&{p;_ zijlJ|Dw=IFykA9oxaI4syID?1!_5?VnZ@?<>nO3W>^-TOQ(?y*pzEeJKSV5}A(4Lt z^A%sbJF*(19>s{ZTW%P>pe=0=b`z>Gu=_Z?s0q>@?|q!Aw0{Xz@7+EOD``U1`@9dq zN}3S$ZtX*`k{TjIFkI0hE*;B6Vpt^QK7Af5b{|trEvB!Fh9(%s=ws9KOd)}!G>XZ- zh^nJa6hO9k2N_ORIEzSW_F$D}D2#%lvvva0@YW0`@h(_pC{IvvHa`XruStyvZFM+X z#W3UJ@95A~c7JSazK3!Amq1s|4*YAZq+|yF^;VA2?oCgRI-vPjp1(O7>L}Afpk5sf zb$)UL6ir)c@2*LPctqT>rjq0pQSy8Q++n+&|FT*qM;XJ1h&0=gIQbZkI_nD8tK&r7 z8gj}o(?Igov^lPT8<>wDuMN!TaoXUF9iIvlN6B~Edw)}cR~Kzu(CMO$8xS<-#@RF9 z;_S-x6h@4_J$4~>xeLxf$O!5uR_UxEHNl?7pm79(E z=te8tgMYy>&aRRJ%5stWj>PO!oR4?o6D-N<4wLtBnN5b=;%I^^*KofM;iG&|?FmM~ zZYd#-{v7YlU4rg5qty>E3Gx0SP2=Z@jQ3fz-I261&nJQwI3dsSVk(Ag4ksc$!Tlai zWPB$3u|G+TVEi$TraJVwL=y?WrfBjK zy`8D%OmH_Q2tDTN7YU5nfztq~Gb{}^69Km&$;Ec2%sGRLy&A#IXd}lMW0fQ9%=RZG zE`QdIBs(Vz$&8C-CATxzop-oT9*yE+yq&9z2ojRvVz`l-xLf4_7n5C?jJxdy$;pXw zQjDZ>7jwpR$=$4uaWUV_C`K#_oZw>Aluo%D)+kQK8!6Zb!<=AgbV>ueG;K4vb1@{e z(ok7MWaGa_MHU6xb;=v|z?436K_heQrGL!wl0=8>*?37|1g=a&k=%@-UE_c*3v4iv zV7V!~P9ny#rdHPsomT0)#lxf?t@KkXr$EyxL~mi;z#`a?#l5tjNQ#3sIp$^l|^yw zmsokq{g~~)y6IV&&>6bI$%+&g^rsvM-Nm=1&$n>inq8(F7Ex zh>Z#Pkd^fp@@=EE8bh`6gsm$u-M2%Z-s^hy3rwn1HKkiiauk4op+JEFLCIDnCVLEV zH)y0QcF1QtKA(+9mtiXzfG_}K0M1x+z`gp-gl=2;4rWbjq!DXskIqg153!f%o5Jdi zTsJ}lq;R28I5IDu$w)k)tbfB%8#-fv8e?H*Gn?$2vb@MjHtC(sQ8`fZMTPQ}O6)&V7uF!UBdeBV?66D zw&g&Twkkw*^TR3Ctn@(BUmBpq(t#u_WP8DSg2c6)G(zki*eVX|dQjtBcH5BlY3I2| zHzgK8?&f3V3<&p?!9R0#%jcc&dY(0RHZZD$#cX>Rrd`gN1(23K>x(p z5k?j5Lsz>rhl4a#?PzCkPtWDNtWdtA^Me1@h+eDqI~yrVLi9t&NFAWMS`)FE_Rg<< z#KeBUsQpaTtA9)mZ0;mM7Z}rR!^Gjfsq&XCL`7X4m5P#(re@@ikAuu5%qu0J$`AZ4 z`QzU$9tf9od4SsOW^?xOL5XXiZ5a{!QG#+;rbYo8MKk1osvg&DlB7Q+`FFIA$o4Io zX1T@E?LRa=^w5tKX9dkHagzM7>Y5eT2+Qm>?G&c}P=Dd>Zg2nXZ4hK*BBTF~Atzz} z@BX^nlDDjs%R(qfK#~CA0Kxg#XNCBH!ZmCjn+)z5KMI+*p+7sLXAiB!tvMNJNgzvp z)MVZkB`jFf_3|mt^snekQnH|4koiZC48xZqm1t}lx=TrxmVoSFyrEfA5akvrTf2*2 z_@8S8Pk&WKxTDo#Fwk}9UjvmQe$=WZcbE0!J+^Vh{xhZ*M52_%F^8$ZYZnxwO zNe+-tFZMf~V+j(VB%qN1C0*-gK5J$^m+Q(0)_*i&AIG*|C>%Px>)W$Qhgj!`4V=J= z^StXJ!@aHz?eUK9_0aaSw?aeidV5mN7jZT3%xq{WP?fr&8WC@EPkjG2m}I^1A7TF8 zM+X>}eDZSl>DMoN@wJ{_qwNiAYq4}RwrF~QHtpX0+HTKl?$1o}+TN{sD4+t4Nh-pS zFL!xfeN$kPXu-zUHyB=fD`1j0HVS@od*Rgvs)?XRu&-??yaeuPr;&y`kpAlbGSU<{ zxgO~H+ZNKR@92eR_5FNn|Blzc)5T%r@qY|gYdg<7j#Gy=7Rh__#D}KoYz2%_xJ9mc zEn8-}@{U06*-OSFi9iyCsm6%eO@}0-N?;lu2>3@IlNG_DJj^&Y;#~3oINLbmR zjQ-;>_i$D*`BK265@7zbNz{sFP#?~(?sYLm$+bj%aWZtMY8vng1b@`` zC<1ifZN#HLI~2#>G(t|wRs@S~RA?k_<}3GgQ_YM;3%mmn`Tn`s@z|~cGuxH4zHxJt{Oo8sOeIr%NA3sgnJF<5?))11| z6FwU7Uh#x!)i|6z-lYggY!>1BmDII$*tP>uPJke8MlsS4@+A!x0j{<#bU^!;Y z!=w8)T9*mKoi+WUTK6j11vIRsR_o;8abB0f?z2Ca1HJCy*KfDJmF~ae)<4wx?X(7^ z%_gEN7Fd|lD`dJkcQ~2NXD9worzfX#aB>Dt&VTN`a(Flaq#RJieSg3ibH$jyv0%)B zDlQl&!jV^CPAtraAt&4uQJzyyRc+3}d>C?C)rJIK>sEBeuMFoST{Foqx z!S~jglMRpSfz(s9IC;3|kG$G%S}acJzv|^Z`wi2J_73T32T2p{a2bbwg8%>k delta 1868 zcmV-S2ebIcAgT|LI|DN=Fp)nzf2CSYZ`(K!z4KQH*kG}Hpb@8NQRLRfO*V_9$=YuA zXwVYv2$4jUq@1R|zWk6VlKNIpp``cTkeuOgNGBp8r;7-2#4u-ztB3U}$lor*1ThvY z1M}gTr^z2@0R)jtxc>HaGmY_ZwO;A=B-&>EaQkHvBP2BP1_XUt)H3{re~@WB#VjV- zoZg!T#~CB^kdW6dwV^(C^rm4FXCaC3j^XcxXksQU9EvRDf;6Vf0?Q)bzeAnV@P<}G zP=x71_VrFRCrus+X=~IBb;jZ}G#Mo^_Je9jP{WND35yhG;{7Me@d1TyNSLqwu*`?g z2?<{&a#m$)CT+o<$*bh1f0#d$Xd3xCPVh{-lDarhlJ4RZ9d$6y?Sj_Hqr>lu6f~JC zau;V)C*g5*J)N;YZ01}^@)7eLDx-3?z^h20)5)UCQ%5T(vjX#f(ZPwfaDuzmENlpL zuJayUZ&446YC?};d}I4?+)uJe|%GqVifHp!QTLf zgGEu}^f;4Qrl{#mxmC92-0_ZAyiRo|BaQHsfLB_nC@Ki`fZNzT;$f$bVOXACj2k85-XU zl=1i{-l^A91Sk&4e=&>IJcKmMcBkWU`C+%u=8B|x+F}gRGQ{33e@A1^ zl>8(_)}Ipx!70kvfzVVODM&)-tqcuWt=qem1?r=xIbnD*?+&H=2yLquh|e+pRWcJ1 zkCT}X8GblSX01^ck@QoZCvP*kpJ{x2<{4&eroa`+#5=}kf6{MInxdK9e+oR4`EQPo z{}sYheD1t$5HIMwAX#HJCqlO5hN9`+73-}?Fk9@!iL7&N!KT>Ix*Rg)1@ssTnldEZ z8uMf1ZDNlR>%yUtOUBKUCXF-EpLWLBYgHI$yd_ikkw(3wJj#^jj5FY;?=c2S zPGz9rv8bHHd7s9iSob89i%<c7_oe3s#6wq;JmU!U(a4tM$EVPV9u? z%{9EmKlnZ3;qyephBwvlDQ1P4H}iydd3m6>%3V*Tf4X7{7>_O!v=?U<*mBCP?o9wX zp;*Ag(X_f#^_b4>t3gA9{$vEon_UL>>i(p^1L}?i?29;wf$tPIhqf5m4zZj>hZeY3^uXK( zlqWOSqJ3I3NLV6Q9@Ww^ZXKm(p;s*u-cJ#|Gb)jF^f&Hvdc(dX8?1Y>?HP#Tm8j-! z&>vr#Y@wZ0<8rNJqG=jaGA_+{0xmLJV4dbWe;2DeWGH#i=B-G$U0(*~c2uU!U|j=% zG1;F_Dghf$i%@LRc*rHXXEl7)PynubtEhLB0xu8%&P0+TQ2Yro~2f=5J$*@C6wV@5Y_sON4zSSI3 zDpB_qrR4u4q^D-rh<7Fe0B^oM0Ff3UNkgtmlf{syZLnscuz-!foTJ=E_%`I`n1 zvmo{W63V#{;UV&1P;Vujli{+UV8NR8*dy6yuQOQ=ShRVMy|U6O#ovpV#k?7%{?d80 z!N8j4f%$)Y6Xk2>y20+_&|YR?s~v6KpgW3Vwt8FD!mnFp0K*8d_ue*|J2o z?K;3K0?Qoh#ZKTCjDWuqxp|A^e~EIDP*C`cdlr{LLkqoakpP9tOAMWGr1y(XO@)LO z@|&V=r!b_=gb9mIRfOCz}Q~EsFI53>6&YH - - - - - - - - - - Python Module Index - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    - -
    -

    Python Module Index

    - -
    - - - - - - - - - - - -
     
    d
    - doctr -
    - -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.6.0/search.html b/v0.6.0/search.html index 73772822d2..d050f5eac7 100644 --- a/v0.6.0/search.html +++ b/v0.6.0/search.html @@ -14,7 +14,7 @@ - + Search - docTR documentation @@ -226,20 +226,42 @@ - - + + diff --git a/v0.6.0/searchindex.js b/v0.6.0/searchindex.js index 803f4f4bcf..6f154115ab 100644 --- a/v0.6.0/searchindex.js +++ b/v0.6.0/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles": {"Artefact": [[2, "artefact"]], "Available Datasets": [[1, "available-datasets"]], "Block": [[2, "block"]], "Build & train your predictor": [[3, "build-train-your-predictor"]], "Changelog": [[0, null]], "Composing transformations": [[6, "composing-transformations"]], "Data Loading": [[1, "data-loading"]], "Detection models": [[5, "detection-models"]], "Detection predictors": [[5, "detection-predictors"]], "DocTR Vocabs": [[1, "id1"]], "DocTR: Document Text Recognition": [[3, null]], "Document": [[2, "document"]], "Document structure": [[2, "document-structure"]], "End-to-End OCR": [[5, "end-to-end-ocr"]], "File reading": [[2, "file-reading"]], "Getting Started": [[3, "getting-started"]], "Installation": [[4, null]], "Line": [[2, "line"]], "Main Features": [[3, "main-features"]], "Model compression": [[5, "model-compression"]], "Model export": [[5, "model-export"]], "Model zoo": [[3, "model-zoo"]], "Notes": [[3, null]], "Package Reference": [[3, null]], "Page": [[2, "page"]], "Pre-processing for detection": [[5, "pre-processing-for-detection"]], "Pre-processing for recognition": [[5, "pre-processing-for-recognition"]], "Prerequisites": [[4, "prerequisites"]], "Recognition models": [[5, "recognition-models"]], "Recognition predictors": [[5, "recognition-predictors"]], "Supported Vocabs": [[1, "supported-vocabs"]], "Supported datasets": [[3, "supported-datasets"]], "Supported transformations": [[6, "supported-transformations"]], "Task evaluation": [[7, "task-evaluation"]], "Text Detection": [[5, "text-detection"]], "Text Recognition": [[5, "text-recognition"]], "Text detection models": [[3, "text-detection-models"]], "Text recognition model zoo": [[5, "id2"]], "Text recognition models": [[3, "text-recognition-models"]], "Two-stage approaches": [[5, "two-stage-approaches"]], "Using SavedModel": [[5, "using-savedmodel"]], "Via Git": [[4, "via-git"]], "Via Python Package": [[4, "via-python-package"]], "Visualization": [[7, "visualization"]], "Word": [[2, "word"]], "doctr.datasets": [[1, null]], "doctr.documents": [[2, null]], "doctr.models": [[5, null]], "doctr.transforms": [[6, null]], "doctr.utils": [[7, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]]}, "docnames": ["changelog", "datasets", "documents", "index", "installing", "models", "transforms", "utils"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "datasets.rst", "documents.rst", "index.rst", "installing.rst", "models.rst", "transforms.rst", "utils.rst"], "indexentries": {"artefact (class in doctr.documents)": [[2, "doctr.documents.Artefact", false]], "as_images() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.as_images", false]], "block (class in doctr.documents)": [[2, "doctr.documents.Block", false]], "colorinversion (class in doctr.transforms)": [[6, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[6, "doctr.transforms.Compose", false]], "convert_to_fp16() (in module doctr.models.export)": [[5, "doctr.models.export.convert_to_fp16", false]], "convert_to_tflite() (in module doctr.models.export)": [[5, "doctr.models.export.convert_to_tflite", false]], "cord (class in doctr.datasets)": [[1, "doctr.datasets.CORD", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.crnn_vgg16_bn", false]], "dataloader (class in doctr.datasets.loader)": [[1, "doctr.datasets.loader.DataLoader", false]], "db_resnet50() (in module doctr.models.detection)": [[5, "doctr.models.detection.db_resnet50", false]], "detection_predictor() (in module doctr.models.detection)": [[5, "doctr.models.detection.detection_predictor", false]], "document (class in doctr.documents)": [[2, "doctr.documents.Document", false]], "documentfile (class in doctr.documents)": [[2, "doctr.documents.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[1, "doctr.datasets.encode_sequences", false]], "from_images() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_images", false]], "from_pdf() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_pdf", false]], "from_url() (doctr.documents.documentfile class method)": [[2, "doctr.documents.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[1, "doctr.datasets.FUNSD", false]], "get_artefacts() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.get_artefacts", false]], "get_words() (doctr.documents.pdf method)": [[2, "doctr.documents.PDF.get_words", false]], "lambdatransformation (class in doctr.transforms)": [[6, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.documents)": [[2, "doctr.documents.Line", false]], "linknet16() (in module doctr.models.detection)": [[5, "doctr.models.detection.linknet16", false]], "localizationconfusion (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.LocalizationConfusion", false]], "master() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.master", false]], "normalize (class in doctr.transforms)": [[6, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models.zoo)": [[5, "doctr.models.zoo.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[1, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[6, "doctr.transforms.OneOf", false]], "page (class in doctr.documents)": [[2, "doctr.documents.Page", false]], "pdf (class in doctr.documents)": [[2, "doctr.documents.PDF", false]], "quantize_model() (in module doctr.models.export)": [[5, "doctr.models.export.quantize_model", false]], "randomapply (class in doctr.transforms)": [[6, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[6, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[6, "doctr.transforms.RandomContrast", false]], "randomgamma (class in doctr.transforms)": [[6, "doctr.transforms.RandomGamma", false]], "randomhue (class in doctr.transforms)": [[6, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[6, "doctr.transforms.RandomJpegQuality", false]], "randomsaturation (class in doctr.transforms)": [[6, "doctr.transforms.RandomSaturation", false]], "read_html() (in module doctr.documents)": [[2, "doctr.documents.read_html", false]], "read_img() (in module doctr.documents)": [[2, "doctr.documents.read_img", false]], "read_pdf() (in module doctr.documents)": [[2, "doctr.documents.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.recognition_predictor", false]], "resize (class in doctr.transforms)": [[6, "doctr.transforms.Resize", false]], "sar_resnet31() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.sar_resnet31", false]], "sar_vgg16_bn() (in module doctr.models.recognition)": [[5, "doctr.models.recognition.sar_vgg16_bn", false]], "show() (doctr.documents.document method)": [[2, "doctr.documents.Document.show", false]], "show() (doctr.documents.page method)": [[2, "doctr.documents.Page.show", false]], "sroie (class in doctr.datasets)": [[1, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[7, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[7, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[7, "doctr.utils.metrics.TextMatch.summary", false]], "textmatch (class in doctr.utils.metrics)": [[7, "doctr.utils.metrics.TextMatch", false]], "togray (class in doctr.transforms)": [[6, "doctr.transforms.ToGray", false]], "visiondataset (class in doctr.datasets.datasets)": [[1, "doctr.datasets.datasets.VisionDataset", false]], "visualize_page() (in module doctr.utils.visualization)": [[7, "doctr.utils.visualization.visualize_page", false]], "word (class in doctr.documents)": [[2, "doctr.documents.Word", false]]}, "objects": {"doctr.datasets": [[1, 0, 1, "", "CORD"], [1, 0, 1, "", "FUNSD"], [1, 0, 1, "", "OCRDataset"], [1, 0, 1, "", "SROIE"], [1, 1, 1, "", "encode_sequences"]], "doctr.datasets.datasets": [[1, 0, 1, "", "VisionDataset"]], "doctr.datasets.loader": [[1, 0, 1, "", "DataLoader"]], "doctr.documents": [[2, 0, 1, "", "Artefact"], [2, 0, 1, "", "Block"], [2, 0, 1, "", "Document"], [2, 0, 1, "", "DocumentFile"], [2, 0, 1, "", "Line"], [2, 0, 1, "", "PDF"], [2, 0, 1, "", "Page"], [2, 0, 1, "", "Word"], [2, 1, 1, "", "read_html"], [2, 1, 1, "", "read_img"], [2, 1, 1, "", "read_pdf"]], "doctr.documents.Document": [[2, 2, 1, "", "show"]], "doctr.documents.DocumentFile": [[2, 2, 1, "", "from_images"], [2, 2, 1, "", "from_pdf"], [2, 2, 1, "", "from_url"]], "doctr.documents.PDF": [[2, 2, 1, "", "as_images"], [2, 2, 1, "", "get_artefacts"], [2, 2, 1, "", "get_words"]], "doctr.documents.Page": [[2, 2, 1, "", "show"]], "doctr.models.detection": [[5, 1, 1, "", "db_resnet50"], [5, 1, 1, "", "detection_predictor"], [5, 1, 1, "", "linknet16"]], "doctr.models.export": [[5, 1, 1, "", "convert_to_fp16"], [5, 1, 1, "", "convert_to_tflite"], [5, 1, 1, "", "quantize_model"]], "doctr.models.recognition": [[5, 1, 1, "", "crnn_vgg16_bn"], [5, 1, 1, "", "master"], [5, 1, 1, "", "recognition_predictor"], [5, 1, 1, "", "sar_resnet31"], [5, 1, 1, "", "sar_vgg16_bn"]], "doctr.models.zoo": [[5, 1, 1, "", "ocr_predictor"]], "doctr.transforms": [[6, 0, 1, "", "ColorInversion"], [6, 0, 1, "", "Compose"], [6, 0, 1, "", "LambdaTransformation"], [6, 0, 1, "", "Normalize"], [6, 0, 1, "", "OneOf"], [6, 0, 1, "", "RandomApply"], [6, 0, 1, "", "RandomBrightness"], [6, 0, 1, "", "RandomContrast"], [6, 0, 1, "", "RandomGamma"], [6, 0, 1, "", "RandomHue"], [6, 0, 1, "", "RandomJpegQuality"], [6, 0, 1, "", "RandomSaturation"], [6, 0, 1, "", "Resize"], [6, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[7, 0, 1, "", "LocalizationConfusion"], [7, 0, 1, "", "OCRMetric"], [7, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.LocalizationConfusion": [[7, 2, 1, "", "summary"]], "doctr.utils.metrics.OCRMetric": [[7, 2, 1, "", "summary"]], "doctr.utils.metrics.TextMatch": [[7, 2, 1, "", "summary"]], "doctr.utils.visualization": [[7, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [2, 7], "0": [1, 3, 5, 6, 7], "00": 5, "01": 5, "0123456789": 1, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 1, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": [], "02": 5, "02562": 5, "03": 3, "035": [], "0361328125": [], "04": [], "05": 3, "06": [], "06640625": [], "07": [], "08": 5, "09": [], "0966796875": [], "1": [1, 3, 5, 6, 7], "10": [1, 5, 7], "100": [5, 6, 7], "1000": 5, "101": [], "1024": [5, 7], "104": [], "106": [], "108": [], "1095": [], "11": 3, "110": 7, "1107": [], "114": [], "115": [], "1156": [], "116": [], "118": [], "11800h": [], "11th": [], "12": 5, "120": [], "123": [], "126": [], "1268": [], "128": 5, "13": 5, "130": [], "13068": [], "131": [], "1337891": [], "1357421875": [], "1396484375": [], "14": 5, "1420": [], "14470v1": [], "149": [], "15": 5, "150": 7, "154": 1, "1552": [], "16": 5, "160": 5, "1630859375": [], "1684": [], "16x16": [], "17": [], "1778": [], "1782": [], "18": 3, "185546875": [], "19": 5, "1900": [], "1910": 5, "19342": [], "19370": [], "195": [], "19598": [], "199": 5, "1999": [], "1m": 5, "2": [3, 5, 6], "20": 5, "200": 7, "2000": [], "2003": [], "2012": [], "2013": [], "2015": [], "2019": 3, "2021": 3, "2023": [], "207901": [], "21": 5, "2103": [], "2186": [], "21888": [], "22": [], "224": [5, 6], "225": 6, "22672": [], "229": 6, "23": [], "233": [], "236": [], "24": [], "246": [], "249": [], "25": 5, "2504": [], "255": [5, 6, 7], "256": 5, "257": [], "26": [], "26032": [], "264": [], "27": 5, "2700": [], "2710": [], "2749": [], "28": 3, "287": [], "29": 5, "296": [], "299": [], "2d": [], "3": [2, 3, 4, 5, 6, 7], "30": 5, "300": [], "3000": [], "301": [], "30595": 5, "30ghz": [], "31": 5, "32": [1, 5, 6], "3232421875": [], "33": [], "33402": [], "33608": [], "34": [], "340": [], "3456": [], "3515625": [], "36": [], "360": [], "37": [], "38": [], "39": 5, "4": [], "40": [], "406": 6, "41": [], "42": [], "43": 5, "44": [], "45": [], "456": 6, "46": 5, "47": 5, "472": [], "48": 5, "485": 6, "49": 5, "49377": [], "5": [1, 6, 7], "50": 5, "51": [], "51171875": [], "512": [], "52": [1, 5], "529": [], "53": 5, "533": [], "54": [], "540": [], "5478515625": [], "55": [], "56": [], "57": [], "58": [], "580": [], "5810546875": [], "583": [], "59": 5, "595": [], "597": [], "5k": [], "5m": 5, "6": [4, 5, 6], "60": 6, "600": [5, 7], "61": 5, "611": [], "62": 5, "625": [], "626": [], "629": [], "63": 5, "630": [], "64": [5, 6], "640": [], "641": [], "647": [], "65": 5, "66": 5, "660": [], "664": [], "666": [], "67": 5, "672": [], "68": 5, "689": [], "69": 5, "693": [], "694": [], "695": [], "6m": [], "7": 5, "70": [5, 7], "700": [], "701": [], "702": [], "707470": [], "71": [], "7100000": [], "713": [], "7141797": [], "7149": [], "72": [], "72dpi": [], "73": [], "73257": [], "733": [], "74": 5, "745": [], "75": 5, "753": [], "7581382": [], "76": [], "77": 5, "772": [], "772875": [], "78": 5, "780": [], "781": [], "783": [], "785": [], "789": [], "79": 5, "793533": [], "796": [], "798": [], "7m": [], "8": [5, 6], "80": [], "800": [5, 7], "81": 5, "817": [], "82": 5, "8275l": 5, "83": 5, "830": [], "84": [], "849": [], "85": 5, "8564453125": [], "857": [], "85875": [], "86": 5, "860": [], "8603515625": [], "862": [], "863": [], "87": 5, "8707": [], "875": [], "88": [], "89": 5, "8m": 5, "9": [], "90": 5, "90k": [], "90kdict32px": [], "91": 5, "913": [], "914085328578949": [], "917": [], "92": 5, "921": [], "93": [], "94": [], "95": 7, "9578408598899841": [], "96": 1, "97": [], "98": [], "99": [], "9949972033500671": [], "A": [1, 2, 3, 5], "And": 5, "As": [], "Be": [], "Being": [], "By": [], "For": [4, 5], "If": [2, 4, 5], "In": [1, 5], "It": 6, "Its": 5, "No": [], "Of": 1, "Or": [], "The": [1, 2, 5, 7], "Then": 5, "To": [], "_": [1, 5], "__call__": [], "_build": [], "_i": 7, "ab": [], "abc": [], "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 1, "abdef": [], "abl": [], "about": 5, "abov": 5, "abstract": 1, "abstractdataset": [], "abus": [], "accent": [], "accept": [], "access": [1, 2, 3], "account": [], "accur": [], "accuraci": 7, "achiev": [], "act": [], "action": [], "activ": [], "ad": 6, "adapt": [], "add": [6, 7], "add_hook": [], "add_label": 7, "addit": [], "addition": 5, "address": 2, "adjust": 6, "advanc": [], "advantag": [], "advis": [], "aesthet": [], "affect": [], "after": [], "ag": [], "again": [], "aggreg": [1, 7], "aggress": [], "align": 2, "all": [1, 2, 3, 5, 6, 7], "allow": [], "along": 5, "alreadi": [], "also": [], "alwai": [], "an": [1, 2, 3, 5, 7], "analysi": [2, 5], "ancient_greek": [], "andrej": [], "angl": 2, "ani": [1, 2, 3, 5, 6, 7], "annot": 2, "anot": [], "anoth": [1, 4, 5], "answer": [], "anyascii": [], "anyon": 3, "anyth": [], "api": [], "apolog": [], "apologi": [], "app": [], "appear": [], "appli": [1, 6], "applic": 5, "appoint": [], "appreci": [], "appropri": [], "ar": [1, 2, 4, 5, 6, 7], "arab": [], "arabic_diacrit": [], "arabic_lett": [], "arabic_punctu": [], "arbitrarili": [], "arch": 5, "architectur": [3, 5], "archiv": [], "area": [], "argument": [1, 2], "around": 5, "arrai": [2, 7], "art": 3, "artefact": 7, "artefact_typ": 2, "articl": [], "artifici": [], "arxiv": 5, "as_imag": 2, "asarrai": 7, "ascii_lett": 1, "aspect": [3, 6], "assess": 7, "assign": 7, "associ": 2, "assum": [], "assume_straight_pag": [], "astyp": [5, 7], "attack": [], "attend": [3, 5], "attent": [], "autoclass": [], "autom": 3, "automat": [], "autoregress": [], "avail": [3, 5, 6], "averag": [5, 6], "avoid": [], "aw": [3, 5], "awar": [], "azur": [], "b": 7, "b_j": 7, "back": [], "backbon": 5, "backend": 5, "background": [], "bangla": [], "bar": [], "bar_cod": [], "baranovskij": [], "base": 5, "baselin": 5, "batch": [1, 5, 6], "batch_siz": 1, "bblanchon": [], "bbox": [], "becaus": [], "been": [5, 7], "befor": 1, "begin": 7, "behavior": [], "being": [5, 7], "belong": [], "benchmark": [], "best": [], "beta": 3, "better": [], "between": [6, 7], "bgr": 2, "bilinear": [5, 6], "bin_thresh": [], "binar": [3, 5], "binari": 2, "bit": [], "block": [5, 7], "block_1_1": [], "blur": [], "bmvc": [], "bn": [], "bodi": [], "bool": [1, 2, 5, 6, 7], "boolean": [], "both": [3, 5, 6], "bottom": [], "bound": [1, 2, 6, 7], "box": [1, 2, 7], "box_thresh": [], "brew": 4, "bright": 6, "browser": [], "build": [], "built": [], "byte": [2, 5], "c": [], "c5": 5, "c_j": [], "cach": [], "cache_sampl": [], "cairo": 4, "call": [], "callabl": [1, 6], "can": [1, 4, 5], "capabl": 5, "case": [1, 7], "cf": 5, "cfg": [], "challeng": [], "challenge2_test_task12_imag": [], "challenge2_test_task1_gt": [], "challenge2_training_task12_imag": [], "challenge2_training_task1_gt": [], "chang": [], "changelog": 3, "channel": [2, 5, 6], "channel_prior": [], "channelshuffl": [], "charact": [1, 2, 3, 5, 7], "charactergener": [], "characterist": [], "charg": 5, "charset": [], "chart": 2, "check": [], "checkpoint": [], "chip": [], "christian": [], "ci": [], "clarifi": [], "clariti": [], "class": [1, 2, 6, 7], "class_nam": [], "classif": [], "classmethod": 2, "clear": [], "clone": 4, "close": [], "co": [], "code": [2, 3], "codecov": [], "colab": [], "collate_fn": [], "collect": 2, "color": 6, "colorinvers": 6, "column": 2, "com": [2, 4], "combin": 5, "command": [], "comment": [], "commit": [], "common": [6, 7], "commun": [], "compar": 3, "comparison": 7, "competit": 1, "compil": [], "complaint": [], "complementari": 7, "complet": [], "compon": 5, "compos": [1, 3, 5], "comprehens": [], "comput": [5, 7], "conf_threshold": [], "confid": 2, "config": [], "configur": [], "confus": 7, "consecut": [5, 6], "consequ": [], "consid": [1, 2, 7], "consist": [], "consolid": [1, 3], "constant": 6, "construct": [], "contact": [], "contain": [], "content": [1, 2], "context": [], "contib": [], "continu": [], "contrast": 6, "contrast_factor": 6, "contrib": [], "contribut": [], "contributor": [], "conv_sequ": 5, "convers": 2, "convert": [2, 5, 6], "convert_page_to_numpi": 2, "convert_to_fp16": 5, "convert_to_tflit": 5, "convolut": 3, "cool": [], "coordin": 2, "cord": [1, 3, 5], "core": 7, "corner": [], "correct": 6, "correspond": [4, 5], "could": [], "counterpart": 7, "cover": [], "coverag": [], "cpu": [3, 5], "creat": [], "crnn": [3, 5], "crnn_mobilenet_v3_larg": [], "crnn_mobilenet_v3_smal": [], "crnn_resnet31": 5, "crnn_vgg16_bn": 5, "crop": 5, "crop_orient": [], "crop_orientation_predictor": [], "crop_param": [], "cuda": [], "currenc": 1, "current": [], "custom": [], "custom_crop_orientation_model": [], "custom_page_orientation_model": [], "customhook": [], "cvit": [], "czczup": [], "czech": [], "d": [], "daili": 3, "danish": [], "data": [2, 3, 5, 6, 7], "dataload": 1, "dataset": 5, "dataset_info": [], "date": [], "db": [], "db_crnn_resnet": 5, "db_crnn_vgg": 5, "db_mobilenet_v3_larg": [], "db_resnet34": [], "db_resnet50": 5, "db_sar_resnet": 5, "db_sar_vgg": 5, "dbnet": [3, 5], "deal": [], "decis": [], "decod": 2, "decode_img_as_tensor": [], "dedic": [], "deem": [], "deep": 5, "def": [], "default": [2, 5], "defer": 1, "defin": 7, "deform": 5, "degre": [], "degress": 2, "delet": [], "delimit": [], "delta": 6, "demo": [], "demonstr": [], "depend": [3, 4], "deploi": [], "deploy": [], "derogatori": [], "describ": 5, "descript": [], "design": 6, "desir": [], "det_arch": 5, "det_b": [], "det_model": [], "det_param": [], "det_predictor": [], "detail": [], "detect": [], "detect_languag": [], "detect_orient": [], "detection_predictor": 5, "detection_task": [], "detectiondataset": [], "detectionmetr": [], "detectionpredictor": 5, "detector": [], "deterior": [], "determin": [], "dev": [], "develop": [], "developp": 4, "deviat": 6, "devic": [], "dict": [2, 7], "dictionari": [2, 7], "differ": [], "differenti": [3, 5], "digit": 1, "dimens": [2, 5, 7], "dimension": 6, "direct": [], "directli": 5, "directori": [], "disabl": [], "disable_crop_orient": [], "disable_page_orient": [], "disclaim": 5, "discuss": [], "disk": [], "disparag": [], "displai": [2, 7], "display_artefact": 7, "distanc": [], "distribut": 6, "div": [], "divers": [], "divid": [], "do": 4, "doc": [2, 5], "docartefact": [], "docstr": [], "doctr": 4, "doctr_cache_dir": [], "doctr_multiprocessing_dis": [], "document": [1, 5, 7], "documentbuild": [], "documentfil": 2, "doesn": [], "don": [], "done": 6, "download": 1, "downsiz": [], "draw": 6, "drop": 1, "drop_last": 1, "dtype": 5, "dual": [], "dummi": [], "dummy_img": [], "dummy_input": [], "dure": [], "dutch": [], "dynam": [], "dynamic_seq_length": [], "e": [2, 4], "each": [1, 2, 3, 5, 6, 7], "eas": [], "easi": [3, 7], "easier": 5, "easili": [2, 5, 7], "econom": [], "edit": [], "educ": [], "effect": [], "effici": [1, 5], "either": 5, "element": [1, 2, 5], "els": [], "email": [], "empathi": [], "en": [], "enabl": 2, "enclos": 2, "encod": [1, 2, 5], "encode_sequ": 1, "encount": [], "encrypt": [], "end": [1, 3, 7], "english": [], "enough": 5, "ensur": [], "entir": 2, "entri": [], "environ": [], "eo": 1, "equiv": [], "error": [], "estim": [], "etc": 2, "ethnic": [], "evalu": [1, 3, 5], "event": [], "everyon": [], "everyth": [], "exact": 7, "exactmatch": [], "exampl": [1, 2, 5, 6, 7], "exchang": [], "exclud": 5, "execut": [], "exist": [], "expand": [], "expect": [2, 5, 6], "experi": 5, "explan": 5, "explicit": [], "exploit": 5, "export": [2, 3, 7], "export_as_straight_box": [], "export_as_xml": [], "export_model_to_onnx": [], "express": 6, "extens": 2, "extern": [], "extra": 4, "extract": [1, 3], "extract_arch": 1, "extractor": 5, "f_": 7, "f_a": 7, "factor": 6, "fair": [], "fairli": [], "fals": [1, 5, 6, 7], "faq": [], "fascan": [], "fast": 1, "fast_bas": [], "fast_smal": [], "fast_tini": [], "faster": [], "fasterrcnn_mobilenet_v3_large_fpn": [], "favorit": [], "featur": [5, 7], "feed": 5, "feedback": [], "feel": [], "felix92": [], "few": 4, "figsiz": 7, "figur": 7, "file": [1, 3], "file_hash": 1, "file_nam": 1, "final": [], "find": 4, "fine": 3, "finnish": [], "first": [], "firsthand": [], "fit": [], "fitz": 2, "flag": [], "flexibl": 7, "flip": [], "float": [2, 6, 7], "float32": 5, "fn": 6, "focu": [], "focus": [], "folder": [1, 5], "follow": [1, 4, 5, 6, 7], "font": [], "font_famili": [], "foral": 7, "forc": [], "forg": [], "form": [1, 3], "format": [2, 5], "forpost": [1, 3], "forum": [], "found": [], "fp": 5, "fp16": 5, "frac": 7, "frame": 5, "framework": 1, "free": [], "french": [1, 5], "friendli": 3, "from": [1, 2, 3, 5, 6, 7], "from_hub": [], "from_imag": 2, "from_pdf": 2, "from_url": 2, "full": [1, 5, 7], "fulli": [], "function": [5, 6, 7], "funsd": [1, 3, 5], "further": [], "futur": [], "g": 2, "g_": 7, "g_x": 7, "gallagh": [], "gamma": 6, "gaussian": 6, "gaussianblur": [], "gaussiannois": [], "gdk": 4, "gen": [], "gender": [], "gener": [], "generic_cyrillic_lett": [], "geometri": 2, "geq": 7, "german": [], "get": 2, "get_artefact": 2, "get_word": 2, "gettextword": 2, "git": 3, "github": 4, "give": [], "given": [1, 2, 5, 7], "global": [], "go": [], "good": [], "googl": [], "googlevis": 3, "gpu": 3, "gracefulli": [], "graph": 2, "grayscal": 6, "ground": 7, "groung": [], "group": [], "gt": [], "gt_box": [], "gt_label": [], "gtk": 4, "guid": [], "guidanc": [], "gvision": 5, "h": 2, "h_": 7, "ha": [1, 7], "half": 5, "handl": 1, "handwrit": [], "handwritten": [], "harass": [], "hardwar": [], "harm": [], "hat": 7, "have": [1, 5, 7], "head": [], "healthi": [], "hebrew": [], "height": 2, "hello": 7, "help": [], "here": [1, 4, 6], "hf": [], "hf_hub_download": [], "high": 2, "higher": 4, "hindi": [], "hindi_digit": [], "hocr": [], "hook": [], "horizont": 2, "hous": [], "how": [], "howev": [], "hsv": 6, "html": [], "http": [2, 4, 5], "hub": [], "hue": 6, "huggingfac": [], "hw": [], "i": [1, 2, 5, 6, 7], "i7": [], "ibrahimov": [], "ic03": [], "ic13": [], "icdar": 3, "icdar2019": 1, "id": 5, "ident": [], "identifi": [3, 5], "ignor": [], "ignore_acc": [], "ignore_cas": [], "iiit": [], "iiit5k": [], "iiithw": [], "imag": [1, 2, 5, 6, 7], "imagenet": [], "imageri": [], "images_90k_norm": [], "img": [1, 6], "img_cont": [], "img_fold": 1, "img_path": [], "img_transform": [], "imgur5k": [], "imgur5k_annot": [], "imlist": [], "impact": [], "implement": [1, 2, 5, 6, 7], "import": [1, 2, 5, 6, 7], "improv": [], "inappropri": [], "incid": [], "includ": [4, 5], "inclus": [], "increas": 6, "independ": [], "index": 2, "indic": 7, "individu": [], "infer": [3, 6], "inform": [1, 3, 5], "inherit": [1, 5], "input": [2, 5, 6], "input_crop": [], "input_pag": [5, 7], "input_shap": 5, "input_t": 5, "input_tensor": 5, "inspir": 6, "instal": 3, "instanc": 5, "instanti": 5, "instead": [1, 2], "insult": [], "int": [1, 2, 5, 6, 7], "int64": [], "integ": 7, "integr": 3, "intel": [], "interact": [2, 7], "interfac": [], "interoper": [], "interpol": [5, 6], "interpret": [1, 2], "intersect": 7, "invert": 6, "investig": [], "invis": [], "invoic": 5, "involv": 5, "io": [], "iou": 7, "iou_thresh": 7, "iou_threshold": [], "irregular": 5, "isn": 1, "issu": [], "italian": [], "iter": 1, "its": [1, 2, 5, 7], "itself": [], "j": 7, "jame": [], "job": [], "join": [], "jpeg": 6, "jpegqual": 6, "jpg": [1, 2], "json": [], "json_output": [], "jump": [], "just": 5, "kei": [], "kera": 5, "kernel": [], "kernel_s": 5, "kernel_shap": [], "keywoard": [], "keyword": [1, 2], "kie": [], "kie_predictor": [], "kiepredictor": [], "kind": [], "know": [], "kwarg": [1, 2, 5, 7], "l": 7, "l_j": 7, "label": [1, 7], "label_fil": 1, "label_fold": [], "label_path": [], "labels_path": [], "ladder": [], "lambda": 6, "lambdatransform": 6, "lang": [], "languag": [2, 3], "larg": [], "largest": 7, "last": [1, 4, 5], "latenc": [], "later": [], "latest": 4, "latin": 1, "layer": [], "layout": [], "lead": [], "leader": [], "learn": 5, "least": 4, "left": 7, "legacy_french": [], "length": 1, "less": [], "let": 5, "letter": [], "level": [5, 7], "levenshtein": [], "leverag": [], "lf": [], "libffi": 4, "librari": 4, "light": 3, "lightweight": [], "like": [], "limits_": 7, "line": [3, 7], "line_1_1": [], "link": [], "linknet": [3, 5], "linknet16": 5, "linknet_resnet18": [], "linknet_resnet34": [], "linknet_resnet50": [], "linux": 4, "list": [1, 2, 6], "ll": 7, "load": [3, 5], "load_state_dict": [], "load_weight": [], "loader": 1, "loc_pr": [], "local": [1, 3, 5, 7], "localis": [], "localizationconfus": 7, "locat": [], "login": [], "login_to_hub": [], "logo": 2, "love": [], "lower": [6, 7], "m": [5, 7], "m1": [], "macbook": [], "machin": [], "maco": 4, "made": 3, "magc_resnet31": [], "mai": [], "mail": [], "main": [], "maintain": 3, "mainten": [], "make": [5, 7], "mani": [], "manipul": [], "map": 1, "map_loc": [], "mask_shap": 7, "master": [3, 5], "match": [3, 7], "mathcal": 7, "matplotlib": 7, "max": 7, "max_angl": [], "max_area": [], "max_char": [], "max_delta": 6, "max_dist": [], "max_gain": 6, "max_gamma": 6, "max_qual": 6, "max_ratio": [], "maximum": 1, "maxval": [5, 6], "mbox": 7, "mean": [6, 7], "meaniou": 7, "meant": 2, "measur": 5, "media": [], "median": [], "meet": [], "member": [], "memori": [], "mention": [], "merg": [], "messag": [], "meta": [], "metadata": [], "metal": [], "method": 6, "metric": [5, 7], "middl": [], "might": 5, "min": [], "min_area": [], "min_char": [], "min_gain": 6, "min_gamma": 6, "min_qual": 6, "min_ratio": [], "min_val": 6, "minde": 4, "minim": [], "minimalist": [], "minimum": 7, "minval": 6, "miss": [], "mistak": [], "mix": 3, "mixed_float16": [], "mixed_precis": [], "mjsynth": [], "mnt": [], "mobilenet": [], "mobilenet_v3_larg": [], "mobilenet_v3_large_r": [], "mobilenet_v3_smal": [], "mobilenet_v3_small_crop_orient": [], "mobilenet_v3_small_page_orient": [], "mobilenet_v3_small_r": [], "mobilenetv3": [], "modal": [], "mode": 4, "model": [1, 7], "model_nam": [], "model_path": [], "moder": [], "modif": [], "modifi": [], "modul": [2, 5, 6, 7], "more": [], "moscardi": [], "most": 5, "mozilla": [], "multi": 3, "multilingu": [], "multipl": [1, 2, 6], "multipli": 6, "multiprocess": [], "my": [], "my_awesome_model": [], "my_hook": [], "n": [1, 5, 7], "na": [], "name": [1, 5], "nation": [], "natur": 3, "ndarrai": [1, 2, 7], "necessari": [], "need": [4, 7], "neg": 6, "nest": [], "nestedobject": [], "netraj": [], "network": [3, 5], "neural": [3, 5], "new": [], "newer": [], "next": 1, "nois": [], "noisi": [1, 3], "non": [2, 3, 6, 7], "none": [1, 2, 7], "normal": [5, 6], "norwegian": [], "note": 0, "now": 3, "np": [5, 7], "num_output_channel": [], "num_sampl": [], "number": [1, 6, 7], "numpi": [2, 5, 7], "o": 4, "obb": [], "obj_detect": [], "object": 1, "objectness_scor": [], "oblig": [], "obtain": [], "occupi": [], "ocr": [1, 3, 7], "ocr_carea": [], "ocr_db_crnn": 7, "ocr_lin": [], "ocr_pag": [], "ocr_par": [], "ocr_predictor": 5, "ocrdataset": 1, "ocrmetr": 7, "ocrpredictor": 5, "ocrx_word": [], "offens": [], "offici": [], "offlin": [], "offset": 6, "onc": 5, "one": [1, 5, 6], "oneof": 6, "ones": 1, "onli": [6, 7], "onlin": [], "onnx": [], "onnxruntim": [], "onnxtr": [], "opac": [], "opacity_rang": [], "open": [], "opinion": [], "optic": [3, 5], "optim": 3, "option": 1, "order": [1, 2, 5], "org": 5, "organ": 2, "orient": 2, "orientationpredictor": [], "other": [], "otherwis": 7, "our": 5, "out": [5, 6, 7], "outpout": [], "output": [2, 5, 6], "output_s": [2, 6], "outsid": [], "over": [4, 7], "overal": [], "overlai": 2, "overview": [], "overwrit": 1, "overwritten": [], "own": 3, "p": 6, "packag": 7, "pad": [1, 5, 6], "page": [4, 5, 7], "page1": 2, "page2": 2, "page_1": [], "page_idx": 2, "page_orientation_predictor": [], "page_param": [], "pair": 7, "pango": 4, "paper": 5, "par_1_1": [], "paragraph": [], "paragraph_break": [], "parallel": [], "param": [5, 6], "paramet": [1, 2, 3, 5, 6, 7], "pars": [1, 3], "parseq": [], "part": 6, "parti": [], "partial": [], "particip": [], "pass": [1, 5], "password": [], "patch": [], "path": [1, 2, 5], "path_to_checkpoint": [], "path_to_custom_model": [], "path_to_pt": [], "patil": [], "pattern": [], "pdf": [2, 5], "pdfpage": [], "peopl": [], "per": [5, 6], "perform": [2, 3, 5, 6, 7], "period": [], "permiss": [], "permut": [], "persian_lett": [], "person": [], "phase": [], "photo": [], "physic": 2, "pick": 6, "pictur": 2, "pip": 4, "pipelin": [], "pixbuf": 4, "pixel": [2, 6], "platinum": 5, "pleas": [], "plot": 7, "plt": 7, "plug": [], "plugin": [], "png": 2, "point": [], "polici": [], "polish": [], "polit": [], "polygon": 1, "pool": [], "portugues": [], "posit": 7, "possibl": 7, "post": 5, "postprocessor": [], "potenti": 5, "power": 3, "ppageno": [], "pre": [], "precis": [5, 7], "pred": [], "pred_box": [], "pred_label": [], "predefin": 1, "predict": [2, 7], "predictor": [], "prefer": 1, "preinstal": [], "preprocessor": 5, "prerequisit": 3, "present": [], "preserv": 6, "preserve_aspect_ratio": 6, "pretrain": [3, 5, 7], "pretrained_backbon": [], "print": [], "prior": [], "privaci": [], "privat": 5, "probabl": 6, "problem": [], "procedur": 6, "process": [2, 3], "processor": 5, "produc": 5, "product": [], "profession": [], "project": [], "promptli": [], "proper": [], "properli": 1, "properti": 5, "provid": [3, 5], "public": 3, "publicli": [], "publish": [], "pull": [], "punctuat": 1, "pure": [], "purpos": [], "push_to_hf_hub": [], "py": [], "pypdfium2": [], "pyplot": 7, "python": 3, "python3": [], "pytorch": [3, 4], "q": [], "qr": 2, "qr_code": [], "qualiti": 6, "quantiz": 5, "quantize_model": 5, "question": [], "quickli": 3, "quicktour": [], "r": [], "race": [], "ramdisk": [], "rand": [5, 7], "random": [5, 6, 7], "randomappli": 6, "randombright": 6, "randomcontrast": 6, "randomcrop": [], "randomgamma": 6, "randomhorizontalflip": [], "randomhu": 6, "randomjpegqu": 6, "randomli": 6, "randomres": [], "randomrot": [], "randomsatur": 6, "randomshadow": [], "rang": 6, "rassi": [], "ratio": 6, "raw": [2, 7], "re": [], "read": [3, 5], "read_html": 2, "read_img": 2, "read_img_as_numpi": [], "read_img_as_tensor": [], "read_pdf": 2, "readi": [], "real": [5, 6], "realli": [], "reason": [], "rebuild": [], "rebuilt": [], "recal": [5, 7], "receipt": [1, 3, 5], "reco_arch": 5, "reco_b": [], "reco_model": [], "reco_param": [], "reco_predictor": [], "recogn": [], "recognit": 7, "recognition_predictor": 5, "recognition_task": [], "recognitiondataset": [], "recognitionpredictor": 5, "rectangular": [], "recurr": 3, "reduc": 6, "refer": 4, "regardless": [], "region": [], "regroup": 7, "regular": [], "reject": [], "rel": 2, "relat": [], "releas": [0, 4], "relev": [], "religion": [], "relu": 5, "remov": [], "render": [], "repo": [], "repo_id": [], "report": [], "repositori": [], "repres": [2, 5], "represent": 5, "request": [], "requir": [4, 6], "research": 3, "residu": [], "resiz": [5, 6], "resnet": 5, "resnet18": [], "resnet31": [], "resnet34": [], "resnet50": [], "resolv": 2, "resolve_block": [], "resolve_lin": [], "resourc": [], "respect": [], "rest": [6, 7], "restrict": [], "result": [2, 5], "return": [1, 2, 5, 7], "reusabl": 5, "review": [], "rgb": [2, 6], "rgb_mode": [], "rgb_output": 2, "right": [5, 7], "roboflow": [], "robust": 3, "root": 1, "rotat": [1, 2], "rotated_bbox": [1, 7], "run": 4, "same": [2, 7], "sampl": 1, "sample_transform": 1, "sanjin": [], "sar": [3, 5], "sar_resnet31": 5, "sar_vgg16_bn": 5, "satur": 6, "save": [1, 5], "saved_model": 5, "scale": 7, "scale_rang": [], "scan": [1, 3], "scene": [3, 5], "scheme": 5, "score": 7, "scratch": 3, "script": [], "seamless": 3, "seamlessli": [], "search": [], "searchabl": [], "sec": [], "second": 5, "section": [], "secur": [], "see": [], "seemlessli": 3, "seen": 5, "segment": 5, "self": [], "semant": 5, "send": [], "sens": 7, "sensit": [], "separ": 5, "sequenc": [1, 2, 5, 7], "sequenti": [5, 6], "seri": [], "serial": 5, "serialized_model": 5, "seriou": [], "set": [1, 5, 7], "set_global_polici": [], "sever": [2, 6], "sex": [], "sexual": [], "sha256": [], "shade": [], "shape": [2, 5, 6, 7], "share": [], "shift": 6, "shm": [], "should": [1, 2, 7], "show": [2, 3, 5, 7], "showcas": [], "shuffl": 1, "side": 7, "signatur": 2, "signific": 1, "simpl": 5, "simpler": [], "sinc": 1, "singl": [], "single_img_doc": [], "size": [1, 2, 5, 6], "skew": [], "slack": [], "slightli": [], "small": 3, "smallest": 2, "snapshot_download": [], "snippet": [], "so": [1, 4], "social": [], "socio": [], "some": [], "someth": [], "somewher": [], "sort": [], "sourc": [1, 2, 5, 6, 7], "space": [], "span": [], "spanish": [], "spatial": 2, "special": 3, "specif": [1, 5, 7], "specifi": 2, "speed": [3, 5], "sphinx": [], "sroie": [1, 3], "stabl": 4, "stackoverflow": [], "stage": 3, "standalon": [], "standard": 6, "start": 1, "state": 3, "static": 7, "statist": 5, "statu": [], "std": 6, "step": [], "still": [], "str": [1, 2, 5, 6, 7], "straight": 1, "straighten": [], "straighten_pag": [], "straigten_pag": [], "stream": 2, "street": [], "strict": [], "strictli": 7, "string": [1, 2, 5, 7], "strive": [], "strong": 5, "structur": [3, 5], "subset": [1, 5], "suggest": [], "sum": 7, "summari": 7, "support": 5, "sustain": [], "svhn": [], "svt": [], "swedish": [], "symbol": [], "symmetr": 6, "symmetric_pad": 6, "synthet": [], "synthtext": [], "system": [], "t": 1, "tabl": [], "take": [], "target": [1, 2, 5, 6], "target_s": 1, "task": [1, 3, 5], "task2": [], "team": [], "techminde": [], "templat": 2, "tensor": [1, 5, 6], "tensorflow": [3, 4, 5, 6], "tensorspec": [], "term": [], "test": [], "test_set": [], "text": [2, 7], "text_output": [], "textmatch": 7, "textnet": [], "textnet_bas": [], "textnet_smal": [], "textnet_tini": [], "textract": [3, 5], "textstylebrush": [], "textual": [1, 2, 3], "tf": [5, 6], "tf_model": 5, "tflite": 5, "than": [4, 7], "thank": [], "thei": [], "them": [1, 4], "thi": [4, 5, 7], "thing": [], "third": [], "those": [2, 4, 5], "threaten": [], "threshold": [], "through": [1, 6], "tilman": [], "time": [1, 5, 7], "tini": [], "titl": 2, "tm": [], "tmp": [], "togeth": [2, 5], "tograi": 6, "tool": [], "top": 7, "topic": [], "torch": [], "torchvis": 6, "total": [], "toward": [], "train": [1, 5, 6], "train_it": 1, "train_load": 1, "train_pytorch": [], "train_set": 1, "train_tensorflow": [], "trainabl": 5, "tranform": 6, "transcrib": [], "transfer": [], "transfo": 6, "transform": [1, 3], "translat": [], "troll": [], "true": [1, 2, 5, 6, 7], "truth": 7, "tune": 3, "tupl": [2, 5, 6, 7], "turn": [], "two": 2, "txt": [], "type": [2, 5], "typic": [], "u": [], "ucsd": [], "udac": [], "uint8": [2, 5, 7], "ukrainian": [], "unaccept": [], "underli": 1, "underneath": 2, "understand": [1, 3], "unidecod": 7, "uniform": [5, 6], "uniformli": [], "uninterrupt": 2, "union": 7, "unit": [], "unittest": [], "unlock": [], "unoffici": [], "unprofession": [], "unsolicit": [], "unsupervis": [], "unwelcom": [], "up": 5, "updat": 7, "upgrad": [], "upper": 6, "uppercas": [], "url": [1, 2], "us": [1, 4, 7], "usabl": 5, "usag": 5, "use_polygon": [], "useabl": [], "user": [2, 3, 4], "utf": [], "util": [3, 5], "v0": 3, "v1": [], "v3": [], "valid": [], "valu": [2, 6], "valuabl": 3, "variabl": [], "varieti": [], "veri": [], "verifi": 1, "verma": [], "version": 5, "vgg": 5, "vgg16": 5, "vgg16_bn_r": [], "via": 3, "video": [], "vietnames": [], "view": [], "viewpoint": [], "violat": [], "visibl": [], "vision": [], "visiondataset": 1, "visiontransform": [], "visual": 3, "visualize_pag": 7, "vit_": [], "vit_b": [], "vitstr": [], "vitstr_bas": [], "vitstr_smal": [], "viz": [], "vocab": [3, 5], "vocabulari": [], "w": [2, 7], "w3": [], "wa": [], "wai": [1, 3, 5], "want": [], "warm": 5, "warmup": [], "wasn": [], "we": [2, 3, 5, 6], "weasyprint": [], "web": 2, "websit": [], "welcom": 3, "well": [], "were": 2, "what": [], "when": [], "whenev": [], "where": [2, 7], "whether": [1, 2, 7], "which": 5, "whichev": 4, "while": 6, "why": [], "width": 2, "wiki": [], "wildreceipt": [], "window": [4, 7], "wish": [], "within": [], "without": 5, "wonder": [], "word": [3, 5, 7], "word_1_1": [], "word_1_2": [], "word_1_3": [], "wordgener": [], "words_onli": 7, "work": [], "worker": 1, "workflow": [], "worklow": [], "world": 7, "worth": [], "wrap": [], "wrapper": [1, 6], "write": [], "written": 2, "www": 2, "x": [2, 6, 7], "x12larg": 5, "x_ascend": [], "x_descend": [], "x_i": 7, "x_size": [], "x_wconf": [], "xeon": 5, "xhtml": [], "xmax": 2, "xmin": 2, "xml": [], "xml_bytes_str": [], "xml_element": [], "xml_output": [], "xmln": [], "y": 7, "y_i": 7, "y_j": 7, "yet": [], "ymax": 2, "ymin": 2, "yolov8": [], "you": [4, 5], "your": [1, 2, 5, 7], "yoursit": 2, "yugesh": [], "zero": [5, 6], "zoo": [], "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 1, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": [], "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": [], "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": [], "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": [], "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": [], "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": [], "\u00e4\u00f6\u00e4\u00f6": [], "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": [], "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": [], "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": [], "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": [], "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": [], "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": [], "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": [], "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": [], "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": [], "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": [], "\u067e\u0686\u06a2\u06a4\u06af": [], "\u0905": [], "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": [], "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": [], "\u0950": [], "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": [], "\u09bd": [], "\u09ce": [], "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": []}, "titles": ["Changelog", "doctr.datasets", "doctr.documents", "DocTR: Document Text Recognition", "Installation", "doctr.models", "doctr.transforms", "doctr.utils"], "titleterms": {"": [], "0": 0, "01": [], "02": [], "03": 0, "04": [], "05": 0, "07": [], "08": [], "09": [], "1": 0, "10": [], "11": 0, "12": [], "18": 0, "2": 0, "2021": 0, "2022": [], "2023": [], "2024": [], "21": [], "22": [], "27": [], "28": 0, "29": [], "3": [], "31": [], "4": [], "5": [], "6": [], "7": [], "8": [], "9": [], "advanc": [], "approach": 5, "architectur": [], "arg": [], "artefact": 2, "artefactdetect": [], "attribut": [], "avail": 1, "aw": [], "ban": [], "block": 2, "bug": [], "build": 3, "changelog": 0, "choos": [], "classif": [], "code": [], "codebas": [], "commit": [], "commun": [], "compos": 6, "compress": 5, "conda": [], "conduct": [], "connect": [], "content": [], "continu": [], "contrib": [], "contribut": [], "contributor": [], "convent": [], "correct": [], "coven": [], "custom": [], "data": 1, "dataload": [], "dataset": [1, 3], "detect": [3, 5], "develop": [], "do": [], "doctr": [1, 2, 3, 5, 6, 7], "document": [2, 3], "end": 5, "enforc": [], "evalu": 7, "export": 5, "factori": [], "featur": 3, "feedback": [], "file": 2, "from": [], "gener": [], "get": 3, "git": 4, "guidelin": [], "half": [], "hub": [], "huggingfac": [], "i": [], "implement": [], "infer": [], "instal": 4, "integr": [], "io": [], "lambda": [], "let": [], "line": 2, "linux": [], "load": 1, "loader": [], "main": 3, "mode": [], "model": [3, 5], "modifi": [], "modul": [], "name": [], "note": 3, "notebook": [], "object": [], "ocr": 5, "onli": [], "onnx": [], "optim": [], "option": [], "orient": [], "our": [], "output": [], "own": [], "packag": [3, 4], "page": 2, "perman": [], "pipelin": [], "pledg": [], "post": [], "pre": 5, "precis": [], "predictor": [3, 5], "prepar": [], "prerequisit": 4, "pretrain": [], "process": 5, "push": [], "python": 4, "qualiti": [], "question": [], "read": 2, "readi": [], "recognit": [3, 5], "refer": 3, "report": [], "request": [], "resourc": [], "respons": [], "return": [], "right": [], "savedmodel": 5, "scope": [], "share": [], "should": [], "stage": 5, "standard": [], "start": 3, "structur": 2, "style": [], "support": [1, 3, 6], "synthet": [], "task": 7, "temporari": [], "test": [], "text": [3, 5], "train": 3, "transform": 6, "two": 5, "unit": [], "us": 5, "util": 7, "v0": 0, "verif": [], "via": 4, "visual": 7, "vocab": 1, "warn": [], "what": [], "word": 2, "your": 3, "zoo": [3, 5]}}) \ No newline at end of file +Search.setIndex({"alltitles": {"1. Correction": [[2, "correction"]], "2. Warning": [[2, "warning"]], "3. Temporary Ban": [[2, "temporary-ban"]], "4. Permanent Ban": [[2, "permanent-ban"]], "AWS Lambda": [[14, null]], "Advanced options": [[19, "advanced-options"]], "Args:": [[7, "args"], [7, "id4"], [7, "id7"], [7, "id10"], [7, "id13"], [7, "id16"], [7, "id19"], [7, "id22"], [7, "id25"], [7, "id29"], [7, "id32"], [7, "id37"], [7, "id40"], [7, "id46"], [7, "id49"], [7, "id50"], [7, "id51"], [7, "id54"], [7, "id57"], [7, "id60"], [7, "id61"], [8, "args"], [8, "id2"], [8, "id3"], [8, "id4"], [8, "id5"], [8, "id6"], [8, "id7"], [8, "id10"], [8, "id12"], [8, "id14"], [8, "id16"], [8, "id20"], [8, "id24"], [8, "id28"], [9, "args"], [9, "id3"], [9, "id8"], [9, "id13"], [9, "id17"], [9, "id21"], [9, "id26"], [9, "id31"], [9, "id36"], [9, "id41"], [9, "id46"], [9, "id50"], [9, "id54"], [9, "id59"], [9, "id63"], [9, "id68"], [9, "id73"], [9, "id77"], [9, "id81"], [9, "id85"], [9, "id90"], [9, "id95"], [9, "id99"], [9, "id104"], [9, "id109"], [9, "id114"], [9, "id119"], [9, "id123"], [9, "id127"], [9, "id132"], [9, "id137"], [9, "id142"], [9, "id146"], [9, "id150"], [9, "id155"], [9, "id159"], [9, "id163"], [9, "id167"], [9, "id169"], [9, "id171"], [9, "id173"], [10, "args"], [10, "id1"], [10, "id2"], [10, "id3"], [10, "id4"], [10, "id5"], [10, "id6"], [10, "id7"], [10, "id8"], [10, "id9"], [10, "id10"], [10, "id11"], [10, "id12"], [10, "id13"], [10, "id14"], [10, "id15"], [10, "id16"], [10, "id17"], [10, "id18"], [10, "id19"], [11, "args"], [11, "id3"], [11, "id4"], [11, "id5"], [11, "id6"], [11, "id7"], [11, "id8"], [11, "id9"]], "Artefact": [[8, "artefact"]], "ArtefactDetection": [[16, "artefactdetection"]], "Attribution": [[2, "attribution"]], "Available Datasets": [[17, "available-datasets"]], "Available architectures": [[19, "available-architectures"], [19, "id1"], [19, "id2"]], "Available contribution modules": [[16, "available-contribution-modules"]], "Block": [[8, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[17, null]], "Choosing the right model": [[19, null]], "Classification": [[15, "classification"]], "Code quality": [[3, "code-quality"]], "Code style verification": [[3, "code-style-verification"]], "Codebase structure": [[3, "codebase-structure"]], "Commits": [[3, "commits"]], "Community resources": [[1, null]], "Composing transformations": [[10, "composing-transformations"]], "Continuous Integration": [[3, "continuous-integration"]], "Contributing to docTR": [[3, null]], "Contributor Covenant Code of Conduct": [[2, null]], "Custom dataset loader": [[7, "custom-dataset-loader"]], "Custom orientation classification models": [[13, "custom-orientation-classification-models"]], "Data Loading": [[17, "data-loading"]], "Dataloader": [[7, "dataloader"]], "Detection": [[15, "detection"], [17, "detection"]], "Detection predictors": [[19, "detection-predictors"]], "Developer mode installation": [[3, "developer-mode-installation"]], "Developing docTR": [[3, "developing-doctr"]], "Document": [[8, "document"]], "Document structure": [[8, "document-structure"]], "End-to-End OCR": [[19, "end-to-end-ocr"]], "Enforcement": [[2, "enforcement"]], "Enforcement Guidelines": [[2, "enforcement-guidelines"]], "Enforcement Responsibilities": [[2, "enforcement-responsibilities"]], "Export to ONNX": [[18, "export-to-onnx"]], "Feature requests & bug report": [[3, "feature-requests-bug-report"]], "Feedback": [[3, "feedback"]], "File reading": [[8, "file-reading"]], "Half-precision": [[18, "half-precision"]], "Installation": [[4, null]], "Integrate contributions into your pipeline": [[16, null]], "Let\u2019s connect": [[3, "let-s-connect"]], "Line": [[8, "line"]], "Loading from Huggingface Hub": [[15, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[13, "loading-your-custom-trained-model"]], "Loading your custom trained orientation classification model": [[13, "loading-your-custom-trained-orientation-classification-model"]], "Main Features": [[5, "main-features"]], "Model optimization": [[18, "model-optimization"]], "Model zoo": [[5, "model-zoo"]], "Modifying the documentation": [[3, "modifying-the-documentation"]], "Naming conventions": [[15, "naming-conventions"]], "OCR": [[17, "ocr"]], "Object Detection": [[17, "object-detection"]], "Our Pledge": [[2, "our-pledge"]], "Our Standards": [[2, "our-standards"]], "Page": [[8, "page"]], "Preparing your model for inference": [[18, null]], "Prerequisites": [[4, "prerequisites"]], "Pretrained community models": [[15, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[15, "pushing-to-the-huggingface-hub"]], "Questions": [[3, "questions"]], "Recognition": [[15, "recognition"], [17, "recognition"]], "Recognition predictors": [[19, "recognition-predictors"]], "Returns:": [[7, "returns"], [8, "returns"], [8, "id11"], [8, "id13"], [8, "id15"], [8, "id19"], [8, "id23"], [8, "id27"], [8, "id31"], [9, "returns"], [9, "id6"], [9, "id11"], [9, "id16"], [9, "id20"], [9, "id24"], [9, "id29"], [9, "id34"], [9, "id39"], [9, "id44"], [9, "id49"], [9, "id53"], [9, "id57"], [9, "id62"], [9, "id66"], [9, "id71"], [9, "id76"], [9, "id80"], [9, "id84"], [9, "id88"], [9, "id93"], [9, "id98"], [9, "id102"], [9, "id107"], [9, "id112"], [9, "id117"], [9, "id122"], [9, "id126"], [9, "id130"], [9, "id135"], [9, "id140"], [9, "id145"], [9, "id149"], [9, "id153"], [9, "id158"], [9, "id162"], [9, "id166"], [9, "id168"], [9, "id170"], [9, "id172"], [11, "returns"]], "Scope": [[2, "scope"]], "Share your model with the community": [[15, null]], "Supported Vocabs": [[7, "supported-vocabs"]], "Supported contribution modules": [[6, "supported-contribution-modules"]], "Supported datasets": [[5, "supported-datasets"]], "Supported transformations": [[10, "supported-transformations"]], "Synthetic dataset generator": [[7, "synthetic-dataset-generator"], [17, "synthetic-dataset-generator"]], "Task evaluation": [[11, "task-evaluation"]], "Text Detection": [[19, "text-detection"]], "Text Recognition": [[19, "text-recognition"]], "Text detection models": [[5, "text-detection-models"]], "Text recognition models": [[5, "text-recognition-models"]], "Train your own model": [[13, null]], "Two-stage approaches": [[19, "two-stage-approaches"]], "Unit tests": [[3, "unit-tests"]], "Use your own datasets": [[17, "use-your-own-datasets"]], "Using your ONNX exported model": [[18, "using-your-onnx-exported-model"]], "Via Conda (Only for Linux)": [[4, "via-conda-only-for-linux"]], "Via Git": [[4, "via-git"]], "Via Python Package": [[4, "via-python-package"]], "Visualization": [[11, "visualization"]], "What should I do with the output?": [[19, "what-should-i-do-with-the-output"]], "Word": [[8, "word"]], "docTR Notebooks": [[12, null]], "docTR Vocabs": [[7, "id62"]], "docTR: Document Text Recognition": [[5, null]], "doctr.contrib": [[6, null]], "doctr.datasets": [[7, null], [7, "datasets"]], "doctr.io": [[8, null]], "doctr.models": [[9, null]], "doctr.models.classification": [[9, "doctr-models-classification"]], "doctr.models.detection": [[9, "doctr-models-detection"]], "doctr.models.factory": [[9, "doctr-models-factory"]], "doctr.models.recognition": [[9, "doctr-models-recognition"]], "doctr.models.zoo": [[9, "doctr-models-zoo"]], "doctr.transforms": [[10, null]], "doctr.utils": [[11, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.10.0 (2024-10-21)": [[0, "v0-10-0-2024-10-21"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]], "v0.7.0 (2023-09-09)": [[0, "v0-7-0-2023-09-09"]], "v0.8.0 (2024-02-28)": [[0, "v0-8-0-2024-02-28"]], "v0.8.1 (2024-03-04)": [[0, "v0-8-1-2024-03-04"]], "v0.9.0 (2024-08-08)": [[0, "v0-9-0-2024-08-08"]]}, "docnames": ["changelog", "community/resources", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/contrib", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_contrib_modules", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "community/resources.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/contrib.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_contrib_modules.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[8, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[8, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[10, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[7, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[10, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[10, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[7, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[7, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[9, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[7, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[7, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[8, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[8, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[7, "doctr.datasets.encode_sequences", false]], "fast_base() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_base", false]], "fast_small() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_small", false]], "fast_tiny() (in module doctr.models.detection)": [[9, "doctr.models.detection.fast_tiny", false]], "from_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[8, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[7, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[10, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[10, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[7, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[7, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[7, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[7, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[7, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[9, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[10, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[8, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet34() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[9, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[7, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_crop_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_crop_orientation", false]], "mobilenet_v3_small_page_orientation() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_page_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[10, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[9, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[7, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[10, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[8, "doctr.io.Page", false]], "page_orientation_predictor() (in module doctr.models.classification)": [[9, "doctr.models.classification.page_orientation_predictor", false]], "parseq() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[9, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[10, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[10, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[10, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[10, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[10, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[10, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[10, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[10, "doctr.transforms.RandomJpegQuality", false]], "randomresize (class in doctr.transforms)": [[10, "doctr.transforms.RandomResize", false]], "randomrotate (class in doctr.transforms)": [[10, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[10, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[10, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[8, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[8, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[8, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[8, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[7, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[10, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[9, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[8, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[8, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[7, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[7, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[7, "doctr.datasets.SVT", false]], "synthtext (class in doctr.datasets)": [[7, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[11, "doctr.utils.metrics.TextMatch", false]], "textnet_base() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_base", false]], "textnet_small() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_small", false]], "textnet_tiny() (in module doctr.models.classification)": [[9, "doctr.models.classification.textnet_tiny", false]], "togray (class in doctr.transforms)": [[10, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[11, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[11, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[11, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[11, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[9, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[11, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[9, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[9, "doctr.models.recognition.vitstr_small", false]], "wildreceipt (class in doctr.datasets)": [[7, "doctr.datasets.WILDRECEIPT", false]], "word (class in doctr.io)": [[8, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[7, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[7, 0, 1, "", "CORD"], [7, 0, 1, "", "CharacterGenerator"], [7, 0, 1, "", "DetectionDataset"], [7, 0, 1, "", "DocArtefacts"], [7, 0, 1, "", "FUNSD"], [7, 0, 1, "", "IC03"], [7, 0, 1, "", "IC13"], [7, 0, 1, "", "IIIT5K"], [7, 0, 1, "", "IIITHWS"], [7, 0, 1, "", "IMGUR5K"], [7, 0, 1, "", "MJSynth"], [7, 0, 1, "", "OCRDataset"], [7, 0, 1, "", "RecognitionDataset"], [7, 0, 1, "", "SROIE"], [7, 0, 1, "", "SVHN"], [7, 0, 1, "", "SVT"], [7, 0, 1, "", "SynthText"], [7, 0, 1, "", "WILDRECEIPT"], [7, 0, 1, "", "WordGenerator"], [7, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[7, 0, 1, "", "DataLoader"]], "doctr.io": [[8, 0, 1, "", "Artefact"], [8, 0, 1, "", "Block"], [8, 0, 1, "", "Document"], [8, 0, 1, "", "DocumentFile"], [8, 0, 1, "", "Line"], [8, 0, 1, "", "Page"], [8, 0, 1, "", "Word"], [8, 1, 1, "", "decode_img_as_tensor"], [8, 1, 1, "", "read_html"], [8, 1, 1, "", "read_img_as_numpy"], [8, 1, 1, "", "read_img_as_tensor"], [8, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[8, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[8, 2, 1, "", "from_images"], [8, 2, 1, "", "from_pdf"], [8, 2, 1, "", "from_url"]], "doctr.io.Page": [[8, 2, 1, "", "show"]], "doctr.models": [[9, 1, 1, "", "kie_predictor"], [9, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[9, 1, 1, "", "crop_orientation_predictor"], [9, 1, 1, "", "magc_resnet31"], [9, 1, 1, "", "mobilenet_v3_large"], [9, 1, 1, "", "mobilenet_v3_large_r"], [9, 1, 1, "", "mobilenet_v3_small"], [9, 1, 1, "", "mobilenet_v3_small_crop_orientation"], [9, 1, 1, "", "mobilenet_v3_small_page_orientation"], [9, 1, 1, "", "mobilenet_v3_small_r"], [9, 1, 1, "", "page_orientation_predictor"], [9, 1, 1, "", "resnet18"], [9, 1, 1, "", "resnet31"], [9, 1, 1, "", "resnet34"], [9, 1, 1, "", "resnet50"], [9, 1, 1, "", "textnet_base"], [9, 1, 1, "", "textnet_small"], [9, 1, 1, "", "textnet_tiny"], [9, 1, 1, "", "vgg16_bn_r"], [9, 1, 1, "", "vit_b"], [9, 1, 1, "", "vit_s"]], "doctr.models.detection": [[9, 1, 1, "", "db_mobilenet_v3_large"], [9, 1, 1, "", "db_resnet50"], [9, 1, 1, "", "detection_predictor"], [9, 1, 1, "", "fast_base"], [9, 1, 1, "", "fast_small"], [9, 1, 1, "", "fast_tiny"], [9, 1, 1, "", "linknet_resnet18"], [9, 1, 1, "", "linknet_resnet34"], [9, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[9, 1, 1, "", "from_hub"], [9, 1, 1, "", "login_to_hub"], [9, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[9, 1, 1, "", "crnn_mobilenet_v3_large"], [9, 1, 1, "", "crnn_mobilenet_v3_small"], [9, 1, 1, "", "crnn_vgg16_bn"], [9, 1, 1, "", "master"], [9, 1, 1, "", "parseq"], [9, 1, 1, "", "recognition_predictor"], [9, 1, 1, "", "sar_resnet31"], [9, 1, 1, "", "vitstr_base"], [9, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[10, 0, 1, "", "ChannelShuffle"], [10, 0, 1, "", "ColorInversion"], [10, 0, 1, "", "Compose"], [10, 0, 1, "", "GaussianBlur"], [10, 0, 1, "", "GaussianNoise"], [10, 0, 1, "", "LambdaTransformation"], [10, 0, 1, "", "Normalize"], [10, 0, 1, "", "OneOf"], [10, 0, 1, "", "RandomApply"], [10, 0, 1, "", "RandomBrightness"], [10, 0, 1, "", "RandomContrast"], [10, 0, 1, "", "RandomCrop"], [10, 0, 1, "", "RandomGamma"], [10, 0, 1, "", "RandomHorizontalFlip"], [10, 0, 1, "", "RandomHue"], [10, 0, 1, "", "RandomJpegQuality"], [10, 0, 1, "", "RandomResize"], [10, 0, 1, "", "RandomRotate"], [10, 0, 1, "", "RandomSaturation"], [10, 0, 1, "", "RandomShadow"], [10, 0, 1, "", "Resize"], [10, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[11, 0, 1, "", "DetectionMetric"], [11, 0, 1, "", "LocalizationConfusion"], [11, 0, 1, "", "OCRMetric"], [11, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[11, 2, 1, "", "summary"], [11, 2, 1, "", "update"]], "doctr.utils.visualization": [[11, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [2, 8, 9, 11, 15, 18], "0": [2, 4, 7, 10, 11, 13, 16, 17, 19], "00": 19, "01": 19, "0123456789": 7, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "02562": 9, "03": 19, "035": 19, "0361328125": 19, "04": 19, "05": 19, "06": 19, "06640625": 19, "07": 19, "08": [10, 19], "09": 19, "0966796875": 19, "1": [7, 8, 9, 10, 11, 13, 17, 19], "10": [4, 7, 11, 19], "100": [7, 10, 11, 17, 19], "1000": 19, "101": 7, "1024": [9, 13, 19], "104": 7, "106": 7, "108": 7, "1095": 17, "11": 19, "110": 11, "1107": 17, "114": 7, "115": 7, "1156": 17, "116": 7, "118": 7, "11800h": 19, "11th": 19, "12": 19, "120": 7, "123": 7, "126": 7, "1268": 17, "128": [9, 13, 18, 19], "13": 19, "130": 7, "13068": 17, "131": 7, "1337891": 17, "1357421875": 19, "1396484375": 19, "14": 19, "1420": 19, "14470v1": 7, "149": 17, "15": 19, "150": [11, 19], "1552": 19, "16": [9, 18, 19], "1630859375": 19, "1684": 19, "16x16": 9, "17": 19, "1778": 19, "1782": 19, "18": [9, 19], "185546875": 19, "1900": 19, "1910": 9, "19342": 17, "19370": 17, "195": 7, "19598": 17, "199": 19, "1999": 19, "2": [4, 5, 7, 8, 9, 10, 16, 19], "20": 19, "200": 11, "2000": 17, "2003": [5, 7], "2012": 7, "2013": [5, 7], "2015": 7, "2019": 5, "2023": 1, "207901": 17, "21": 19, "2103": 7, "2186": 17, "21888": 17, "22": 19, "224": [9, 10], "225": 10, "22672": 17, "229": [10, 17], "23": 19, "233": 17, "236": 7, "24": 19, "246": 17, "249": 17, "25": 19, "2504": 19, "255": [8, 9, 10, 11, 19], "256": 9, "257": 17, "26": 19, "26032": 17, "264": 13, "27": 19, "2700": 17, "2710": 19, "2749": 13, "28": 19, "287": 13, "29": 19, "296": 13, "299": 13, "2d": 19, "3": [4, 5, 8, 9, 10, 11, 18, 19], "30": 19, "300": 17, "3000": 17, "301": 13, "30595": 19, "30ghz": 19, "31": 9, "32": [7, 9, 10, 13, 17, 18, 19], "3232421875": 19, "33": [10, 19], "33402": 17, "33608": 17, "34": [9, 19], "340": 19, "3456": 19, "3515625": 19, "36": 19, "360": 17, "37": [7, 19], "38": 19, "39": 19, "4": [9, 10, 11, 19], "40": 19, "406": 10, "41": 19, "42": 19, "43": 19, "44": 19, "45": 19, "456": 10, "46": 19, "47": 19, "472": 17, "48": [7, 19], "485": 10, "49": 19, "49377": 17, "5": [7, 10, 11, 16, 19], "50": [9, 17, 19], "51": 19, "51171875": 19, "512": 9, "52": [7, 19], "529": 19, "53": 19, "54": 19, "540": 19, "5478515625": 19, "55": 19, "56": 19, "57": 19, "58": [7, 19], "580": 19, "5810546875": 19, "583": 19, "59": 19, "597": 19, "5k": [5, 7], "5m": 19, "6": [10, 19], "60": 10, "600": [9, 11, 19], "61": 19, "62": 19, "626": 17, "63": 19, "64": [9, 10, 19], "641": 19, "647": 17, "65": 19, "66": 19, "67": 19, "68": 19, "69": 19, "693": 13, "694": 13, "695": 13, "6m": 19, "7": 19, "70": [7, 11, 19], "707470": 17, "71": [7, 19], "7100000": 17, "7141797": 17, "7149": 17, "72": 19, "72dpi": 8, "73": 19, "73257": 17, "74": 19, "75": [10, 19], "7581382": 17, "76": 19, "77": 19, "772": 13, "772875": 17, "78": 19, "785": 13, "79": 19, "793533": 17, "796": 17, "798": 13, "7m": 19, "8": [9, 10, 19], "80": 19, "800": [9, 11, 17, 19], "81": 19, "82": 19, "83": 19, "84": 19, "849": 17, "85": 19, "8564453125": 19, "857": 19, "85875": 17, "86": 19, "8603515625": 19, "87": 19, "8707": 17, "88": 19, "89": 19, "9": [10, 19], "90": 19, "90k": 7, "90kdict32px": 7, "91": 19, "914085328578949": 19, "92": 19, "93": 19, "94": [7, 19], "95": [11, 19], "9578408598899841": 19, "96": 19, "97": 19, "98": 19, "99": 19, "9949972033500671": 19, "A": [2, 3, 5, 7, 8, 9, 12, 18], "As": 3, "Be": 19, "Being": 2, "By": 14, "For": [2, 3, 4, 13, 19], "If": [3, 8, 9, 13, 19], "In": [3, 7, 17], "It": [10, 15, 16, 18], "Its": [5, 9], "No": [2, 19], "Of": 7, "Or": [16, 18], "The": [2, 3, 7, 8, 11, 14, 16, 17, 18, 19], "Then": 9, "To": [3, 4, 14, 15, 16, 18, 19], "_": [2, 7, 9], "__call__": 19, "_build": 3, "_i": 11, "ab": 7, "abc": 18, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 7, "abdef": [7, 17], "abl": [17, 19], "about": [2, 17, 19], "abov": 19, "abstract": 1, "abstractdataset": 7, "abus": 2, "accept": 2, "access": [5, 8, 17, 19], "account": [2, 15], "accur": 19, "accuraci": 11, "achiev": 18, "act": 2, "action": 2, "activ": 5, "ad": [3, 9, 10], "adapt": 2, "add": [10, 11, 15, 19], "add_hook": 19, "add_label": 11, "addit": [3, 4, 8, 16, 19], "addition": [3, 19], "address": [2, 8], "adjust": 10, "advanc": 2, "advantag": 18, "advis": 3, "aesthet": [5, 7], "affect": 2, "after": [15, 19], "ag": 2, "again": 9, "aggreg": [11, 17], "aggress": 2, "align": [2, 8, 10], "all": [2, 3, 6, 7, 8, 10, 11, 16, 17, 19], "allow": [2, 18], "along": 19, "alreadi": [3, 18], "also": [2, 9, 15, 16, 17, 19], "alwai": 17, "an": [2, 3, 5, 7, 8, 9, 11, 16, 18, 19], "analysi": [8, 16], "ancient_greek": 7, "andrej": 1, "angl": [8, 10], "ani": [2, 7, 8, 9, 10, 11, 18, 19], "annot": 7, "anot": 17, "anoth": [9, 13, 17], "answer": 2, "anyascii": 11, "anyon": 5, "anyth": 16, "api": [3, 5], "apolog": 2, "apologi": 2, "app": 3, "appear": 2, "appli": [2, 7, 10], "applic": [5, 9], "appoint": 2, "appreci": 15, "appropri": [2, 3, 19], "ar": [2, 3, 4, 6, 7, 8, 10, 11, 12, 16, 17, 19], "arab": 7, "arabic_diacrit": 7, "arabic_lett": 7, "arabic_punctu": 7, "arbitrarili": [5, 9], "arch": [9, 15], "architectur": [5, 9, 15, 16], "area": 19, "argument": [7, 8, 9, 11, 13, 19], "around": 2, "arrai": [8, 10, 11], "art": [5, 16], "artefact": [11, 16, 19], "artefact_typ": 8, "articl": 1, "artifici": [5, 7], "arxiv": [7, 9], "asarrai": 11, "ascii_lett": 7, "aspect": [5, 9, 10, 19], "assess": 11, "assign": 11, "associ": 8, "assum": 9, "assume_straight_pag": [9, 13, 19], "astyp": [9, 11, 19], "attack": 2, "attend": [5, 9], "attent": [2, 9], "autom": 5, "automat": 19, "autoregress": [5, 9], "avail": [2, 5, 6, 10], "averag": [10, 19], "avoid": [2, 4], "aw": [5, 19], "awar": 19, "azur": 19, "b": [9, 11, 19], "b_j": 11, "back": 3, "backbon": 9, "backend": 19, "background": 17, "bangla": 7, "bar": 16, "bar_cod": 17, "baranovskij": 1, "base": [5, 9, 16], "baselin": [5, 9, 19], "batch": [7, 9, 10, 16, 17, 19], "batch_siz": [7, 9, 13, 16, 17, 18], "bblanchon": 4, "bbox": 19, "becaus": 14, "been": [3, 11, 17, 19], "befor": [7, 9, 10, 19], "begin": 11, "behavior": [2, 19], "being": [11, 19], "belong": 19, "benchmark": 19, "best": [1, 2], "better": [12, 19], "between": [10, 11, 19], "bgr": 8, "bilinear": 10, "bin_thresh": 19, "binar": [5, 9, 19], "binari": [8, 18, 19], "bit": 18, "block": [11, 19], "block_1_1": 19, "blur": 10, "bmvc": 7, "bn": 15, "bodi": [2, 19], "bool": [7, 8, 9, 10, 11], "boolean": [9, 19], "both": [5, 7, 10, 17, 19], "bottom": [9, 19], "bound": [7, 8, 9, 10, 11, 16, 17, 19], "box": [7, 8, 9, 10, 11, 16, 17, 19], "box_thresh": 19, "bright": 10, "browser": [3, 5], "build": [3, 4, 18], "built": 3, "byte": [8, 19], "c": [4, 8, 11], "c_j": 11, "cach": [3, 7, 14], "cache_sampl": 7, "call": 18, "callabl": [7, 10], "can": [3, 4, 13, 14, 15, 16, 17, 19], "capabl": [3, 12, 19], "case": [7, 11], "cf": 19, "cfg": 19, "challeng": 7, "challenge2_test_task12_imag": 7, "challenge2_test_task1_gt": 7, "challenge2_training_task12_imag": 7, "challenge2_training_task1_gt": 7, "chang": [14, 19], "channel": [2, 3, 8, 10], "channel_prior": 4, "channelshuffl": 10, "charact": [5, 7, 8, 11, 17, 19], "charactergener": [7, 17], "characterist": 2, "charg": 19, "charset": 19, "chart": 8, "check": [3, 15, 19], "checkpoint": 9, "chip": 4, "christian": 1, "ci": 3, "clarifi": 2, "clariti": 2, "class": [2, 7, 8, 10, 11, 19], "class_nam": 13, "classif": [17, 19], "classmethod": 8, "clear": 3, "clone": 4, "close": 3, "co": 15, "code": [5, 8, 16], "codecov": 3, "colab": 12, "collate_fn": 7, "collect": [8, 16], "color": 10, "colorinvers": 10, "column": 8, "com": [2, 4, 8, 9, 15], "combin": 19, "command": [3, 16], "comment": 2, "commit": 2, "common": [2, 10, 11, 18], "commun": 2, "compar": 5, "comparison": [11, 19], "competit": 7, "compil": [12, 19], "complaint": 2, "complementari": 11, "complet": 3, "compon": 19, "compos": [7, 19], "comprehens": 19, "comput": [7, 11, 18, 19], "conf_threshold": 16, "confid": [8, 19], "config": [4, 9], "configur": 9, "confus": 11, "consecut": [10, 19], "consequ": 2, "consid": [2, 3, 7, 8, 11, 19], "consist": 19, "consolid": [5, 7], "constant": 10, "construct": 2, "contact": 2, "contain": [1, 6, 7, 12, 17, 19], "content": [7, 8, 19], "context": 9, "contib": 4, "continu": 2, "contrast": 10, "contrast_factor": 10, "contrib": [4, 16], "contribut": 2, "contributor": 3, "convers": 8, "convert": [8, 10], "convolut": 9, "cool": 1, "coordin": [8, 19], "cord": [5, 7, 17, 19], "core": [11, 19], "corner": 19, "correct": 10, "correspond": [4, 8, 10, 19], "could": [2, 16], "counterpart": 11, "cover": 3, "coverag": 3, "cpu": [5, 13, 18], "creat": [1, 15], "crnn": [5, 9, 15], "crnn_mobilenet_v3_larg": [9, 15, 19], "crnn_mobilenet_v3_smal": [9, 18, 19], "crnn_vgg16_bn": [9, 13, 15, 19], "crop": [8, 9, 10, 13, 17, 19], "crop_orient": [8, 19], "crop_orientation_predictor": [9, 13], "crop_param": 13, "cuda": 18, "currenc": 7, "current": [3, 13, 19], "custom": [15, 16, 18, 19], "custom_crop_orientation_model": 13, "custom_page_orientation_model": 13, "customhook": 19, "cvit": 5, "czczup": 9, "czech": 7, "d": [7, 17], "danish": 7, "data": [5, 7, 8, 10, 11, 13, 15], "dataload": 17, "dataset": [9, 13, 19], "dataset_info": 7, "date": [13, 19], "db": 15, "db_mobilenet_v3_larg": [9, 15, 19], "db_resnet34": 19, "db_resnet50": [9, 13, 15, 19], "dbnet": [5, 9], "deal": [12, 19], "decis": 2, "decod": 8, "decode_img_as_tensor": 8, "dedic": 18, "deem": 2, "deep": [9, 19], "def": 19, "default": [4, 8, 13, 14, 19], "defer": 17, "defin": [11, 18], "degre": [8, 10, 19], "degress": 8, "delet": 3, "delimit": 19, "delta": 10, "demo": [3, 5], "demonstr": 2, "depend": [3, 4, 5, 19], "deploi": 3, "deploy": 5, "derogatori": 2, "describ": 9, "descript": 12, "design": 10, "desir": 8, "det_arch": [9, 13, 15, 18], "det_b": 19, "det_model": [13, 15, 18], "det_param": 13, "det_predictor": [13, 19], "detail": [13, 19], "detect": [1, 7, 8, 11, 12, 13, 16], "detect_languag": 9, "detect_orient": [9, 13, 19], "detection_predictor": [9, 19], "detection_task": [7, 17], "detectiondataset": [7, 17], "detectionmetr": 11, "detectionpredictor": [9, 13], "detector": [5, 9, 16], "deterior": 9, "determin": 2, "dev": [3, 14], "develop": 4, "deviat": 10, "devic": 18, "dict": [8, 11, 19], "dictionari": [8, 11], "differ": 2, "differenti": [5, 9], "digit": [5, 7, 17], "dimens": [8, 11, 19], "dimension": 10, "direct": 7, "directli": [15, 19], "directori": [3, 14], "disabl": [2, 14, 19], "disable_crop_orient": 19, "disable_page_orient": 19, "disclaim": 19, "discuss": 3, "disparag": 2, "displai": [8, 11], "display_artefact": 11, "distribut": 10, "div": 19, "divers": 2, "divid": 8, "do": [3, 4, 9], "doc": [3, 8, 16, 18, 19], "docartefact": [7, 17], "docstr": 3, "doctr": [1, 4, 13, 14, 15, 16, 17, 18, 19], "doctr_cache_dir": 14, "doctr_multiprocessing_dis": 14, "document": [1, 7, 9, 11, 12, 13, 16, 17, 18, 19], "documentbuild": 19, "documentfil": [8, 13, 15, 16, 18], "doesn": 18, "don": [13, 19], "done": 10, "download": [7, 17], "downsiz": 9, "draw": 10, "drop": 7, "drop_last": 7, "dtype": [8, 9, 10, 11, 18], "dual": [5, 7], "dummi": 15, "dummy_img": 19, "dummy_input": 18, "dure": 2, "dutch": 7, "dynam": [7, 16], "dynamic_seq_length": 7, "e": [2, 3, 4, 8, 9], "each": [5, 7, 8, 9, 10, 11, 17, 19], "eas": 3, "easi": [5, 11, 15, 18], "easili": [8, 11, 13, 15, 17, 19], "econom": 2, "edit": 2, "educ": 2, "effect": 19, "effici": [3, 5, 7, 9], "either": [11, 19], "element": [7, 8, 9, 19], "els": [3, 16], "email": 2, "empathi": 2, "en": 19, "enabl": [7, 8], "enclos": 8, "encod": [5, 7, 8, 9, 19], "encode_sequ": 7, "encount": 3, "encrypt": 8, "end": [5, 7, 9, 11], "english": [7, 17], "enough": [3, 19], "ensur": 3, "entri": 7, "environ": [2, 14], "eo": 7, "equiv": 19, "estim": 9, "etc": [8, 16], "ethnic": 2, "evalu": [17, 19], "event": 2, "everyon": 2, "everyth": [3, 19], "exact": [11, 19], "exampl": [2, 3, 5, 7, 9, 15, 19], "exchang": 18, "execut": 19, "exist": 15, "expand": 10, "expect": [8, 10, 11], "experi": 2, "explan": [2, 19], "explicit": 2, "exploit": [5, 9], "export": [8, 9, 11, 12, 16, 19], "export_as_straight_box": [9, 19], "export_as_xml": 19, "export_model_to_onnx": 18, "express": [2, 10], "extens": 8, "extern": [2, 17], "extract": [1, 5, 7], "extractor": 9, "f_": 11, "f_a": 11, "factor": 10, "fair": 2, "fairli": 2, "fals": [7, 8, 9, 10, 11, 13, 19], "faq": 2, "fascan": 15, "fast": [5, 7, 9], "fast_bas": [9, 19], "fast_smal": [9, 19], "fast_tini": [9, 19], "faster": [5, 9, 18], "fasterrcnn_mobilenet_v3_large_fpn": 9, "favorit": 19, "featur": [4, 9, 11, 12, 13, 16], "feedback": 2, "feel": [3, 15], "felix92": 15, "few": [18, 19], "figsiz": 11, "figur": [11, 16], "file": [3, 7], "final": 9, "find": [3, 17], "fine": 1, "finnish": 7, "first": [3, 7], "firsthand": 7, "fit": [9, 19], "flag": 19, "flip": 10, "float": [8, 10, 11, 18], "float32": [8, 9, 10, 18], "fn": 10, "focu": 15, "focus": [2, 7], "folder": 7, "follow": [2, 3, 4, 7, 10, 11, 13, 14, 15, 16, 19], "font": 7, "font_famili": 7, "foral": 11, "forc": 3, "forg": 4, "form": [5, 7, 19], "format": [8, 11, 13, 17, 18, 19], "forpost": [5, 7], "forum": 3, "found": 1, "fp16": 18, "frac": 11, "framework": [4, 15, 17, 19], "free": [2, 3, 15], "french": [7, 13, 15, 19], "friendli": 5, "from": [1, 2, 5, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19], "from_hub": [9, 15], "from_imag": [8, 15, 16, 18], "from_pdf": 8, "from_url": 8, "full": [7, 11, 19], "function": [7, 10, 11, 16], "funsd": [5, 7, 17, 19], "further": 17, "futur": 7, "g": [8, 9], "g_": 11, "g_x": 11, "gallagh": 1, "gamma": 10, "gaussian": 10, "gaussianblur": 10, "gaussiannois": 10, "gen": 19, "gender": 2, "gener": [3, 5, 8, 9], "generic_cyrillic_lett": 7, "geometri": [5, 8, 19], "geq": 11, "german": [7, 13, 15], "get": [18, 19], "git": 15, "github": [3, 4, 9, 15], "give": [2, 16], "given": [7, 8, 10, 11, 19], "global": 9, "go": 19, "good": 18, "googl": 3, "googlevis": 5, "gpu": [5, 16, 18], "gracefulli": 2, "graph": [5, 7, 8], "grayscal": 10, "ground": 11, "groung": 11, "group": [5, 19], "gt": 11, "gt_box": 11, "gt_label": 11, "guid": 3, "guidanc": 17, "gvision": 19, "h": [8, 9, 10], "h_": 11, "ha": [3, 7, 11, 17], "handl": [12, 17, 19], "handwrit": 7, "handwritten": 17, "harass": 2, "hardwar": 19, "harm": 2, "hat": 11, "have": [2, 3, 11, 13, 15, 17, 18, 19], "head": [9, 19], "healthi": 2, "hebrew": 7, "height": [8, 10], "hello": [11, 19], "help": 18, "here": [6, 10, 12, 16, 17, 19], "hf": 9, "hf_hub_download": 9, "high": 8, "higher": [4, 7, 19], "hindi": 7, "hindi_digit": 7, "hocr": 19, "hook": 19, "horizont": [8, 10, 19], "hous": 7, "how": [1, 3, 12, 13, 15, 17], "howev": 17, "hsv": 10, "html": [2, 3, 4, 8, 19], "http": [2, 4, 7, 8, 9, 15, 19], "hub": 9, "hue": 10, "huggingfac": 9, "hw": 7, "i": [2, 3, 7, 8, 9, 10, 11, 14, 15, 16, 17, 18], "i7": 19, "ibrahimov": 1, "ic03": [5, 7, 17], "ic13": [5, 7, 17], "icdar": [5, 7], "icdar2019": 7, "id": 19, "ident": 2, "identifi": 5, "iiit": [5, 7], "iiit5k": [7, 17], "iiithw": [5, 7, 17], "imag": [1, 5, 7, 8, 9, 10, 11, 15, 16, 17, 19], "imagenet": 9, "imageri": 2, "images_90k_norm": 7, "img": [7, 10, 17, 18], "img_cont": 8, "img_fold": [7, 17], "img_path": 8, "img_transform": 7, "imgur5k": [5, 7, 17], "imgur5k_annot": 7, "imlist": 7, "impact": 2, "implement": [7, 8, 9, 10, 11, 19], "import": [7, 8, 9, 10, 11, 13, 15, 16, 17, 18, 19], "improv": 9, "inappropri": 2, "incid": 2, "includ": [2, 7, 17, 18], "inclus": 2, "increas": 10, "independ": 10, "index": [3, 8], "indic": 11, "individu": 2, "infer": [5, 9, 10, 16, 19], "inform": [1, 2, 3, 5, 7, 17], "input": [3, 8, 9, 10, 18, 19], "input_crop": 9, "input_pag": [9, 11, 19], "input_shap": 18, "input_tensor": 9, "inspir": [2, 10], "instal": [15, 16, 18], "instanc": [2, 19], "instanti": [9, 19], "instead": [7, 8, 9], "insult": 2, "int": [7, 8, 9, 10], "int64": 11, "integ": 11, "integr": [1, 5, 15, 17], "intel": 19, "interact": [2, 8, 11], "interfac": [15, 18], "interoper": 18, "interpol": 10, "interpret": [7, 8], "intersect": 11, "invert": 10, "investig": 2, "invis": 2, "involv": [2, 19], "io": [13, 15, 16, 18], "iou": 11, "iou_thresh": 11, "iou_threshold": 16, "irregular": [5, 9, 17], "isn": 7, "issu": [2, 3, 15], "italian": 7, "iter": [7, 10, 17, 19], "its": [8, 9, 10, 11, 17, 19], "itself": [9, 15], "j": 11, "jame": 1, "job": 3, "join": 3, "jpeg": 10, "jpegqual": 10, "jpg": [7, 8, 15, 18], "json": [7, 17, 19], "json_output": 19, "jump": 3, "just": 2, "kei": [5, 7], "kera": [9, 18], "kernel": [5, 9, 10], "kernel_shap": 10, "keywoard": 9, "keyword": [7, 8, 9, 11], "kie": [9, 13], "kie_predictor": [9, 13], "kiepredictor": 9, "kind": 2, "know": [3, 18], "kwarg": [7, 8, 9, 11], "l": 11, "l_j": 11, "label": [7, 11, 16, 17], "label_fil": [7, 17], "label_fold": 7, "label_path": [7, 17], "labels_path": [7, 17], "ladder": 2, "lambda": 10, "lambdatransform": 10, "lang": 19, "languag": [2, 5, 7, 8, 9, 15, 19], "larg": [9, 15], "largest": 11, "last": [4, 7], "latenc": 9, "later": 3, "latest": 19, "latin": 7, "layer": 18, "layout": 19, "lead": 2, "leader": 2, "learn": [2, 5, 9, 18, 19], "least": 4, "left": [11, 19], "legacy_french": 7, "length": [7, 19], "less": [18, 19], "level": [2, 7, 11, 19], "leverag": 12, "lf": 15, "librari": [3, 4, 12, 13], "light": 5, "lightweight": 18, "like": 2, "limits_": 11, "line": [5, 9, 11, 19], "line_1_1": 19, "link": 13, "linknet": [5, 9], "linknet_resnet18": [9, 13, 18, 19], "linknet_resnet34": [9, 18, 19], "linknet_resnet50": [9, 19], "list": [7, 8, 10, 11, 15], "ll": 11, "load": [5, 7, 9, 16, 18], "load_state_dict": 13, "load_weight": 13, "loc_pr": 19, "local": [3, 5, 7, 9, 11, 17, 19], "localis": 7, "localizationconfus": 11, "locat": [3, 8, 19], "login": 9, "login_to_hub": [9, 15], "logo": [8, 16, 17], "love": 15, "lower": [10, 11, 19], "m": [3, 11, 19], "m1": 4, "macbook": 4, "machin": 18, "made": 5, "magc_resnet31": 9, "mai": [2, 3], "mail": 2, "main": 12, "maintain": 5, "mainten": 3, "make": [2, 3, 11, 13, 14, 15, 18, 19], "mani": [17, 19], "manipul": 19, "map": [7, 9], "map_loc": 13, "master": [5, 9, 19], "match": [11, 19], "mathcal": 11, "matplotlib": [8, 11], "max": [7, 10, 11], "max_angl": 10, "max_area": 10, "max_char": [7, 17], "max_delta": 10, "max_gain": 10, "max_gamma": 10, "max_qual": 10, "max_ratio": 10, "maximum": [7, 10], "maxval": [9, 10], "mbox": 11, "mean": [10, 11, 13], "meaniou": 11, "meant": [8, 18], "measur": 19, "media": 2, "median": 9, "meet": 13, "member": 2, "memori": [14, 18], "mention": 19, "merg": 7, "messag": 3, "meta": 19, "metadata": 18, "metal": 4, "method": [8, 10, 19], "metric": [11, 19], "middl": 19, "might": [18, 19], "min": 10, "min_area": 10, "min_char": [7, 17], "min_gain": 10, "min_gamma": 10, "min_qual": 10, "min_ratio": 10, "min_val": 10, "minde": [1, 2, 4, 5, 9], "minim": [3, 5], "minimalist": [5, 9], "minimum": [4, 7, 10, 11, 19], "minval": 10, "miss": 4, "mistak": 2, "mixed_float16": 18, "mixed_precis": 18, "mjsynth": [5, 7, 17], "mnt": 7, "mobilenet": [9, 15], "mobilenet_v3_larg": 9, "mobilenet_v3_large_r": 9, "mobilenet_v3_smal": [9, 13], "mobilenet_v3_small_crop_orient": [9, 13], "mobilenet_v3_small_page_orient": [9, 13], "mobilenet_v3_small_r": 9, "mobilenetv3": 9, "modal": [5, 7], "mode": 4, "model": [7, 11, 14, 16, 17], "model_nam": [9, 15, 18], "model_path": [16, 18], "moder": 2, "modif": 3, "modifi": [9, 14, 19], "modul": [4, 8, 9, 10, 11, 19], "more": [3, 17, 19], "moscardi": 1, "most": 19, "mozilla": 2, "multi": [5, 9], "multilingu": [7, 15], "multipl": [7, 8, 10, 19], "multipli": 10, "multiprocess": 14, "my": 9, "my_awesome_model": 15, "my_hook": 19, "n": [7, 11], "name": [7, 9, 18, 19], "nation": 2, "natur": [2, 5, 7], "ndarrai": [7, 8, 10, 11], "necessari": [4, 13, 14], "need": [3, 4, 7, 11, 13, 14, 15, 16, 19], "neg": 10, "nest": 19, "netraj": 1, "network": [5, 7, 9, 18], "neural": [5, 7, 9, 18], "new": [3, 11], "next": [7, 17], "nois": 10, "noisi": [5, 7], "non": [5, 7, 8, 9, 10, 11], "none": [7, 8, 9, 10, 11, 19], "normal": [9, 10], "norwegian": 7, "note": [0, 3, 7, 9, 13, 15, 16, 18], "now": 3, "np": [9, 10, 11, 19], "num_output_channel": 10, "num_sampl": [7, 17], "number": [7, 9, 10, 11, 19], "numpi": [8, 9, 11, 19], "o": 4, "obb": 16, "obj_detect": 15, "object": [7, 8, 11, 16, 19], "objectness_scor": [8, 19], "oblig": 2, "obtain": 19, "occupi": 18, "ocr": [1, 5, 7, 9, 11, 15], "ocr_carea": 19, "ocr_db_crnn": 11, "ocr_lin": 19, "ocr_pag": 19, "ocr_par": 19, "ocr_predictor": [9, 13, 15, 18, 19], "ocrdataset": [7, 17], "ocrmetr": 11, "ocrpredictor": [9, 13], "ocrx_word": 19, "offens": 2, "offici": [2, 9], "offlin": 2, "offset": 10, "onc": 19, "one": [3, 7, 9, 10, 13, 15, 19], "oneof": 10, "ones": [7, 11], "onli": [3, 9, 10, 11, 13, 15, 17, 18, 19], "onlin": 2, "onnx": 16, "onnxruntim": [16, 18], "onnxtr": 18, "opac": 10, "opacity_rang": 10, "open": [1, 2, 3, 15, 18], "opinion": 2, "optic": [5, 19], "optim": [5, 19], "option": [7, 9, 13], "order": [3, 7, 8, 10], "org": [2, 7, 9, 19], "organ": 8, "orient": [2, 8, 9, 12, 16, 19], "orientationpredictor": 9, "other": [2, 3], "otherwis": [2, 8, 11], "our": [1, 3, 9, 19], "out": [3, 9, 10, 11, 19], "outpout": 19, "output": [8, 10, 18], "output_s": [8, 10], "outsid": 14, "over": [7, 11, 19], "overal": [2, 9], "overlai": 8, "overview": 16, "overwrit": 13, "overwritten": 15, "own": 5, "p": [10, 19], "packag": [3, 5, 11, 14, 16, 17, 18], "pad": [7, 9, 10, 19], "page": [4, 7, 9, 11, 13, 19], "page1": 8, "page2": 8, "page_1": 19, "page_idx": [8, 19], "page_orientation_predictor": [9, 13], "page_param": 13, "pair": 11, "paper": 9, "par_1_1": 19, "paragraph": 19, "paragraph_break": 19, "parallel": 9, "param": [10, 19], "paramet": [5, 8, 9, 18], "pars": [5, 7], "parseq": [5, 9, 15, 18, 19], "part": [7, 10, 19], "parti": 4, "partial": 19, "particip": 2, "pass": [7, 8, 9, 13, 19], "password": 8, "patch": [9, 11], "path": [7, 8, 16, 17, 18], "path_to_checkpoint": 13, "path_to_custom_model": 18, "path_to_pt": 13, "patil": 1, "pattern": 2, "pdf": [8, 9, 12], "pdfpage": 8, "peopl": 2, "per": [10, 19], "perform": [5, 8, 9, 10, 11, 14, 18, 19], "period": 2, "permiss": 2, "permut": [5, 9], "persian_lett": 7, "person": [2, 17], "phase": 19, "photo": 17, "physic": [2, 8], "pick": 10, "pictur": 8, "pip": [3, 4, 16, 18], "pipelin": 19, "pixel": [8, 10, 19], "pleas": 3, "plot": 11, "plt": 11, "plug": 15, "plugin": 4, "png": 8, "point": 18, "polici": 14, "polish": 7, "polit": 2, "polygon": [7, 11, 19], "pool": 9, "portugues": 7, "posit": [2, 11], "possibl": [3, 11, 15, 19], "post": [2, 19], "postprocessor": 19, "potenti": 9, "power": 5, "ppageno": 19, "pre": [3, 9, 18], "precis": [11, 19], "pred": 11, "pred_box": 11, "pred_label": 11, "predefin": 17, "predict": [8, 9, 11, 19], "predictor": [5, 8, 9, 12, 13, 15, 18], "prefer": 17, "preinstal": 4, "preprocessor": [13, 19], "prerequisit": 15, "present": 12, "preserv": [9, 10, 19], "preserve_aspect_ratio": [8, 9, 10, 13, 19], "pretrain": [5, 9, 11, 13, 18, 19], "pretrained_backbon": [9, 13], "print": 19, "prior": 7, "privaci": 2, "privat": 2, "probabl": [1, 10], "problem": 3, "procedur": 10, "process": [3, 5, 8, 9, 13, 19], "processor": 19, "produc": [12, 19], "product": 18, "profession": 2, "project": [3, 17], "promptli": 2, "proper": 3, "properli": 7, "provid": [2, 3, 5, 15, 16, 17, 19], "public": [2, 5], "publicli": 19, "publish": 2, "pull": 15, "punctuat": 7, "pure": 7, "purpos": 3, "push_to_hf_hub": [9, 15], "py": 15, "pypdfium2": [4, 8], "pyplot": [8, 11], "python": [1, 3, 16], "python3": 15, "pytorch": [4, 5, 9, 10, 13, 15, 18, 19], "q": 3, "qr": [8, 16], "qr_code": 17, "qualiti": 10, "question": 2, "quickli": 5, "quicktour": 12, "r": 19, "race": 2, "ramdisk": 7, "rand": [9, 10, 11, 18, 19], "random": [9, 10, 11, 19], "randomappli": 10, "randombright": 10, "randomcontrast": 10, "randomcrop": 10, "randomgamma": 10, "randomhorizontalflip": 10, "randomhu": 10, "randomjpegqu": 10, "randomli": 10, "randomres": 10, "randomrot": 10, "randomsatur": 10, "randomshadow": 10, "rang": 10, "rassi": 15, "ratio": [9, 10, 19], "raw": [8, 11], "re": 18, "read": [5, 7, 9], "read_html": 8, "read_img_as_numpi": 8, "read_img_as_tensor": 8, "read_pdf": 8, "readi": 18, "real": [1, 5, 9, 10], "realli": 1, "reason": [2, 5, 7], "rebuild": 3, "rebuilt": 3, "recal": [11, 19], "receipt": [5, 7, 19], "reco_arch": [9, 13, 15, 18], "reco_b": 19, "reco_model": [13, 15, 18], "reco_param": 13, "reco_predictor": 13, "recogn": 19, "recognit": [7, 11, 12, 13], "recognition_predictor": [9, 19], "recognition_task": [7, 17], "recognitiondataset": [7, 17], "recognitionpredictor": [9, 13], "rectangular": 9, "reduc": [4, 10], "refer": [3, 4, 13, 15, 16, 17, 19], "regardless": 2, "region": 19, "regroup": 11, "regular": 17, "reject": 2, "rel": [8, 10, 11, 19], "relat": 8, "releas": [0, 4], "relev": 16, "religion": 2, "remov": 2, "render": [8, 19], "repo": 9, "repo_id": [9, 15], "report": 2, "repositori": [7, 9, 15], "repres": [2, 18, 19], "represent": [5, 9], "request": [2, 15], "requir": [4, 10, 18], "research": 5, "residu": 9, "resiz": [10, 19], "resnet": 9, "resnet18": [9, 15], "resnet31": 9, "resnet34": 9, "resnet50": [9, 15], "resolv": 8, "resolve_block": 19, "resolve_lin": 19, "resourc": 17, "respect": 2, "rest": [3, 10, 11], "restrict": 14, "result": [3, 7, 8, 12, 15, 18, 19], "return": 19, "reusabl": 19, "review": 2, "rgb": [8, 10], "rgb_mode": 8, "rgb_output": 8, "right": [2, 9, 11], "roboflow": 1, "robust": [5, 7], "root": 7, "rotat": [7, 8, 9, 10, 11, 12, 13, 17, 19], "run": [3, 4, 9], "same": [3, 8, 11, 17, 18, 19], "sampl": [7, 9, 17, 19], "sample_transform": 7, "sanjin": 1, "sar": [5, 9], "sar_resnet31": [9, 19], "satur": 10, "save": [9, 17], "scale": [8, 9, 10, 11], "scale_rang": 10, "scan": [5, 7], "scene": [5, 7, 9], "score": [8, 11], "script": [3, 17], "seamless": 5, "seamlessli": [5, 19], "search": [1, 9], "searchabl": 12, "sec": 19, "second": 19, "section": [1, 13, 15, 16, 18, 19], "secur": [2, 14], "see": [2, 3], "seen": 19, "segment": [5, 9, 19], "self": 19, "semant": [5, 9], "send": 19, "sens": 11, "sensit": 17, "separ": 19, "sequenc": [5, 7, 8, 9, 11, 19], "sequenti": [10, 19], "seri": 2, "seriou": 2, "set": [2, 4, 7, 9, 11, 14, 16, 19], "set_global_polici": 18, "sever": [8, 10, 19], "sex": 2, "sexual": 2, "shade": 10, "shape": [5, 8, 9, 10, 11, 19], "share": [14, 17], "shift": 10, "shm": 14, "should": [3, 7, 8, 10, 11], "show": [5, 8, 9, 11, 13, 15, 16], "showcas": [3, 12], "shuffl": [7, 10], "side": 11, "signatur": 8, "signific": 17, "simpl": [5, 9, 18], "simpler": 9, "sinc": [7, 17], "singl": [2, 3, 5, 7], "single_img_doc": 18, "size": [2, 7, 8, 10, 16, 19], "skew": 19, "slack": 3, "slightli": 9, "small": [3, 9, 19], "smallest": 8, "snapshot_download": 9, "snippet": 19, "so": [3, 4, 7, 9, 15, 17], "social": 2, "socio": 2, "some": [1, 4, 12, 15, 17], "someth": 3, "somewher": 3, "sort": 2, "sourc": [1, 7, 8, 9, 10, 11, 15], "space": [2, 19], "span": 19, "spanish": 7, "spatial": [5, 7, 8], "specif": [3, 4, 11, 13, 17, 19], "specifi": [2, 7, 8], "speed": [5, 9, 19], "sphinx": 3, "sroie": [5, 7, 17], "stabl": 4, "stackoverflow": 3, "stage": 5, "standalon": 12, "standard": 10, "start": 7, "state": [1, 5, 11, 16], "static": 11, "statist": 1, "statu": 2, "std": [10, 13], "step": 14, "still": 19, "str": [7, 8, 9, 10, 11], "straight": [7, 9, 17, 19], "straighten": 19, "straighten_pag": [9, 13, 19], "straigten_pag": 13, "stream": 8, "street": [5, 7], "strict": 4, "strictli": 11, "string": [7, 8, 11, 19], "strive": 4, "strong": [5, 9], "structur": [18, 19], "subset": [7, 19], "suggest": [3, 15], "sum": 11, "summari": 11, "support": [4, 13, 16, 18, 19], "sustain": 2, "svhn": [5, 7, 17], "svt": [7, 17], "swedish": 7, "symmetr": [9, 10, 19], "symmetric_pad": [9, 10, 19], "synthet": 5, "synthtext": [5, 7, 17], "system": 19, "t": [3, 7, 13, 18, 19], "tabl": [15, 16, 17], "take": [2, 7, 19], "target": [7, 8, 10, 11, 17], "target_s": 7, "task": [5, 7, 9, 15, 17, 19], "task2": 7, "team": 4, "techminde": 4, "templat": [3, 5], "tensor": [7, 8, 10, 19], "tensorflow": [4, 5, 8, 9, 10, 13, 15, 18, 19], "tensorspec": 18, "term": 2, "test": [7, 17], "test_set": 7, "text": [1, 7, 8, 9, 11, 17], "text_output": 19, "textmatch": 11, "textnet": 9, "textnet_bas": 9, "textnet_smal": 9, "textnet_tini": 9, "textract": [5, 19], "textstylebrush": [5, 7], "textual": [5, 7, 8, 9, 19], "tf": [4, 8, 9, 10, 15, 18], "than": [3, 11, 15], "thank": 3, "thei": [2, 11], "them": [7, 19], "thi": [1, 2, 3, 4, 6, 7, 10, 11, 13, 14, 15, 17, 18, 19], "thing": [18, 19], "third": 4, "those": [2, 8, 19], "threaten": 2, "threshold": 19, "through": [2, 10, 16, 17], "tilman": 15, "time": [1, 2, 5, 9, 11, 17], "tini": 9, "titl": [8, 19], "tm": 19, "tmp": 14, "togeth": [3, 8], "tograi": 10, "tool": [1, 17], "top": [11, 18, 19], "topic": 3, "torch": [4, 10, 13, 15, 18], "torchvis": 10, "total": 13, "toward": [2, 4], "train": [3, 7, 9, 10, 15, 16, 17, 18, 19], "train_it": [7, 17], "train_load": [7, 17], "train_pytorch": 15, "train_set": [7, 17], "train_tensorflow": 15, "trainabl": [5, 9], "tranform": 10, "transcrib": 19, "transfer": [5, 7], "transfo": 10, "transform": [5, 7, 9], "translat": 2, "troll": 2, "true": [7, 8, 9, 10, 11, 13, 14, 15, 17, 18, 19], "truth": 11, "tune": [1, 18], "tupl": [7, 8, 10, 11], "two": [8, 14], "txt": 7, "type": [8, 11, 15, 18, 19], "typic": 19, "u": [2, 3], "ucsd": 7, "udac": 3, "uint8": [8, 9, 11, 19], "ukrainian": 7, "unaccept": 2, "underli": [17, 19], "underneath": 8, "understand": [5, 7, 19], "uniform": [9, 10], "uniformli": 10, "uninterrupt": [8, 19], "union": 11, "unit": 1, "unittest": 3, "unlock": 8, "unoffici": 9, "unprofession": 2, "unsolicit": 2, "unsupervis": 5, "unwelcom": 2, "up": [9, 19], "updat": 11, "upgrad": 3, "upper": [7, 10], "uppercas": 17, "url": 8, "us": [2, 3, 4, 7, 9, 11, 12, 13, 14, 15, 16, 19], "usabl": 19, "usag": [14, 18], "use_polygon": [7, 11, 17], "useabl": 19, "user": [5, 8, 12], "utf": 19, "util": 18, "v1": 15, "v3": [9, 15, 19], "valid": 17, "valu": [3, 8, 10, 19], "valuabl": 5, "variabl": 14, "varieti": 7, "veri": 9, "verma": 1, "version": [2, 3, 4, 18, 19], "vgg": 9, "vgg16": 15, "vgg16_bn_r": 9, "via": 2, "video": 1, "vietnames": 7, "view": [5, 7], "viewpoint": 2, "violat": 2, "visibl": 2, "vision": [5, 7, 9], "visiondataset": 7, "visiontransform": 9, "visual": [4, 5, 16], "visualize_pag": 11, "vit_": 9, "vit_b": 9, "vitstr": [5, 9, 18], "vitstr_bas": [9, 19], "vitstr_smal": [9, 13, 18, 19], "viz": 4, "vocab": [13, 15, 17, 18, 19], "vocabulari": [7, 13, 15], "w": [8, 9, 10, 11], "w3": 19, "wa": 2, "wai": [2, 5, 17], "want": [3, 18, 19], "warmup": 19, "wasn": 3, "we": [1, 2, 3, 4, 5, 8, 10, 13, 15, 17, 18, 19], "weasyprint": 8, "web": [3, 8], "websit": 7, "welcom": 2, "well": [1, 2, 18], "were": [2, 8, 19], "what": [1, 2], "when": [2, 3, 9], "whenev": 3, "where": [3, 8, 10, 11], "whether": [3, 7, 8, 10, 11, 17, 19], "which": [2, 9, 14, 16, 17, 19], "whichev": 4, "while": [10, 19], "why": 2, "width": [8, 10], "wiki": 2, "wildreceipt": [5, 7, 17], "window": [9, 11], "wish": 3, "within": 2, "without": [2, 7, 9], "wonder": 3, "word": [5, 7, 9, 11, 19], "word_1_1": 19, "word_1_2": 19, "word_1_3": 19, "wordgener": [7, 17], "words_onli": 11, "work": [1, 13, 14, 19], "workflow": 3, "worklow": 3, "world": [11, 19], "worth": 9, "wrap": 19, "wrapper": [7, 10], "write": 14, "written": [2, 8], "www": [2, 8, 19], "x": [8, 10, 11], "x_ascend": 19, "x_descend": 19, "x_i": 11, "x_size": 19, "x_wconf": 19, "xhtml": 19, "xmax": 8, "xmin": 8, "xml": 19, "xml_bytes_str": 19, "xml_element": 19, "xml_output": 19, "xmln": 19, "y": 11, "y_i": 11, "y_j": 11, "yet": 16, "ymax": 8, "ymin": 8, "yolov8": 16, "you": [3, 4, 7, 8, 9, 13, 14, 15, 16, 17, 18, 19], "your": [3, 5, 8, 11, 19], "yoursit": 8, "yugesh": 1, "zero": [10, 11], "zoo": 13, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 7, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 7, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": 7, "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 7, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 7, "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 7, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 7, "\u00e4\u00f6\u00e4\u00f6": 7, "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 7, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": 7, "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": 7, "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": 7, "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": 7, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": 7, "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": 7, "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 7, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 7, "\u067e\u0686\u06a2\u06a4\u06af": 7, "\u0905": 7, "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": 7, "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": 7, "\u0950": 7, "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": 7, "\u09bd": 7, "\u09ce": 7, "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": 7}, "titles": ["Changelog", "Community resources", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.contrib", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Integrate contributions into your pipeline", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 3, "0": 0, "01": 0, "02": 0, "03": 0, "04": 0, "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 2], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 2], "2021": 0, "2022": 0, "2023": 0, "2024": 0, "21": 0, "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 2], "31": 0, "4": [0, 2], "5": 0, "6": 0, "7": 0, "8": 0, "9": 0, "advanc": 19, "approach": 19, "architectur": 19, "arg": [7, 8, 9, 10, 11], "artefact": 8, "artefactdetect": 16, "attribut": 2, "avail": [16, 17, 19], "aw": 14, "ban": 2, "block": 8, "bug": 3, "changelog": 0, "choos": [17, 19], "classif": [9, 13, 15], "code": [2, 3], "codebas": 3, "commit": 3, "commun": [1, 15], "compos": 10, "conda": 4, "conduct": 2, "connect": 3, "continu": 3, "contrib": 6, "contribut": [3, 6, 16], "contributor": 2, "convent": 15, "correct": 2, "coven": 2, "custom": [7, 13], "data": 17, "dataload": 7, "dataset": [5, 7, 17], "detect": [5, 9, 15, 17, 19], "develop": 3, "do": 19, "doctr": [3, 5, 6, 7, 8, 9, 10, 11, 12], "document": [3, 5, 8], "end": 19, "enforc": 2, "evalu": 11, "export": 18, "factori": 9, "featur": [3, 5], "feedback": 3, "file": 8, "from": 15, "gener": [7, 17], "git": 4, "guidelin": 2, "half": 18, "hub": 15, "huggingfac": 15, "i": 19, "infer": 18, "instal": [3, 4], "integr": [3, 16], "io": 8, "lambda": 14, "let": 3, "line": 8, "linux": 4, "load": [13, 15, 17], "loader": 7, "main": 5, "mode": 3, "model": [5, 9, 13, 15, 18, 19], "modifi": 3, "modul": [6, 16], "name": 15, "notebook": 12, "object": 17, "ocr": [17, 19], "onli": 4, "onnx": 18, "optim": 18, "option": 19, "orient": 13, "our": 2, "output": 19, "own": [13, 17], "packag": 4, "page": 8, "perman": 2, "pipelin": 16, "pledg": 2, "precis": 18, "predictor": 19, "prepar": 18, "prerequisit": 4, "pretrain": 15, "push": 15, "python": 4, "qualiti": 3, "question": 3, "read": 8, "readi": 17, "recognit": [5, 9, 15, 17, 19], "report": 3, "request": 3, "resourc": 1, "respons": 2, "return": [7, 8, 9, 11], "right": 19, "scope": 2, "share": 15, "should": 19, "stage": 19, "standard": 2, "structur": [3, 8], "style": 3, "support": [5, 6, 7, 10], "synthet": [7, 17], "task": 11, "temporari": 2, "test": 3, "text": [5, 19], "train": 13, "transform": 10, "two": 19, "unit": 3, "us": [17, 18], "util": 11, "v0": 0, "verif": 3, "via": 4, "visual": 11, "vocab": 7, "warn": 2, "what": 19, "word": 8, "your": [13, 15, 16, 17, 18], "zoo": [5, 9]}}) \ No newline at end of file diff --git a/v0.6.0/transforms.html b/v0.6.0/transforms.html deleted file mode 100644 index 85e94d8a76..0000000000 --- a/v0.6.0/transforms.html +++ /dev/null @@ -1,684 +0,0 @@ - - - - - - - - - - - - - doctr.transforms - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    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:

    -
    -
    -class doctr.transforms.Resize(output_size: Tuple[int, int], method: str = 'bilinear', preserve_aspect_ratio: bool = False, symmetric_pad: bool = False)[source]
    -

    Resizes a tensor to a target size

    -
    -
    Example::
    >>> from doctr.transforms import Resize
    ->>> import tensorflow as tf
    ->>> transfo = Resize((32, 32))
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • output_size – expected output size

    • -
    • method – interpolation method

    • -
    • preserve_aspect_ratio – if True, preserve aspect ratio and pad the rest with zeros

    • -
    • symmetric_pad – if True while preserving aspect ratio, the padding will be done symmetrically

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.Normalize(mean: Tuple[float, float, float], std: Tuple[float, float, float])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • mean – average value per channel

    • -
    • std – standard deviation per channel

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.LambdaTransformation(fn: Callable[[Tensor], Tensor])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import LambdaTransformation
    ->>> import tensorflow as tf
    ->>> transfo = LambdaTransformation(lambda x: x/ 255.)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    fn – the function to be applied to the input tensor

    -
    -
    -
    - -
    -
    -class doctr.transforms.ToGray[source]
    -

    Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ToGray()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    - -
    -
    -class doctr.transforms.ColorInversion(min_val: float = 0.5)[source]
    -

    Applies the following tranformation to a tensor (image or batch of images): -convert to grayscale, colorize (shift 0-values randomly), and then invert colors

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ColorInversion(min_val=0.6)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    min_val – range [min_val, 1] to colorize RGB pixels

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomBrightness(max_delta: float = 0.3)[source]
    -

    Randomly adjust brightness of a tensor (batch of images or image) by adding a delta -to all pixels

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Brightness()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    • -
    • p – probability to apply transformation

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomContrast(delta: float = 0.3)[source]
    -

    Randomly adjust contrast of a tensor (batch of images or image) by adjusting -each pixel: (img - mean) * contrast_factor + mean.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Contrast()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomSaturation(delta: float = 0.5)[source]
    -

    Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and -increasing saturation by a factor.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Saturation()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomHue(max_delta: float = 0.3)[source]
    -

    Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Hue()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomGamma(min_gamma: float = 0.5, max_gamma: float = 1.5, min_gain: float = 0.8, max_gain: float = 1.2)[source]
    -

    randomly performs gamma correction for a tensor (batch of images or image)

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Gamma()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_gamma – non-negative real number, lower bound for gamma param

    • -
    • max_gamma – non-negative real number, upper bound for gamma

    • -
    • min_gain – lower bound for constant multiplier

    • -
    • max_gain – upper bound for constant multiplier

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomJpegQuality(min_quality: int = 60, max_quality: int = 100)[source]
    -

    Randomly adjust jpeg quality of a 3 dimensional RGB image

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = JpegQuality()
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_quality – int between [0, 100]

    • -
    • max_quality – int between [0, 100]

    • -
    -
    -
    -
    - -
    -
    -

    Composing transformations

    -

    It is common to require several transformations to be performed consecutively.

    -
    -
    -class doctr.transforms.Compose(transforms: List[Callable[[Any], Any]])[source]
    -

    Implements a wrapper that will apply transformations sequentially

    -
    -
    Example::
    >>> from doctr.transforms import Compose, Resize
    ->>> import tensorflow as tf
    ->>> transfos = Compose([Resize((32, 32))])
    ->>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformation modules

    -
    -
    -
    - -
    -
    -class doctr.transforms.OneOf(transforms: List[Callable[[Any], Any]])[source]
    -

    Randomly apply one of the input transformations

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = OneOf([JpegQuality(), Gamma()])
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformations, one only will be picked

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomApply(transform: Callable[[Any], Any], p: float = 0.5)[source]
    -

    Apply with a probability p the input transformation

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = RandomApply(Gamma(), p=.5)
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • transform – transformation to apply

    • -
    • p – probability to apply

    • -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.6.0/using_doctr/custom_models_training.html b/v0.6.0/using_doctr/custom_models_training.html index df39d8d568..b714c1f971 100644 --- a/v0.6.0/using_doctr/custom_models_training.html +++ b/v0.6.0/using_doctr/custom_models_training.html @@ -14,7 +14,7 @@ - + Train your own model - docTR documentation @@ -619,7 +619,7 @@

    Loading your custom trained orientation classification model - + diff --git a/v0.6.0/using_doctr/running_on_aws.html b/v0.6.0/using_doctr/running_on_aws.html index 16ceaca7a1..808ea541cd 100644 --- a/v0.6.0/using_doctr/running_on_aws.html +++ b/v0.6.0/using_doctr/running_on_aws.html @@ -14,7 +14,7 @@ - + AWS Lambda - docTR documentation @@ -362,7 +362,7 @@

    AWS Lambda - + diff --git a/v0.6.0/using_doctr/sharing_models.html b/v0.6.0/using_doctr/sharing_models.html index d76b4017f4..c9e978400a 100644 --- a/v0.6.0/using_doctr/sharing_models.html +++ b/v0.6.0/using_doctr/sharing_models.html @@ -14,7 +14,7 @@ - + Share your model with the community - docTR documentation @@ -544,7 +544,7 @@

    Recognition - + diff --git a/v0.6.0/using_doctr/using_contrib_modules.html b/v0.6.0/using_doctr/using_contrib_modules.html index 50598dae5d..0c5fffdf9f 100644 --- a/v0.6.0/using_doctr/using_contrib_modules.html +++ b/v0.6.0/using_doctr/using_contrib_modules.html @@ -14,7 +14,7 @@ - + Integrate contributions into your pipeline - docTR documentation @@ -415,7 +415,7 @@

    ArtefactDetection - + diff --git a/v0.6.0/using_doctr/using_datasets.html b/v0.6.0/using_doctr/using_datasets.html index 460476dbbf..8a7d4f0a64 100644 --- a/v0.6.0/using_doctr/using_datasets.html +++ b/v0.6.0/using_doctr/using_datasets.html @@ -14,7 +14,7 @@ - + Choose a ready to use dataset - docTR documentation @@ -642,7 +642,7 @@

    Data Loading - + diff --git a/v0.6.0/using_doctr/using_model_export.html b/v0.6.0/using_doctr/using_model_export.html index 6124c00ebe..6790dd0642 100644 --- a/v0.6.0/using_doctr/using_model_export.html +++ b/v0.6.0/using_doctr/using_model_export.html @@ -14,7 +14,7 @@ - + Preparing your model for inference - docTR documentation @@ -467,7 +467,7 @@

    Using your ONNX exported model - + diff --git a/v0.6.0/using_doctr/using_models.html b/v0.6.0/using_doctr/using_models.html index 61f1f5ab7a..9ead8498e1 100644 --- a/v0.6.0/using_doctr/using_models.html +++ b/v0.6.0/using_doctr/using_models.html @@ -14,7 +14,7 @@ - + Choosing the right model - docTR documentation @@ -1253,7 +1253,7 @@

    Advanced options - + diff --git a/v0.6.0/utils.html b/v0.6.0/utils.html deleted file mode 100644 index e2f223f06a..0000000000 --- a/v0.6.0/utils.html +++ /dev/null @@ -1,574 +0,0 @@ - - - - - - - - - - - - - doctr.utils - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.utils

    -

    This module regroups non-core features that are complementary to the rest of the package.

    -
    -

    Visualization

    -

    Easy-to-use functions to make sense of your model’s predictions.

    -
    -
    -doctr.utils.visualization.visualize_page(page: Dict[str, Any], image: ndarray, words_only: bool = True, display_artefacts: bool = True, scale: float = 10, interactive: bool = True, add_labels: bool = True, **kwargs: Any) Figure[source]
    -

    Visualize a full page with predicted blocks, lines and words

    -
    -
    Example::
    >>> import numpy as np
    ->>> import matplotlib.pyplot as plt
    ->>> from doctr.utils.visualization import visualize_page
    ->>> from doctr.models import ocr_db_crnn
    ->>> model = ocr_db_crnn(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([[input_page]])
    ->>> visualize_page(out[0].pages[0].export(), input_page)
    ->>> plt.show()
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • page – the exported Page of a Document

    • -
    • image – np array of the page, needs to have the same shape than page[‘dimensions’]

    • -
    • words_only – whether only words should be displayed

    • -
    • display_artefacts – whether artefacts should be displayed

    • -
    • scale – figsize of the largest windows side

    • -
    • interactive – whether the plot should be interactive

    • -
    • add_labels – for static plot, adds text labels on top of bounding box

    • -
    -
    -
    -
    - -
    -
    -

    Task evaluation

    -

    Implementations of task-specific metrics to easily assess your model performances.

    -
    -
    -class doctr.utils.metrics.TextMatch[source]
    -

    Implements text match metric (word-level accuracy) for recognition task.

    -

    The raw aggregated metric is computed as follows:

    -
    -
    -\[\forall X, Y \in \mathcal{W}^N, -TextMatch(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N f_{Y_i}(X_i)\]
    -
    -

    with the indicator function \(f_{a}\) defined as:

    -
    -
    -\[\begin{split}\forall a, x \in \mathcal{W}, -f_a(x) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } x = a \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{W}\) is the set of all possible character sequences, -\(N\) is a strictly positive integer.

    -
    -
    Example::
    >>> from doctr.utils import TextMatch
    ->>> metric = TextMatch()
    ->>> metric.update(['Hello', 'world'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    -summary() Dict[str, float][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a dictionary with the exact match score for the raw data, its lower-case counterpart, its unidecode -counterpart and its lower-case unidecode counterpart

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.LocalizationConfusion(iou_thresh: float = 0.5, rotated_bbox: bool = False, mask_shape: Tuple[int, int] = (1024, 1024))[source]
    -

    Implements common confusion metrics and mean IoU for localization evaluation.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall Y \in \mathcal{B}^N, \forall X \in \mathcal{B}^M, \\ -Recall(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -Precision(X, Y) = \frac{1}{M} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -meanIoU(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(X_i, Y_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(g_{X}\) defined as:

    -
    -
    -\[\begin{split}\forall y \in \mathcal{B}, -g_X(y) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } y\mbox{ has been assigned to any }(X_i)_i\mbox{ with an }IoU \geq 0.5 \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import LocalizationConfusion
    ->>> metric = LocalizationConfusion(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]))
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[float | None, float | None, float | None][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall, precision and meanIoU scores

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.OCRMetric(iou_thresh: float = 0.5, rotated_bbox: bool = False, mask_shape: Tuple[int, int] = (1024, 1024))[source]
    -

    Implements end-to-end OCR metric.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall (B, L) \in \mathcal{B}^N \times \mathcal{L}^N, -\forall (\hat{B}, \hat{L}) \in \mathcal{B}^M \times \mathcal{L}^M, \\ -Recall(B, \hat{B}, L, \hat{L}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -Precision(B, \hat{B}, L, \hat{L}) = \frac{1}{M} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(h_{B, L}\) defined as:

    -
    -
    -\[\begin{split}\forall (b, l) \in \mathcal{B} \times \mathcal{L}, -h_{B,L}(b, l) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ - & IoU \geq 0.5 \mbox{ and that for this assignment, } l = L_j\\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(\mathcal{L}\) is the set of possible character sequences, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import OCRMetric
    ->>> metric = OCRMetric(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]),
    -['hello'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[Dict[str, float | None], Dict[str, float | None], float | None][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall & precision for each string comparison flexibility and the mean IoU

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.7.0/_modules/doctr/datasets/cord.html b/v0.7.0/_modules/doctr/datasets/cord.html index 46e00abe77..0e7141c9cf 100644 --- a/v0.7.0/_modules/doctr/datasets/cord.html +++ b/v0.7.0/_modules/doctr/datasets/cord.html @@ -13,7 +13,7 @@ - + doctr.datasets.cord - docTR documentation @@ -445,7 +445,7 @@

    Source code for doctr.datasets.cord

         
       
    - + diff --git a/v0.7.0/_modules/doctr/datasets/core.html b/v0.7.0/_modules/doctr/datasets/core.html deleted file mode 100644 index b3dcc29ff9..0000000000 --- a/v0.7.0/_modules/doctr/datasets/core.html +++ /dev/null @@ -1,417 +0,0 @@ - - - - - - - - - - - - doctr.datasets.core - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.core

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from pathlib import Path
    -from zipfile import ZipFile
    -from typing import List, Any, Optional, Tuple
    -import tensorflow as tf
    -
    -from doctr.models.utils import download_from_url
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset:
    -
    -    data: List[Any] = []
    -
    -    def __len__(self):
    -        return len(self.data)
    -
    -    def __getitem__(
    -        self,
    -        index: int
    -    ) -> Tuple[tf.Tensor, Any]:
    -
    -        img_name, target = self.data[index]
    -        # Read image
    -        img = tf.io.read_file(os.path.join(self.root, img_name))
    -        img = tf.image.decode_jpeg(img, channels=3)
    -        if self.sample_transforms is not None:
    -            img = self.sample_transforms(img)
    -
    -        return img, target
    -
    -    def extra_repr(self) -> str:
    -        return ""
    -
    -    def __repr__(self) -> str:
    -        return f"{self.__class__.__name__}({self.extra_repr()})"
    -
    -    @staticmethod
    -    def collate_fn(samples: List[Tuple[tf.Tensor, Any]]) -> Tuple[tf.Tensor, List[Any]]:
    -
    -        images, targets = zip(*samples)
    -        images = tf.stack(images, axis=0)
    -
    -        return images, list(targets)
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset): - """Implements an abstract dataset - - Args: - url: URL of the dataset - file_name: name of the file once downloaded - file_hash: expected SHA256 of the file - extract_archive: whether the downloaded file is an archive to be extracted - download: whether the dataset should be downloaded if not present on disk - overwrite: whether the archive should be re-extracted - """ - - def __init__( - self, - url: str, - file_name: Optional[str] = None, - file_hash: Optional[str] = None, - extract_archive: bool = False, - download: bool = False, - overwrite: bool = False, - ) -> None: - - dataset_cache = os.path.join(os.path.expanduser('~'), '.cache', 'doctr', 'datasets') - - file_name = file_name if isinstance(file_name, str) else os.path.basename(url) - # Download the file if not present - archive_path = os.path.join(dataset_cache, file_name) - - if not os.path.exists(archive_path) and not download: - raise ValueError("the dataset needs to be downloaded first with download=True") - - archive_path = download_from_url(url, file_name, file_hash, cache_subdir='datasets') - - # Extract the archive - if extract_archive: - archive_path = Path(archive_path) - dataset_path = archive_path.parent.joinpath(archive_path.stem) - if not dataset_path.is_dir() or overwrite: - with ZipFile(archive_path, 'r') as f: - f.extractall(path=dataset_path) - - # List images - self._root = dataset_path if extract_archive else archive_path - self.data: List[Any] = []
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.7.0/_modules/doctr/datasets/datasets/tensorflow.html b/v0.7.0/_modules/doctr/datasets/datasets/tensorflow.html deleted file mode 100644 index a236abd9fe..0000000000 --- a/v0.7.0/_modules/doctr/datasets/datasets/tensorflow.html +++ /dev/null @@ -1,356 +0,0 @@ - - - - - - - - - - - - doctr.datasets.datasets.tensorflow - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.datasets.tensorflow

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from typing import List, Any, Tuple
    -import tensorflow as tf
    -
    -from .base import _AbstractDataset, _VisionDataset
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset(_AbstractDataset):
    -
    -    def _read_sample(self, index: int) -> Tuple[tf.Tensor, Any]:
    -        img_name, target = self.data[index]
    -        # Read image
    -        img = tf.io.read_file(os.path.join(self.root, img_name))
    -        img = tf.image.decode_jpeg(img, channels=3)
    -
    -        return img, target
    -
    -    @staticmethod
    -    def collate_fn(samples: List[Tuple[tf.Tensor, Any]]) -> Tuple[tf.Tensor, List[Any]]:
    -
    -        images, targets = zip(*samples)
    -        images = tf.stack(images, axis=0)
    -
    -        return images, list(targets)
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset, _VisionDataset): - pass
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.7.0/_modules/doctr/datasets/detection.html b/v0.7.0/_modules/doctr/datasets/detection.html index e7009409d8..520a53e311 100644 --- a/v0.7.0/_modules/doctr/datasets/detection.html +++ b/v0.7.0/_modules/doctr/datasets/detection.html @@ -13,7 +13,7 @@ - + doctr.datasets.detection - docTR documentation @@ -421,7 +421,7 @@

    Source code for doctr.datasets.detection

         
       
    - + diff --git a/v0.7.0/_modules/doctr/datasets/doc_artefacts.html b/v0.7.0/_modules/doctr/datasets/doc_artefacts.html index 906edfbd02..007c71c365 100644 --- a/v0.7.0/_modules/doctr/datasets/doc_artefacts.html +++ b/v0.7.0/_modules/doctr/datasets/doc_artefacts.html @@ -13,7 +13,7 @@ - + doctr.datasets.doc_artefacts - docTR documentation @@ -407,7 +407,7 @@

    Source code for doctr.datasets.doc_artefacts

       
    - + diff --git a/v0.7.0/_modules/doctr/datasets/funsd.html b/v0.7.0/_modules/doctr/datasets/funsd.html index 578b6ac937..1ac37edbc6 100644 --- a/v0.7.0/_modules/doctr/datasets/funsd.html +++ b/v0.7.0/_modules/doctr/datasets/funsd.html @@ -13,7 +13,7 @@ - + doctr.datasets.funsd - docTR documentation @@ -439,7 +439,7 @@

    Source code for doctr.datasets.funsd

         
       
    - + diff --git a/v0.7.0/_modules/doctr/datasets/generator/tensorflow.html b/v0.7.0/_modules/doctr/datasets/generator/tensorflow.html index 97764a5b1b..4ad437bd89 100644 --- a/v0.7.0/_modules/doctr/datasets/generator/tensorflow.html +++ b/v0.7.0/_modules/doctr/datasets/generator/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.datasets.generator.tensorflow - docTR documentation @@ -387,7 +387,7 @@

    Source code for doctr.datasets.generator.tensorflow

    - + diff --git a/v0.7.0/_modules/doctr/datasets/ic03.html b/v0.7.0/_modules/doctr/datasets/ic03.html index e394592496..bbaae590fa 100644 --- a/v0.7.0/_modules/doctr/datasets/ic03.html +++ b/v0.7.0/_modules/doctr/datasets/ic03.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic03 - docTR documentation @@ -451,7 +451,7 @@

    Source code for doctr.datasets.ic03

         
       
    - + diff --git a/v0.7.0/_modules/doctr/datasets/ic13.html b/v0.7.0/_modules/doctr/datasets/ic13.html index 8cc7c5d9d1..d2c344d276 100644 --- a/v0.7.0/_modules/doctr/datasets/ic13.html +++ b/v0.7.0/_modules/doctr/datasets/ic13.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic13 - docTR documentation @@ -424,7 +424,7 @@

    Source code for doctr.datasets.ic13

         
       
    - + diff --git a/v0.7.0/_modules/doctr/datasets/iiit5k.html b/v0.7.0/_modules/doctr/datasets/iiit5k.html index ad0c8a6a63..8506e634b4 100644 --- a/v0.7.0/_modules/doctr/datasets/iiit5k.html +++ b/v0.7.0/_modules/doctr/datasets/iiit5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiit5k - docTR documentation @@ -427,7 +427,7 @@

    Source code for doctr.datasets.iiit5k

         
       
    - + diff --git a/v0.7.0/_modules/doctr/datasets/iiithws.html b/v0.7.0/_modules/doctr/datasets/iiithws.html index cb80799b4d..56de57d502 100644 --- a/v0.7.0/_modules/doctr/datasets/iiithws.html +++ b/v0.7.0/_modules/doctr/datasets/iiithws.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiithws - docTR documentation @@ -400,7 +400,7 @@

    Source code for doctr.datasets.iiithws

         
       
    - + diff --git a/v0.7.0/_modules/doctr/datasets/imgur5k.html b/v0.7.0/_modules/doctr/datasets/imgur5k.html index b6bb0a7a6c..c29f733ae2 100644 --- a/v0.7.0/_modules/doctr/datasets/imgur5k.html +++ b/v0.7.0/_modules/doctr/datasets/imgur5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.imgur5k - docTR documentation @@ -472,7 +472,7 @@

    Source code for doctr.datasets.imgur5k

         
       
    - + diff --git a/v0.7.0/_modules/doctr/datasets/loader.html b/v0.7.0/_modules/doctr/datasets/loader.html index 58b250220e..40fb3525de 100644 --- a/v0.7.0/_modules/doctr/datasets/loader.html +++ b/v0.7.0/_modules/doctr/datasets/loader.html @@ -13,7 +13,7 @@ - + doctr.datasets.loader - docTR documentation @@ -426,7 +426,7 @@

    Source code for doctr.datasets.loader

         
       
    - + diff --git a/v0.7.0/_modules/doctr/datasets/mjsynth.html b/v0.7.0/_modules/doctr/datasets/mjsynth.html index c46f57431c..da0b98a607 100644 --- a/v0.7.0/_modules/doctr/datasets/mjsynth.html +++ b/v0.7.0/_modules/doctr/datasets/mjsynth.html @@ -13,7 +13,7 @@ - + doctr.datasets.mjsynth - docTR documentation @@ -431,7 +431,7 @@

    Source code for doctr.datasets.mjsynth

         
       
    - + diff --git a/v0.7.0/_modules/doctr/datasets/ocr.html b/v0.7.0/_modules/doctr/datasets/ocr.html index 1c8c1ed153..8582c04ea4 100644 --- a/v0.7.0/_modules/doctr/datasets/ocr.html +++ b/v0.7.0/_modules/doctr/datasets/ocr.html @@ -13,7 +13,7 @@ - + doctr.datasets.ocr - docTR documentation @@ -396,7 +396,7 @@

    Source code for doctr.datasets.ocr

         
       
    - + diff --git a/v0.7.0/_modules/doctr/datasets/recognition.html b/v0.7.0/_modules/doctr/datasets/recognition.html index 99e48e8086..941dbea77c 100644 --- a/v0.7.0/_modules/doctr/datasets/recognition.html +++ b/v0.7.0/_modules/doctr/datasets/recognition.html @@ -13,7 +13,7 @@ - + doctr.datasets.recognition - docTR documentation @@ -381,7 +381,7 @@

    Source code for doctr.datasets.recognition

         
       
    - + diff --git a/v0.7.0/_modules/doctr/datasets/sroie.html b/v0.7.0/_modules/doctr/datasets/sroie.html index d9eb3c6f9b..84f2df9766 100644 --- a/v0.7.0/_modules/doctr/datasets/sroie.html +++ b/v0.7.0/_modules/doctr/datasets/sroie.html @@ -13,7 +13,7 @@ - + doctr.datasets.sroie - docTR documentation @@ -428,7 +428,7 @@

    Source code for doctr.datasets.sroie

         
       
    - + diff --git a/v0.7.0/_modules/doctr/datasets/svhn.html b/v0.7.0/_modules/doctr/datasets/svhn.html index 59fefb9738..ddef2456c5 100644 --- a/v0.7.0/_modules/doctr/datasets/svhn.html +++ b/v0.7.0/_modules/doctr/datasets/svhn.html @@ -13,7 +13,7 @@ - + doctr.datasets.svhn - docTR documentation @@ -456,7 +456,7 @@

    Source code for doctr.datasets.svhn

         
       
    - + diff --git a/v0.7.0/_modules/doctr/datasets/svt.html b/v0.7.0/_modules/doctr/datasets/svt.html index 7cbeddf891..a3fde4dc2f 100644 --- a/v0.7.0/_modules/doctr/datasets/svt.html +++ b/v0.7.0/_modules/doctr/datasets/svt.html @@ -13,7 +13,7 @@ - + doctr.datasets.svt - docTR documentation @@ -442,7 +442,7 @@

    Source code for doctr.datasets.svt

         
       
    - + diff --git a/v0.7.0/_modules/doctr/datasets/synthtext.html b/v0.7.0/_modules/doctr/datasets/synthtext.html index f92c9fcf3e..fa5189063b 100644 --- a/v0.7.0/_modules/doctr/datasets/synthtext.html +++ b/v0.7.0/_modules/doctr/datasets/synthtext.html @@ -13,7 +13,7 @@ - + doctr.datasets.synthtext - docTR documentation @@ -453,7 +453,7 @@

    Source code for doctr.datasets.synthtext

         
       
    - + diff --git a/v0.7.0/_modules/doctr/datasets/utils.html b/v0.7.0/_modules/doctr/datasets/utils.html index ab7f6e75e1..6057b2e54e 100644 --- a/v0.7.0/_modules/doctr/datasets/utils.html +++ b/v0.7.0/_modules/doctr/datasets/utils.html @@ -13,7 +13,7 @@ - + doctr.datasets.utils - docTR documentation @@ -526,7 +526,7 @@

    Source code for doctr.datasets.utils

         
       
    - + diff --git a/v0.7.0/_modules/doctr/datasets/wildreceipt.html b/v0.7.0/_modules/doctr/datasets/wildreceipt.html index c543ee7cac..12c6aebd14 100644 --- a/v0.7.0/_modules/doctr/datasets/wildreceipt.html +++ b/v0.7.0/_modules/doctr/datasets/wildreceipt.html @@ -13,7 +13,7 @@ - + doctr.datasets.wildreceipt - docTR documentation @@ -454,7 +454,7 @@

    Source code for doctr.datasets.wildreceipt

         
       
    - + diff --git a/v0.7.0/_modules/doctr/documents/elements.html b/v0.7.0/_modules/doctr/documents/elements.html deleted file mode 100644 index 10c1e142d2..0000000000 --- a/v0.7.0/_modules/doctr/documents/elements.html +++ /dev/null @@ -1,577 +0,0 @@ - - - - - - - - - - - - doctr.documents.elements - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.elements

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import matplotlib.pyplot as plt
    -from typing import Tuple, Dict, List, Any, Optional, Union
    -
    -from doctr.utils.geometry import resolve_enclosing_bbox, resolve_enclosing_rbbox
    -from doctr.utils.visualization import visualize_page
    -from doctr.utils.common_types import BoundingBox, RotatedBbox
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['Element', 'Word', 'Artefact', 'Line', 'Block', 'Page', 'Document']
    -
    -
    -class Element(NestedObject):
    -    """Implements an abstract document element with exporting and text rendering capabilities"""
    -
    -    _exported_keys: List[str] = []
    -
    -    def __init__(self, **kwargs: Any) -> None:
    -        self._children_names: List[str] = []
    -        for k, v in kwargs.items():
    -            setattr(self, k, v)
    -            self._children_names.append(k)
    -
    -    def export(self) -> Dict[str, Any]:
    -        """Exports the object into a nested dict format"""
    -
    -        export_dict = {k: getattr(self, k) for k in self._exported_keys}
    -        for children_name in self._children_names:
    -            export_dict[children_name] = [c.export() for c in getattr(self, children_name)]
    -
    -        return export_dict
    -
    -    def render(self) -> str:
    -        raise NotImplementedError
    -
    -
    -
    -[docs] -class Word(Element): - """Implements a word element - - Args: - value: the text string of the word - confidence: the confidence associated with the text prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size - """ - - _exported_keys: List[str] = ["value", "confidence", "geometry"] - - def __init__(self, value: str, confidence: float, geometry: Union[BoundingBox, RotatedBbox]) -> None: - super().__init__() - self.value = value - self.confidence = confidence - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return self.value - - def extra_repr(self) -> str: - return f"value='{self.value}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Artefact(Element): - """Implements a non-textual element - - Args: - artefact_type: the type of artefact - confidence: the confidence of the type prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. - """ - - _exported_keys: List[str] = ["geometry", "type", "confidence"] - - def __init__(self, artefact_type: str, confidence: float, geometry: BoundingBox) -> None: - super().__init__() - self.geometry = geometry - self.type = artefact_type - self.confidence = confidence - - def render(self) -> str: - """Renders the full text of the element""" - return f"[{self.type.upper()}]" - - def extra_repr(self) -> str: - return f"type='{self.type}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Line(Element): - """Implements a line element as a collection of words - - Args: - words: list of word elements - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all words in it. - """ - - _exported_keys: List[str] = ["geometry"] - words: List[Word] = [] - - def __init__( - self, - words: List[Word], - geometry: Optional[Union[BoundingBox, RotatedBbox]] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - # Check whether this is a rotated or straight box - box_resolution_fn = resolve_enclosing_rbbox if len(words[0].geometry) == 5 else resolve_enclosing_bbox - geometry = box_resolution_fn([w.geometry for w in words]) # type: ignore[operator, misc] - - super().__init__(words=words) - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return " ".join(w.render() for w in self.words)
    - - - -
    -[docs] -class Block(Element): - """Implements a block element as a collection of lines and artefacts - - Args: - lines: list of line elements - artefacts: list of artefacts - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all lines and artefacts in it. - """ - - _exported_keys: List[str] = ["geometry"] - lines: List[Line] = [] - artefacts: List[Artefact] = [] - - def __init__( - self, - lines: List[Line] = [], - artefacts: List[Artefact] = [], - geometry: Optional[Union[BoundingBox, RotatedBbox]] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - line_boxes = [word.geometry for line in lines for word in line.words] - artefact_boxes = [artefact.geometry for artefact in artefacts] - box_resolution_fn = resolve_enclosing_rbbox if len(lines[0].geometry) == 5 else resolve_enclosing_bbox - geometry = box_resolution_fn(line_boxes + artefact_boxes) # type: ignore[operator, arg-type] - - super().__init__(lines=lines, artefacts=artefacts) - self.geometry = geometry - - def render(self, line_break: str = '\n') -> str: - """Renders the full text of the element""" - return line_break.join(line.render() for line in self.lines)
    - - - -
    -[docs] -class Page(Element): - """Implements a page element as a collection of blocks - - Args: - blocks: list of block elements - page_idx: the index of the page in the input raw document - dimensions: the page size in pixels in format (width, height) - orientation: a dictionary with the value of the rotation angle in degress and confidence of the prediction - language: a dictionary with the language value and confidence of the prediction - """ - - _exported_keys: List[str] = ["page_idx", "dimensions", "orientation", "language"] - blocks: List[Block] = [] - - def __init__( - self, - blocks: List[Block], - page_idx: int, - dimensions: Tuple[int, int], - orientation: Optional[Dict[str, Any]] = None, - language: Optional[Dict[str, Any]] = None, - ) -> None: - super().__init__(blocks=blocks) - self.page_idx = page_idx - self.dimensions = dimensions - self.orientation = orientation if isinstance(orientation, dict) else dict(value=None, confidence=None) - self.language = language if isinstance(language, dict) else dict(value=None, confidence=None) - - def render(self, block_break: str = '\n\n') -> str: - """Renders the full text of the element""" - return block_break.join(b.render() for b in self.blocks) - - def extra_repr(self) -> str: - return f"dimensions={self.dimensions}" - -
    -[docs] - def show( - self, page: np.ndarray, interactive: bool = True, **kwargs - ) -> None: - """Overlay the result on a given image - - Args: - page: image encoded as a numpy array in uint8 - interactive: whether the display should be interactive - """ - visualize_page(self.export(), page, interactive=interactive) - plt.show(**kwargs)
    -
    - - - -
    -[docs] -class Document(Element): - """Implements a document element as a collection of pages - - Args: - pages: list of page elements - """ - - pages: List[Page] = [] - - def __init__( - self, - pages: List[Page], - ) -> None: - super().__init__(pages=pages) - - def render(self, page_break: str = '\n\n\n\n') -> str: - """Renders the full text of the element""" - return page_break.join(p.render() for p in self.pages) - -
    -[docs] - def show(self, pages: List[np.ndarray], **kwargs) -> None: - """Overlay the result on a given image - - Args: - pages: list of images encoded as numpy arrays in uint8 - """ - for img, result in zip(pages, self.pages): - result.show(img, **kwargs)
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.7.0/_modules/doctr/documents/reader.html b/v0.7.0/_modules/doctr/documents/reader.html deleted file mode 100644 index cdcd814b6c..0000000000 --- a/v0.7.0/_modules/doctr/documents/reader.html +++ /dev/null @@ -1,612 +0,0 @@ - - - - - - - - - - - - doctr.documents.reader - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.reader

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import cv2
    -from pathlib import Path
    -import fitz
    -from weasyprint import HTML
    -from typing import List, Tuple, Optional, Any, Union, Sequence, Dict
    -
    -__all__ = ['read_pdf', 'read_img', 'read_html', 'DocumentFile', 'PDF']
    -
    -
    -AbstractPath = Union[str, Path]
    -AbstractFile = Union[AbstractPath, bytes]
    -Bbox = Tuple[float, float, float, float]
    -
    -
    -
    -[docs] -def read_img( - file: AbstractFile, - output_size: Optional[Tuple[int, int]] = None, - rgb_output: bool = True, -) -> np.ndarray: - """Read an image file into numpy format - - Example:: - >>> from doctr.documents import read_img - >>> page = read_img("path/to/your/doc.jpg") - - Args: - file: the path to the image file - output_size: the expected output size of each page in format H x W - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - Returns: - the page decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)): - if not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - img = cv2.imread(str(file), cv2.IMREAD_COLOR) - elif isinstance(file, bytes): - file = np.frombuffer(file, np.uint8) - img = cv2.imdecode(file, cv2.IMREAD_COLOR) - else: - raise TypeError("unsupported object type for argument 'file'") - - # Validity check - if img is None: - raise ValueError("unable to read file.") - # Resizing - if isinstance(output_size, tuple): - img = cv2.resize(img, output_size[::-1], interpolation=cv2.INTER_LINEAR) - # Switch the channel order - if rgb_output: - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - return img
    - - - -
    -[docs] -def read_pdf(file: AbstractFile, **kwargs: Any) -> fitz.Document: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_pdf - >>> doc = read_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)) and not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - - fitz_args: Dict[str, AbstractFile] = {} - - if isinstance(file, (str, Path)): - fitz_args['filename'] = file - elif isinstance(file, bytes): - fitz_args['stream'] = file - else: - raise TypeError("unsupported object type for argument 'file'") - - # Read pages with fitz and convert them to numpy ndarrays - return fitz.open(**fitz_args, filetype="pdf", **kwargs)
    - - - -def convert_page_to_numpy( - page: fitz.fitz.Page, - output_size: Optional[Tuple[int, int]] = None, - bgr_output: bool = False, - default_scales: Tuple[float, float] = (2, 2), -) -> np.ndarray: - """Convert a fitz page to a numpy-formatted image - - Args: - page: the page of a file read with PyMuPDF - output_size: the expected output size of each page in format H x W. Default goes to 840 x 595 for A4 pdf, - if you want to increase the resolution while preserving the original A4 aspect ratio can pass (1024, 726) - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - default_scales: spatial scaling to be applied when output_size is not specified where (1, 1) - corresponds to 72 dpi rendering. - - Returns: - the rendered image in numpy format - """ - - # If no output size is specified, keep the origin one - if output_size is not None: - scales = (output_size[1] / page.MediaBox[2], output_size[0] / page.MediaBox[3]) - else: - # Default 72 DPI (scales of (1, 1)) is unnecessarily low - scales = default_scales - - transform_matrix = fitz.Matrix(*scales) - - # Generate the pixel map using the transformation matrix - pixmap = page.getPixmap(matrix=transform_matrix) - # Decode it into a numpy - img = np.frombuffer(pixmap.samples, dtype=np.uint8).reshape(pixmap.height, pixmap.width, 3) - - # Switch the channel order - if bgr_output: - img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) - - return img - - -
    -[docs] -def read_html(url: str, **kwargs: Any) -> bytes: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_html - >>> doc = read_html("https://www.yoursite.com") - - Args: - url: URL of the target web page - Returns: - decoded PDF file as a bytes stream - """ - - return HTML(url, **kwargs).write_pdf()
    - - - -
    -[docs] -class PDF: - """PDF document template - - Args: - doc: input PDF document - """ - def __init__(self, doc: fitz.Document) -> None: - self.doc = doc - -
    -[docs] - def as_images(self, **kwargs) -> List[np.ndarray]: - """Convert all document pages to images - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images() - - Args: - kwargs: keyword arguments of `convert_page_to_numpy` - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - return [convert_page_to_numpy(page, **kwargs) for page in self.doc]
    - - - def get_page_words(self, idx, **kwargs) -> List[Tuple[Bbox, str]]: - """Get the annotations for all words of a given page""" - - # xmin, ymin, xmax, ymax, value, block_idx, line_idx, word_idx - return [(info[:4], info[4]) for info in self.doc[idx].getTextWords(**kwargs)] - -
    -[docs] - def get_words(self, **kwargs) -> List[List[Tuple[Bbox, str]]]: - """Get the annotations for all words in the document - - Example:: - >>> from doctr.documents import DocumentFile - >>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words() - - Args: - kwargs: keyword arguments of `fitz.Page.getTextWords` - Returns: - the list of pages annotations, represented as a list of tuple (bounding box, value) - """ - return [self.get_page_words(idx, **kwargs) for idx in range(len(self.doc))]
    - - - def get_page_artefacts(self, idx) -> List[Tuple[float, float, float, float]]: - return [tuple(self.doc[idx].getImageBbox(artefact)) # type: ignore[misc] - for artefact in self.doc[idx].get_images(full=True)] - -
    -[docs] - def get_artefacts(self) -> List[List[Tuple[float, float, float, float]]]: - """Get the artefacts for the entire document - - Example:: - >>> from doctr.documents import DocumentFile - >>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts() - - Returns: - the list of pages artefacts, represented as a list of bounding boxes - """ - - return [self.get_page_artefacts(idx) for idx in range(len(self.doc))]
    -
    - - - -
    -[docs] -class DocumentFile: - """Read a document from multiple extensions""" - -
    -[docs] - @classmethod - def from_pdf(cls, file: AbstractFile, **kwargs) -> PDF: - """Read a PDF file - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file or a binary stream - Returns: - a PDF document - """ - - doc = read_pdf(file, **kwargs) - - return PDF(doc)
    - - -
    -[docs] - @classmethod - def from_url(cls, url: str, **kwargs) -> PDF: - """Interpret a web page as a PDF document - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_url("https://www.yoursite.com") - - Args: - url: the URL of the target web page - Returns: - a PDF document - """ - pdf_stream = read_html(url) - return cls.from_pdf(pdf_stream, **kwargs)
    - - -
    -[docs] - @classmethod - def from_images(cls, files: Union[Sequence[AbstractFile], AbstractFile], **kwargs) -> List[np.ndarray]: - """Read an image file (or a collection of image files) and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"]) - - Args: - files: the path to the image file or a binary stream, or a collection of those - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - if isinstance(files, (str, Path, bytes)): - files = [files] - - return [read_img(file, **kwargs) for file in files]
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.7.0/_modules/doctr/io/elements.html b/v0.7.0/_modules/doctr/io/elements.html index f9743f9c90..2621746349 100644 --- a/v0.7.0/_modules/doctr/io/elements.html +++ b/v0.7.0/_modules/doctr/io/elements.html @@ -13,7 +13,7 @@ - + doctr.io.elements - docTR documentation @@ -943,7 +943,7 @@

    Source code for doctr.io.elements

         
       
    - + diff --git a/v0.7.0/_modules/doctr/io/html.html b/v0.7.0/_modules/doctr/io/html.html index 43f83891e1..16fdac956a 100644 --- a/v0.7.0/_modules/doctr/io/html.html +++ b/v0.7.0/_modules/doctr/io/html.html @@ -13,7 +13,7 @@ - + doctr.io.html - docTR documentation @@ -352,7 +352,7 @@

    Source code for doctr.io.html

         
       
    - + diff --git a/v0.7.0/_modules/doctr/io/image/base.html b/v0.7.0/_modules/doctr/io/image/base.html index 4cb926010b..9d386f7e77 100644 --- a/v0.7.0/_modules/doctr/io/image/base.html +++ b/v0.7.0/_modules/doctr/io/image/base.html @@ -13,7 +13,7 @@ - + doctr.io.image.base - docTR documentation @@ -381,7 +381,7 @@

    Source code for doctr.io.image.base

         
       
    - + diff --git a/v0.7.0/_modules/doctr/io/image/tensorflow.html b/v0.7.0/_modules/doctr/io/image/tensorflow.html index 81a9c6db3c..7be0d18e8e 100644 --- a/v0.7.0/_modules/doctr/io/image/tensorflow.html +++ b/v0.7.0/_modules/doctr/io/image/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.io.image.tensorflow - docTR documentation @@ -434,7 +434,7 @@

    Source code for doctr.io.image.tensorflow

         
       
    - + diff --git a/v0.7.0/_modules/doctr/io/pdf.html b/v0.7.0/_modules/doctr/io/pdf.html index e5d9a0b5d0..4f5af28982 100644 --- a/v0.7.0/_modules/doctr/io/pdf.html +++ b/v0.7.0/_modules/doctr/io/pdf.html @@ -13,7 +13,7 @@ - + doctr.io.pdf - docTR documentation @@ -367,7 +367,7 @@

    Source code for doctr.io.pdf

         
       
    - + diff --git a/v0.7.0/_modules/doctr/io/reader.html b/v0.7.0/_modules/doctr/io/reader.html index 299779cf2c..66cb1f947a 100644 --- a/v0.7.0/_modules/doctr/io/reader.html +++ b/v0.7.0/_modules/doctr/io/reader.html @@ -13,7 +13,7 @@ - + doctr.io.reader - docTR documentation @@ -406,7 +406,7 @@

    Source code for doctr.io.reader

         
       
    - + diff --git a/v0.7.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html b/v0.7.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html index fb26f46a98..018c4f3df6 100644 --- a/v0.7.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html +++ b/v0.7.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.magc_resnet.tensorflow - docTR documentation @@ -515,7 +515,7 @@

    Source code for doctr.models.classification.magc_resnet.tensorflow

    - + diff --git a/v0.7.0/_modules/doctr/models/classification/mobilenet/tensorflow.html b/v0.7.0/_modules/doctr/models/classification/mobilenet/tensorflow.html index 6d63a1299e..2eb4b47bbd 100644 --- a/v0.7.0/_modules/doctr/models/classification/mobilenet/tensorflow.html +++ b/v0.7.0/_modules/doctr/models/classification/mobilenet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.mobilenet.tensorflow - docTR documentation @@ -738,7 +738,7 @@

    Source code for doctr.models.classification.mobilenet.tensorflow

    - + diff --git a/v0.7.0/_modules/doctr/models/classification/resnet/tensorflow.html b/v0.7.0/_modules/doctr/models/classification/resnet/tensorflow.html index 6383146867..d0e05415df 100644 --- a/v0.7.0/_modules/doctr/models/classification/resnet/tensorflow.html +++ b/v0.7.0/_modules/doctr/models/classification/resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.resnet.tensorflow - docTR documentation @@ -723,7 +723,7 @@

    Source code for doctr.models.classification.resnet.tensorflow

    - + diff --git a/v0.7.0/_modules/doctr/models/classification/textnet/tensorflow.html b/v0.7.0/_modules/doctr/models/classification/textnet/tensorflow.html index ad254ebbfb..c5567d7d67 100644 --- a/v0.7.0/_modules/doctr/models/classification/textnet/tensorflow.html +++ b/v0.7.0/_modules/doctr/models/classification/textnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.textnet.tensorflow - docTR documentation @@ -611,7 +611,7 @@

    Source code for doctr.models.classification.textnet.tensorflow

    - + diff --git a/v0.7.0/_modules/doctr/models/classification/vgg/tensorflow.html b/v0.7.0/_modules/doctr/models/classification/vgg/tensorflow.html index dbd2845713..0f24518c3a 100644 --- a/v0.7.0/_modules/doctr/models/classification/vgg/tensorflow.html +++ b/v0.7.0/_modules/doctr/models/classification/vgg/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vgg.tensorflow - docTR documentation @@ -436,7 +436,7 @@

    Source code for doctr.models.classification.vgg.tensorflow

    - + diff --git a/v0.7.0/_modules/doctr/models/classification/vit/tensorflow.html b/v0.7.0/_modules/doctr/models/classification/vit/tensorflow.html index 05a2a2ca0c..5b7b117dc6 100644 --- a/v0.7.0/_modules/doctr/models/classification/vit/tensorflow.html +++ b/v0.7.0/_modules/doctr/models/classification/vit/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vit.tensorflow - docTR documentation @@ -515,7 +515,7 @@

    Source code for doctr.models.classification.vit.tensorflow

    - + diff --git a/v0.7.0/_modules/doctr/models/classification/zoo.html b/v0.7.0/_modules/doctr/models/classification/zoo.html index 7c6beed9b2..8c361d3bb9 100644 --- a/v0.7.0/_modules/doctr/models/classification/zoo.html +++ b/v0.7.0/_modules/doctr/models/classification/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.classification.zoo - docTR documentation @@ -395,7 +395,7 @@

    Source code for doctr.models.classification.zoo

    <
    - + diff --git a/v0.7.0/_modules/doctr/models/detection/differentiable_binarization.html b/v0.7.0/_modules/doctr/models/detection/differentiable_binarization.html deleted file mode 100644 index 38e9b36ec2..0000000000 --- a/v0.7.0/_modules/doctr/models/detection/differentiable_binarization.html +++ /dev/null @@ -1,879 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.differentiable_binarization - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.differentiable_binarization

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -import cv2
    -from copy import deepcopy
    -import numpy as np
    -from shapely.geometry import Polygon
    -import pyclipper
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers
    -from typing import Union, List, Tuple, Optional, Any, Dict
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..utils import IntermediateLayerGetter, load_pretrained_params, conv_sequence
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['DBPostProcessor', 'DBNet', 'db_resnet50']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'db_resnet50': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'backbone': 'ResNet50',
    -        'fpn_layers': ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"],
    -        'fpn_channels': 128,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'DBPostProcessor',
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.2.0/db_resnet50-adcafc63.zip',
    -    },
    -}
    -
    -
    -class DBPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for DBNet adapted from the implementation of `xuannianz
    -    <https://github.com/xuannianz/DifferentiableBinarization>`_.
    -
    -    Args:
    -        unclip ratio: ratio used to unshrink polygons
    -        min_size_box: minimal length (pix) to keep a box
    -        max_candidates: maximum boxes to consider in a single page
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        unclip_ratio: Union[float, int] = 1.5,
    -        max_candidates: int = 1000,
    -        box_thresh: float = 0.1,
    -        bin_thresh: float = 0.3,
    -    ) -> None:
    -
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -        self.unclip_ratio = unclip_ratio
    -        self.max_candidates = max_candidates
    -
    -    def polygon_to_box(
    -        self,
    -        points: np.ndarray,
    -    ) -> Optional[Tuple[int, int, int, int]]:
    -        """Expand a polygon (points) by a factor unclip_ratio, and returns a 4-points box
    -
    -        Args:
    -            points: The first parameter.
    -
    -        Returns:
    -            a box in absolute coordinates (x, y, w, h)
    -        """
    -        poly = Polygon(points)
    -        distance = poly.area * self.unclip_ratio / poly.length  # compute distance to expand polygon
    -        offset = pyclipper.PyclipperOffset()
    -        offset.AddPath(points, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        _points = offset.Execute(distance)
    -        # Take biggest stack of points
    -        idx = 0
    -        if len(_points) > 1:
    -            max_size = 0
    -            for _idx, p in enumerate(_points):
    -                if len(p) > max_size:
    -                    idx = _idx
    -                    max_size = len(p)
    -            # We ensure that _points can be correctly casted to a ndarray
    -            _points = [_points[idx]]
    -        expanded_points = np.asarray(_points)  # expand polygon
    -        if len(expanded_points) < 1:
    -            return None
    -        x, y, w, h = cv2.boundingRect(expanded_points)  # compute a 4-points box from expanded polygon
    -        return x, y, w, h
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map
    -
    -        Args:
    -            pred: Pred map from differentiable binarization output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        # get contours from connected components on the bitmap
    -        contours, _ = cv2.findContours(bitmap.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    -        for contour in contours[:self.max_candidates]:
    -            # Check whether smallest enclosing bounding box is not too small
    -            if np.any(contour[:, 0].max(axis=0) - contour[:, 0].min(axis=0) < min_size_box):
    -                continue
    -            x, y, w, h = cv2.boundingRect(contour)
    -            points = np.array([[x, y], [x, y + h], [x + w, y + h], [x + w, y]])
    -            # Compute objectness
    -            score = self.box_score(pred, points)
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            _box = self.polygon_to_box(points)
    -
    -            if _box is None or _box[2] < min_size_box or _box[3] < min_size_box:  # remove to small boxes
    -                continue
    -            x, y, w, h = _box
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -class FeaturePyramidNetwork(layers.Layer, NestedObject):
    -    """Feature Pyramid Network as described in `"Feature Pyramid Networks for Object Detection"
    -    <https://arxiv.org/pdf/1612.03144.pdf>`_.
    -
    -    Args:
    -        channels: number of channel to output
    -    """
    -
    -    def __init__(
    -        self,
    -        channels: int,
    -    ) -> None:
    -        super().__init__()
    -        self.channels = channels
    -        self.upsample = layers.UpSampling2D(size=(2, 2), interpolation='nearest')
    -        self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer='he_normal') for _ in range(4)]
    -        self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2 ** idx) for idx in range(4)]
    -
    -    @staticmethod
    -    def build_upsampling(
    -        channels: int,
    -        dilation_factor: int = 1,
    -    ) -> layers.Layer:
    -        """Module which performs a 3x3 convolution followed by up-sampling
    -
    -        Args:
    -            channels: number of output channels
    -            dilation_factor (int): dilation factor to scale the convolution output before concatenation
    -
    -        Returns:
    -            a keras.layers.Layer object, wrapping these operations in a sequential module
    -
    -        """
    -
    -        _layers = conv_sequence(channels, 'relu', True, kernel_size=3)
    -
    -        if dilation_factor > 1:
    -            _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation='nearest'))
    -
    -        module = keras.Sequential(_layers)
    -
    -        return module
    -
    -    def extra_repr(self) -> str:
    -        return f"channels={self.channels}"
    -
    -    def call(
    -        self,
    -        x: List[tf.Tensor],
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # Channel mapping
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.inner_blocks, x)]
    -        # Upsample & sum
    -        for idx in range(len(results) - 1, -1):
    -            results[idx] += self.upsample(results[idx + 1])
    -        # Conv & upsample
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.layer_blocks, results)]
    -
    -        return layers.concatenate(results)
    -
    -
    -class DBNet(DetectionModel, NestedObject):
    -    """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization"
    -    <https://arxiv.org/pdf/1911.08947.pdf>`_.
    -
    -    Args:
    -        feature extractor: the backbone serving as feature extractor
    -        fpn_channels: number of channels each extracted feature maps is mapped to
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'fpn', 'probability_head', 'threshold_head', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: IntermediateLayerGetter,
    -        fpn_channels: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(cfg=cfg)
    -
    -        self.shrink_ratio = 0.4
    -        self.thresh_min = 0.3
    -        self.thresh_max = 0.7
    -        self.min_size_box = 3
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.fpn = FeaturePyramidNetwork(channels=fpn_channels)
    -        # Initialize kernels
    -        _inputs = [layers.Input(shape=in_shape[1:]) for in_shape in self.feat_extractor.output_shape]
    -        output_shape = tuple(self.fpn(_inputs).shape)
    -
    -        self.probability_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -        self.threshold_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -
    -        self.postprocessor = DBPostProcessor()
    -
    -    @staticmethod
    -    def compute_distance(
    -        xs: np.array,
    -        ys: np.array,
    -        a: np.array,
    -        b: np.array,
    -        eps: float = 1e-7,
    -    ) -> float:
    -        """Compute the distance for each point of the map (xs, ys) to the (a, b) segment
    -
    -        Args:
    -            xs : map of x coordinates (height, width)
    -            ys : map of y coordinates (height, width)
    -            a: first point defining the [ab] segment
    -            b: second point defining the [ab] segment
    -
    -        Returns:
    -            The computed distance
    -
    -        """
    -        square_dist_1 = np.square(xs - a[0]) + np.square(ys - a[1])
    -        square_dist_2 = np.square(xs - b[0]) + np.square(ys - b[1])
    -        square_dist = np.square(a[0] - b[0]) + np.square(a[1] - b[1])
    -        cosin = (square_dist - square_dist_1 - square_dist_2) / (2 * np.sqrt(square_dist_1 * square_dist_2) + eps)
    -        square_sin = 1 - np.square(cosin)
    -        square_sin = np.nan_to_num(square_sin)
    -        result = np.sqrt(square_dist_1 * square_dist_2 * square_sin / square_dist)
    -        result[cosin < 0] = np.sqrt(np.fmin(square_dist_1, square_dist_2))[cosin < 0]
    -        return result
    -
    -    def draw_thresh_map(
    -        self,
    -        polygon: np.array,
    -        canvas: np.array,
    -        mask: np.array,
    -    ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    -        """Draw a polygon treshold map on a canvas, as described in the DB paper
    -
    -        Args:
    -            polygon : array of coord., to draw the boundary of the polygon
    -            canvas : threshold map to fill with polygons
    -            mask : mask for training on threshold polygons
    -        """
    -        if polygon.ndim != 2 or polygon.shape[1] != 2:
    -            raise AttributeError("polygon should be a 2 dimensional array of coords")
    -
    -        # Augment polygon by shrink_ratio
    -        polygon_shape = Polygon(polygon)
    -        distance = polygon_shape.area * (1 - np.power(self.shrink_ratio, 2)) / polygon_shape.length
    -        subject = [tuple(coor) for coor in polygon]  # Get coord as list of tuples
    -        padding = pyclipper.PyclipperOffset()
    -        padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        padded_polygon = np.array(padding.Execute(distance)[0])
    -
    -        # Fill the mask with 1 on the new padded polygon
    -        cv2.fillPoly(mask, [padded_polygon.astype(np.int32)], 1.0)
    -
    -        # Get min/max to recover polygon after distance computation
    -        xmin = padded_polygon[:, 0].min()
    -        xmax = padded_polygon[:, 0].max()
    -        ymin = padded_polygon[:, 1].min()
    -        ymax = padded_polygon[:, 1].max()
    -        width = xmax - xmin + 1
    -        height = ymax - ymin + 1
    -        # Get absolute polygon for distance computation
    -        polygon[:, 0] = polygon[:, 0] - xmin
    -        polygon[:, 1] = polygon[:, 1] - ymin
    -        # Get absolute padded polygon
    -        xs = np.broadcast_to(np.linspace(0, width - 1, num=width).reshape(1, width), (height, width))
    -        ys = np.broadcast_to(np.linspace(0, height - 1, num=height).reshape(height, 1), (height, width))
    -
    -        # Compute distance map to fill the padded polygon
    -        distance_map = np.zeros((polygon.shape[0], height, width), dtype=np.float32)
    -        for i in range(polygon.shape[0]):
    -            j = (i + 1) % polygon.shape[0]
    -            absolute_distance = self.compute_distance(xs, ys, polygon[i], polygon[j])
    -            distance_map[i] = np.clip(absolute_distance / distance, 0, 1)
    -        distance_map = np.min(distance_map, axis=0)
    -
    -        # Clip the padded polygon inside the canvas
    -        xmin_valid = min(max(0, xmin), canvas.shape[1] - 1)
    -        xmax_valid = min(max(0, xmax), canvas.shape[1] - 1)
    -        ymin_valid = min(max(0, ymin), canvas.shape[0] - 1)
    -        ymax_valid = min(max(0, ymax), canvas.shape[0] - 1)
    -
    -        # Fill the canvas with the distances computed inside the valid padded polygon
    -        canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1] = np.fmax(
    -            1 - distance_map[
    -                ymin_valid - ymin:ymax_valid - ymin + 1,
    -                xmin_valid - xmin:xmax_valid - xmin + 1
    -            ],
    -            canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1]
    -        )
    -
    -        return polygon, canvas, mask
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor, tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.uint8)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -        thresh_target = np.zeros(output_shape, dtype=np.uint8)
    -        thresh_mask = np.ones(output_shape, dtype=np.uint8)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            polys = np.stack([
    -                abs_boxes[:, [0, 1]],
    -                abs_boxes[:, [0, 3]],
    -                abs_boxes[:, [2, 3]],
    -                abs_boxes[:, [2, 1]],
    -            ], axis=1)
    -
    -            for box, box_size, poly, is_ambiguous in zip(abs_boxes, boxes_size, polys, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -
    -                # Negative shrink for gt, as described in paper
    -                polygon = Polygon(poly)
    -                distance = polygon.area * (1 - np.power(self.shrink_ratio, 2)) / polygon.length
    -                subject = [tuple(coor) for coor in poly]
    -                padding = pyclipper.PyclipperOffset()
    -                padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -                shrinked = padding.Execute(-distance)
    -
    -                # Draw polygon on gt if it is valid
    -                if len(shrinked) == 0:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                shrinked = np.array(shrinked[0]).reshape(-1, 2)
    -                if shrinked.shape[0] <= 2 or not Polygon(shrinked).is_valid:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                cv2.fillPoly(seg_target[idx], [shrinked.astype(np.int32)], 1)
    -
    -                # Draw on both thresh map and thresh mask
    -                poly, thresh_target[idx], thresh_mask[idx] = self.draw_thresh_map(poly, thresh_target[idx],
    -                                                                                  thresh_mask[idx])
    -
    -        thresh_target = thresh_target.astype(np.float32) * (self.thresh_max - self.thresh_min) + self.thresh_min
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -        thresh_target = tf.convert_to_tensor(thresh_target, dtype=tf.float32)
    -        thresh_mask = tf.convert_to_tensor(thresh_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask, thresh_target, thresh_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        thresh_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts, masks, thresh_gts, thresh_masks from a list of boxes
    -        and a list of masks for each image. From there it computes the loss with the model output
    -
    -        Args:
    -            out_map: output feature map of the model of shape (N, H, W, C)
    -            thresh_map: threshold map of shape (N, H, W, C)
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -
    -        prob_map = tf.math.sigmoid(tf.squeeze(out_map, axis=[-1]))
    -        thresh_map = tf.math.sigmoid(tf.squeeze(thresh_map, axis=[-1]))
    -
    -        seg_target, seg_mask, thresh_target, thresh_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute balanced BCE loss for proba_map
    -        bce_scale = 5.
    -        bce_loss = tf.keras.losses.binary_crossentropy(seg_target[..., None], out_map, from_logits=True)[seg_mask]
    -
    -        neg_target = 1 - seg_target[seg_mask]
    -        positive_count = tf.math.reduce_sum(seg_target[seg_mask])
    -        negative_count = tf.math.reduce_min([tf.math.reduce_sum(neg_target), 3. * positive_count])
    -        negative_loss = bce_loss * neg_target
    -        negative_loss, _ = tf.nn.top_k(negative_loss, tf.cast(negative_count, tf.int32))
    -        sum_losses = tf.math.reduce_sum(bce_loss * seg_target[seg_mask]) + tf.math.reduce_sum(negative_loss)
    -        balanced_bce_loss = sum_losses / (positive_count + negative_count + 1e-6)
    -
    -        # Compute dice loss for approxbin_map
    -        bin_map = 1 / (1 + tf.exp(-50. * (prob_map[seg_mask] - thresh_map[seg_mask])))
    -
    -        bce_min = tf.math.reduce_min(bce_loss)
    -        weights = (bce_loss - bce_min) / (tf.math.reduce_max(bce_loss) - bce_min) + 1.
    -        inter = tf.math.reduce_sum(bin_map * seg_target[seg_mask] * weights)
    -        union = tf.math.reduce_sum(bin_map) + tf.math.reduce_sum(seg_target[seg_mask]) + 1e-8
    -        dice_loss = 1 - 2.0 * inter / union
    -
    -        # Compute l1 loss for thresh_map
    -        l1_scale = 10.
    -        if tf.reduce_any(thresh_mask):
    -            l1_loss = tf.math.reduce_mean(tf.math.abs(thresh_map[thresh_mask] - thresh_target[thresh_mask]))
    -        else:
    -            l1_loss = tf.constant(0.)
    -
    -        return l1_scale * l1_loss + bce_scale * balanced_bce_loss + dice_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        feat_maps = self.feat_extractor(x, **kwargs)
    -        feat_concat = self.fpn(feat_maps, **kwargs)
    -        logits = self.probability_head(feat_concat, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            thresh_map = self.threshold_head(feat_concat, **kwargs)
    -            loss = self.compute_loss(logits, thresh_map, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _db_resnet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> DBNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['fpn_channels'] = kwargs.get('fpn_channels', _cfg['fpn_channels'])
    -
    -    # Feature extractor
    -    resnet = tf.keras.applications.__dict__[_cfg['backbone']](
    -        include_top=False,
    -        weights=None,
    -        input_shape=_cfg['input_shape'],
    -        pooling=None,
    -    )
    -
    -    feat_extractor = IntermediateLayerGetter(
    -        resnet,
    -        _cfg['fpn_layers'],
    -    )
    -
    -    kwargs['fpn_channels'] = _cfg['fpn_channels']
    -
    -    # Build the model
    -    model = DBNet(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def db_resnet50(pretrained: bool = False, **kwargs: Any) -> DBNet: - """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" - <https://arxiv.org/pdf/1911.08947.pdf>`_, using a ResNet-50 backbone. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _db_resnet('db_resnet50', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.7.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html b/v0.7.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html index f7e27fdc68..90b457edb2 100644 --- a/v0.7.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html +++ b/v0.7.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.differentiable_binarization.tensorflow - docTR documentation @@ -712,7 +712,7 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo

    - + diff --git a/v0.7.0/_modules/doctr/models/detection/fast/tensorflow.html b/v0.7.0/_modules/doctr/models/detection/fast/tensorflow.html index 5b84d2dea1..65e1a77af8 100644 --- a/v0.7.0/_modules/doctr/models/detection/fast/tensorflow.html +++ b/v0.7.0/_modules/doctr/models/detection/fast/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.fast.tensorflow - docTR documentation @@ -769,7 +769,7 @@

    Source code for doctr.models.detection.fast.tensorflow

    - + diff --git a/v0.7.0/_modules/doctr/models/detection/linknet.html b/v0.7.0/_modules/doctr/models/detection/linknet.html deleted file mode 100644 index 129cfdce8b..0000000000 --- a/v0.7.0/_modules/doctr/models/detection/linknet.html +++ /dev/null @@ -1,644 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.linknet - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.linknet

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -import numpy as np
    -import cv2
    -from tensorflow.keras import layers, Sequential
    -from typing import Dict, Any, Tuple, Optional, List
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..backbones import ResnetStage
    -from ..utils import conv_sequence, load_pretrained_params
    -from ...utils.repr import NestedObject
    -
    -__all__ = ['LinkNet', 'linknet', 'LinkNetPostProcessor']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'linknet': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'out_chan': 1,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'LinkNetPostProcessor',
    -        'url': None,
    -    },
    -}
    -
    -
    -class LinkNetPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for LinkNet model.
    -
    -    Args:
    -        min_size_box: minimal length (pix) to keep a box
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        min_size_box: int = 3,
    -        bin_thresh: float = 0.15,
    -        box_thresh: float = 0.1,
    -    ) -> None:
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map: find connected components then filter boxes
    -
    -        Args:
    -            pred: Pred map from differentiable linknet output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        label_num, labelimage = cv2.connectedComponents(bitmap.astype(np.uint8), connectivity=4)
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        for label in range(1, label_num + 1):
    -            points = np.array(np.where(labelimage == label)[::-1]).T
    -            if points.shape[0] < 4:  # remove polygons with 3 points or less
    -                continue
    -            score = self.box_score(pred, points.reshape(-1, 2))
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            x, y, w, h = cv2.boundingRect(points)
    -            if min(w, h) < min_size_box:  # filter too small boxes
    -                continue
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -def decoder_block(in_chan: int, out_chan: int) -> Sequential:
    -    """Creates a LinkNet decoder block"""
    -
    -    return Sequential([
    -        *conv_sequence(in_chan // 4, 'relu', True, kernel_size=1),
    -        layers.Conv2DTranspose(
    -            filters=in_chan // 4,
    -            kernel_size=3,
    -            strides=2,
    -            padding="same",
    -            use_bias=False,
    -            kernel_initializer='he_normal'
    -        ),
    -        layers.BatchNormalization(),
    -        layers.Activation('relu'),
    -        *conv_sequence(out_chan, 'relu', True, kernel_size=1),
    -    ])
    -
    -
    -class LinkNetFPN(layers.Layer, NestedObject):
    -    """LinkNet Encoder-Decoder module
    -
    -    """
    -
    -    def __init__(
    -        self,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.encoder_1 = ResnetStage(num_blocks=2, output_channels=64, downsample=True)
    -        self.encoder_2 = ResnetStage(num_blocks=2, output_channels=128, downsample=True)
    -        self.encoder_3 = ResnetStage(num_blocks=2, output_channels=256, downsample=True)
    -        self.encoder_4 = ResnetStage(num_blocks=2, output_channels=512, downsample=True)
    -        self.decoder_1 = decoder_block(in_chan=64, out_chan=64)
    -        self.decoder_2 = decoder_block(in_chan=128, out_chan=64)
    -        self.decoder_3 = decoder_block(in_chan=256, out_chan=128)
    -        self.decoder_4 = decoder_block(in_chan=512, out_chan=256)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor
    -    ) -> tf.Tensor:
    -        x_1 = self.encoder_1(x)
    -        x_2 = self.encoder_2(x_1)
    -        x_3 = self.encoder_3(x_2)
    -        x_4 = self.encoder_4(x_3)
    -        y_4 = self.decoder_4(x_4)
    -        y_3 = self.decoder_3(y_4 + x_3)
    -        y_2 = self.decoder_2(y_3 + x_2)
    -        y_1 = self.decoder_1(y_2 + x_1)
    -        return y_1
    -
    -
    -class LinkNet(DetectionModel, NestedObject):
    -    """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation"
    -    <https://arxiv.org/pdf/1707.03718.pdf>`_.
    -
    -    Args:
    -        out_chan: number of channels for the output
    -    """
    -
    -    _children_names: List[str] = ['stem', 'fpn', 'classifier', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        out_chan: int = 1,
    -        input_shape: Tuple[int, int, int] = (512, 512, 3),
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(cfg=cfg)
    -
    -        self.stem = Sequential([
    -            *conv_sequence(64, 'relu', True, strides=2, kernel_size=7, input_shape=input_shape),
    -            layers.MaxPool2D(pool_size=(3, 3), strides=2, padding='same'),
    -        ])
    -
    -        self.fpn = LinkNetFPN()
    -
    -        self.classifier = Sequential([
    -            layers.Conv2DTranspose(
    -                filters=32,
    -                kernel_size=3,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -            layers.BatchNormalization(),
    -            layers.Activation('relu'),
    -            *conv_sequence(32, 'relu', True, strides=1, kernel_size=3),
    -            layers.Conv2DTranspose(
    -                filters=out_chan,
    -                kernel_size=2,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -        ])
    -
    -        self.min_size_box = 3
    -
    -        self.postprocessor = LinkNetPostProcessor()
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.bool)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            for box, box_size, is_ambiguous in zip(abs_boxes, boxes_size, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Fill polygon with 1
    -                seg_target[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = True
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts and masks from a list of boxes and a list of masks for each image
    -        Then, it computes the loss function with proba_map, gts and masks
    -
    -        Args:
    -            out_map: output feature map of the model of shape N x H x W x 1
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -        seg_target, seg_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute BCE loss
    -        return tf.math.reduce_mean(tf.keras.losses.binary_crossentropy(
    -            seg_target[seg_mask],
    -            tf.squeeze(out_map, axis=[-1])[seg_mask],
    -            from_logits=True
    -        ))
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        logits = self.stem(x)
    -        logits = self.fpn(logits)
    -        logits = self.classifier(logits)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            loss = self.compute_loss(logits, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _linknet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> LinkNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['out_chan'] = kwargs.get('out_chan', _cfg['out_chan'])
    -
    -    kwargs['out_chan'] = _cfg['out_chan']
    -    kwargs['input_shape'] = _cfg['input_shape']
    -    # Build the model
    -    model = LinkNet(cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def linknet(pretrained: bool = False, **kwargs: Any) -> LinkNet: - """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" - <https://arxiv.org/pdf/1707.03718.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import linknet - >>> model = linknet(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _linknet('linknet', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.7.0/_modules/doctr/models/detection/linknet/tensorflow.html b/v0.7.0/_modules/doctr/models/detection/linknet/tensorflow.html index ec15d41068..c36f166f89 100644 --- a/v0.7.0/_modules/doctr/models/detection/linknet/tensorflow.html +++ b/v0.7.0/_modules/doctr/models/detection/linknet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.linknet.tensorflow - docTR documentation @@ -721,7 +721,7 @@

    Source code for doctr.models.detection.linknet.tensorflow

    - + diff --git a/v0.7.0/_modules/doctr/models/detection/zoo.html b/v0.7.0/_modules/doctr/models/detection/zoo.html index e90b7350c5..1e47b8e170 100644 --- a/v0.7.0/_modules/doctr/models/detection/zoo.html +++ b/v0.7.0/_modules/doctr/models/detection/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.detection.zoo - docTR documentation @@ -421,7 +421,7 @@

    Source code for doctr.models.detection.zoo

         
       
    - + diff --git a/v0.7.0/_modules/doctr/models/export.html b/v0.7.0/_modules/doctr/models/export.html deleted file mode 100644 index f25a81aa21..0000000000 --- a/v0.7.0/_modules/doctr/models/export.html +++ /dev/null @@ -1,411 +0,0 @@ - - - - - - - - - - - - doctr.models.export - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.export

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import logging
    -import numpy as np
    -import tensorflow as tf
    -from tensorflow.keras import Model
    -from typing import Tuple
    -
    -logging.getLogger("tensorflow").setLevel(logging.DEBUG)
    -
    -
    -__all__ = ['convert_to_tflite', 'convert_to_fp16', 'quantize_model']
    -
    -
    -
    -[docs] -def convert_to_tflite(tf_model: Model) -> bytes: - """Converts a model to TFLite format - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_tflite, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_tflite(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - return converter.convert()
    - - - -
    -[docs] -def convert_to_fp16(tf_model: Model) -> bytes: - """Converts a model to half precision - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_fp16, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_fp16(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the serialized FP16 model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - converter.target_spec.supported_types = [tf.float16] - return converter.convert()
    - - - -
    -[docs] -def quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) -> bytes: - """Quantize a Tensorflow model - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import quantize_model, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = quantize_model(model, (224, 224, 3)) - - Args: - tf_model: a keras model - input_shape: shape of the expected input tensor (excluding batch dimension) with channel last order - - Returns: - bytes: the serialized quantized model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - - # Float fallback for operators that do not have an integer implementation - def representative_dataset(): - for _ in range(100): - data = np.random.rand(1, *input_shape) - yield [data.astype(np.float32)] - - converter.representative_dataset = representative_dataset - converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] - converter.inference_input_type = tf.int8 - converter.inference_output_type = tf.int8 - - return converter.convert()
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.7.0/_modules/doctr/models/factory/hub.html b/v0.7.0/_modules/doctr/models/factory/hub.html index 1f713fbcd3..cc1d94b666 100644 --- a/v0.7.0/_modules/doctr/models/factory/hub.html +++ b/v0.7.0/_modules/doctr/models/factory/hub.html @@ -13,7 +13,7 @@ - + doctr.models.factory.hub - docTR documentation @@ -562,7 +562,7 @@

    Source code for doctr.models.factory.hub

         
       
    - + diff --git a/v0.7.0/_modules/doctr/models/recognition/crnn.html b/v0.7.0/_modules/doctr/models/recognition/crnn.html deleted file mode 100644 index daa2393439..0000000000 --- a/v0.7.0/_modules/doctr/models/recognition/crnn.html +++ /dev/null @@ -1,565 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.crnn - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.crnn

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import layers
    -from tensorflow.keras.models import Sequential
    -from typing import Tuple, Dict, Any, Optional, List
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel, RecognitionPostProcessor
    -
    -__all__ = ['CRNN', 'crnn_vgg16_bn', 'crnn_resnet31', 'CTCPostProcessor']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'crnn_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/crnn_vgg16_bn-748c855f.zip',
    -    },
    -    'crnn_resnet31': {
    -        'mean': (0.694, 0.695, 0.693),
    -        'std': (0.299, 0.296, 0.301),
    -        'backbone': 'resnet31', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.1/crnn_resnet31-69ab71db.zip',
    -    },
    -}
    -
    -
    -class CTCPostProcessor(RecognitionPostProcessor):
    -    """
    -    Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor
    -    ) -> List[Tuple[str, float]]:
    -        """
    -        Performs decoding of raw output with CTC and decoding of CTC predictions
    -        with label_to_idx mapping dictionnary
    -
    -        Args:
    -            logits: raw output of the model, shape BATCH_SIZE X SEQ_LEN X NUM_CLASSES + 1
    -
    -        Returns:
    -            A list of decoded words of length BATCH_SIZE
    -
    -        """
    -        # Decode CTC
    -        _decoded, _log_prob = tf.nn.ctc_beam_search_decoder(
    -            tf.transpose(logits, perm=[1, 0, 2]),
    -            tf.fill(logits.shape[0], logits.shape[1]),
    -            beam_width=1, top_paths=1,
    -        )
    -        out_idxs = tf.sparse.to_dense(_decoded[0], default_value=len(self.vocab))
    -        probs = tf.math.exp(tf.squeeze(_log_prob, axis=1))
    -
    -        # Map it to characters
    -        _decoded_strings_pred = tf.strings.reduce_join(
    -            inputs=tf.nn.embedding_lookup(self._embedding, out_idxs),
    -            axis=-1
    -        )
    -        _decoded_strings_pred = tf.strings.split(_decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -class CRNN(RecognitionModel):
    -    """Implements a CRNN architecture as described in `"An End-to-End Trainable Neural Network for Image-based
    -    Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of units in the LSTM layers
    -        cfg: configuration dictionary
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: tf.keras.Model,
    -        vocab: str,
    -        rnn_units: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(vocab=vocab, cfg=cfg)
    -        self.feat_extractor = feature_extractor
    -
    -        # Initialize kernels
    -        h, w, c = self.feat_extractor.output_shape[1:]
    -        self.max_length = w
    -
    -        self.decoder = Sequential(
    -            [
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Dense(units=len(vocab) + 1)
    -            ]
    -        )
    -        self.decoder.build(input_shape=(None, w, h * c))
    -
    -        self.postprocessor = CTCPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        target: List[str],
    -    ) -> tf.Tensor:
    -        """Compute CTC loss for the model.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        gt, seq_len = self.compute_target(target)
    -        batch_len = model_output.shape[0]
    -        input_length = model_output.shape[1] * tf.ones(shape=(batch_len))
    -        ctc_loss = tf.nn.ctc_loss(
    -            gt, model_output, seq_len, input_length, logits_time_major=False, blank_index=len(self.vocab)
    -        )
    -        return ctc_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        # B x H x W x C --> B x W x H x C
    -        transposed_feat = tf.transpose(features, perm=[0, 2, 1, 3])
    -        w, h, c = transposed_feat.get_shape().as_list()[1:]
    -        # B x W x H x C --> B x W x H * C
    -        features_seq = tf.reshape(transposed_feat, shape=(-1, w, h * c))
    -        logits = self.decoder(features_seq, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = logits
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(logits)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(logits, target)
    -
    -        return out
    -
    -
    -def _crnn(arch: str, pretrained: bool, input_shape: Optional[Tuple[int, int, int]] = None, **kwargs: Any) -> CRNN:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[_cfg['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -
    -    # Build the model
    -    model = CRNN(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a VGG-16 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_vgg16_bn - >>> model = crnn_vgg16_bn(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_vgg16_bn', pretrained, **kwargs)
    - - - -def crnn_resnet31(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a resnet31 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_resnet31 - >>> model = crnn_resnet31(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_resnet31', pretrained, **kwargs) -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.7.0/_modules/doctr/models/recognition/crnn/tensorflow.html b/v0.7.0/_modules/doctr/models/recognition/crnn/tensorflow.html index 54ecbff9e6..ee00292e89 100644 --- a/v0.7.0/_modules/doctr/models/recognition/crnn/tensorflow.html +++ b/v0.7.0/_modules/doctr/models/recognition/crnn/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.crnn.tensorflow - docTR documentation @@ -642,7 +642,7 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    - + diff --git a/v0.7.0/_modules/doctr/models/recognition/master/tensorflow.html b/v0.7.0/_modules/doctr/models/recognition/master/tensorflow.html index 90a8655a25..383001a7c3 100644 --- a/v0.7.0/_modules/doctr/models/recognition/master/tensorflow.html +++ b/v0.7.0/_modules/doctr/models/recognition/master/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.master.tensorflow - docTR documentation @@ -632,7 +632,7 @@

    Source code for doctr.models.recognition.master.tensorflow

    - + diff --git a/v0.7.0/_modules/doctr/models/recognition/parseq/tensorflow.html b/v0.7.0/_modules/doctr/models/recognition/parseq/tensorflow.html index ff75ce9e87..6a62b57ec3 100644 --- a/v0.7.0/_modules/doctr/models/recognition/parseq/tensorflow.html +++ b/v0.7.0/_modules/doctr/models/recognition/parseq/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.parseq.tensorflow - docTR documentation @@ -826,7 +826,7 @@

    Source code for doctr.models.recognition.parseq.tensorflow

    - + diff --git a/v0.7.0/_modules/doctr/models/recognition/sar.html b/v0.7.0/_modules/doctr/models/recognition/sar.html deleted file mode 100644 index 2482e9f156..0000000000 --- a/v0.7.0/_modules/doctr/models/recognition/sar.html +++ /dev/null @@ -1,712 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.sar - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.sar

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import Sequential, layers
    -from typing import Tuple, Dict, List, Any, Optional
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel
    -from .core import RecognitionPostProcessor
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['SAR', 'SARPostProcessor', 'sar_vgg16_bn', 'sar_resnet31']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'sar_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1-models/sar_vgg16bn-0d7e2c26.zip',
    -    },
    -    'sar_resnet31': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'resnet31', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/sar_resnet31-ea202587.zip',
    -    },
    -}
    -
    -
    -class AttentionModule(layers.Layer, NestedObject):
    -    """Implements attention module of the SAR model
    -
    -    Args:
    -        attention_units: number of hidden attention units
    -
    -    """
    -    def __init__(
    -        self,
    -        attention_units: int
    -    ) -> None:
    -
    -        super().__init__()
    -        self.hidden_state_projector = layers.Conv2D(
    -            attention_units, 1, strides=1, use_bias=False, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.features_projector = layers.Conv2D(
    -            attention_units, 3, strides=1, use_bias=True, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.attention_projector = layers.Conv2D(
    -            1, 1, strides=1, use_bias=False, padding="same", kernel_initializer='he_normal',
    -        )
    -        self.flatten = layers.Flatten()
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        hidden_state: tf.Tensor,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        [H, W] = features.get_shape().as_list()[1:3]
    -        # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units)
    -        hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs)
    -        # shape (N, H, W, vgg_units) -> (N, H, W, attention_units)
    -        features_projection = self.features_projector(features, **kwargs)
    -        projection = tf.math.tanh(hidden_state_projection + features_projection)
    -        # shape (N, H, W, attention_units) -> (N, H, W, 1)
    -        attention = self.attention_projector(projection, **kwargs)
    -        # shape (N, H, W, 1) -> (N, H * W)
    -        attention = self.flatten(attention)
    -        attention = tf.nn.softmax(attention)
    -        # shape (N, H * W) -> (N, H, W, 1)
    -        attention_map = tf.reshape(attention, [-1, H, W, 1])
    -        glimpse = tf.math.multiply(features, attention_map)
    -        # shape (N, H * W) -> (N, 1)
    -        glimpse = tf.reduce_sum(glimpse, axis=[1, 2])
    -        return glimpse
    -
    -
    -class SARDecoder(layers.Layer, NestedObject):
    -    """Implements decoder module of the SAR model
    -
    -    Args:
    -        rnn_units: number of hidden units in recurrent cells
    -        max_length: maximum length of a sequence
    -        vocab_size: number of classes in the model alphabet
    -        embedding_units: number of hidden embedding units
    -        attention_units: number of hidden attention units
    -        num_decoder_layers: number of LSTM layers to stack
    -
    -    """
    -    def __init__(
    -        self,
    -        rnn_units: int,
    -        max_length: int,
    -        vocab_size: int,
    -        embedding_units: int,
    -        attention_units: int,
    -        num_decoder_layers: int = 2,
    -        input_shape: Optional[List[Tuple[Optional[int]]]] = None,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.vocab_size = vocab_size
    -        self.lstm_decoder = layers.StackedRNNCells(
    -            [layers.LSTMCell(rnn_units, dtype=tf.float32, implementation=1) for _ in range(num_decoder_layers)]
    -        )
    -        self.embed = layers.Dense(embedding_units, use_bias=False, input_shape=(None, self.vocab_size + 1))
    -        self.attention_module = AttentionModule(attention_units)
    -        self.output_dense = layers.Dense(vocab_size + 1, use_bias=True, input_shape=(None, 2 * rnn_units))
    -        self.max_length = max_length
    -
    -        # Initialize kernels
    -        if input_shape is not None:
    -            self.attention_module.call(layers.Input(input_shape[0][1:]), layers.Input((1, 1, rnn_units)))
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        holistic: tf.Tensor,
    -        gt: Optional[tf.Tensor] = None,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # initialize states (each of shape (N, rnn_units))
    -        states = self.lstm_decoder.get_initial_state(
    -            inputs=None, batch_size=features.shape[0], dtype=tf.float32
    -        )
    -        # run first step of lstm
    -        # holistic: shape (N, rnn_units)
    -        _, states = self.lstm_decoder(holistic, states, **kwargs)
    -        # Initialize with the index of virtual START symbol (placed after <eos>)
    -        symbol = tf.fill(features.shape[0], self.vocab_size + 1)
    -        logits_list = []
    -        if kwargs.get('training') and gt is None:
    -            raise ValueError('Need to provide labels during training for teacher forcing')
    -        for t in range(self.max_length + 1):  # keep 1 step for <eos>
    -            # one-hot symbol with depth vocab_size + 1
    -            # embeded_symbol: shape (N, embedding_units)
    -            embeded_symbol = self.embed(tf.one_hot(symbol, depth=self.vocab_size + 1), **kwargs)
    -            logits, states = self.lstm_decoder(embeded_symbol, states, **kwargs)
    -            glimpse = self.attention_module(
    -                features, tf.expand_dims(tf.expand_dims(logits, axis=1), axis=1), **kwargs,
    -            )
    -            # logits: shape (N, rnn_units), glimpse: shape (N, 1)
    -            logits = tf.concat([logits, glimpse], axis=-1)
    -            # shape (N, rnn_units + 1) -> (N, vocab_size + 1)
    -            logits = self.output_dense(logits, **kwargs)
    -            # update symbol with predicted logits for t+1 step
    -            if kwargs.get('training'):
    -                symbol = gt[:, t]
    -            else:
    -                symbol = tf.argmax(logits, axis=-1)
    -            logits_list.append(logits)
    -        outputs = tf.stack(logits_list, axis=1)  # shape (N, max_length + 1, vocab_size + 1)
    -
    -        return outputs
    -
    -
    -class SAR(RecognitionModel):
    -    """Implements a SAR architecture as described in `"Show, Attend and Read:A Simple and Strong Baseline for
    -    Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of hidden units in both encoder and decoder LSTM
    -        embedding_units: number of embedding units
    -        attention_units: number of hidden units in attention module
    -        max_length: maximum word length handled by the model
    -        num_decoders: number of LSTM to stack in decoder layer
    -
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'encoder', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor,
    -        vocab: str,
    -        rnn_units: int = 512,
    -        embedding_units: int = 512,
    -        attention_units: int = 512,
    -        max_length: int = 30,
    -        num_decoders: int = 2,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(vocab=vocab, cfg=cfg)
    -
    -        self.max_length = max_length + 1  # Add 1 timestep for EOS after the longest word
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.encoder = Sequential(
    -            [
    -                layers.LSTM(units=rnn_units, return_sequences=True),
    -                layers.LSTM(units=rnn_units, return_sequences=False)
    -            ]
    -        )
    -        # Initialize the kernels (watch out for reduce_max)
    -        self.encoder.build(input_shape=(None,) + self.feat_extractor.output_shape[2:])
    -
    -        self.decoder = SARDecoder(
    -            rnn_units, max_length, len(vocab), embedding_units, attention_units, num_decoders,
    -            input_shape=[self.feat_extractor.output_shape, self.encoder.output_shape]
    -        )
    -
    -        self.postprocessor = SARPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        gt: tf.Tensor,
    -        seq_len: tf.Tensor,
    -    ) -> tf.Tensor:
    -        """Compute categorical cross-entropy loss for the model.
    -        Sequences are masked after the EOS character.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        # Input length : number of timesteps
    -        input_len = tf.shape(model_output)[1]
    -        # Add one for additional <eos> token
    -        seq_len = seq_len + 1
    -        # One-hot gt labels
    -        oh_gt = tf.one_hot(gt, depth=model_output.shape[2])
    -        # Compute loss
    -        cce = tf.nn.softmax_cross_entropy_with_logits(oh_gt, model_output)
    -        # Compute mask
    -        mask_values = tf.zeros_like(cce)
    -        mask_2d = tf.sequence_mask(seq_len, input_len)
    -        masked_loss = tf.where(mask_2d, cce, mask_values)
    -        ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32))
    -        return tf.expand_dims(ce_loss, axis=1)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        pooled_features = tf.reduce_max(features, axis=1)  # vertical max pooling
    -        encoded = self.encoder(pooled_features, **kwargs)
    -        if target is not None:
    -            gt, seq_len = self.compute_target(target)
    -        decoded_features = self.decoder(features, encoded, gt=None if target is None else gt, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = decoded_features
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(decoded_features)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(decoded_features, gt, seq_len)
    -
    -        return out
    -
    -
    -class SARPostProcessor(RecognitionPostProcessor):
    -    """Post processor for SAR architectures
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor,
    -    ) -> List[Tuple[str, float]]:
    -        # compute pred with argmax for attention models
    -        out_idxs = tf.math.argmax(logits, axis=2)
    -        # N x L
    -        probs = tf.gather(tf.nn.softmax(logits, axis=-1), out_idxs, axis=-1, batch_dims=2)
    -        # Take the minimum confidence of the sequence
    -        probs = tf.math.reduce_min(probs, axis=1)
    -
    -        # decode raw output of the model with tf_label_to_idx
    -        out_idxs = tf.cast(out_idxs, dtype='int32')
    -        decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(self._embedding, out_idxs), axis=-1)
    -        decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -def _sar(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> SAR:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -    _cfg['embedding_units'] = kwargs.get('embedding_units', _cfg['rnn_units'])
    -    _cfg['attention_units'] = kwargs.get('attention_units', _cfg['rnn_units'])
    -    _cfg['max_length'] = kwargs.get('max_length', _cfg['max_length'])
    -    _cfg['num_decoders'] = kwargs.get('num_decoders', _cfg['num_decoders'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[default_cfgs[arch]['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -    kwargs['embedding_units'] = _cfg['embedding_units']
    -    kwargs['attention_units'] = _cfg['attention_units']
    -    kwargs['max_length'] = _cfg['max_length']
    -    kwargs['num_decoders'] = _cfg['num_decoders']
    -
    -    # Build the model
    -    model = SAR(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, default_cfgs[arch]['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a VGG16 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import sar_vgg16_bn - >>> model = sar_vgg16_bn(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_vgg16_bn', pretrained, **kwargs)
    - - - -
    -[docs] -def sar_resnet31(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a resnet-31 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example: - >>> import tensorflow as tf - >>> from doctr.models import sar_resnet31 - >>> model = sar_resnet31(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_resnet31', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.7.0/_modules/doctr/models/recognition/sar/tensorflow.html b/v0.7.0/_modules/doctr/models/recognition/sar/tensorflow.html index 242708ee64..b657759fc4 100644 --- a/v0.7.0/_modules/doctr/models/recognition/sar/tensorflow.html +++ b/v0.7.0/_modules/doctr/models/recognition/sar/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.sar.tensorflow - docTR documentation @@ -744,7 +744,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    - + diff --git a/v0.7.0/_modules/doctr/models/recognition/vitstr/tensorflow.html b/v0.7.0/_modules/doctr/models/recognition/vitstr/tensorflow.html index 775a5943cd..8ed22fb349 100644 --- a/v0.7.0/_modules/doctr/models/recognition/vitstr/tensorflow.html +++ b/v0.7.0/_modules/doctr/models/recognition/vitstr/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.vitstr.tensorflow - docTR documentation @@ -599,7 +599,7 @@

    Source code for doctr.models.recognition.vitstr.tensorflow

    - + diff --git a/v0.7.0/_modules/doctr/models/recognition/zoo.html b/v0.7.0/_modules/doctr/models/recognition/zoo.html index 902f7b7903..0d405abf4d 100644 --- a/v0.7.0/_modules/doctr/models/recognition/zoo.html +++ b/v0.7.0/_modules/doctr/models/recognition/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.zoo - docTR documentation @@ -399,7 +399,7 @@

    Source code for doctr.models.recognition.zoo

       
    - + diff --git a/v0.7.0/_modules/doctr/models/zoo.html b/v0.7.0/_modules/doctr/models/zoo.html index 635257609e..524106f5dd 100644 --- a/v0.7.0/_modules/doctr/models/zoo.html +++ b/v0.7.0/_modules/doctr/models/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.zoo - docTR documentation @@ -552,7 +552,7 @@

    Source code for doctr.models.zoo

         
       
    - + diff --git a/v0.7.0/_modules/doctr/transforms/modules.html b/v0.7.0/_modules/doctr/transforms/modules.html deleted file mode 100644 index ba8269e7ef..0000000000 --- a/v0.7.0/_modules/doctr/transforms/modules.html +++ /dev/null @@ -1,734 +0,0 @@ - - - - - - - - - - - - doctr.transforms.modules - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.transforms.modules

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import random
    -import tensorflow as tf
    -from typing import List, Any, Tuple, Callable
    -
    -from doctr.utils.repr import NestedObject
    -from . import functional as F
    -
    -
    -__all__ = ['Compose', 'Resize', 'Normalize', 'LambdaTransformation', 'ToGray', 'ColorInversion',
    -           'RandomBrightness', 'RandomContrast', 'RandomSaturation', 'RandomHue', 'RandomGamma', 'RandomJpegQuality',
    -           'OneOf', 'RandomApply']
    -
    -
    -
    -[docs] -class Compose(NestedObject): - """Implements a wrapper that will apply transformations sequentially - - Example:: - >>> from doctr.transforms import Compose, Resize - >>> import tensorflow as tf - >>> transfos = Compose([Resize((32, 32))]) - >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformation modules - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, x: Any) -> Any: - for t in self.transforms: - x = t(x) - - return x
    - - - -
    -[docs] -class Resize(NestedObject): - """Resizes a tensor to a target size - - Example:: - >>> from doctr.transforms import Resize - >>> import tensorflow as tf - >>> transfo = Resize((32, 32)) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - output_size: expected output size - method: interpolation method - preserve_aspect_ratio: if `True`, preserve aspect ratio and pad the rest with zeros - symmetric_pad: if `True` while preserving aspect ratio, the padding will be done symmetrically - """ - def __init__( - self, - output_size: Tuple[int, int], - method: str = 'bilinear', - preserve_aspect_ratio: bool = False, - symmetric_pad: bool = False, - ) -> None: - self.output_size = output_size - self.method = method - self.preserve_aspect_ratio = preserve_aspect_ratio - self.symmetric_pad = symmetric_pad - - def extra_repr(self) -> str: - _repr = f"output_size={self.output_size}, method='{self.method}'" - if self.preserve_aspect_ratio: - _repr += f", preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}" - return _repr - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img = tf.image.resize(img, self.output_size, self.method, self.preserve_aspect_ratio) - if self.preserve_aspect_ratio: - # pad width - if not self.symmetric_pad: - offset = (0, 0) - elif self.output_size[0] == img.shape[0]: - offset = (0, int((self.output_size[1] - img.shape[1]) / 2)) - else: - offset = (int((self.output_size[0] - img.shape[0]) / 2), 0) - img = tf.image.pad_to_bounding_box(img, *offset, *self.output_size) - return img
    - - - -
    -[docs] -class Normalize(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - mean: average value per channel - std: standard deviation per channel - """ - def __init__(self, mean: Tuple[float, float, float], std: Tuple[float, float, float]) -> None: - self.mean = tf.constant(mean, dtype=tf.float32) - self.std = tf.constant(std, dtype=tf.float32) - - def extra_repr(self) -> str: - return f"mean={self.mean.numpy().tolist()}, std={self.std.numpy().tolist()}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img -= self.mean - img /= self.std - return img
    - - - -
    -[docs] -class LambdaTransformation(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import LambdaTransformation - >>> import tensorflow as tf - >>> transfo = LambdaTransformation(lambda x: x/ 255.) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - fn: the function to be applied to the input tensor - """ - def __init__(self, fn: Callable[[tf.Tensor], tf.Tensor]) -> None: - self.fn = fn - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return self.fn(img)
    - - - -
    -[docs] -class ToGray(NestedObject): - """Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ToGray() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - """ - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.rgb_to_grayscale(img)
    - - - -
    -[docs] -class ColorInversion(NestedObject): - """Applies the following tranformation to a tensor (image or batch of images): - convert to grayscale, colorize (shift 0-values randomly), and then invert colors - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ColorInversion(min_val=0.6) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_val: range [min_val, 1] to colorize RGB pixels - """ - def __init__(self, min_val: float = 0.6) -> None: - self.min_val = min_val - - def extra_repr(self) -> str: - return f"min_val={self.min_val}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return F.invert_colors(img, self.min_val)
    - - - -
    -[docs] -class RandomBrightness(NestedObject): - """Randomly adjust brightness of a tensor (batch of images or image) by adding a delta - to all pixels - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Brightness() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - p: probability to apply transformation - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_brightness(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomContrast(NestedObject): - """Randomly adjust contrast of a tensor (batch of images or image) by adjusting - each pixel: (img - mean) * contrast_factor + mean. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Contrast() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1) - """ - def __init__(self, delta: float = .3) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_contrast(img, lower=1 - self.delta, upper=1 / (1 - self.delta))
    - - - -
    -[docs] -class RandomSaturation(NestedObject): - """Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and - increasing saturation by a factor. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Saturation() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1) - """ - def __init__(self, delta: float = .5) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_saturation(img, lower=1 - self.delta, upper=1 + self.delta)
    - - - -
    -[docs] -class RandomHue(NestedObject): - """Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Hue() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_hue(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomGamma(NestedObject): - """randomly performs gamma correction for a tensor (batch of images or image) - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Gamma() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_gamma: non-negative real number, lower bound for gamma param - max_gamma: non-negative real number, upper bound for gamma - min_gain: lower bound for constant multiplier - max_gain: upper bound for constant multiplier - """ - def __init__( - self, - min_gamma: float = 0.5, - max_gamma: float = 1.5, - min_gain: float = 0.8, - max_gain: float = 1.2, - ) -> None: - self.min_gamma = min_gamma - self.max_gamma = max_gamma - self.min_gain = min_gain - self.max_gain = max_gain - - def extra_repr(self) -> str: - return f"""gamma_range=({self.min_gamma}, {self.max_gamma}), - gain_range=({self.min_gain}, {self.max_gain})""" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - gamma = random.uniform(self.min_gamma, self.max_gamma) - gain = random.uniform(self.min_gain, self.max_gain) - return tf.image.adjust_gamma(img, gamma=gamma, gain=gain)
    - - - -
    -[docs] -class RandomJpegQuality(NestedObject): - """Randomly adjust jpeg quality of a 3 dimensional RGB image - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = JpegQuality() - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - min_quality: int between [0, 100] - max_quality: int between [0, 100] - """ - def __init__(self, min_quality: int = 60, max_quality: int = 100) -> None: - self.min_quality = min_quality - self.max_quality = max_quality - - def extra_repr(self) -> str: - return f"min_quality={self.min_quality}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_jpeg_quality( - img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality - )
    - - - -
    -[docs] -class OneOf(NestedObject): - """Randomly apply one of the input transformations - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = OneOf([JpegQuality(), Gamma()]) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformations, one only will be picked - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - # Pick transformation - transfo = self.transforms[int(random.random() * len(self.transforms))] - # Apply - return transfo(img)
    - - - -
    -[docs] -class RandomApply(NestedObject): - """Apply with a probability p the input transformation - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = RandomApply(Gamma(), p=.5) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transform: transformation to apply - p: probability to apply - """ - def __init__(self, transform: NestedObject, p: float = .5) -> None: - self.transform = transform - self.p = p - - def extra_repr(self) -> str: - return f"transform={self.transform}, p={self.p}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - if random.random() < self.p: - return self.transform(img) - return img
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.7.0/_modules/doctr/transforms/modules/base.html b/v0.7.0/_modules/doctr/transforms/modules/base.html index c175ed68da..64e32f3dde 100644 --- a/v0.7.0/_modules/doctr/transforms/modules/base.html +++ b/v0.7.0/_modules/doctr/transforms/modules/base.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.base - docTR documentation @@ -608,7 +608,7 @@

    Source code for doctr.transforms.modules.base

    - + diff --git a/v0.7.0/_modules/doctr/transforms/modules/tensorflow.html b/v0.7.0/_modules/doctr/transforms/modules/tensorflow.html index df60d47514..0f165b47a4 100644 --- a/v0.7.0/_modules/doctr/transforms/modules/tensorflow.html +++ b/v0.7.0/_modules/doctr/transforms/modules/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.tensorflow - docTR documentation @@ -880,7 +880,7 @@

    Source code for doctr.transforms.modules.tensorflow

    - + diff --git a/v0.7.0/_modules/doctr/utils/metrics.html b/v0.7.0/_modules/doctr/utils/metrics.html index e76fef948f..655a7c61a7 100644 --- a/v0.7.0/_modules/doctr/utils/metrics.html +++ b/v0.7.0/_modules/doctr/utils/metrics.html @@ -13,7 +13,7 @@ - + doctr.utils.metrics - docTR documentation @@ -1057,7 +1057,7 @@

    Source code for doctr.utils.metrics

         
       
    - + diff --git a/v0.7.0/_modules/doctr/utils/visualization.html b/v0.7.0/_modules/doctr/utils/visualization.html index 38c5c5a857..3f295d2cf5 100644 --- a/v0.7.0/_modules/doctr/utils/visualization.html +++ b/v0.7.0/_modules/doctr/utils/visualization.html @@ -13,7 +13,7 @@ - + doctr.utils.visualization - docTR documentation @@ -807,7 +807,7 @@

    Source code for doctr.utils.visualization

         
       
    - + diff --git a/v0.7.0/_modules/index.html b/v0.7.0/_modules/index.html index a55cff678b..8d79322bb9 100644 --- a/v0.7.0/_modules/index.html +++ b/v0.7.0/_modules/index.html @@ -13,7 +13,7 @@ - + Overview: module code - docTR documentation @@ -369,7 +369,7 @@

    All modules for which code is available

    - + diff --git a/v0.7.0/_sources/datasets.rst.txt b/v0.7.0/_sources/datasets.rst.txt deleted file mode 100644 index 354122f1e5..0000000000 --- a/v0.7.0/_sources/datasets.rst.txt +++ /dev/null @@ -1,68 +0,0 @@ -doctr.datasets -============== - -.. currentmodule:: doctr.datasets - -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. - - -.. _datasets: - -Available Datasets ------------------- -The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL. - -.. autoclass:: doctr.datasets.datasets.VisionDataset - - -Here are all datasets that are available through DocTR: - -.. autoclass:: FUNSD -.. autoclass:: SROIE -.. autoclass:: CORD -.. autoclass:: OCRDataset - - -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. - -.. autoclass:: doctr.datasets.loader.DataLoader - - -.. _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 - * - digits - - 10 - - 0123456789 - * - ascii_letters - - 52 - - abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ - * - punctuation - - 32 - - !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ - * - currency - - 5 - - £€¥¢฿ - * - latin - - 96 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~° - * - french - - 154 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿ - -.. autofunction:: encode_sequences diff --git a/v0.7.0/_sources/documents.rst.txt b/v0.7.0/_sources/documents.rst.txt deleted file mode 100644 index 655730073e..0000000000 --- a/v0.7.0/_sources/documents.rst.txt +++ /dev/null @@ -1,87 +0,0 @@ -doctr.documents -=============== - - -.. currentmodule:: doctr.documents - -The documents module enables users to easily access content from documents and export analysis -results to structured formats. - - -Document structure ------------------- - -Structural organization of the documents. - -Word -^^^^ -A Word is an uninterrupted sequence of characters. - -.. autoclass:: Word - -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 - -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 - - -Document -^^^^^^^^ - -A Document is a collection of Pages. - -.. autoclass:: Document - - .. automethod:: show - - -File reading ------------- - -High-performance file reading and conversion to processable structured data. - -.. autofunction:: read_pdf - -.. autofunction:: read_img - -.. autofunction:: read_html - - -.. autoclass:: DocumentFile - - .. automethod:: from_pdf - - .. automethod:: from_url - - .. automethod:: from_images - -.. autoclass:: PDF - - .. automethod:: as_images - - .. automethod:: get_words - - .. automethod:: get_artefacts diff --git a/v0.7.0/_sources/installing.rst.txt b/v0.7.0/_sources/installing.rst.txt deleted file mode 100644 index 5c8779dc1c..0000000000 --- a/v0.7.0/_sources/installing.rst.txt +++ /dev/null @@ -1,46 +0,0 @@ - -************ -Installation -************ - -This library requires Python 3.6 or higher. - - -Prerequisites -============= - -Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so: - -* TensorFlow: `installation page `_. -* PyTorch: `installation page `_. - -If you are running another OS than Linux, you will need a few extra dependencies. - -For MacOS users, you can install them as follows: - -.. code:: shell - - brew install cairo pango gdk-pixbuf libffi - -For Windows users, those dependencies are included in GTK. You can find the latest installer over `here `_. - - -Via Python Package -================== - -Install the last stable release of the package using pip: - -.. code:: bash - - pip install python-doctr - - -Via Git -======= - -Install the library in developper mode: - -.. code:: bash - - git clone https://github.com/mindee/doctr.git - pip install -e doctr/. diff --git a/v0.7.0/_sources/models.rst.txt b/v0.7.0/_sources/models.rst.txt deleted file mode 100644 index 9830c6c153..0000000000 --- a/v0.7.0/_sources/models.rst.txt +++ /dev/null @@ -1,215 +0,0 @@ -doctr.models -============ - -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. - -.. currentmodule:: doctr.models - -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 TensorFlow model. -* Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable. - - -Text Detection --------------- -Localizing text elements in images - -+---------------------------------------------------+----------------------------+----------------------------+---------+ -| | FUNSD | CORD | | -+==================+=================+==============+============+===============+============+===============+=========+ -| **Architecture** | **Input shape** | **# params** | **Recall** | **Precision** | **Recall** | **Precision** | **FPS** | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ -| db_resnet50 | (1024, 1024, 3) | 25.2 M | 82.14 | 87.64 | 92.49 | 89.66 | 2.1 | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ - -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 combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for detection -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for detection is the following: - -1. resize each input image to the target size (bilinear interpolation by default) with potential deformation. -2. batch images together -3. normalize the batch using the training data statistics - - -Detection models -^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - -.. autofunction:: doctr.models.detection.db_resnet50 -.. autofunction:: doctr.models.detection.linknet16 - -Detection predictors -^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information. - -.. autofunction:: doctr.models.detection.detection_predictor - - -Text Recognition ----------------- -Identifying strings in images - -.. list-table:: Text recognition model zoo - :widths: 20 20 15 10 10 10 - :header-rows: 1 - - * - Architecture - - Input shape - - # params - - FUNSD - - CORD - - FPS - * - crnn_vgg16_bn - - (32, 128, 3) - - 15.8M - - 86.02 - - 91.3 - - 12.8 - * - sar_vgg16_bn - - (32, 128, 3) - - 21.5M - - 86.2 - - 91.7 - - 3.3 - * - sar_resnet31 - - (32, 128, 3) - - 53.1M - - **86.3** - - **92.1** - - 2.7 - -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 metrics being used are available in :ref:`metrics`. - -All these recognition models are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for recognition -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for recognition is the following: - -1. resize each input image to the target size (bilinear interpolation by default) without deformation. -2. pad the image to the target size (with zeros by default) -3. batch images together -4. normalize the batch using the training data statistics - -Recognition models -^^^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - - -.. autofunction:: doctr.models.recognition.crnn_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_resnet31 -.. autofunction:: doctr.models.recognition.master - - -Recognition predictors -^^^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage. - -.. autofunction:: doctr.models.recognition.recognition_predictor - - -End-to-End OCR --------------- -Predictors that localize and identify text elements in images - -+-----------------------------+--------------------------------------+--------------------------------------+ -| | FUNSD | CORD | -+=============================+============+===============+=========+============+===============+=========+ -| **Architecture** | **Recall** | **Precision** | **FPS** | **Recall** | **Precision** | **FPS** | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + crnn_vgg16_bn | 70.08 | 74.77 | 0.85 | 82.19 | **79.67** | 1.6 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_vgg16_bn | N/A | N/A | 0.49 | N/A | N/A | 1.0 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_resnet31 | N/A | N/A | 0.27 | N/A | N/A | 0.83 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision text detection | 59.50 | 62.50 | | 75.30 | 70.00 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision doc. text detection | 64.00 | 53.30 | | 68.90 | 61.10 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| AWS textract | **78.10** | **83.00** | | **87.50** | 66.00 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ - -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`. - -All recognition models of predictors are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Results on private ocr datasets - -+------------------------------------+----------------------------+----------------------------+----------------------------+ -| | Receipts | Invoices | IDs | -+====================================+============+===============+============+===============+============+===============+ -| **Architecture** | **Recall** | **Precision** | **Recall** | **Precision** | **Recall** | **Precision** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| db_resnet50 + crnn_vgg16_bn (ours) | **78.90** | **81.01** | 65.68 | **69.86** | **49.48** | **50.46** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| Gvision doc. text detection | 68.91 | 59.89 | 63.20 | 52.85 | 43.70 | 29.21 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| AWS textract | 75.77 | 77.70 | **70.47** | 69.13 | 46.39 | 43.32 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ - - -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. - -.. autofunction:: doctr.models.zoo.ocr_predictor - - -Model export ------------- -Utility functions to make the most of document analysis models. - -.. currentmodule:: doctr.models.export - -Model compression -^^^^^^^^^^^^^^^^^ - -.. autofunction:: convert_to_tflite - -.. autofunction:: convert_to_fp16 - -.. autofunction:: quantize_model - -Using SavedModel -^^^^^^^^^^^^^^^^ - -Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -`SavedModel `_ format as follows: - - - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> _ = model(input_t, training=False) - >>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/') - -And loaded just as easily: - - - >>> import tensorflow as tf - >>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/') diff --git a/v0.7.0/_sources/transforms.rst.txt b/v0.7.0/_sources/transforms.rst.txt deleted file mode 100644 index 0230fe75f5..0000000000 --- a/v0.7.0/_sources/transforms.rst.txt +++ /dev/null @@ -1,32 +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: - -.. autoclass:: Resize -.. autoclass:: Normalize -.. autoclass:: LambdaTransformation -.. autoclass:: ToGray -.. autoclass:: ColorInversion -.. autoclass:: RandomBrightness -.. autoclass:: RandomContrast -.. autoclass:: RandomSaturation -.. autoclass:: RandomHue -.. autoclass:: RandomGamma -.. autoclass:: RandomJpegQuality - - -Composing transformations ---------------------------------------------- -It is common to require several transformations to be performed consecutively. - -.. autoclass:: Compose -.. autoclass:: OneOf -.. autoclass:: RandomApply diff --git a/v0.7.0/_sources/utils.rst.txt b/v0.7.0/_sources/utils.rst.txt deleted file mode 100644 index 69c1abe0eb..0000000000 --- a/v0.7.0/_sources/utils.rst.txt +++ /dev/null @@ -1,36 +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 - - -.. _metrics: - -Task evaluation ---------------- -Implementations of task-specific metrics to easily assess your model performances. - -.. currentmodule:: doctr.utils.metrics - -.. autoclass:: TextMatch - - .. automethod:: summary - -.. autoclass:: LocalizationConfusion - - .. automethod:: summary - -.. autoclass:: OCRMetric - - .. automethod:: summary diff --git a/v0.7.0/_static/basic.css b/v0.7.0/_static/basic.css index f316efcb47..7ebbd6d07b 100644 --- a/v0.7.0/_static/basic.css +++ b/v0.7.0/_static/basic.css @@ -1,12 +1,5 @@ /* - * basic.css - * ~~~~~~~~~ - * * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ /* -- main layout ----------------------------------------------------------- */ @@ -115,15 +108,11 @@ img { /* -- search page ----------------------------------------------------------- */ ul.search { - margin: 10px 0 0 20px; - padding: 0; + margin-top: 10px; } ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; + padding: 5px 0; } ul.search li a { diff --git a/v0.7.0/_static/doctools.js b/v0.7.0/_static/doctools.js index 4d67807d17..0398ebb9f0 100644 --- a/v0.7.0/_static/doctools.js +++ b/v0.7.0/_static/doctools.js @@ -1,12 +1,5 @@ /* - * doctools.js - * ~~~~~~~~~~~ - * * Base JavaScript utilities for all Sphinx HTML documentation. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; diff --git a/v0.7.0/_static/language_data.js b/v0.7.0/_static/language_data.js index 367b8ed81b..c7fe6c6faf 100644 --- a/v0.7.0/_static/language_data.js +++ b/v0.7.0/_static/language_data.js @@ -1,13 +1,6 @@ /* - * language_data.js - * ~~~~~~~~~~~~~~~~ - * * This script contains the language-specific data used by searchtools.js, * namely the list of stopwords, stemmer, scorer and splitter. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; diff --git a/v0.7.0/_static/searchtools.js b/v0.7.0/_static/searchtools.js index b08d58c9b9..2c774d17af 100644 --- a/v0.7.0/_static/searchtools.js +++ b/v0.7.0/_static/searchtools.js @@ -1,12 +1,5 @@ /* - * searchtools.js - * ~~~~~~~~~~~~~~~~ - * * Sphinx JavaScript utilities for the full-text search. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; @@ -20,7 +13,7 @@ if (typeof Scorer === "undefined") { // and returns the new score. /* score: result => { - const [docname, title, anchor, descr, score, filename] = result + const [docname, title, anchor, descr, score, filename, kind] = result return score }, */ @@ -47,6 +40,14 @@ if (typeof Scorer === "undefined") { }; } +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + const _removeChildren = (element) => { while (element && element.lastChild) element.removeChild(element.lastChild); }; @@ -64,9 +65,13 @@ const _displayItem = (item, searchTerms, highlightTerms) => { const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; const contentRoot = document.documentElement.dataset.content_root; - const [docName, title, anchor, descr, score, _filename] = item; + const [docName, title, anchor, descr, score, _filename, kind] = item; let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); let requestUrl; let linkUrl; if (docBuilder === "dirhtml") { @@ -115,8 +120,10 @@ const _finishSearch = (resultCount) => { "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." ); else - Search.status.innerText = _( - "Search finished, found ${resultCount} page(s) matching the search query." + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, ).replace('${resultCount}', resultCount); }; const _displayNextItem = ( @@ -138,7 +145,7 @@ const _displayNextItem = ( else _finishSearch(resultCount); }; // Helper function used by query() to order search results. -// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. // Order the results by score (in opposite order of appearance, since the // `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. const _orderResultsByScoreThenName = (a, b) => { @@ -248,6 +255,7 @@ const Search = { searchSummary.classList.add("search-summary"); searchSummary.innerText = ""; const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); searchList.classList.add("search"); const out = document.getElementById("search-results"); @@ -318,7 +326,7 @@ const Search = { const indexEntries = Search._index.indexentries; // Collect multiple result groups to be sorted separately and then ordered. - // Each is an array of [docname, title, anchor, descr, score, filename]. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. const normalResults = []; const nonMainIndexResults = []; @@ -337,6 +345,7 @@ const Search = { null, score + boost, filenames[file], + SearchResultKind.title, ]); } } @@ -354,6 +363,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.index, ]; if (isMain) { normalResults.push(result); @@ -475,6 +485,7 @@ const Search = { descr, score, filenames[match[0]], + SearchResultKind.object, ]); }; Object.keys(objects).forEach((prefix) => @@ -585,6 +596,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.text, ]); } return results; diff --git a/v0.7.0/changelog.html b/v0.7.0/changelog.html index 84b977cf6b..f68335ccd2 100644 --- a/v0.7.0/changelog.html +++ b/v0.7.0/changelog.html @@ -14,7 +14,7 @@ - + Changelog - docTR documentation @@ -415,7 +415,7 @@

    v0.1.0 (2021-03-05) - + diff --git a/v0.7.0/community/resources.html b/v0.7.0/community/resources.html index 2564037893..9a1988258c 100644 --- a/v0.7.0/community/resources.html +++ b/v0.7.0/community/resources.html @@ -14,7 +14,7 @@ - + Community resources - docTR documentation @@ -389,7 +389,7 @@

    Community resources - + diff --git a/v0.7.0/contributing/code_of_conduct.html b/v0.7.0/contributing/code_of_conduct.html index c138b6694b..46e37028be 100644 --- a/v0.7.0/contributing/code_of_conduct.html +++ b/v0.7.0/contributing/code_of_conduct.html @@ -14,7 +14,7 @@ - + Contributor Covenant Code of Conduct - docTR documentation @@ -498,7 +498,7 @@

    Attribution - + diff --git a/v0.7.0/contributing/contributing.html b/v0.7.0/contributing/contributing.html index fa79ab0bf9..5e77704cd2 100644 --- a/v0.7.0/contributing/contributing.html +++ b/v0.7.0/contributing/contributing.html @@ -14,7 +14,7 @@ - + Contributing to docTR - docTR documentation @@ -475,7 +475,7 @@

    Let’s connect - + diff --git a/v0.7.0/datasets.html b/v0.7.0/datasets.html deleted file mode 100644 index 193e576c57..0000000000 --- a/v0.7.0/datasets.html +++ /dev/null @@ -1,578 +0,0 @@ - - - - - - - - - - - - - doctr.datasets - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.datasets

    -

    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.

    -
    -

    Available Datasets

    -

    The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL.

    -
    -
    -class doctr.datasets.datasets.VisionDataset(url: str, file_name: str | None = None, file_hash: str | None = None, extract_archive: bool = False, download: bool = False, overwrite: bool = False)[source]
    -
    - -

    Here are all datasets that are available through DocTR:

    -
    -
    -class doctr.datasets.FUNSD(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    FUNSD dataset from “FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents”.

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD
    ->>> train_set = FUNSD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.SROIE(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    SROIE dataset from “ICDAR2019 Competition on Scanned Receipt OCR and Information Extraction”.

    -
    -
    Example::
    >>> from doctr.datasets import SROIE
    ->>> train_set = SROIE(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.CORD(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    CORD dataset from “CORD: A Consolidated Receipt Dataset forPost-OCR Parsing”.

    -
    -
    Example::
    >>> from doctr.datasets import CORD
    ->>> train_set = CORD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.OCRDataset(img_folder: str, label_file: str, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    Implements an OCR dataset

    -
    -
    Parameters:
    -
      -
    • img_folder – local path to image folder (all jpg at the root)

    • -
    • label_file – local path to the label file

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -

    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.

    -
    -
    -class doctr.datasets.loader.DataLoader(dataset, shuffle: bool = True, batch_size: int = 1, drop_last: bool = False, workers: int | None = None)[source]
    -

    Implements a dataset wrapper for fast data loading

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD, 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)
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • dataset – the dataset

    • -
    • shuffle – whether the samples should be shuffled before passing it to the iterator

    • -
    • batch_size – number of elements in each batch

    • -
    • drop_last – if True, drops the last batch if it isn’t full

    • -
    • workers – number of workers to use for data loading

    • -
    -
    -
    -
    - -
    -
    -

    Supported Vocabs

    -

    Since textual content has to be encoded properly for models to interpret them efficiently, DocTR supports multiple sets -of vocabs.

    -
    - - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    DocTR Vocabs

    Name

    size

    characters

    digits

    10

    0123456789

    ascii_letters

    52

    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

    punctuation

    32

    !”#$%&'()*+,-./:;<=>?@[\]^_`{|}~

    currency

    5

    £€¥¢฿

    latin

    96

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°

    french

    154

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿

    -
    -
    -
    -doctr.datasets.encode_sequences(sequences: List[str], vocab: str, target_size: int | None = None, eos: int = -1, sos: int | None = None, pad: int | None = None, **kwargs: Any) ndarray[source]
    -

    Encode character sequences using a given vocab as mapping

    -
    -
    Parameters:
    -
      -
    • sequences – the list of character sequences of size N

    • -
    • vocab – the ordered vocab to use for encoding

    • -
    • target_size – maximum length of the encoded data

    • -
    • eos – encoding of End Of String

    • -
    • sos – optional encoding of Start Of String

    • -
    • pad – optional encoding for padding. In case of padding, all sequences are followed by 1 EOS then PAD

    • -
    -
    -
    Returns:
    -

    the padded encoded data as a tensor

    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.7.0/documents.html b/v0.7.0/documents.html deleted file mode 100644 index 98cbb2c5ef..0000000000 --- a/v0.7.0/documents.html +++ /dev/null @@ -1,772 +0,0 @@ - - - - - - - - - - - - - doctr.documents - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.documents

    -

    The documents module enables users to easily access content from documents and export analysis -results to structured formats.

    -
    -

    Document structure

    -

    Structural organization of the documents.

    -
    -

    Word

    -

    A Word is an uninterrupted sequence of characters.

    -
    -
    -class doctr.documents.Word(value: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float])[source]
    -

    Implements a word element

    -
    -
    Parameters:
    -
      -
    • value – the text string of the word

    • -
    • confidence – the confidence associated with the text prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to

    • -
    • size (the page's)

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Line(words: List[Word], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float] | None = None)[source]
    -

    Implements a line element as a collection of words

    -
    -
    Parameters:
    -
      -
    • words – list of word elements

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all words in it.

    • -
    -
    -
    -
    - -
    -
    -

    Artefact

    -

    An Artefact is a non-textual element (e.g. QR code, picture, chart, signature, logo, etc.).

    -
    -
    -class doctr.documents.Artefact(artefact_type: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]])[source]
    -

    Implements a non-textual element

    -
    -
    Parameters:
    -
      -
    • artefact_type – the type of artefact

    • -
    • confidence – the confidence of the type prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size.

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Block(lines: List[Line] = [], artefacts: List[Artefact] = [], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float] | None = None)[source]
    -

    Implements a block element as a collection of lines and artefacts

    -
    -
    Parameters:
    -
      -
    • lines – list of line elements

    • -
    • artefacts – list of artefacts

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all lines and artefacts in it.

    • -
    -
    -
    -
    - -
    -
    -

    Page

    -

    A Page is a collection of Blocks that were on the same physical page.

    -
    -
    -class doctr.documents.Page(blocks: List[Block], page_idx: int, dimensions: Tuple[int, int], orientation: Dict[str, Any] | None = None, language: Dict[str, Any] | None = None)[source]
    -

    Implements a page element as a collection of blocks

    -
    -
    Parameters:
    -
      -
    • blocks – list of block elements

    • -
    • page_idx – the index of the page in the input raw document

    • -
    • dimensions – the page size in pixels in format (width, height)

    • -
    • orientation – a dictionary with the value of the rotation angle in degress and confidence of the prediction

    • -
    • language – a dictionary with the language value and confidence of the prediction

    • -
    -
    -
    -
    -
    -show(page: ndarray, interactive: bool = True, **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -
      -
    • page – image encoded as a numpy array in uint8

    • -
    • interactive – whether the display should be interactive

    • -
    -
    -
    -
    - -
    - -
    -
    -

    Document

    -

    A Document is a collection of Pages.

    -
    -
    -class doctr.documents.Document(pages: List[Page])[source]
    -

    Implements a document element as a collection of pages

    -
    -
    Parameters:
    -

    pages – list of page elements

    -
    -
    -
    -
    -show(pages: List[ndarray], **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -

    pages – list of images encoded as numpy arrays in uint8

    -
    -
    -
    - -
    - -
    -
    -
    -

    File reading

    -

    High-performance file reading and conversion to processable structured data.

    -
    -
    -doctr.documents.read_pdf(file: str | Path | bytes, **kwargs: Any) Document[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_pdf
    ->>> doc = read_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_img(file: str | Path | bytes, output_size: Tuple[int, int] | None = None, rgb_output: bool = True) ndarray[source]
    -

    Read an image file into numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_img
    ->>> page = read_img("path/to/your/doc.jpg")
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • file – the path to the image file

    • -
    • output_size – the expected output size of each page in format H x W

    • -
    • rgb_output – whether the output ndarray channel order should be RGB instead of BGR.

    • -
    -
    -
    Returns:
    -

    the page decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_html(url: str, **kwargs: Any) bytes[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_html
    ->>> doc = read_html("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – URL of the target web page

    -
    -
    Returns:
    -

    decoded PDF file as a bytes stream

    -
    -
    -
    - -
    -
    -class doctr.documents.DocumentFile[source]
    -

    Read a document from multiple extensions

    -
    -
    -classmethod from_pdf(file: str | Path | bytes, **kwargs) PDF[source]
    -

    Read a PDF file

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file or a binary stream

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_url(url: str, **kwargs) PDF[source]
    -

    Interpret a web page as a PDF document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_url("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – the URL of the target web page

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_images(files: Sequence[str | Path | bytes] | str | Path | bytes, **kwargs) List[ndarray][source]
    -

    Read an image file (or a collection of image files) and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"])
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    files – the path to the image file or a binary stream, or a collection of those

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    - -
    -
    -class doctr.documents.PDF(doc: Document)[source]
    -

    PDF document template

    -
    -
    Parameters:
    -

    doc – input PDF document

    -
    -
    -
    -
    -as_images(**kwargs) List[ndarray][source]
    -

    Convert all document pages to images

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of convert_page_to_numpy

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -get_words(**kwargs) List[List[Tuple[Tuple[float, float, float, float], str]]][source]
    -

    Get the annotations for all words in the document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of fitz.Page.getTextWords

    -
    -
    Returns:
    -

    the list of pages annotations, represented as a list of tuple (bounding box, value)

    -
    -
    -
    - -
    -
    -get_artefacts() List[List[Tuple[float, float, float, float]]][source]
    -

    Get the artefacts for the entire document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts()
    -
    -
    -
    -
    -
    -
    Returns:
    -

    the list of pages artefacts, represented as a list of bounding boxes

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.7.0/genindex.html b/v0.7.0/genindex.html index 488b287a3c..fe751c2c46 100644 --- a/v0.7.0/genindex.html +++ b/v0.7.0/genindex.html @@ -13,7 +13,7 @@ - Index - docTR documentation + Index - docTR documentation @@ -734,7 +734,7 @@

    W

    - + diff --git a/v0.7.0/getting_started/installing.html b/v0.7.0/getting_started/installing.html index 9cefe0dd45..2401ccb3e3 100644 --- a/v0.7.0/getting_started/installing.html +++ b/v0.7.0/getting_started/installing.html @@ -14,7 +14,7 @@ - + Installation - docTR documentation @@ -422,7 +422,7 @@

    Via Git - + diff --git a/v0.7.0/index.html b/v0.7.0/index.html index 6f6ba7496b..ff6712b40d 100644 --- a/v0.7.0/index.html +++ b/v0.7.0/index.html @@ -14,7 +14,7 @@ - + docTR documentation @@ -435,7 +435,7 @@

    Supported datasets - + diff --git a/v0.7.0/installing.html b/v0.7.0/installing.html deleted file mode 100644 index b61c60134b..0000000000 --- a/v0.7.0/installing.html +++ /dev/null @@ -1,395 +0,0 @@ - - - - - - - - - - - - - Installation - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    Installation

    -

    This library requires Python 3.6 or higher.

    -
    -

    Prerequisites

    -

    Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so:

    - -

    If you are running another OS than Linux, you will need a few extra dependencies.

    -

    For MacOS users, you can install them as follows:

    -
    brew install cairo pango gdk-pixbuf libffi
    -
    -
    -

    For Windows users, those dependencies are included in GTK. You can find the latest installer over here.

    -
    -
    -

    Via Python Package

    -

    Install the last stable release of the package using pip:

    -
    pip install python-doctr
    -
    -
    -
    -
    -

    Via Git

    -

    Install the library in developper mode:

    -
    git clone https://github.com/mindee/doctr.git
    -pip install -e doctr/.
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.7.0/models.html b/v0.7.0/models.html deleted file mode 100644 index b5cd44c9fa..0000000000 --- a/v0.7.0/models.html +++ /dev/null @@ -1,1002 +0,0 @@ - - - - - - - - - - - - - doctr.models - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.models

    -

    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 TensorFlow model.

    • -
    • Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable.

    • -
    -
    -

    Text Detection

    -

    Localizing text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Input shape

    # params

    Recall

    Precision

    Recall

    Precision

    FPS

    db_resnet50

    (1024, 1024, 3)

    25.2 M

    82.14

    87.64

    92.49

    89.66

    2.1

    -
    -

    All text detection models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for detection

    -

    In DocTR, the pre-processing scheme for detection is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) with potential deformation.

    2. -
    3. batch images together

    4. -
    5. normalize the batch using the training data statistics

    6. -
    -
    -
    -

    Detection models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.detection.db_resnet50(pretrained: bool = False, **kwargs: Any) DBNet[source]
    -

    DBNet as described in “Real-time Scene Text Detection with Differentiable Binarization”, using a ResNet-50 backbone.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -doctr.models.detection.linknet16(pretrained: bool = False, **kwargs: Any) LinkNet[source]
    -

    LinkNet as described in “LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import linknet16
    ->>> model = linknet16(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -

    Detection predictors

    -

    Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information.

    -
    -
    -doctr.models.detection.detection_predictor(arch: str = 'db_resnet50', pretrained: bool = False, **kwargs: Any) DetectionPredictor[source]
    -

    Text detection architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import detection_predictor
    ->>> model = detection_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_resnet50’)

    • -
    • pretrained – If True, returns a model pre-trained on our text detection dataset

    • -
    -
    -
    Returns:
    -

    Detection predictor

    -
    -
    -
    - -
    -
    -
    -

    Text Recognition

    -

    Identifying strings in images

    -
    - - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Text recognition model zoo

    Architecture

    Input shape

    # params

    FUNSD

    CORD

    FPS

    crnn_vgg16_bn

    (32, 128, 3)

    15.8M

    86.02

    91.3

    12.8

    sar_vgg16_bn

    (32, 128, 3)

    21.5M

    86.2

    91.7

    3.3

    sar_resnet31

    (32, 128, 3)

    53.1M

    86.3

    92.1

    2.7

    -
    -

    All text recognition models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All these recognition models are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for recognition

    -

    In DocTR, the pre-processing scheme for recognition is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) without deformation.

    2. -
    3. pad the image to the target size (with zeros by default)

    4. -
    5. batch images together

    6. -
    7. normalize the batch using the training data statistics

    8. -
    -
    -
    -

    Recognition models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.recognition.crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) CRNN[source]
    -

    CRNN with a VGG-16 backbone as described in “An End-to-End Trainable Neural Network for Image-based -Sequence Recognition and Its Application to Scene Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import crnn_vgg16_bn
    ->>> model = crnn_vgg16_bn(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a VGG16 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_vgg16_bn
    ->>> model = sar_vgg16_bn(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_resnet31(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a resnet-31 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -

    Example

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_resnet31
    ->>> model = sar_resnet31(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.master(pretrained: bool = False, **kwargs: Any) MASTER[source]
    -

    MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_. -Example:

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import master
    ->>> model = master(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 48, 160, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -

    Recognition predictors

    -

    Combining the right components around a given architecture for easier usage.

    -
    -
    -doctr.models.recognition.recognition_predictor(arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) RecognitionPredictor[source]
    -

    Text recognition architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import recognition_predictor
    ->>> model = recognition_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(32, 128, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘crnn_vgg16_bn’, ‘crnn_resnet31’, ‘sar_vgg16_bn’, ‘sar_resnet31’)

    • -
    • pretrained – If True, returns a model pre-trained on our text recognition dataset

    • -
    -
    -
    Returns:
    -

    Recognition predictor

    -
    -
    -
    - -
    -
    -
    -

    End-to-End OCR

    -

    Predictors that localize and identify text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Recall

    Precision

    FPS

    Recall

    Precision

    FPS

    db_resnet50 + crnn_vgg16_bn

    70.08

    74.77

    0.85

    82.19

    79.67

    1.6

    db_resnet50 + sar_vgg16_bn

    N/A

    N/A

    0.49

    N/A

    N/A

    1.0

    db_resnet50 + sar_resnet31

    N/A

    N/A

    0.27

    N/A

    N/A

    0.83

    Gvision text detection

    59.50

    62.50

    75.30

    70.00

    Gvision doc. text detection

    64.00

    53.30

    68.90

    61.10

    AWS textract

    78.10

    83.00

    87.50

    66.00

    -
    -

    All OCR models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All recognition models of predictors are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -

    Results on private ocr datasets

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Receipts

    Invoices

    IDs

    Architecture

    Recall

    Precision

    Recall

    Precision

    Recall

    Precision

    db_resnet50 + crnn_vgg16_bn (ours)

    78.90

    81.01

    65.68

    69.86

    49.48

    50.46

    Gvision doc. text detection

    68.91

    59.89

    63.20

    52.85

    43.70

    29.21

    AWS textract

    75.77

    77.70

    70.47

    69.13

    46.39

    43.32

    -
    -
    -

    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.

    -
    -
    -doctr.models.zoo.ocr_predictor(det_arch: str = 'db_resnet50', reco_arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) OCRPredictor[source]
    -

    End-to-end OCR architecture using one model for localization, and another for text recognition.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import ocr_predictor
    ->>> model = ocr_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_sar_vgg’, ‘db_sar_resnet’, ‘db_crnn_vgg’, ‘db_crnn_resnet’)

    • -
    • pretrained – If True, returns a model pre-trained on our OCR dataset

    • -
    -
    -
    Returns:
    -

    OCR predictor

    -
    -
    -
    - -
    -
    -
    -

    Model export

    -

    Utility functions to make the most of document analysis models.

    -
    -

    Model compression

    -
    -
    -doctr.models.export.convert_to_tflite(tf_model: Model) bytes[source]
    -

    Converts a model to TFLite format

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_tflite, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_tflite(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.convert_to_fp16(tf_model: Model) bytes[source]
    -

    Converts a model to half precision

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_fp16, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_fp16(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the serialized FP16 model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) bytes[source]
    -

    Quantize a Tensorflow model

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import quantize_model, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = quantize_model(model, (224, 224, 3))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • tf_model – a keras model

    • -
    • input_shape – shape of the expected input tensor (excluding batch dimension) with channel last order

    • -
    -
    -
    Returns:
    -

    the serialized quantized model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -

    Using SavedModel

    -

    Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -SavedModel format as follows:

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> _ = model(input_t, training=False)
    ->>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/')
    -
    -
    -

    And loaded just as easily:

    -
    >>> import tensorflow as tf
    ->>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/')
    -
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.7.0/modules/contrib.html b/v0.7.0/modules/contrib.html index e99f6b3f74..7fb86b8b38 100644 --- a/v0.7.0/modules/contrib.html +++ b/v0.7.0/modules/contrib.html @@ -14,7 +14,7 @@ - + doctr.contrib - docTR documentation @@ -380,7 +380,7 @@

    Supported contribution modules - + diff --git a/v0.7.0/modules/datasets.html b/v0.7.0/modules/datasets.html index 54975cb877..1bf096233d 100644 --- a/v0.7.0/modules/datasets.html +++ b/v0.7.0/modules/datasets.html @@ -14,7 +14,7 @@ - + doctr.datasets - docTR documentation @@ -997,7 +997,7 @@

    Dataloader - + diff --git a/v0.7.0/modules/io.html b/v0.7.0/modules/io.html index c44084b35a..4da4f1e597 100644 --- a/v0.7.0/modules/io.html +++ b/v0.7.0/modules/io.html @@ -14,7 +14,7 @@ - + doctr.io - docTR documentation @@ -726,7 +726,7 @@

    File reading - + diff --git a/v0.7.0/modules/models.html b/v0.7.0/modules/models.html index b2cdfc1f6e..0a479d67cb 100644 --- a/v0.7.0/modules/models.html +++ b/v0.7.0/modules/models.html @@ -14,7 +14,7 @@ - + doctr.models - docTR documentation @@ -1222,7 +1222,7 @@

    doctr.models.factory - + diff --git a/v0.7.0/modules/transforms.html b/v0.7.0/modules/transforms.html index a81fa8cb7d..485046a896 100644 --- a/v0.7.0/modules/transforms.html +++ b/v0.7.0/modules/transforms.html @@ -14,7 +14,7 @@ - + doctr.transforms - docTR documentation @@ -803,7 +803,7 @@

    Composing transformations - + diff --git a/v0.7.0/modules/utils.html b/v0.7.0/modules/utils.html index e8529c4f80..bce9a29e76 100644 --- a/v0.7.0/modules/utils.html +++ b/v0.7.0/modules/utils.html @@ -14,7 +14,7 @@ - + doctr.utils - docTR documentation @@ -733,7 +733,7 @@

    Visualization - + diff --git a/v0.7.0/notebooks.html b/v0.7.0/notebooks.html index f484efbf80..8b0a78272b 100644 --- a/v0.7.0/notebooks.html +++ b/v0.7.0/notebooks.html @@ -14,7 +14,7 @@ - + docTR Notebooks - docTR documentation @@ -379,7 +379,7 @@

    docTR Notebooks - + diff --git a/v0.7.0/py-modindex.html b/v0.7.0/py-modindex.html deleted file mode 100644 index c1569be607..0000000000 --- a/v0.7.0/py-modindex.html +++ /dev/null @@ -1,330 +0,0 @@ - - - - - - - - - - - Python Module Index - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    - -
    -

    Python Module Index

    - -
    - - - - - - - - - - - -
     
    d
    - doctr -
    - -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.7.0/search.html b/v0.7.0/search.html index 975e2efd79..5243968edf 100644 --- a/v0.7.0/search.html +++ b/v0.7.0/search.html @@ -14,7 +14,7 @@ - + Search - docTR documentation @@ -334,7 +334,7 @@ - + diff --git a/v0.7.0/searchindex.js b/v0.7.0/searchindex.js index efd2407926..1e43f55781 100644 --- a/v0.7.0/searchindex.js +++ b/v0.7.0/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles": {"1. Correction": [[1, "correction"]], "2. Warning": [[1, "warning"]], "3. Temporary Ban": [[1, "temporary-ban"]], "4. Permanent Ban": [[1, "permanent-ban"]], "AWS Lambda": [[12, null]], "Artefact": [[6, "artefact"]], "Attribution": [[1, "attribution"]], "Available Datasets": [[14, "available-datasets"]], "Available architectures": [[16, "available-architectures"], [16, "id1"], [16, "id2"]], "Block": [[6, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[14, null]], "Choosing the right model": [[16, null]], "Classification": [[13, "classification"]], "Code quality": [[2, "code-quality"]], "Code style verification": [[2, "code-style-verification"]], "Codebase structure": [[2, "codebase-structure"]], "Commits": [[2, "commits"]], "Composing transformations": [[8, "composing-transformations"]], "Continuous Integration": [[2, "continuous-integration"]], "Contributing to docTR": [[2, null]], "Contributor Covenant Code of Conduct": [[1, null]], "Custom dataset loader": [[5, "custom-dataset-loader"]], "Data Loading": [[14, "data-loading"]], "Dataloader": [[5, "dataloader"]], "Detection": [[13, "detection"], [14, "detection"]], "Detection predictors": [[16, "detection-predictors"]], "Developer mode installation": [[2, "developer-mode-installation"]], "Developing docTR": [[2, "developing-doctr"]], "Document": [[6, "document"]], "Document structure": [[6, "document-structure"]], "End-to-End OCR": [[16, "end-to-end-ocr"]], "Enforcement": [[1, "enforcement"]], "Enforcement Guidelines": [[1, "enforcement-guidelines"]], "Enforcement Responsibilities": [[1, "enforcement-responsibilities"]], "Export to ONNX": [[15, "export-to-onnx"]], "Feature requests & bug report": [[2, "feature-requests-bug-report"]], "Feedback": [[2, "feedback"]], "File reading": [[6, "file-reading"]], "Half-precision": [[15, "half-precision"]], "Installation": [[3, null]], "Let\u2019s connect": [[2, "let-s-connect"]], "Line": [[6, "line"]], "Loading from Huggingface Hub": [[13, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[11, "loading-your-custom-trained-model"]], "Main Features": [[4, "main-features"]], "Model optimization": [[15, "model-optimization"]], "Model zoo": [[4, "model-zoo"]], "Modifying the documentation": [[2, "modifying-the-documentation"]], "Naming conventions": [[13, "naming-conventions"]], "Object Detection": [[14, "object-detection"]], "Our Pledge": [[1, "our-pledge"]], "Our Standards": [[1, "our-standards"]], "Page": [[6, "page"]], "Preparing your model for inference": [[15, null]], "Prerequisites": [[3, "prerequisites"]], "Pretrained community models": [[13, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[13, "pushing-to-the-huggingface-hub"]], "Questions": [[2, "questions"]], "Recognition": [[13, "recognition"], [14, "recognition"]], "Recognition predictors": [[16, "recognition-predictors"]], "Scope": [[1, "scope"]], "Share your model with the community": [[13, null]], "Supported Vocabs": [[5, "supported-vocabs"]], "Supported datasets": [[4, "supported-datasets"]], "Supported transformations": [[8, "supported-transformations"]], "Synthetic dataset generator": [[5, "synthetic-dataset-generator"], [14, "synthetic-dataset-generator"]], "Task evaluation": [[9, "task-evaluation"]], "Text Detection": [[16, "text-detection"]], "Text Recognition": [[16, "text-recognition"]], "Text detection models": [[4, "text-detection-models"]], "Text recognition models": [[4, "text-recognition-models"]], "Train your own model": [[11, null]], "Two-stage approaches": [[16, "two-stage-approaches"]], "Unit tests": [[2, "unit-tests"]], "Use your own datasets": [[14, "use-your-own-datasets"]], "Using your ONNX exported model in docTR": [[15, "using-your-onnx-exported-model-in-doctr"]], "Via Git": [[3, "via-git"]], "Via Python Package": [[3, "via-python-package"]], "Visualization": [[9, "visualization"]], "What should I do with the output?": [[16, "what-should-i-do-with-the-output"]], "Word": [[6, "word"]], "docTR Notebooks": [[10, null]], "docTR Vocabs": [[5, "id5"]], "docTR: Document Text Recognition": [[4, null]], "doctr.datasets": [[5, null], [5, "datasets"]], "doctr.io": [[6, null]], "doctr.models": [[7, null]], "doctr.models.classification": [[7, "doctr-models-classification"]], "doctr.models.detection": [[7, "doctr-models-detection"]], "doctr.models.factory": [[7, "doctr-models-factory"]], "doctr.models.recognition": [[7, "doctr-models-recognition"]], "doctr.models.zoo": [[7, "doctr-models-zoo"]], "doctr.transforms": [[8, null]], "doctr.utils": [[9, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]]}, "docnames": ["changelog", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[6, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[6, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[8, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[5, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[8, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[8, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[5, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[7, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[5, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[7, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[7, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[6, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[7, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[5, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[5, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[6, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[6, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[5, "doctr.datasets.encode_sequences", false]], "from_hub() (in module doctr.models.factory)": [[7, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[6, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[6, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[6, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[5, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[8, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[8, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[5, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[5, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[5, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[5, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[5, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[7, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[8, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[6, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[7, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet18_rotation() (in module doctr.models.detection)": [[7, "doctr.models.detection.linknet_resnet18_rotation", false]], "linknet_resnet34() (in module doctr.models.detection)": [[7, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[7, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[7, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[7, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[5, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_orientation() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_small_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[8, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[7, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[5, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[8, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[6, "doctr.io.Page", false]], "parseq() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[7, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[8, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[8, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[8, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[8, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[8, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[8, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[8, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[8, "doctr.transforms.RandomJpegQuality", false]], "randomrotate (class in doctr.transforms)": [[8, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[8, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[8, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[6, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[6, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[6, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[6, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[5, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[8, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[6, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[6, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[5, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[9, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[9, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[9, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[9, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[5, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[5, "doctr.datasets.SVT", false]], "synthesize_page() (in module doctr.utils.visualization)": [[9, "doctr.utils.visualization.synthesize_page", false]], "synthtext (class in doctr.datasets)": [[5, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.TextMatch", false]], "togray (class in doctr.transforms)": [[8, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[9, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[9, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[9, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[9, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[7, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[9, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[7, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[7, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.vitstr_small", false]], "word (class in doctr.io)": [[6, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[5, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[5, 0, 1, "", "CORD"], [5, 0, 1, "", "CharacterGenerator"], [5, 0, 1, "", "DetectionDataset"], [5, 0, 1, "", "DocArtefacts"], [5, 0, 1, "", "FUNSD"], [5, 0, 1, "", "IC03"], [5, 0, 1, "", "IC13"], [5, 0, 1, "", "IIIT5K"], [5, 0, 1, "", "IIITHWS"], [5, 0, 1, "", "IMGUR5K"], [5, 0, 1, "", "MJSynth"], [5, 0, 1, "", "OCRDataset"], [5, 0, 1, "", "RecognitionDataset"], [5, 0, 1, "", "SROIE"], [5, 0, 1, "", "SVHN"], [5, 0, 1, "", "SVT"], [5, 0, 1, "", "SynthText"], [5, 0, 1, "", "WordGenerator"], [5, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[5, 0, 1, "", "DataLoader"]], "doctr.io": [[6, 0, 1, "", "Artefact"], [6, 0, 1, "", "Block"], [6, 0, 1, "", "Document"], [6, 0, 1, "", "DocumentFile"], [6, 0, 1, "", "Line"], [6, 0, 1, "", "Page"], [6, 0, 1, "", "Word"], [6, 1, 1, "", "decode_img_as_tensor"], [6, 1, 1, "", "read_html"], [6, 1, 1, "", "read_img_as_numpy"], [6, 1, 1, "", "read_img_as_tensor"], [6, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[6, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[6, 2, 1, "", "from_images"], [6, 2, 1, "", "from_pdf"], [6, 2, 1, "", "from_url"]], "doctr.io.Page": [[6, 2, 1, "", "show"]], "doctr.models": [[7, 1, 1, "", "kie_predictor"], [7, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[7, 1, 1, "", "crop_orientation_predictor"], [7, 1, 1, "", "magc_resnet31"], [7, 1, 1, "", "mobilenet_v3_large"], [7, 1, 1, "", "mobilenet_v3_large_r"], [7, 1, 1, "", "mobilenet_v3_small"], [7, 1, 1, "", "mobilenet_v3_small_orientation"], [7, 1, 1, "", "mobilenet_v3_small_r"], [7, 1, 1, "", "resnet18"], [7, 1, 1, "", "resnet31"], [7, 1, 1, "", "resnet34"], [7, 1, 1, "", "resnet50"], [7, 1, 1, "", "vgg16_bn_r"], [7, 1, 1, "", "vit_b"], [7, 1, 1, "", "vit_s"]], "doctr.models.detection": [[7, 1, 1, "", "db_mobilenet_v3_large"], [7, 1, 1, "", "db_resnet50"], [7, 1, 1, "", "detection_predictor"], [7, 1, 1, "", "linknet_resnet18"], [7, 1, 1, "", "linknet_resnet18_rotation"], [7, 1, 1, "", "linknet_resnet34"], [7, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[7, 1, 1, "", "from_hub"], [7, 1, 1, "", "login_to_hub"], [7, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[7, 1, 1, "", "crnn_mobilenet_v3_large"], [7, 1, 1, "", "crnn_mobilenet_v3_small"], [7, 1, 1, "", "crnn_vgg16_bn"], [7, 1, 1, "", "master"], [7, 1, 1, "", "parseq"], [7, 1, 1, "", "recognition_predictor"], [7, 1, 1, "", "sar_resnet31"], [7, 1, 1, "", "vitstr_base"], [7, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[8, 0, 1, "", "ChannelShuffle"], [8, 0, 1, "", "ColorInversion"], [8, 0, 1, "", "Compose"], [8, 0, 1, "", "GaussianBlur"], [8, 0, 1, "", "GaussianNoise"], [8, 0, 1, "", "LambdaTransformation"], [8, 0, 1, "", "Normalize"], [8, 0, 1, "", "OneOf"], [8, 0, 1, "", "RandomApply"], [8, 0, 1, "", "RandomBrightness"], [8, 0, 1, "", "RandomContrast"], [8, 0, 1, "", "RandomCrop"], [8, 0, 1, "", "RandomGamma"], [8, 0, 1, "", "RandomHorizontalFlip"], [8, 0, 1, "", "RandomHue"], [8, 0, 1, "", "RandomJpegQuality"], [8, 0, 1, "", "RandomRotate"], [8, 0, 1, "", "RandomSaturation"], [8, 0, 1, "", "RandomShadow"], [8, 0, 1, "", "Resize"], [8, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[9, 0, 1, "", "DetectionMetric"], [9, 0, 1, "", "LocalizationConfusion"], [9, 0, 1, "", "OCRMetric"], [9, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.visualization": [[9, 1, 1, "", "synthesize_page"], [9, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [1, 6, 7, 9, 13], "0": [1, 3, 5, 8, 9, 11, 14, 16], "00": 16, "01": 16, "0123456789": 5, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 5, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 5, "02": 16, "02562": 7, "03": 16, "035": [], "0361328125": 16, "04": [], "05": 16, "06": 16, "06640625": 16, "07": 16, "08": [8, 16], "09": 16, "0966796875": 16, "1": [3, 5, 6, 7, 8, 9, 11, 14, 16], "10": [5, 9, 16], "100": [5, 8, 9, 14, 16], "1000": 16, "101": 5, "1024": [7, 9, 11, 16], "104": [], "106": [], "108": 5, "1095": 14, "11": 16, "110": 9, "1107": 14, "114": [], "115": [], "1156": 14, "116": 5, "118": [], "11800h": 16, "11th": 16, "12": [3, 16], "120": [], "123": 5, "126": 5, "1268": [], "128": [7, 11, 15, 16], "13": [9, 16], "130": 5, "13068": 14, "131": 5, "1337891": 14, "1357421875": 16, "1396484375": 16, "14": 16, "1420": 16, "14470v1": [], "149": 14, "15": 16, "150": [9, 16], "154": [], "1552": 16, "16": [7, 15], "160": [], "1630859375": 16, "1684": 16, "16x16": 7, "17": 16, "1778": 16, "1782": 16, "18": [7, 16], "185546875": 16, "19": [], "1900": 16, "1910": 7, "19342": 14, "19370": 14, "195": [], "19598": [], "199": 16, "1999": 16, "1m": [], "2": [3, 4, 5, 6, 8, 16], "20": [], "200": 9, "2000": 14, "2003": [4, 5], "2012": 5, "2013": [4, 5], "2015": 5, "2019": 4, "2021": [], "2023": [], "207901": 14, "21": 16, "2103": [], "2186": 14, "21888": 14, "22": 16, "224": [7, 8], "225": 8, "22672": 14, "229": [8, 14], "23": 16, "233": 14, "234": 5, "236": [], "24": 16, "246": 14, "249": 14, "25": 16, "2504": 16, "255": [6, 7, 8, 9, 16], "256": 7, "257": 14, "26": [], "26032": 14, "264": 11, "27": 16, "2700": 14, "2710": 16, "2749": 11, "28": 16, "287": 11, "29": 16, "296": 11, "299": 11, "2d": 16, "3": [3, 4, 6, 7, 8, 9, 15, 16], "30": 16, "300": 14, "3000": 14, "301": 11, "30595": 16, "30ghz": 16, "31": [7, 16], "32": [5, 7, 8, 11, 14, 15, 16], "3232421875": 16, "33": 8, "33402": 14, "33608": 14, "34": [7, 16], "340": 16, "3456": 16, "35": 16, "3515625": 16, "36": [], "360": 14, "37": [5, 16], "38": 16, "39": [], "4": [7, 8, 9, 16], "40": 16, "406": 8, "41": 16, "42": 16, "43": 16, "44": 16, "45": 16, "456": 8, "46": 16, "47": 16, "472": [], "48": [5, 16], "485": 8, "49": [], "49377": [], "5": [5, 8, 9, 16], "50": [7, 14, 16], "51": 16, "51171875": 16, "512": 7, "52": [5, 16], "529": 16, "53": 16, "533": [], "54": 16, "540": 16, "5478515625": 16, "55": 16, "56": 16, "57": 16, "58": 16, "580": 16, "5810546875": 16, "583": 16, "59": 16, "595": [], "597": 16, "5k": [4, 5], "5m": [], "6": [8, 16], "60": 8, "600": [7, 9, 16], "61": 16, "611": [], "62": 16, "625": [], "626": 14, "629": [], "63": 16, "630": [], "64": [7, 8, 16], "640": [], "641": 16, "647": 14, "65": 16, "66": 16, "660": [], "664": [], "666": [], "67": 16, "672": [], "68": 16, "689": [], "69": 16, "693": 11, "694": 11, "695": 11, "6m": [], "7": 16, "70": [9, 16], "700": [], "701": [], "702": [], "707470": 14, "71": 16, "7100000": 14, "713": [], "7141797": 14, "7149": 14, "72": 16, "72dpi": 6, "73": 16, "73257": 14, "733": [], "74": 16, "745": [], "75": [8, 16], "753": [], "7581382": 14, "76": 16, "77": 16, "772": 11, "772875": 14, "78": 16, "780": [], "781": [], "783": [], "785": 11, "789": [], "79": 16, "793533": 14, "796": 14, "798": 11, "7m": [], "8": [3, 7, 8, 16], "80": 16, "800": [7, 9, 14, 16], "81": 16, "817": [], "82": 16, "8275l": [], "83": 16, "830": [], "84": 16, "849": 14, "85": 16, "8564453125": 16, "857": 16, "85875": 14, "86": 16, "860": [], "8603515625": 16, "862": [], "863": [], "87": 16, "8707": 14, "875": [], "88": 16, "89": 16, "8m": [], "9": 16, "90": 16, "90k": 5, "90kdict32px": 5, "91": 16, "913": [], "914085328578949": 16, "917": [], "92": 16, "921": [], "93": 16, "94": [5, 16], "95": [9, 16], "9578408598899841": 16, "96": 16, "97": 16, "98": 16, "99": 16, "9949972033500671": 16, "A": [1, 2, 4, 5, 6, 7, 10, 15], "And": [], "As": 2, "Be": [], "Being": 1, "By": 12, "For": [1, 2, 3, 11, 16], "If": [2, 3, 6, 7, 11, 16], "In": [2, 5, 14], "It": [8, 13, 15], "Its": [4, 7], "No": [1, 16], "Of": 5, "Or": [], "The": [1, 2, 5, 6, 9, 12, 16], "Then": [], "To": [2, 3, 12, 13, 16], "_": [1, 5, 7], "__call__": [], "_build": 2, "_i": 9, "ab": [], "abc": [], "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 5, "abdef": [5, 14], "abl": [14, 16], "about": [1, 14, 16], "abov": 16, "abstract": [], "abstractdataset": 5, "abus": 1, "accent": [], "accept": 1, "access": [4, 6, 14, 16], "account": [1, 13], "accur": [], "accuraci": 9, "achiev": 15, "act": 1, "action": 1, "activ": 4, "ad": [2, 7, 8], "adapt": 1, "add": [8, 9, 13], "add_hook": [], "add_label": 9, "addit": [2, 3, 6], "addition": [2, 16], "address": [1, 6], "adjust": 8, "advanc": 1, "advantag": 15, "advis": 2, "aesthet": [4, 5], "affect": 1, "after": [13, 16], "ag": 1, "again": [], "aggreg": [9, 14], "aggress": 1, "align": [1, 6], "all": [1, 2, 5, 6, 8, 9, 14, 16], "allow": 1, "along": 16, "alreadi": 2, "also": [1, 7, 13, 14, 16], "alwai": 14, "an": [1, 2, 4, 5, 6, 7, 9, 15, 16], "analysi": 6, "ancient_greek": 5, "andrej": [], "angl": [6, 8], "ani": [1, 5, 6, 7, 8, 9, 16], "annot": 5, "anot": 14, "anoth": [3, 7, 11, 14], "answer": 1, "anyascii": [], "anyon": 4, "anyth": [], "api": [2, 4], "apolog": 1, "apologi": 1, "app": 2, "appear": 1, "appli": [1, 5, 8], "applic": [4, 7], "appoint": 1, "appreci": 13, "appropri": [1, 2, 16], "ar": [1, 2, 3, 5, 6, 8, 9, 10, 14, 16], "arab": 5, "arabic_diacrit": 5, "arabic_lett": 5, "arabic_punctu": 5, "arbitrarili": [], "arch": [7, 13], "architectur": [4, 7, 13], "archiv": [], "area": 16, "arg": [5, 7], "argument": [5, 7, 16], "around": 1, "arrai": [6, 8, 9], "art": 4, "artefact": [9, 10, 16], "artefact_typ": 6, "articl": [], "artifici": [4, 5], "arxiv": 7, "as_imag": [], "asarrai": 9, "ascii_lett": 5, "aspect": [4, 7, 8, 16], "assess": 9, "assign": 9, "associ": 6, "assum": 7, "assume_straight_pag": [7, 16], "astyp": [7, 9, 16], "attack": 1, "attend": [4, 7], "attent": [1, 7], "autoclass": [], "autom": 4, "automat": [], "autoregress": [4, 7], "avail": [1, 4, 8], "averag": [8, 16], "avoid": [1, 3], "aw": [4, 16], "awar": [], "azur": 16, "b": [7, 9, 16], "b_j": 9, "back": 2, "backbon": 7, "backend": 16, "background": 14, "bangla": [], "bar": [], "bar_cod": 14, "baranovskij": [], "base": [4, 7], "baselin": [4, 7, 16], "batch": [5, 7, 8, 14, 16], "batch_siz": [5, 11, 14, 15], "bblanchon": [], "bbox": 16, "becaus": 12, "been": [2, 9, 14, 16], "befor": [5, 7, 8, 16], "begin": 9, "behavior": 1, "being": [9, 16], "belong": 16, "benchmark": 16, "best": 1, "beta": [], "better": [10, 16], "between": [8, 9], "bgr": 6, "bilinear": 8, "bin_thresh": [], "binar": [4, 7], "binari": [6, 15, 16], "bit": 15, "blank": 9, "block": [9, 16], "block_1_1": 16, "blue": 9, "blur": 8, "bmvc": 5, "bn": 13, "bodi": [1, 16], "bool": [5, 6, 7, 8, 9], "boolean": [7, 16], "both": [4, 5, 8, 14, 16], "bottom": [7, 16], "bound": [5, 6, 7, 8, 9, 16], "box": [5, 6, 7, 8, 9, 14, 16], "box_thresh": [], "brew": 3, "bright": 8, "broadcast": 9, "browser": [2, 4], "build": [2, 3], "built": 2, "byte": [6, 16], "c": [6, 9], "c5": [], "c_j": 9, "cach": [2, 5, 12], "cache_sampl": 5, "cairo": 3, "call": [], "callabl": [5, 8], "can": [2, 3, 11, 12, 13, 14, 16], "capabl": [2, 10, 16], "case": [5, 9], "cf": 16, "cfg": 16, "challeng": 5, "challenge2_test_task12_imag": 5, "challenge2_test_task1_gt": 5, "challenge2_training_task12_imag": 5, "challenge2_training_task1_gt": 5, "chang": 12, "changelog": [], "channel": [1, 2, 6, 8], "channel_prior": [], "channelshuffl": 8, "charact": [4, 5, 6, 9, 14, 16], "charactergener": [5, 14], "characterist": 1, "charg": 16, "charset": 16, "chart": 6, "check": [2, 13, 16], "checkpoint": 7, "chip": 3, "christian": [], "ci": 2, "clarifi": 1, "clariti": 1, "class": [1, 5, 6, 8, 9, 16], "class_nam": 11, "classif": 14, "classif_mobilenet_v3_smal": 7, "classmethod": 6, "clear": 2, "clone": 3, "close": 2, "co": 13, "code": [4, 6], "codecov": 2, "colab": 10, "collate_fn": 5, "collect": 6, "color": [8, 9], "colorinvers": 8, "column": 6, "com": [1, 3, 6, 13], "combin": 16, "come": 15, "command": 2, "comment": 1, "commit": 1, "common": [1, 8, 9, 15], "commun": 1, "compar": 4, "comparison": [9, 16], "competit": 5, "compil": [10, 16], "complaint": 1, "complementari": 9, "complet": 2, "compon": 16, "compos": [5, 16], "comprehens": 16, "comput": [5, 9, 15, 16], "conf_threshold": [], "confid": [6, 9, 16], "config": 7, "configur": 7, "confus": 9, "consecut": [8, 16], "consequ": 1, "consid": [1, 2, 5, 6, 9, 16], "consist": 16, "consolid": [4, 5], "constant": 8, "construct": 1, "consum": 9, "contact": 1, "contain": [5, 14], "content": [5, 6, 9, 16], "context": 7, "contib": [], "continu": 1, "contrast": 8, "contrast_factor": 8, "contrib": [], "contribut": 1, "contributor": 2, "conv_sequ": [], "convers": 6, "convert": [6, 8], "convert_page_to_numpi": [], "convert_to_fp16": [], "convert_to_tflit": [], "convolut": 7, "cool": [], "coordin": [6, 16], "cord": [4, 5, 14, 16], "core": [9, 16], "corner": 16, "correct": 8, "correspond": [3, 6, 16], "could": 1, "counterpart": 9, "cover": 2, "coverag": 2, "cpu": [4, 11], "creat": 13, "crnn": [4, 7, 13], "crnn_mobilenet_v3_larg": [7, 13, 16], "crnn_mobilenet_v3_smal": [7, 15, 16], "crnn_resnet31": [], "crnn_vgg16_bn": [7, 11, 13, 16], "crop": [7, 8, 14, 16], "crop_orient": [], "crop_orientation_predictor": 7, "crop_param": [], "croporientationpredictor": 7, "cuda": 15, "currenc": 5, "current": [2, 16], "custom": 13, "custom_crop_orientation_model": [], "custom_page_orientation_model": [], "customhook": [], "cvit": 4, "czczup": [], "czech": 5, "d": [5, 14], "daili": [], "danish": [], "data": [4, 5, 6, 8, 9, 11, 13], "dataload": 14, "dataset": [7, 11, 16], "dataset_info": 5, "date": [11, 16], "db": 13, "db_crnn_resnet": [], "db_crnn_vgg": [], "db_mobilenet_v3_larg": [7, 13, 16], "db_resnet34": 16, "db_resnet50": [7, 11, 13, 16], "db_resnet50_rot": 16, "db_sar_resnet": [], "db_sar_vgg": [], "dbnet": [4, 7], "deal": [], "decis": 1, "decod": 6, "decode_img_as_tensor": 6, "dedic": [], "deem": 1, "deep": [7, 16], "def": [], "default": [6, 9, 11, 12], "defer": 14, "defin": [9, 15], "deform": [], "degre": 8, "degress": 6, "delet": 2, "delimit": 16, "delta": 8, "demo": [2, 4], "demonstr": 1, "depend": [2, 3, 4], "deploi": 2, "deploy": 4, "derogatori": 1, "describ": [7, 9], "descript": 10, "design": 8, "desir": 6, "det_arch": [7, 11, 13, 15], "det_b": [], "det_model": [11, 13], "det_param": 11, "det_predictor": 11, "detail": [11, 16], "detect": [5, 9, 10, 11], "detect_languag": 7, "detect_orient": 7, "detection_predictor": [7, 16], "detection_task": [], "detectiondataset": [5, 14], "detectionmetr": 9, "detectionpredictor": [7, 11], "detector": [], "deterior": 7, "determin": 1, "dev": [2, 12], "develop": 3, "developp": [], "deviat": 8, "devic": 15, "dict": [6, 9, 16], "dictionari": [6, 9], "differ": 1, "differenti": [4, 7], "digit": [4, 5, 14], "dimens": [6, 9, 16], "dimension": 8, "direct": 5, "directli": [13, 16], "directori": [2, 12], "disabl": [1, 12], "disable_crop_orient": [], "disable_page_orient": [], "disclaim": 16, "discuss": 2, "disk": [], "disparag": 1, "displai": [6, 9], "display_artefact": 9, "distanc": [], "distribut": 8, "div": 16, "divers": 1, "divid": 6, "do": [2, 3, 7], "doc": [2, 6, 15, 16], "docartefact": [5, 14], "docstr": 2, "doctr": [3, 11, 12, 13, 14, 16], "doctr_cache_dir": 12, "doctr_multiprocessing_dis": 12, "document": [5, 7, 9, 10, 14, 16], "documentbuild": [], "documentfil": [6, 13], "doesn": [], "don": [11, 16], "done": 8, "download": [5, 14], "downsiz": 7, "draw": [8, 9], "draw_proba": 9, "drop": 5, "drop_last": 5, "dtype": [6, 7, 8, 9, 15], "dual": [], "dummi": 13, "dummy_img": 16, "dummy_input": 15, "dure": 1, "dutch": [], "dynam": 5, "dynamic_seq_length": 5, "e": [1, 2, 3, 6, 7], "each": [4, 5, 6, 7, 8, 9, 14, 16], "eas": 2, "easi": [4, 9, 13], "easier": [], "easili": [6, 9, 11, 13, 14, 16], "econom": 1, "edit": 1, "educ": 1, "effect": [], "effici": [2, 4, 5, 7], "either": [9, 16], "element": [5, 6, 7, 9, 16], "els": 2, "email": 1, "empathi": 1, "en": 16, "enabl": [5, 6], "enclos": 6, "encod": [4, 5, 6, 7, 16], "encode_sequ": 5, "encount": 2, "encrypt": 6, "end": [4, 5, 7, 9], "english": [5, 14], "enough": [2, 16], "ensur": 2, "entir": [], "entri": 5, "environ": [1, 12], "eo": 5, "equiv": 16, "error": [], "estim": 7, "etc": 6, "ethnic": 1, "evalu": [14, 16], "event": 1, "everyon": 1, "everyth": [2, 16], "exact": [9, 16], "exactmatch": [], "exampl": [1, 2, 4, 5, 7, 13], "exchang": 15, "exclud": [], "execut": [], "exist": 13, "expand": 8, "expect": [6, 8, 9], "experi": 1, "explan": [1, 16], "explicit": 1, "exploit": [4, 7], "export": [6, 7, 9, 10, 16], "export_as_straight_box": [7, 16], "export_as_xml": 16, "export_model_to_onnx": 15, "express": [1, 8], "extens": 6, "extern": [1, 14], "extra": 3, "extract": [4, 5], "extract_arch": [], "extractor": 7, "f_": 9, "f_a": 9, "factor": 8, "fair": 1, "fairli": 1, "fals": [5, 6, 7, 8, 9, 11, 16], "famili": 9, "faq": 1, "fascan": [], "fast": [4, 5, 7], "fast_bas": [], "fast_smal": [], "fast_tini": [], "faster": 15, "fasterrcnn_mobilenet_v3_large_fpn": 7, "favorit": 16, "featur": [3, 7, 9, 10], "feed": [], "feedback": 1, "feel": [2, 13], "felix92": 13, "few": [3, 15], "figsiz": 9, "figur": 9, "file": [2, 5], "file_hash": [], "file_nam": [], "final": 7, "find": [2, 3, 14], "fine": [], "finnish": [], "first": 2, "firsthand": 5, "fit": [7, 16], "fitz": [], "flag": 16, "flexibl": [], "flip": 8, "float": [6, 8, 9, 15], "float32": [6, 7, 8, 15], "fn": 8, "focu": 13, "focus": [1, 5], "folder": 5, "follow": [1, 2, 3, 5, 8, 9, 11, 12, 13, 16], "font": [5, 9], "font_famili": [5, 9], "font_siz": 9, "foral": 9, "forc": 2, "forg": [], "form": [4, 5, 16], "format": [6, 9, 11, 14, 15, 16], "forpost": [4, 5], "forum": 2, "found": [], "fp": [], "fp16": 15, "frac": 9, "frame": [], "framework": [3, 13, 14, 16], "free": [1, 2, 13], "french": [5, 11, 13, 16], "friendli": 4, "from": [1, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16], "from_hub": [7, 13], "from_imag": [6, 13], "from_pdf": 6, "from_url": 6, "full": [5, 9, 16], "fulli": [], "function": [5, 8, 9], "funsd": [4, 5, 14, 16], "further": 14, "futur": 5, "g": [6, 7], "g_": 9, "g_x": 9, "gallagh": [], "gamma": 8, "gaussian": 8, "gaussianblur": 8, "gaussiannois": 8, "gdk": 3, "gen": 16, "gender": 1, "gener": [2, 4, 7], "generic_cyrillic_lett": [], "geometri": [4, 6, 16], "geq": 9, "german": [5, 11], "get": 16, "get_artefact": [], "get_word": [], "gettextword": [], "git": 13, "github": [2, 3, 13], "give": 1, "given": [5, 6, 8, 9, 16], "global": 7, "go": 16, "good": 15, "googl": 2, "googlevis": 4, "gpu": [4, 15], "gracefulli": 1, "graph": 6, "grayscal": 8, "ground": 9, "groung": 9, "group": 4, "gt": 9, "gt_box": 9, "gt_label": 9, "gtk": 3, "guid": 2, "guidanc": 14, "gvision": 16, "h": [6, 7, 8], "h_": 9, "ha": [2, 5, 9, 14], "half": [], "handl": 14, "handwrit": 5, "handwritten": 14, "harass": 1, "hardwar": [], "harm": 1, "hat": 9, "have": [1, 2, 9, 11, 13, 14, 16], "head": [7, 16], "healthi": 1, "hebrew": [], "height": 6, "hello": [9, 16], "help": 15, "here": [3, 8, 10, 14, 16], "hf": 7, "hf_hub_download": 7, "high": 6, "higher": [3, 5], "hindi": [], "hindi_digit": 5, "hocr": 16, "homebrew": 3, "hook": [], "horizont": [6, 8], "hous": 5, "how": [2, 11, 13, 14], "howev": 14, "hsv": 8, "html": [1, 2, 16], "http": [1, 3, 6, 7, 13, 16], "hub": 7, "hue": 8, "huggingfac": 7, "hw": 5, "i": [1, 2, 5, 6, 7, 8, 9, 12, 13, 14, 15], "i7": 16, "ibrahimov": [], "ic03": [4, 5, 14], "ic13": [4, 5, 14], "icdar": [4, 5], "icdar2019": 5, "id": 16, "ident": 1, "identifi": 4, "ignor": [], "ignore_acc": [], "ignore_cas": [], "iiit": [4, 5], "iiit5k": [5, 14], "iiithw": [4, 5, 14], "imag": [4, 5, 6, 7, 8, 9, 13, 14, 16], "imagenet": 7, "imageri": 1, "images_90k_norm": 5, "img": [5, 8, 14], "img_cont": 6, "img_fold": [5, 14], "img_path": 6, "img_transform": 5, "imgur5k": [4, 5, 14], "imgur5k_annot": 5, "imlist": 5, "impact": 1, "implement": [5, 6, 8, 9, 16], "import": [5, 6, 7, 8, 9, 11, 13, 14, 15, 16], "improv": [], "inappropri": 1, "incid": 1, "includ": [1, 3, 5, 14, 15], "inclus": 1, "increas": 8, "independ": [], "index": [2, 6], "indic": 9, "individu": 1, "infer": [4, 7, 8], "inform": [1, 2, 4, 5, 14], "inherit": [], "input": [2, 6, 7, 8, 15, 16], "input_crop": 7, "input_pag": [7, 9, 16], "input_shap": 15, "input_t": [], "input_tensor": 7, "inspir": [1, 8], "instal": 13, "instanc": [1, 16], "instanti": [7, 16], "instead": [5, 6, 7], "insult": 1, "int": [5, 6, 8, 9], "int64": [8, 9], "integ": 9, "integr": [4, 13, 14], "intel": 16, "interact": [1, 6, 9], "interfac": 13, "interoper": 15, "interpol": 8, "interpret": [5, 6], "intersect": 9, "invert": 8, "investig": 1, "invis": 1, "invoic": [], "involv": [1, 16], "io": 13, "iou": 9, "iou_thresh": 9, "iou_threshold": [], "irregular": [4, 7, 14], "isn": 5, "issu": [1, 2, 13], "italian": [], "iter": [5, 8, 14, 16], "its": [6, 7, 8, 9, 14, 16], "itself": [7, 13], "j": 9, "jame": [], "job": 2, "join": 2, "jpeg": 8, "jpegqual": 8, "jpg": [5, 6, 13], "json": [5, 14, 16], "json_output": 16, "jump": 2, "just": 1, "kei": [], "kera": [7, 15], "kernel": 8, "kernel_s": [], "kernel_shap": 8, "keywoard": [], "keyword": [5, 7], "kie": [7, 11], "kie_predictor": [7, 11], "kiepredictor": 7, "kind": [1, 16], "know": 2, "kwarg": [5, 6, 7, 9], "l": 9, "l_j": 9, "label": [5, 8, 9, 14], "label_fil": [5, 14], "label_fold": 5, "label_path": [5, 14], "labels_path": [5, 14], "ladder": 1, "lambda": 8, "lambdatransform": 8, "lang": 16, "languag": [1, 4, 5, 6, 7, 13, 16], "larg": [7, 13], "largest": 9, "last": [3, 5], "latenc": 7, "later": 2, "latest": [3, 16], "latin": 5, "layer": 15, "layout": 16, "lead": 1, "leader": 1, "learn": [1, 4, 7, 15, 16], "least": 3, "left": [9, 16], "legacy_french": 5, "length": 5, "less": 15, "let": [], "letter": [], "level": [1, 5, 9, 16], "levenshtein": [], "leverag": 10, "lf": 13, "libffi": 3, "librari": [2, 3, 10, 11], "light": 4, "lightweight": [], "like": 1, "limits_": 9, "line": [4, 9, 16], "line_1_1": 16, "link": 11, "linknet": [4, 7], "linknet16": [], "linknet_resnet18": [7, 11, 16], "linknet_resnet18_rot": [7, 16], "linknet_resnet34": [7, 15, 16], "linknet_resnet50": [7, 16], "linux": 3, "list": [5, 6, 8, 9, 13], "ll": 9, "load": [4, 5, 7], "load_state_dict": 11, "load_weight": 11, "loader": [], "loc_pr": [], "local": [2, 4, 5, 7, 9, 14, 16], "localis": 5, "localizationconfus": 9, "locat": [2, 6], "login": 7, "login_to_hub": [7, 13], "logo": [6, 14], "love": 13, "lower": [8, 9], "m": [2, 9, 16], "m1": 3, "macbook": 3, "machin": 15, "maco": 3, "made": 4, "magc_resnet31": 7, "mai": [1, 2], "mail": 1, "main": 10, "maintain": 4, "mainten": 2, "make": [1, 2, 9, 12, 13, 15, 16], "mani": [14, 16], "manipul": [], "map": 5, "map_loc": 11, "mask_shap": 9, "master": [4, 7, 16], "match": [9, 16], "mathcal": 9, "matplotlib": 9, "max": [5, 8, 9], "max_angl": 8, "max_area": 8, "max_char": [5, 14], "max_delta": 8, "max_dist": [], "max_gain": 8, "max_gamma": 8, "max_qual": 8, "max_ratio": 8, "maximum": [5, 8], "maxval": [7, 8], "mbox": 9, "mean": [8, 9, 11], "meaniou": 9, "meant": [6, 15], "measur": 16, "media": 1, "median": [], "meet": 11, "member": 1, "memori": [9, 12, 15], "mention": 16, "merg": 5, "messag": 2, "meta": 16, "metadata": 15, "metal": 3, "method": [8, 16], "metric": [9, 16], "middl": [], "might": [15, 16], "min": 8, "min_area": 8, "min_char": [5, 14], "min_gain": 8, "min_gamma": 8, "min_qual": 8, "min_ratio": 8, "min_val": 8, "minde": [1, 3, 4, 7], "minim": [2, 4], "minimalist": [], "minimum": [3, 5, 8, 9], "minval": 8, "miss": 3, "mistak": 1, "mix": [], "mixed_float16": 15, "mixed_precis": 15, "mjsynth": [4, 5, 14], "mnt": 5, "mobilenet": [7, 13], "mobilenet_v3_larg": 7, "mobilenet_v3_large_r": 7, "mobilenet_v3_smal": 7, "mobilenet_v3_small_crop_orient": [], "mobilenet_v3_small_orient": 7, "mobilenet_v3_small_page_orient": [], "mobilenet_v3_small_r": 7, "mobilenetv3": 7, "modal": [], "mode": 3, "model": [5, 9, 12, 14], "model_nam": [7, 13, 15], "model_path": 15, "moder": 1, "modif": 2, "modifi": [7, 12], "modul": [6, 8, 9, 16], "moment": 16, "more": [2, 9, 14, 16], "moscardi": [], "most": 16, "mozilla": 1, "multi": [4, 7], "multilingu": [], "multipl": [5, 6, 8], "multipli": 8, "multiprocess": 12, "my": 7, "my_awesome_model": 13, "my_hook": [], "n": [5, 9], "na": [], "name": [5, 7, 15, 16], "nation": 1, "natur": [1, 4, 5], "nb": 16, "ndarrai": [5, 6, 8, 9], "necessari": [3, 11, 12], "need": [2, 3, 5, 9, 11, 12, 13], "neg": 8, "nest": 16, "nestedobject": [], "netraj": [], "network": [4, 5, 7, 15], "neural": [4, 5, 7, 15], "new": [2, 9], "newer": [], "next": [5, 14], "nois": 8, "noisi": [4, 5], "non": [4, 5, 6, 7, 8, 9], "none": [5, 6, 7, 8, 9, 16], "normal": [7, 8], "norwegian": [], "note": [0, 2, 5, 7, 13, 15], "now": 2, "np": [7, 8, 9, 16], "num_output_channel": 8, "num_sampl": [5, 14], "num_work": 5, "number": [5, 8, 9, 16], "numpi": [6, 7, 9, 16], "o": 3, "obb": [], "obj_detect": 13, "object": [5, 9, 10, 16], "objectness_scor": [], "oblig": 1, "obtain": 16, "occupi": 15, "ocr": [4, 5, 7, 9, 13, 14], "ocr_carea": 16, "ocr_db_crnn": 9, "ocr_lin": 16, "ocr_pag": 16, "ocr_par": 16, "ocr_predictor": [7, 11, 13, 15, 16], "ocrdataset": [5, 14], "ocrmetr": 9, "ocrpredictor": [7, 11], "ocrx_word": 16, "offens": 1, "offici": 1, "offlin": 1, "offset": 8, "onc": 16, "one": [2, 5, 7, 8, 11, 13, 16], "oneof": 8, "ones": [5, 8, 9], "onli": [2, 7, 8, 9, 13, 14, 15, 16], "onlin": 1, "onnx": [], "onnxruntim": [], "onnxtr": [], "opac": 8, "opacity_rang": 8, "open": [1, 2, 13, 15], "opinion": 1, "optic": [4, 16], "optim": 4, "option": [5, 11], "order": [2, 5, 6, 8], "org": [1, 7, 16], "organ": 6, "orient": [1, 6, 7, 16], "orientationpredictor": [], "other": [1, 2], "otherwis": [1, 6, 9], "our": [2, 7, 16], "out": [2, 7, 8, 9, 16], "outpout": 16, "output": [6, 8, 15], "output_s": [6, 8], "outsid": 12, "over": [3, 5, 9, 16], "overal": [1, 7], "overlai": 6, "overview": [], "overwrit": [], "overwritten": 13, "own": 4, "p": [8, 9, 16], "packag": [2, 4, 9, 12, 14], "pad": [5, 7, 8, 16], "page": [3, 5, 7, 9, 16], "page1": 6, "page2": 6, "page_1": 16, "page_idx": [6, 16], "page_orientation_predictor": [], "page_param": [], "pair": 9, "pango": 3, "paper": 7, "par_1_1": 16, "paragraph": [], "paragraph_break": [], "parallel": [], "param": [8, 16], "paramet": [4, 5, 6, 7, 8, 9, 15], "pars": [4, 5], "parseq": [4, 7, 16], "part": [5, 8, 16], "parti": 3, "partial": 16, "particip": 1, "pass": [5, 6, 7, 16], "password": 6, "patch": 7, "path": [5, 6, 14], "path_to_checkpoint": 11, "path_to_custom_model": [], "path_to_pt": 11, "patil": [], "pattern": 1, "pdf": [6, 7, 10], "pdfpage": 6, "peopl": 1, "per": [8, 16], "perform": [4, 6, 8, 9, 12, 15, 16], "period": 1, "permiss": 1, "permut": [4, 7], "persian_lett": 5, "person": [1, 14], "phase": 16, "photo": 14, "physic": [1, 6], "pick": 8, "pictur": 6, "pip": [2, 3], "pipelin": [], "pixbuf": 3, "pixel": [6, 8, 16], "platinum": [], "pleas": 2, "plot": 9, "plt": 9, "plug": 13, "plugin": 3, "png": 6, "point": 15, "polici": 12, "polish": [], "polit": 1, "polygon": [5, 16], "pool": 7, "portugues": 5, "posit": [1, 9], "possibl": [2, 9, 13], "post": [1, 16], "postprocessor": [], "potenti": 7, "power": 4, "ppageno": 16, "pre": [2, 7], "precis": [9, 16], "pred": 9, "pred_box": 9, "pred_label": 9, "predefin": 14, "predict": [6, 7, 9], "predictor": [4, 6, 7, 11, 13, 15], "prefer": 14, "preinstal": [], "preprocessor": [11, 16], "prerequisit": 13, "present": 10, "preserv": [7, 8, 16], "preserve_aspect_ratio": [6, 7, 8, 11, 16], "pretrain": [4, 7, 9, 11, 15, 16], "pretrained_backbon": [7, 11], "print": 16, "prior": 5, "privaci": 1, "privat": 1, "probabl": 8, "problem": 2, "procedur": 8, "process": [2, 4, 6, 11, 16], "processor": 16, "produc": [10, 16], "product": 15, "profession": 1, "project": [2, 14], "promptli": 1, "proper": 2, "properli": 5, "properti": [], "provid": [1, 2, 4, 13, 14, 16], "public": [1, 4], "publicli": 16, "publish": 1, "pull": 13, "punctuat": 5, "pure": 5, "purpos": 2, "push_to_hf_hub": [7, 13], "py": 13, "pypdfium2": 6, "pyplot": 9, "python": 2, "python3": 13, "pytorch": [3, 4, 7, 8, 11, 13, 15, 16], "q": 2, "qr": 6, "qr_code": 14, "qualiti": 8, "quantiz": [], "quantize_model": [], "question": 1, "quickli": 4, "quicktour": 10, "r": 16, "race": 1, "ramdisk": 5, "rand": [7, 8, 9, 15, 16], "random": [7, 8, 9, 16], "randomappli": 8, "randombright": 8, "randomcontrast": 8, "randomcrop": 8, "randomgamma": 8, "randomhorizontalflip": 8, "randomhu": 8, "randomjpegqu": 8, "randomli": 8, "randomres": [], "randomrot": 8, "randomsatur": 8, "randomshadow": 8, "rang": 8, "rassi": [], "ratio": [7, 8, 16], "raw": [6, 9], "re": 15, "read": [4, 5, 7], "read_html": 6, "read_img": 6, "read_img_as_numpi": 6, "read_img_as_tensor": 6, "read_pdf": 6, "readi": 15, "real": [4, 7, 8], "realli": [], "reason": 1, "rebuild": 2, "rebuilt": 2, "recal": [9, 16], "receipt": [4, 5, 16], "reco_arch": [7, 11, 13, 15], "reco_b": [], "reco_model": [11, 13], "reco_param": 11, "reco_predictor": 11, "recogn": 16, "recognit": [5, 9, 11], "recognition_predictor": [7, 16], "recognition_task": [5, 14], "recognitiondataset": [5, 14], "recognitionpredictor": [7, 11], "rectangular": 7, "recurr": [], "red": 9, "reduc": [3, 8], "refer": [2, 3, 11, 13, 14, 16], "regardless": 1, "region": [], "regroup": 9, "regular": 14, "reject": 1, "rel": [6, 8, 9], "relat": 6, "releas": [0, 3], "relev": [], "religion": 1, "relu": [], "remov": 1, "render": 6, "repo": 7, "repo_id": [7, 13], "report": 1, "repositori": [5, 7, 13], "repres": [1, 9, 15, 16], "represent": [4, 7], "request": [1, 13], "requir": [3, 8], "research": 4, "residu": 7, "resiz": [8, 16], "resnet": 7, "resnet18": [7, 13], "resnet31": 7, "resnet34": 7, "resnet50": [7, 13], "resolv": 6, "resolve_block": [], "resolve_lin": [], "resourc": 14, "respect": 1, "respons": 9, "rest": [2, 8, 9], "restrict": 12, "result": [2, 5, 6, 10, 13, 16], "return": [5, 6, 7, 9, 16], "reusabl": 16, "review": 1, "rgb": [6, 8], "rgb_mode": 6, "rgb_output": 6, "right": [1, 7, 9], "roboflow": [], "robust": [4, 5], "root": 5, "rotat": [5, 6, 7, 8, 9, 14, 16], "rotated_bbox": [], "run": [2, 3, 7], "same": [2, 6, 9, 14, 16], "sampl": [5, 14, 16], "sample_transform": 5, "sanjin": [], "sar": [4, 7], "sar_resnet31": [7, 16], "sar_vgg16_bn": [], "satur": 8, "save": [7, 14], "saved_model": [], "scale": [6, 7, 8, 9], "scale_rang": [], "scan": [4, 5], "scene": [4, 5, 7], "scheme": [], "score": 9, "scratch": [], "script": [2, 14], "seamless": 4, "seamlessli": [4, 16], "search": 7, "searchabl": 10, "sec": 16, "second": 16, "section": [11, 13, 15, 16], "secur": [1, 12], "see": [1, 2], "seemlessli": [], "seen": 16, "segment": [4, 7, 16], "self": [], "semant": [4, 7], "send": 16, "sens": 9, "sensit": 14, "separ": 16, "sequenc": [4, 5, 6, 7, 9, 16], "sequenti": 8, "seri": 1, "serial": [], "serialized_model": [], "seriou": 1, "set": [1, 5, 7, 9, 12, 16], "set_global_polici": 15, "sever": [6, 8, 16], "sex": 1, "sexual": 1, "sha256": [], "shade": 8, "shape": [6, 7, 8, 9, 16], "share": [12, 14], "shift": 8, "shm": 12, "should": [2, 5, 6, 8, 9], "show": [4, 6, 7, 9, 11, 13], "showcas": 2, "shuffl": [5, 8], "side": 9, "signatur": 6, "signific": 14, "simpl": [4, 7], "simpler": 7, "sinc": [5, 14], "singl": [1, 2, 4, 5], "single_img_doc": [], "size": [1, 5, 6, 8, 9, 16], "skew": 16, "slack": 2, "slightli": 7, "small": [2, 7], "smallest": 6, "snapshot_download": 7, "snippet": 16, "so": [2, 3, 5, 7, 13, 14], "social": 1, "socio": 1, "some": [3, 10, 13, 14], "someth": 2, "somewher": 2, "soon": 15, "sort": 1, "sourc": [5, 6, 7, 8, 9, 13], "space": 1, "span": 16, "spanish": 5, "spatial": [6, 9], "special": [], "specif": [2, 3, 9, 11, 14, 16], "specifi": [1, 5, 6], "speed": [4, 7], "sphinx": 2, "sroie": [4, 5, 14], "stabl": 3, "stackoverflow": 2, "stage": 4, "standalon": [], "standard": 8, "start": 5, "state": [4, 9], "static": 9, "statist": [], "statu": 1, "std": [8, 11], "step": 12, "still": 16, "str": [5, 6, 7, 8, 9], "straight": [5, 7, 14, 16], "straighten": [], "straighten_pag": [], "straigten_pag": [], "stream": 6, "street": [4, 5], "strict": [], "strictli": 9, "string": [5, 6, 9, 16], "strive": 3, "strong": [4, 7], "structur": [15, 16], "subset": [5, 16], "suggest": [2, 13], "sum": 9, "summari": 9, "support": [15, 16], "sustain": 1, "svhn": [4, 5, 14], "svt": [5, 14], "swedish": [], "symbol": [], "symmetr": [7, 8, 16], "symmetric_pad": [7, 8, 16], "synthes": 9, "synthesize_pag": 9, "synthet": 4, "synthtext": [4, 5, 14], "system": 16, "t": [2, 5, 11, 16], "tabl": 13, "take": [1, 5, 16], "target": [5, 6, 8, 9, 14], "target_s": 5, "task": [4, 5, 7, 13, 14, 16], "task2": 5, "team": [], "techminde": [], "templat": [2, 4], "tensor": [5, 6, 8, 16], "tensorflow": [3, 4, 6, 7, 8, 11, 13, 15, 16], "tensorspec": 15, "term": 1, "test": 14, "test_set": 5, "text": [5, 6, 7, 9, 14], "text_output": [], "textmatch": 9, "textnet": [], "textnet_bas": [], "textnet_smal": [], "textnet_tini": [], "textract": [4, 16], "textstylebrush": [4, 5], "textual": [4, 5, 6, 7, 16], "tf": [3, 6, 7, 8, 13, 15], "tf_model": [], "tflite": [], "than": [2, 3, 9, 13], "thank": 2, "thei": [1, 9], "them": [3, 5, 16], "thi": [1, 2, 3, 5, 9, 11, 12, 13, 14, 15, 16], "thing": [15, 16], "third": 3, "those": [1, 3, 6, 16], "threaten": 1, "threshold": [], "through": [1, 8, 14], "tilman": [], "time": [1, 4, 7, 9, 14], "tini": [], "titl": [6, 16], "tm": 16, "tmp": 12, "togeth": [2, 6], "tograi": 8, "tool": 14, "top": [9, 16], "topic": 2, "torch": [3, 8, 11, 13, 15], "torchvis": 8, "total": 11, "toward": [1, 3], "train": [2, 5, 7, 8, 13, 14, 15, 16], "train_it": [5, 14], "train_load": [5, 14], "train_pytorch": 13, "train_set": [5, 14], "train_tensorflow": 13, "trainabl": [4, 7], "tranform": 8, "transcrib": 16, "transfer": [4, 5], "transfo": 8, "transform": [4, 5, 7], "translat": 1, "troll": 1, "true": [5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16], "truth": 9, "tune": 15, "tupl": [5, 6, 8, 9], "turn": [], "two": [6, 12], "txt": 5, "type": [6, 13, 15, 16], "typic": 16, "u": [1, 2], "ucsd": 5, "udac": 2, "uint8": [6, 7, 9, 16], "ukrainian": [], "unaccept": 1, "underli": 14, "underneath": 6, "understand": [4, 5, 16], "unidecod": 9, "uniform": [7, 8], "uniformli": 8, "uninterrupt": [6, 16], "union": 9, "unit": [], "unittest": 2, "unlock": 6, "unoffici": 7, "unprofession": 1, "unsolicit": 1, "unsupervis": 4, "unwelcom": 1, "up": [7, 16], "updat": 9, "upgrad": 2, "upper": [5, 8], "uppercas": 14, "url": 6, "us": [1, 2, 3, 5, 7, 9, 11, 12, 13, 16], "usabl": 16, "usag": [12, 15], "use_broadcast": 9, "use_polygon": [5, 9, 14], "useabl": 16, "user": [3, 4, 6, 10], "utf": 16, "util": 15, "v0": [], "v1": 13, "v3": [7, 13, 16], "valid": 14, "valu": [2, 6, 8, 16], "valuabl": 4, "variabl": 12, "varieti": 5, "veri": 7, "verifi": [], "verma": [], "version": [1, 2, 3, 15, 16], "vgg": 7, "vgg16": 13, "vgg16_bn_r": 7, "via": 1, "video": [], "vietnames": 5, "view": [4, 5], "viewpoint": 1, "violat": 1, "visibl": 1, "vision": [4, 5, 7], "visiondataset": 5, "visiontransform": 7, "visual": 4, "visualize_pag": 9, "vit_": 7, "vit_b": 7, "vitstr": [4, 7, 15], "vitstr_bas": [7, 16], "vitstr_smal": [7, 11, 15, 16], "viz": [], "vocab": [11, 13, 14, 16], "vocabulari": [5, 11, 13], "w": [6, 7, 8, 9], "w3": 16, "wa": 1, "wai": [1, 4, 14], "want": [2, 15, 16], "warm": [], "warmup": 16, "wasn": 2, "we": [1, 2, 3, 4, 6, 8, 13, 14, 15, 16], "weasyprint": [], "web": [2, 6], "websit": 5, "weight": 11, "welcom": 1, "well": [1, 15], "were": [1, 6, 16], "what": 1, "when": [1, 2, 7], "whenev": 2, "where": [2, 6, 8, 9], "whether": [2, 5, 6, 8, 9, 14], "which": [1, 7, 12, 14, 16], "whichev": 3, "while": [8, 16], "why": 1, "width": 6, "wiki": 1, "wildreceipt": [], "window": [3, 7, 9], "wish": 2, "within": 1, "without": [1, 5, 7], "wonder": 2, "word": [4, 5, 7, 9, 16], "word_1_1": 16, "word_1_2": 16, "word_1_3": 16, "wordgener": [5, 14], "words_onli": 9, "work": [12, 16], "worker": 5, "workflow": 2, "worklow": 2, "world": [9, 16], "worth": 7, "wrap": 16, "wrapper": [5, 8], "write": 12, "written": [1, 6], "www": [1, 6, 16], "x": [6, 8, 9], "x12larg": [], "x_ascend": 16, "x_descend": 16, "x_i": 9, "x_size": 16, "x_wconf": 16, "xeon": [], "xhtml": 16, "xmax": 6, "xmin": 6, "xml": 16, "xml_bytes_str": 16, "xml_element": 16, "xml_output": 16, "xmln": 16, "y": 9, "y_i": 9, "y_j": 9, "yet": [], "ymax": 6, "ymin": 6, "yolov8": [], "you": [2, 3, 5, 6, 7, 11, 12, 13, 14, 15, 16], "your": [2, 4, 6, 9, 16], "yoursit": 6, "yugesh": [], "zero": [8, 9], "zoo": [], "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 5, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 5, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": [], "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 5, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 5, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": [], "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 5, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 5, "\u00e4\u00f6\u00e4\u00f6": [], "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 5, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": [], "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": [], "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": [], "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 5, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": [], "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": [], "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": [], "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 5, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 5, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 5, "\u067e\u0686\u06a2\u06a4\u06af": 5, "\u0905": [], "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": [], "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": [], "\u0950": [], "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": [], "\u09bd": [], "\u09ce": [], "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": []}, "titles": ["Changelog", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 2, "0": 0, "01": 0, "02": 0, "03": 0, "04": [], "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 1], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 1], "2021": 0, "2022": 0, "2023": [], "2024": [], "21": [], "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 1], "31": 0, "4": [0, 1], "5": 0, "6": 0, "7": [], "8": [], "9": [], "advanc": [], "approach": 16, "architectur": 16, "arg": [], "artefact": 6, "artefactdetect": [], "attribut": 1, "avail": [14, 16], "aw": 12, "ban": 1, "block": 6, "bug": 2, "build": [], "changelog": 0, "choos": [14, 16], "classif": [7, 13], "code": [1, 2], "codebas": 2, "commit": 2, "commun": 13, "compos": 8, "compress": [], "conda": [], "conduct": 1, "connect": 2, "content": [], "continu": 2, "contrib": [], "contribut": 2, "contributor": 1, "convent": 13, "correct": 1, "coven": 1, "custom": [5, 11], "data": 14, "dataload": 5, "dataset": [4, 5, 14], "detect": [4, 7, 13, 14, 16], "develop": 2, "do": 16, "doctr": [2, 4, 5, 6, 7, 8, 9, 10, 15], "document": [2, 4, 6], "end": 16, "enforc": 1, "evalu": 9, "export": 15, "factori": 7, "featur": [2, 4], "feedback": 2, "file": 6, "from": 13, "gener": [5, 14], "get": [], "git": 3, "guidelin": 1, "half": 15, "hub": 13, "huggingfac": 13, "i": 16, "implement": [], "infer": 15, "instal": [2, 3], "integr": 2, "io": 6, "lambda": 12, "let": 2, "line": 6, "linux": [], "load": [11, 13, 14], "loader": 5, "main": 4, "mode": 2, "model": [4, 7, 11, 13, 15, 16], "modifi": 2, "modul": [], "name": 13, "note": [], "notebook": 10, "object": 14, "ocr": 16, "onli": [], "onnx": 15, "optim": 15, "option": [], "orient": [], "our": 1, "output": 16, "own": [11, 14], "packag": 3, "page": 6, "perman": 1, "pipelin": [], "pledg": 1, "post": [], "pre": [], "precis": 15, "predictor": 16, "prepar": 15, "prerequisit": 3, "pretrain": 13, "process": [], "push": 13, "python": 3, "qualiti": 2, "question": 2, "read": 6, "readi": 14, "recognit": [4, 7, 13, 14, 16], "refer": [], "report": 2, "request": 2, "resourc": [], "respons": 1, "return": [], "right": 16, "savedmodel": [], "scope": 1, "share": 13, "should": 16, "stage": 16, "standard": 1, "start": [], "structur": [2, 6], "style": 2, "support": [4, 5, 8], "synthet": [5, 14], "task": 9, "temporari": 1, "test": 2, "text": [4, 16], "train": 11, "transform": 8, "two": 16, "unit": 2, "us": [14, 15], "util": 9, "v0": 0, "verif": 2, "via": 3, "visual": 9, "vocab": 5, "warn": 1, "what": 16, "word": 6, "your": [11, 13, 14, 15], "zoo": [4, 7]}}) \ No newline at end of file +Search.setIndex({"alltitles": {"1. Correction": [[1, "correction"]], "2. Warning": [[1, "warning"]], "3. Temporary Ban": [[1, "temporary-ban"]], "4. Permanent Ban": [[1, "permanent-ban"]], "AWS Lambda": [[12, null]], "Artefact": [[6, "artefact"]], "Attribution": [[1, "attribution"]], "Available Datasets": [[14, "available-datasets"]], "Available architectures": [[16, "available-architectures"], [16, "id1"], [16, "id2"]], "Block": [[6, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[14, null]], "Choosing the right model": [[16, null]], "Classification": [[13, "classification"]], "Code quality": [[2, "code-quality"]], "Code style verification": [[2, "code-style-verification"]], "Codebase structure": [[2, "codebase-structure"]], "Commits": [[2, "commits"]], "Composing transformations": [[8, "composing-transformations"]], "Continuous Integration": [[2, "continuous-integration"]], "Contributing to docTR": [[2, null]], "Contributor Covenant Code of Conduct": [[1, null]], "Custom dataset loader": [[5, "custom-dataset-loader"]], "Data Loading": [[14, "data-loading"]], "Dataloader": [[5, "dataloader"]], "Detection": [[13, "detection"], [14, "detection"]], "Detection predictors": [[16, "detection-predictors"]], "Developer mode installation": [[2, "developer-mode-installation"]], "Developing docTR": [[2, "developing-doctr"]], "Document": [[6, "document"]], "Document structure": [[6, "document-structure"]], "End-to-End OCR": [[16, "end-to-end-ocr"]], "Enforcement": [[1, "enforcement"]], "Enforcement Guidelines": [[1, "enforcement-guidelines"]], "Enforcement Responsibilities": [[1, "enforcement-responsibilities"]], "Export to ONNX": [[15, "export-to-onnx"]], "Feature requests & bug report": [[2, "feature-requests-bug-report"]], "Feedback": [[2, "feedback"]], "File reading": [[6, "file-reading"]], "Half-precision": [[15, "half-precision"]], "Installation": [[3, null]], "Let\u2019s connect": [[2, "let-s-connect"]], "Line": [[6, "line"]], "Loading from Huggingface Hub": [[13, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[11, "loading-your-custom-trained-model"]], "Main Features": [[4, "main-features"]], "Model optimization": [[15, "model-optimization"]], "Model zoo": [[4, "model-zoo"]], "Modifying the documentation": [[2, "modifying-the-documentation"]], "Naming conventions": [[13, "naming-conventions"]], "Object Detection": [[14, "object-detection"]], "Our Pledge": [[1, "our-pledge"]], "Our Standards": [[1, "our-standards"]], "Page": [[6, "page"]], "Preparing your model for inference": [[15, null]], "Prerequisites": [[3, "prerequisites"]], "Pretrained community models": [[13, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[13, "pushing-to-the-huggingface-hub"]], "Questions": [[2, "questions"]], "Recognition": [[13, "recognition"], [14, "recognition"]], "Recognition predictors": [[16, "recognition-predictors"]], "Scope": [[1, "scope"]], "Share your model with the community": [[13, null]], "Supported Vocabs": [[5, "supported-vocabs"]], "Supported datasets": [[4, "supported-datasets"]], "Supported transformations": [[8, "supported-transformations"]], "Synthetic dataset generator": [[5, "synthetic-dataset-generator"], [14, "synthetic-dataset-generator"]], "Task evaluation": [[9, "task-evaluation"]], "Text Detection": [[16, "text-detection"]], "Text Recognition": [[16, "text-recognition"]], "Text detection models": [[4, "text-detection-models"]], "Text recognition models": [[4, "text-recognition-models"]], "Train your own model": [[11, null]], "Two-stage approaches": [[16, "two-stage-approaches"]], "Unit tests": [[2, "unit-tests"]], "Use your own datasets": [[14, "use-your-own-datasets"]], "Using your ONNX exported model in docTR": [[15, "using-your-onnx-exported-model-in-doctr"]], "Via Git": [[3, "via-git"]], "Via Python Package": [[3, "via-python-package"]], "Visualization": [[9, "visualization"]], "What should I do with the output?": [[16, "what-should-i-do-with-the-output"]], "Word": [[6, "word"]], "docTR Notebooks": [[10, null]], "docTR Vocabs": [[5, "id5"]], "docTR: Document Text Recognition": [[4, null]], "doctr.datasets": [[5, null], [5, "datasets"]], "doctr.io": [[6, null]], "doctr.models": [[7, null]], "doctr.models.classification": [[7, "doctr-models-classification"]], "doctr.models.detection": [[7, "doctr-models-detection"]], "doctr.models.factory": [[7, "doctr-models-factory"]], "doctr.models.recognition": [[7, "doctr-models-recognition"]], "doctr.models.zoo": [[7, "doctr-models-zoo"]], "doctr.transforms": [[8, null]], "doctr.utils": [[9, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]]}, "docnames": ["changelog", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[6, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[6, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[8, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[5, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[8, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[8, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[5, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[7, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[5, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[7, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[7, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[6, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[7, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[5, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[5, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[6, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[6, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[5, "doctr.datasets.encode_sequences", false]], "from_hub() (in module doctr.models.factory)": [[7, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[6, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[6, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[6, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[5, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[8, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[8, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[5, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[5, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[5, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[5, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[5, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[7, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[8, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[6, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[7, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet18_rotation() (in module doctr.models.detection)": [[7, "doctr.models.detection.linknet_resnet18_rotation", false]], "linknet_resnet34() (in module doctr.models.detection)": [[7, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[7, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[7, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[7, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[5, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_orientation() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_small_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[8, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[7, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[5, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[8, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[6, "doctr.io.Page", false]], "parseq() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[7, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[8, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[8, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[8, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[8, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[8, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[8, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[8, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[8, "doctr.transforms.RandomJpegQuality", false]], "randomrotate (class in doctr.transforms)": [[8, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[8, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[8, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[6, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[6, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[6, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[6, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[5, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[8, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[6, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[6, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[5, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[9, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[9, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[9, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[9, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[5, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[5, "doctr.datasets.SVT", false]], "synthesize_page() (in module doctr.utils.visualization)": [[9, "doctr.utils.visualization.synthesize_page", false]], "synthtext (class in doctr.datasets)": [[5, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.TextMatch", false]], "togray (class in doctr.transforms)": [[8, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[9, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[9, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[9, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[9, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[7, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[9, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[7, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[7, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.vitstr_small", false]], "word (class in doctr.io)": [[6, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[5, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[5, 0, 1, "", "CORD"], [5, 0, 1, "", "CharacterGenerator"], [5, 0, 1, "", "DetectionDataset"], [5, 0, 1, "", "DocArtefacts"], [5, 0, 1, "", "FUNSD"], [5, 0, 1, "", "IC03"], [5, 0, 1, "", "IC13"], [5, 0, 1, "", "IIIT5K"], [5, 0, 1, "", "IIITHWS"], [5, 0, 1, "", "IMGUR5K"], [5, 0, 1, "", "MJSynth"], [5, 0, 1, "", "OCRDataset"], [5, 0, 1, "", "RecognitionDataset"], [5, 0, 1, "", "SROIE"], [5, 0, 1, "", "SVHN"], [5, 0, 1, "", "SVT"], [5, 0, 1, "", "SynthText"], [5, 0, 1, "", "WordGenerator"], [5, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[5, 0, 1, "", "DataLoader"]], "doctr.io": [[6, 0, 1, "", "Artefact"], [6, 0, 1, "", "Block"], [6, 0, 1, "", "Document"], [6, 0, 1, "", "DocumentFile"], [6, 0, 1, "", "Line"], [6, 0, 1, "", "Page"], [6, 0, 1, "", "Word"], [6, 1, 1, "", "decode_img_as_tensor"], [6, 1, 1, "", "read_html"], [6, 1, 1, "", "read_img_as_numpy"], [6, 1, 1, "", "read_img_as_tensor"], [6, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[6, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[6, 2, 1, "", "from_images"], [6, 2, 1, "", "from_pdf"], [6, 2, 1, "", "from_url"]], "doctr.io.Page": [[6, 2, 1, "", "show"]], "doctr.models": [[7, 1, 1, "", "kie_predictor"], [7, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[7, 1, 1, "", "crop_orientation_predictor"], [7, 1, 1, "", "magc_resnet31"], [7, 1, 1, "", "mobilenet_v3_large"], [7, 1, 1, "", "mobilenet_v3_large_r"], [7, 1, 1, "", "mobilenet_v3_small"], [7, 1, 1, "", "mobilenet_v3_small_orientation"], [7, 1, 1, "", "mobilenet_v3_small_r"], [7, 1, 1, "", "resnet18"], [7, 1, 1, "", "resnet31"], [7, 1, 1, "", "resnet34"], [7, 1, 1, "", "resnet50"], [7, 1, 1, "", "vgg16_bn_r"], [7, 1, 1, "", "vit_b"], [7, 1, 1, "", "vit_s"]], "doctr.models.detection": [[7, 1, 1, "", "db_mobilenet_v3_large"], [7, 1, 1, "", "db_resnet50"], [7, 1, 1, "", "detection_predictor"], [7, 1, 1, "", "linknet_resnet18"], [7, 1, 1, "", "linknet_resnet18_rotation"], [7, 1, 1, "", "linknet_resnet34"], [7, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[7, 1, 1, "", "from_hub"], [7, 1, 1, "", "login_to_hub"], [7, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[7, 1, 1, "", "crnn_mobilenet_v3_large"], [7, 1, 1, "", "crnn_mobilenet_v3_small"], [7, 1, 1, "", "crnn_vgg16_bn"], [7, 1, 1, "", "master"], [7, 1, 1, "", "parseq"], [7, 1, 1, "", "recognition_predictor"], [7, 1, 1, "", "sar_resnet31"], [7, 1, 1, "", "vitstr_base"], [7, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[8, 0, 1, "", "ChannelShuffle"], [8, 0, 1, "", "ColorInversion"], [8, 0, 1, "", "Compose"], [8, 0, 1, "", "GaussianBlur"], [8, 0, 1, "", "GaussianNoise"], [8, 0, 1, "", "LambdaTransformation"], [8, 0, 1, "", "Normalize"], [8, 0, 1, "", "OneOf"], [8, 0, 1, "", "RandomApply"], [8, 0, 1, "", "RandomBrightness"], [8, 0, 1, "", "RandomContrast"], [8, 0, 1, "", "RandomCrop"], [8, 0, 1, "", "RandomGamma"], [8, 0, 1, "", "RandomHorizontalFlip"], [8, 0, 1, "", "RandomHue"], [8, 0, 1, "", "RandomJpegQuality"], [8, 0, 1, "", "RandomRotate"], [8, 0, 1, "", "RandomSaturation"], [8, 0, 1, "", "RandomShadow"], [8, 0, 1, "", "Resize"], [8, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[9, 0, 1, "", "DetectionMetric"], [9, 0, 1, "", "LocalizationConfusion"], [9, 0, 1, "", "OCRMetric"], [9, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.visualization": [[9, 1, 1, "", "synthesize_page"], [9, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [1, 6, 7, 9, 13], "0": [1, 3, 5, 8, 9, 11, 14, 16], "00": 16, "01": 16, "0123456789": 5, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 5, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 5, "02": 16, "02562": 7, "03": 16, "035": [], "0361328125": 16, "04": [], "05": 16, "06": 16, "06640625": 16, "07": 16, "08": [8, 16], "09": 16, "0966796875": 16, "1": [3, 5, 6, 7, 8, 9, 11, 14, 16], "10": [5, 9, 16], "100": [5, 8, 9, 14, 16], "1000": 16, "101": 5, "1024": [7, 9, 11, 16], "104": [], "106": [], "108": 5, "1095": 14, "11": 16, "110": 9, "1107": 14, "114": [], "115": [], "1156": 14, "116": 5, "118": [], "11800h": 16, "11th": 16, "12": [3, 16], "120": [], "123": 5, "126": 5, "1268": [], "128": [7, 11, 15, 16], "13": [9, 16], "130": 5, "13068": 14, "131": 5, "1337891": 14, "1357421875": 16, "1396484375": 16, "14": 16, "1420": 16, "14470v1": [], "149": 14, "15": 16, "150": [9, 16], "1552": 16, "16": [7, 15], "1630859375": 16, "1684": 16, "16x16": 7, "17": 16, "1778": 16, "1782": 16, "18": [7, 16], "185546875": 16, "1900": 16, "1910": 7, "19342": 14, "19370": 14, "195": [], "19598": [], "199": 16, "1999": 16, "2": [3, 4, 5, 6, 8, 16], "20": [], "200": 9, "2000": 14, "2003": [4, 5], "2012": 5, "2013": [4, 5], "2015": 5, "2019": 4, "2023": [], "207901": 14, "21": 16, "2103": [], "2186": 14, "21888": 14, "22": 16, "224": [7, 8], "225": 8, "22672": 14, "229": [8, 14], "23": 16, "233": 14, "234": 5, "236": [], "24": 16, "246": 14, "249": 14, "25": 16, "2504": 16, "255": [6, 7, 8, 9, 16], "256": 7, "257": 14, "26": [], "26032": 14, "264": 11, "27": 16, "2700": 14, "2710": 16, "2749": 11, "28": 16, "287": 11, "29": 16, "296": 11, "299": 11, "2d": 16, "3": [3, 4, 6, 7, 8, 9, 15, 16], "30": 16, "300": 14, "3000": 14, "301": 11, "30595": 16, "30ghz": 16, "31": [7, 16], "32": [5, 7, 8, 11, 14, 15, 16], "3232421875": 16, "33": 8, "33402": 14, "33608": 14, "34": [7, 16], "340": 16, "3456": 16, "35": 16, "3515625": 16, "36": [], "360": 14, "37": [5, 16], "38": 16, "39": [], "4": [7, 8, 9, 16], "40": 16, "406": 8, "41": 16, "42": 16, "43": 16, "44": 16, "45": 16, "456": 8, "46": 16, "47": 16, "472": [], "48": [5, 16], "485": 8, "49": [], "49377": [], "5": [5, 8, 9, 16], "50": [7, 14, 16], "51": 16, "51171875": 16, "512": 7, "52": [5, 16], "529": 16, "53": 16, "54": 16, "540": 16, "5478515625": 16, "55": 16, "56": 16, "57": 16, "58": 16, "580": 16, "5810546875": 16, "583": 16, "59": 16, "597": 16, "5k": [4, 5], "5m": [], "6": [8, 16], "60": 8, "600": [7, 9, 16], "61": 16, "62": 16, "626": 14, "63": 16, "64": [7, 8, 16], "641": 16, "647": 14, "65": 16, "66": 16, "67": 16, "68": 16, "69": 16, "693": 11, "694": 11, "695": 11, "6m": [], "7": 16, "70": [9, 16], "707470": 14, "71": 16, "7100000": 14, "7141797": 14, "7149": 14, "72": 16, "72dpi": 6, "73": 16, "73257": 14, "74": 16, "75": [8, 16], "7581382": 14, "76": 16, "77": 16, "772": 11, "772875": 14, "78": 16, "785": 11, "79": 16, "793533": 14, "796": 14, "798": 11, "7m": [], "8": [3, 7, 8, 16], "80": 16, "800": [7, 9, 14, 16], "81": 16, "82": 16, "83": 16, "84": 16, "849": 14, "85": 16, "8564453125": 16, "857": 16, "85875": 14, "86": 16, "8603515625": 16, "87": 16, "8707": 14, "88": 16, "89": 16, "9": 16, "90": 16, "90k": 5, "90kdict32px": 5, "91": 16, "914085328578949": 16, "92": 16, "93": 16, "94": [5, 16], "95": [9, 16], "9578408598899841": 16, "96": 16, "97": 16, "98": 16, "99": 16, "9949972033500671": 16, "A": [1, 2, 4, 5, 6, 7, 10, 15], "As": 2, "Be": [], "Being": 1, "By": 12, "For": [1, 2, 3, 11, 16], "If": [2, 3, 6, 7, 11, 16], "In": [2, 5, 14], "It": [8, 13, 15], "Its": [4, 7], "No": [1, 16], "Of": 5, "Or": [], "The": [1, 2, 5, 6, 9, 12, 16], "Then": [], "To": [2, 3, 12, 13, 16], "_": [1, 5, 7], "__call__": [], "_build": 2, "_i": 9, "ab": [], "abc": [], "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 5, "abdef": [5, 14], "abl": [14, 16], "about": [1, 14, 16], "abov": 16, "abstract": [], "abstractdataset": 5, "abus": 1, "accept": 1, "access": [4, 6, 14, 16], "account": [1, 13], "accur": [], "accuraci": 9, "achiev": 15, "act": 1, "action": 1, "activ": 4, "ad": [2, 7, 8], "adapt": 1, "add": [8, 9, 13], "add_hook": [], "add_label": 9, "addit": [2, 3, 6], "addition": [2, 16], "address": [1, 6], "adjust": 8, "advanc": 1, "advantag": 15, "advis": 2, "aesthet": [4, 5], "affect": 1, "after": [13, 16], "ag": 1, "again": [], "aggreg": [9, 14], "aggress": 1, "align": [1, 6], "all": [1, 2, 5, 6, 8, 9, 14, 16], "allow": 1, "along": 16, "alreadi": 2, "also": [1, 7, 13, 14, 16], "alwai": 14, "an": [1, 2, 4, 5, 6, 7, 9, 15, 16], "analysi": 6, "ancient_greek": 5, "andrej": [], "angl": [6, 8], "ani": [1, 5, 6, 7, 8, 9, 16], "annot": 5, "anot": 14, "anoth": [3, 7, 11, 14], "answer": 1, "anyascii": [], "anyon": 4, "anyth": [], "api": [2, 4], "apolog": 1, "apologi": 1, "app": 2, "appear": 1, "appli": [1, 5, 8], "applic": [4, 7], "appoint": 1, "appreci": 13, "appropri": [1, 2, 16], "ar": [1, 2, 3, 5, 6, 8, 9, 10, 14, 16], "arab": 5, "arabic_diacrit": 5, "arabic_lett": 5, "arabic_punctu": 5, "arbitrarili": [], "arch": [7, 13], "architectur": [4, 7, 13], "area": 16, "arg": [5, 7], "argument": [5, 7, 16], "around": 1, "arrai": [6, 8, 9], "art": 4, "artefact": [9, 10, 16], "artefact_typ": 6, "articl": [], "artifici": [4, 5], "arxiv": 7, "asarrai": 9, "ascii_lett": 5, "aspect": [4, 7, 8, 16], "assess": 9, "assign": 9, "associ": 6, "assum": 7, "assume_straight_pag": [7, 16], "astyp": [7, 9, 16], "attack": 1, "attend": [4, 7], "attent": [1, 7], "autom": 4, "automat": [], "autoregress": [4, 7], "avail": [1, 4, 8], "averag": [8, 16], "avoid": [1, 3], "aw": [4, 16], "awar": [], "azur": 16, "b": [7, 9, 16], "b_j": 9, "back": 2, "backbon": 7, "backend": 16, "background": 14, "bangla": [], "bar": [], "bar_cod": 14, "baranovskij": [], "base": [4, 7], "baselin": [4, 7, 16], "batch": [5, 7, 8, 14, 16], "batch_siz": [5, 11, 14, 15], "bblanchon": [], "bbox": 16, "becaus": 12, "been": [2, 9, 14, 16], "befor": [5, 7, 8, 16], "begin": 9, "behavior": 1, "being": [9, 16], "belong": 16, "benchmark": 16, "best": 1, "better": [10, 16], "between": [8, 9], "bgr": 6, "bilinear": 8, "bin_thresh": [], "binar": [4, 7], "binari": [6, 15, 16], "bit": 15, "blank": 9, "block": [9, 16], "block_1_1": 16, "blue": 9, "blur": 8, "bmvc": 5, "bn": 13, "bodi": [1, 16], "bool": [5, 6, 7, 8, 9], "boolean": [7, 16], "both": [4, 5, 8, 14, 16], "bottom": [7, 16], "bound": [5, 6, 7, 8, 9, 16], "box": [5, 6, 7, 8, 9, 14, 16], "box_thresh": [], "brew": 3, "bright": 8, "broadcast": 9, "browser": [2, 4], "build": [2, 3], "built": 2, "byte": [6, 16], "c": [6, 9], "c_j": 9, "cach": [2, 5, 12], "cache_sampl": 5, "cairo": 3, "call": [], "callabl": [5, 8], "can": [2, 3, 11, 12, 13, 14, 16], "capabl": [2, 10, 16], "case": [5, 9], "cf": 16, "cfg": 16, "challeng": 5, "challenge2_test_task12_imag": 5, "challenge2_test_task1_gt": 5, "challenge2_training_task12_imag": 5, "challenge2_training_task1_gt": 5, "chang": 12, "channel": [1, 2, 6, 8], "channel_prior": [], "channelshuffl": 8, "charact": [4, 5, 6, 9, 14, 16], "charactergener": [5, 14], "characterist": 1, "charg": 16, "charset": 16, "chart": 6, "check": [2, 13, 16], "checkpoint": 7, "chip": 3, "christian": [], "ci": 2, "clarifi": 1, "clariti": 1, "class": [1, 5, 6, 8, 9, 16], "class_nam": 11, "classif": 14, "classif_mobilenet_v3_smal": 7, "classmethod": 6, "clear": 2, "clone": 3, "close": 2, "co": 13, "code": [4, 6], "codecov": 2, "colab": 10, "collate_fn": 5, "collect": 6, "color": [8, 9], "colorinvers": 8, "column": 6, "com": [1, 3, 6, 13], "combin": 16, "come": 15, "command": 2, "comment": 1, "commit": 1, "common": [1, 8, 9, 15], "commun": 1, "compar": 4, "comparison": [9, 16], "competit": 5, "compil": [10, 16], "complaint": 1, "complementari": 9, "complet": 2, "compon": 16, "compos": [5, 16], "comprehens": 16, "comput": [5, 9, 15, 16], "conf_threshold": [], "confid": [6, 9, 16], "config": 7, "configur": 7, "confus": 9, "consecut": [8, 16], "consequ": 1, "consid": [1, 2, 5, 6, 9, 16], "consist": 16, "consolid": [4, 5], "constant": 8, "construct": 1, "consum": 9, "contact": 1, "contain": [5, 14], "content": [5, 6, 9, 16], "context": 7, "contib": [], "continu": 1, "contrast": 8, "contrast_factor": 8, "contrib": [], "contribut": 1, "contributor": 2, "convers": 6, "convert": [6, 8], "convolut": 7, "cool": [], "coordin": [6, 16], "cord": [4, 5, 14, 16], "core": [9, 16], "corner": 16, "correct": 8, "correspond": [3, 6, 16], "could": 1, "counterpart": 9, "cover": 2, "coverag": 2, "cpu": [4, 11], "creat": 13, "crnn": [4, 7, 13], "crnn_mobilenet_v3_larg": [7, 13, 16], "crnn_mobilenet_v3_smal": [7, 15, 16], "crnn_vgg16_bn": [7, 11, 13, 16], "crop": [7, 8, 14, 16], "crop_orient": [], "crop_orientation_predictor": 7, "crop_param": [], "croporientationpredictor": 7, "cuda": 15, "currenc": 5, "current": [2, 16], "custom": 13, "custom_crop_orientation_model": [], "custom_page_orientation_model": [], "customhook": [], "cvit": 4, "czczup": [], "czech": 5, "d": [5, 14], "danish": [], "data": [4, 5, 6, 8, 9, 11, 13], "dataload": 14, "dataset": [7, 11, 16], "dataset_info": 5, "date": [11, 16], "db": 13, "db_mobilenet_v3_larg": [7, 13, 16], "db_resnet34": 16, "db_resnet50": [7, 11, 13, 16], "db_resnet50_rot": 16, "dbnet": [4, 7], "deal": [], "decis": 1, "decod": 6, "decode_img_as_tensor": 6, "dedic": [], "deem": 1, "deep": [7, 16], "def": [], "default": [6, 9, 11, 12], "defer": 14, "defin": [9, 15], "degre": 8, "degress": 6, "delet": 2, "delimit": 16, "delta": 8, "demo": [2, 4], "demonstr": 1, "depend": [2, 3, 4], "deploi": 2, "deploy": 4, "derogatori": 1, "describ": [7, 9], "descript": 10, "design": 8, "desir": 6, "det_arch": [7, 11, 13, 15], "det_b": [], "det_model": [11, 13], "det_param": 11, "det_predictor": 11, "detail": [11, 16], "detect": [5, 9, 10, 11], "detect_languag": 7, "detect_orient": 7, "detection_predictor": [7, 16], "detection_task": [], "detectiondataset": [5, 14], "detectionmetr": 9, "detectionpredictor": [7, 11], "detector": [], "deterior": 7, "determin": 1, "dev": [2, 12], "develop": 3, "deviat": 8, "devic": 15, "dict": [6, 9, 16], "dictionari": [6, 9], "differ": 1, "differenti": [4, 7], "digit": [4, 5, 14], "dimens": [6, 9, 16], "dimension": 8, "direct": 5, "directli": [13, 16], "directori": [2, 12], "disabl": [1, 12], "disable_crop_orient": [], "disable_page_orient": [], "disclaim": 16, "discuss": 2, "disparag": 1, "displai": [6, 9], "display_artefact": 9, "distribut": 8, "div": 16, "divers": 1, "divid": 6, "do": [2, 3, 7], "doc": [2, 6, 15, 16], "docartefact": [5, 14], "docstr": 2, "doctr": [3, 11, 12, 13, 14, 16], "doctr_cache_dir": 12, "doctr_multiprocessing_dis": 12, "document": [5, 7, 9, 10, 14, 16], "documentbuild": [], "documentfil": [6, 13], "doesn": [], "don": [11, 16], "done": 8, "download": [5, 14], "downsiz": 7, "draw": [8, 9], "draw_proba": 9, "drop": 5, "drop_last": 5, "dtype": [6, 7, 8, 9, 15], "dual": [], "dummi": 13, "dummy_img": 16, "dummy_input": 15, "dure": 1, "dutch": [], "dynam": 5, "dynamic_seq_length": 5, "e": [1, 2, 3, 6, 7], "each": [4, 5, 6, 7, 8, 9, 14, 16], "eas": 2, "easi": [4, 9, 13], "easili": [6, 9, 11, 13, 14, 16], "econom": 1, "edit": 1, "educ": 1, "effect": [], "effici": [2, 4, 5, 7], "either": [9, 16], "element": [5, 6, 7, 9, 16], "els": 2, "email": 1, "empathi": 1, "en": 16, "enabl": [5, 6], "enclos": 6, "encod": [4, 5, 6, 7, 16], "encode_sequ": 5, "encount": 2, "encrypt": 6, "end": [4, 5, 7, 9], "english": [5, 14], "enough": [2, 16], "ensur": 2, "entri": 5, "environ": [1, 12], "eo": 5, "equiv": 16, "estim": 7, "etc": 6, "ethnic": 1, "evalu": [14, 16], "event": 1, "everyon": 1, "everyth": [2, 16], "exact": [9, 16], "exampl": [1, 2, 4, 5, 7, 13], "exchang": 15, "execut": [], "exist": 13, "expand": 8, "expect": [6, 8, 9], "experi": 1, "explan": [1, 16], "explicit": 1, "exploit": [4, 7], "export": [6, 7, 9, 10, 16], "export_as_straight_box": [7, 16], "export_as_xml": 16, "export_model_to_onnx": 15, "express": [1, 8], "extens": 6, "extern": [1, 14], "extra": 3, "extract": [4, 5], "extractor": 7, "f_": 9, "f_a": 9, "factor": 8, "fair": 1, "fairli": 1, "fals": [5, 6, 7, 8, 9, 11, 16], "famili": 9, "faq": 1, "fascan": [], "fast": [4, 5, 7], "fast_bas": [], "fast_smal": [], "fast_tini": [], "faster": 15, "fasterrcnn_mobilenet_v3_large_fpn": 7, "favorit": 16, "featur": [3, 7, 9, 10], "feedback": 1, "feel": [2, 13], "felix92": 13, "few": [3, 15], "figsiz": 9, "figur": 9, "file": [2, 5], "final": 7, "find": [2, 3, 14], "fine": [], "finnish": [], "first": 2, "firsthand": 5, "fit": [7, 16], "flag": 16, "flip": 8, "float": [6, 8, 9, 15], "float32": [6, 7, 8, 15], "fn": 8, "focu": 13, "focus": [1, 5], "folder": 5, "follow": [1, 2, 3, 5, 8, 9, 11, 12, 13, 16], "font": [5, 9], "font_famili": [5, 9], "font_siz": 9, "foral": 9, "forc": 2, "forg": [], "form": [4, 5, 16], "format": [6, 9, 11, 14, 15, 16], "forpost": [4, 5], "forum": 2, "found": [], "fp16": 15, "frac": 9, "framework": [3, 13, 14, 16], "free": [1, 2, 13], "french": [5, 11, 13, 16], "friendli": 4, "from": [1, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16], "from_hub": [7, 13], "from_imag": [6, 13], "from_pdf": 6, "from_url": 6, "full": [5, 9, 16], "function": [5, 8, 9], "funsd": [4, 5, 14, 16], "further": 14, "futur": 5, "g": [6, 7], "g_": 9, "g_x": 9, "gallagh": [], "gamma": 8, "gaussian": 8, "gaussianblur": 8, "gaussiannois": 8, "gdk": 3, "gen": 16, "gender": 1, "gener": [2, 4, 7], "generic_cyrillic_lett": [], "geometri": [4, 6, 16], "geq": 9, "german": [5, 11], "get": 16, "git": 13, "github": [2, 3, 13], "give": 1, "given": [5, 6, 8, 9, 16], "global": 7, "go": 16, "good": 15, "googl": 2, "googlevis": 4, "gpu": [4, 15], "gracefulli": 1, "graph": 6, "grayscal": 8, "ground": 9, "groung": 9, "group": 4, "gt": 9, "gt_box": 9, "gt_label": 9, "gtk": 3, "guid": 2, "guidanc": 14, "gvision": 16, "h": [6, 7, 8], "h_": 9, "ha": [2, 5, 9, 14], "handl": 14, "handwrit": 5, "handwritten": 14, "harass": 1, "hardwar": [], "harm": 1, "hat": 9, "have": [1, 2, 9, 11, 13, 14, 16], "head": [7, 16], "healthi": 1, "hebrew": [], "height": 6, "hello": [9, 16], "help": 15, "here": [3, 8, 10, 14, 16], "hf": 7, "hf_hub_download": 7, "high": 6, "higher": [3, 5], "hindi": [], "hindi_digit": 5, "hocr": 16, "homebrew": 3, "hook": [], "horizont": [6, 8], "hous": 5, "how": [2, 11, 13, 14], "howev": 14, "hsv": 8, "html": [1, 2, 16], "http": [1, 3, 6, 7, 13, 16], "hub": 7, "hue": 8, "huggingfac": 7, "hw": 5, "i": [1, 2, 5, 6, 7, 8, 9, 12, 13, 14, 15], "i7": 16, "ibrahimov": [], "ic03": [4, 5, 14], "ic13": [4, 5, 14], "icdar": [4, 5], "icdar2019": 5, "id": 16, "ident": 1, "identifi": 4, "iiit": [4, 5], "iiit5k": [5, 14], "iiithw": [4, 5, 14], "imag": [4, 5, 6, 7, 8, 9, 13, 14, 16], "imagenet": 7, "imageri": 1, "images_90k_norm": 5, "img": [5, 8, 14], "img_cont": 6, "img_fold": [5, 14], "img_path": 6, "img_transform": 5, "imgur5k": [4, 5, 14], "imgur5k_annot": 5, "imlist": 5, "impact": 1, "implement": [5, 6, 8, 9, 16], "import": [5, 6, 7, 8, 9, 11, 13, 14, 15, 16], "improv": [], "inappropri": 1, "incid": 1, "includ": [1, 3, 5, 14, 15], "inclus": 1, "increas": 8, "independ": [], "index": [2, 6], "indic": 9, "individu": 1, "infer": [4, 7, 8], "inform": [1, 2, 4, 5, 14], "input": [2, 6, 7, 8, 15, 16], "input_crop": 7, "input_pag": [7, 9, 16], "input_shap": 15, "input_tensor": 7, "inspir": [1, 8], "instal": 13, "instanc": [1, 16], "instanti": [7, 16], "instead": [5, 6, 7], "insult": 1, "int": [5, 6, 8, 9], "int64": [8, 9], "integ": 9, "integr": [4, 13, 14], "intel": 16, "interact": [1, 6, 9], "interfac": 13, "interoper": 15, "interpol": 8, "interpret": [5, 6], "intersect": 9, "invert": 8, "investig": 1, "invis": 1, "involv": [1, 16], "io": 13, "iou": 9, "iou_thresh": 9, "iou_threshold": [], "irregular": [4, 7, 14], "isn": 5, "issu": [1, 2, 13], "italian": [], "iter": [5, 8, 14, 16], "its": [6, 7, 8, 9, 14, 16], "itself": [7, 13], "j": 9, "jame": [], "job": 2, "join": 2, "jpeg": 8, "jpegqual": 8, "jpg": [5, 6, 13], "json": [5, 14, 16], "json_output": 16, "jump": 2, "just": 1, "kei": [], "kera": [7, 15], "kernel": 8, "kernel_shap": 8, "keywoard": [], "keyword": [5, 7], "kie": [7, 11], "kie_predictor": [7, 11], "kiepredictor": 7, "kind": [1, 16], "know": 2, "kwarg": [5, 6, 7, 9], "l": 9, "l_j": 9, "label": [5, 8, 9, 14], "label_fil": [5, 14], "label_fold": 5, "label_path": [5, 14], "labels_path": [5, 14], "ladder": 1, "lambda": 8, "lambdatransform": 8, "lang": 16, "languag": [1, 4, 5, 6, 7, 13, 16], "larg": [7, 13], "largest": 9, "last": [3, 5], "latenc": 7, "later": 2, "latest": [3, 16], "latin": 5, "layer": 15, "layout": 16, "lead": 1, "leader": 1, "learn": [1, 4, 7, 15, 16], "least": 3, "left": [9, 16], "legacy_french": 5, "length": 5, "less": 15, "level": [1, 5, 9, 16], "leverag": 10, "lf": 13, "libffi": 3, "librari": [2, 3, 10, 11], "light": 4, "lightweight": [], "like": 1, "limits_": 9, "line": [4, 9, 16], "line_1_1": 16, "link": 11, "linknet": [4, 7], "linknet_resnet18": [7, 11, 16], "linknet_resnet18_rot": [7, 16], "linknet_resnet34": [7, 15, 16], "linknet_resnet50": [7, 16], "linux": 3, "list": [5, 6, 8, 9, 13], "ll": 9, "load": [4, 5, 7], "load_state_dict": 11, "load_weight": 11, "loc_pr": [], "local": [2, 4, 5, 7, 9, 14, 16], "localis": 5, "localizationconfus": 9, "locat": [2, 6], "login": 7, "login_to_hub": [7, 13], "logo": [6, 14], "love": 13, "lower": [8, 9], "m": [2, 9, 16], "m1": 3, "macbook": 3, "machin": 15, "maco": 3, "made": 4, "magc_resnet31": 7, "mai": [1, 2], "mail": 1, "main": 10, "maintain": 4, "mainten": 2, "make": [1, 2, 9, 12, 13, 15, 16], "mani": [14, 16], "manipul": [], "map": 5, "map_loc": 11, "mask_shap": 9, "master": [4, 7, 16], "match": [9, 16], "mathcal": 9, "matplotlib": 9, "max": [5, 8, 9], "max_angl": 8, "max_area": 8, "max_char": [5, 14], "max_delta": 8, "max_gain": 8, "max_gamma": 8, "max_qual": 8, "max_ratio": 8, "maximum": [5, 8], "maxval": [7, 8], "mbox": 9, "mean": [8, 9, 11], "meaniou": 9, "meant": [6, 15], "measur": 16, "media": 1, "median": [], "meet": 11, "member": 1, "memori": [9, 12, 15], "mention": 16, "merg": 5, "messag": 2, "meta": 16, "metadata": 15, "metal": 3, "method": [8, 16], "metric": [9, 16], "middl": [], "might": [15, 16], "min": 8, "min_area": 8, "min_char": [5, 14], "min_gain": 8, "min_gamma": 8, "min_qual": 8, "min_ratio": 8, "min_val": 8, "minde": [1, 3, 4, 7], "minim": [2, 4], "minimalist": [], "minimum": [3, 5, 8, 9], "minval": 8, "miss": 3, "mistak": 1, "mixed_float16": 15, "mixed_precis": 15, "mjsynth": [4, 5, 14], "mnt": 5, "mobilenet": [7, 13], "mobilenet_v3_larg": 7, "mobilenet_v3_large_r": 7, "mobilenet_v3_smal": 7, "mobilenet_v3_small_crop_orient": [], "mobilenet_v3_small_orient": 7, "mobilenet_v3_small_page_orient": [], "mobilenet_v3_small_r": 7, "mobilenetv3": 7, "modal": [], "mode": 3, "model": [5, 9, 12, 14], "model_nam": [7, 13, 15], "model_path": 15, "moder": 1, "modif": 2, "modifi": [7, 12], "modul": [6, 8, 9, 16], "moment": 16, "more": [2, 9, 14, 16], "moscardi": [], "most": 16, "mozilla": 1, "multi": [4, 7], "multilingu": [], "multipl": [5, 6, 8], "multipli": 8, "multiprocess": 12, "my": 7, "my_awesome_model": 13, "my_hook": [], "n": [5, 9], "name": [5, 7, 15, 16], "nation": 1, "natur": [1, 4, 5], "nb": 16, "ndarrai": [5, 6, 8, 9], "necessari": [3, 11, 12], "need": [2, 3, 5, 9, 11, 12, 13], "neg": 8, "nest": 16, "netraj": [], "network": [4, 5, 7, 15], "neural": [4, 5, 7, 15], "new": [2, 9], "next": [5, 14], "nois": 8, "noisi": [4, 5], "non": [4, 5, 6, 7, 8, 9], "none": [5, 6, 7, 8, 9, 16], "normal": [7, 8], "norwegian": [], "note": [0, 2, 5, 7, 13, 15], "now": 2, "np": [7, 8, 9, 16], "num_output_channel": 8, "num_sampl": [5, 14], "num_work": 5, "number": [5, 8, 9, 16], "numpi": [6, 7, 9, 16], "o": 3, "obb": [], "obj_detect": 13, "object": [5, 9, 10, 16], "objectness_scor": [], "oblig": 1, "obtain": 16, "occupi": 15, "ocr": [4, 5, 7, 9, 13, 14], "ocr_carea": 16, "ocr_db_crnn": 9, "ocr_lin": 16, "ocr_pag": 16, "ocr_par": 16, "ocr_predictor": [7, 11, 13, 15, 16], "ocrdataset": [5, 14], "ocrmetr": 9, "ocrpredictor": [7, 11], "ocrx_word": 16, "offens": 1, "offici": 1, "offlin": 1, "offset": 8, "onc": 16, "one": [2, 5, 7, 8, 11, 13, 16], "oneof": 8, "ones": [5, 8, 9], "onli": [2, 7, 8, 9, 13, 14, 15, 16], "onlin": 1, "onnx": [], "onnxruntim": [], "onnxtr": [], "opac": 8, "opacity_rang": 8, "open": [1, 2, 13, 15], "opinion": 1, "optic": [4, 16], "optim": 4, "option": [5, 11], "order": [2, 5, 6, 8], "org": [1, 7, 16], "organ": 6, "orient": [1, 6, 7, 16], "orientationpredictor": [], "other": [1, 2], "otherwis": [1, 6, 9], "our": [2, 7, 16], "out": [2, 7, 8, 9, 16], "outpout": 16, "output": [6, 8, 15], "output_s": [6, 8], "outsid": 12, "over": [3, 5, 9, 16], "overal": [1, 7], "overlai": 6, "overview": [], "overwrit": [], "overwritten": 13, "own": 4, "p": [8, 9, 16], "packag": [2, 4, 9, 12, 14], "pad": [5, 7, 8, 16], "page": [3, 5, 7, 9, 16], "page1": 6, "page2": 6, "page_1": 16, "page_idx": [6, 16], "page_orientation_predictor": [], "page_param": [], "pair": 9, "pango": 3, "paper": 7, "par_1_1": 16, "paragraph": [], "paragraph_break": [], "parallel": [], "param": [8, 16], "paramet": [4, 5, 6, 7, 8, 9, 15], "pars": [4, 5], "parseq": [4, 7, 16], "part": [5, 8, 16], "parti": 3, "partial": 16, "particip": 1, "pass": [5, 6, 7, 16], "password": 6, "patch": 7, "path": [5, 6, 14], "path_to_checkpoint": 11, "path_to_custom_model": [], "path_to_pt": 11, "patil": [], "pattern": 1, "pdf": [6, 7, 10], "pdfpage": 6, "peopl": 1, "per": [8, 16], "perform": [4, 6, 8, 9, 12, 15, 16], "period": 1, "permiss": 1, "permut": [4, 7], "persian_lett": 5, "person": [1, 14], "phase": 16, "photo": 14, "physic": [1, 6], "pick": 8, "pictur": 6, "pip": [2, 3], "pipelin": [], "pixbuf": 3, "pixel": [6, 8, 16], "pleas": 2, "plot": 9, "plt": 9, "plug": 13, "plugin": 3, "png": 6, "point": 15, "polici": 12, "polish": [], "polit": 1, "polygon": [5, 16], "pool": 7, "portugues": 5, "posit": [1, 9], "possibl": [2, 9, 13], "post": [1, 16], "postprocessor": [], "potenti": 7, "power": 4, "ppageno": 16, "pre": [2, 7], "precis": [9, 16], "pred": 9, "pred_box": 9, "pred_label": 9, "predefin": 14, "predict": [6, 7, 9], "predictor": [4, 6, 7, 11, 13, 15], "prefer": 14, "preinstal": [], "preprocessor": [11, 16], "prerequisit": 13, "present": 10, "preserv": [7, 8, 16], "preserve_aspect_ratio": [6, 7, 8, 11, 16], "pretrain": [4, 7, 9, 11, 15, 16], "pretrained_backbon": [7, 11], "print": 16, "prior": 5, "privaci": 1, "privat": 1, "probabl": 8, "problem": 2, "procedur": 8, "process": [2, 4, 6, 11, 16], "processor": 16, "produc": [10, 16], "product": 15, "profession": 1, "project": [2, 14], "promptli": 1, "proper": 2, "properli": 5, "provid": [1, 2, 4, 13, 14, 16], "public": [1, 4], "publicli": 16, "publish": 1, "pull": 13, "punctuat": 5, "pure": 5, "purpos": 2, "push_to_hf_hub": [7, 13], "py": 13, "pypdfium2": 6, "pyplot": 9, "python": 2, "python3": 13, "pytorch": [3, 4, 7, 8, 11, 13, 15, 16], "q": 2, "qr": 6, "qr_code": 14, "qualiti": 8, "question": 1, "quickli": 4, "quicktour": 10, "r": 16, "race": 1, "ramdisk": 5, "rand": [7, 8, 9, 15, 16], "random": [7, 8, 9, 16], "randomappli": 8, "randombright": 8, "randomcontrast": 8, "randomcrop": 8, "randomgamma": 8, "randomhorizontalflip": 8, "randomhu": 8, "randomjpegqu": 8, "randomli": 8, "randomres": [], "randomrot": 8, "randomsatur": 8, "randomshadow": 8, "rang": 8, "rassi": [], "ratio": [7, 8, 16], "raw": [6, 9], "re": 15, "read": [4, 5, 7], "read_html": 6, "read_img": 6, "read_img_as_numpi": 6, "read_img_as_tensor": 6, "read_pdf": 6, "readi": 15, "real": [4, 7, 8], "realli": [], "reason": 1, "rebuild": 2, "rebuilt": 2, "recal": [9, 16], "receipt": [4, 5, 16], "reco_arch": [7, 11, 13, 15], "reco_b": [], "reco_model": [11, 13], "reco_param": 11, "reco_predictor": 11, "recogn": 16, "recognit": [5, 9, 11], "recognition_predictor": [7, 16], "recognition_task": [5, 14], "recognitiondataset": [5, 14], "recognitionpredictor": [7, 11], "rectangular": 7, "red": 9, "reduc": [3, 8], "refer": [2, 3, 11, 13, 14, 16], "regardless": 1, "region": [], "regroup": 9, "regular": 14, "reject": 1, "rel": [6, 8, 9], "relat": 6, "releas": [0, 3], "relev": [], "religion": 1, "remov": 1, "render": 6, "repo": 7, "repo_id": [7, 13], "report": 1, "repositori": [5, 7, 13], "repres": [1, 9, 15, 16], "represent": [4, 7], "request": [1, 13], "requir": [3, 8], "research": 4, "residu": 7, "resiz": [8, 16], "resnet": 7, "resnet18": [7, 13], "resnet31": 7, "resnet34": 7, "resnet50": [7, 13], "resolv": 6, "resolve_block": [], "resolve_lin": [], "resourc": 14, "respect": 1, "respons": 9, "rest": [2, 8, 9], "restrict": 12, "result": [2, 5, 6, 10, 13, 16], "return": [5, 6, 7, 9, 16], "reusabl": 16, "review": 1, "rgb": [6, 8], "rgb_mode": 6, "rgb_output": 6, "right": [1, 7, 9], "roboflow": [], "robust": [4, 5], "root": 5, "rotat": [5, 6, 7, 8, 9, 14, 16], "run": [2, 3, 7], "same": [2, 6, 9, 14, 16], "sampl": [5, 14, 16], "sample_transform": 5, "sanjin": [], "sar": [4, 7], "sar_resnet31": [7, 16], "satur": 8, "save": [7, 14], "scale": [6, 7, 8, 9], "scale_rang": [], "scan": [4, 5], "scene": [4, 5, 7], "score": 9, "script": [2, 14], "seamless": 4, "seamlessli": [4, 16], "search": 7, "searchabl": 10, "sec": 16, "second": 16, "section": [11, 13, 15, 16], "secur": [1, 12], "see": [1, 2], "seen": 16, "segment": [4, 7, 16], "self": [], "semant": [4, 7], "send": 16, "sens": 9, "sensit": 14, "separ": 16, "sequenc": [4, 5, 6, 7, 9, 16], "sequenti": 8, "seri": 1, "seriou": 1, "set": [1, 5, 7, 9, 12, 16], "set_global_polici": 15, "sever": [6, 8, 16], "sex": 1, "sexual": 1, "shade": 8, "shape": [6, 7, 8, 9, 16], "share": [12, 14], "shift": 8, "shm": 12, "should": [2, 5, 6, 8, 9], "show": [4, 6, 7, 9, 11, 13], "showcas": 2, "shuffl": [5, 8], "side": 9, "signatur": 6, "signific": 14, "simpl": [4, 7], "simpler": 7, "sinc": [5, 14], "singl": [1, 2, 4, 5], "single_img_doc": [], "size": [1, 5, 6, 8, 9, 16], "skew": 16, "slack": 2, "slightli": 7, "small": [2, 7], "smallest": 6, "snapshot_download": 7, "snippet": 16, "so": [2, 3, 5, 7, 13, 14], "social": 1, "socio": 1, "some": [3, 10, 13, 14], "someth": 2, "somewher": 2, "soon": 15, "sort": 1, "sourc": [5, 6, 7, 8, 9, 13], "space": 1, "span": 16, "spanish": 5, "spatial": [6, 9], "specif": [2, 3, 9, 11, 14, 16], "specifi": [1, 5, 6], "speed": [4, 7], "sphinx": 2, "sroie": [4, 5, 14], "stabl": 3, "stackoverflow": 2, "stage": 4, "standalon": [], "standard": 8, "start": 5, "state": [4, 9], "static": 9, "statist": [], "statu": 1, "std": [8, 11], "step": 12, "still": 16, "str": [5, 6, 7, 8, 9], "straight": [5, 7, 14, 16], "straighten": [], "straighten_pag": [], "straigten_pag": [], "stream": 6, "street": [4, 5], "strict": [], "strictli": 9, "string": [5, 6, 9, 16], "strive": 3, "strong": [4, 7], "structur": [15, 16], "subset": [5, 16], "suggest": [2, 13], "sum": 9, "summari": 9, "support": [15, 16], "sustain": 1, "svhn": [4, 5, 14], "svt": [5, 14], "swedish": [], "symmetr": [7, 8, 16], "symmetric_pad": [7, 8, 16], "synthes": 9, "synthesize_pag": 9, "synthet": 4, "synthtext": [4, 5, 14], "system": 16, "t": [2, 5, 11, 16], "tabl": 13, "take": [1, 5, 16], "target": [5, 6, 8, 9, 14], "target_s": 5, "task": [4, 5, 7, 13, 14, 16], "task2": 5, "team": [], "techminde": [], "templat": [2, 4], "tensor": [5, 6, 8, 16], "tensorflow": [3, 4, 6, 7, 8, 11, 13, 15, 16], "tensorspec": 15, "term": 1, "test": 14, "test_set": 5, "text": [5, 6, 7, 9, 14], "text_output": [], "textmatch": 9, "textnet": [], "textnet_bas": [], "textnet_smal": [], "textnet_tini": [], "textract": [4, 16], "textstylebrush": [4, 5], "textual": [4, 5, 6, 7, 16], "tf": [3, 6, 7, 8, 13, 15], "than": [2, 3, 9, 13], "thank": 2, "thei": [1, 9], "them": [3, 5, 16], "thi": [1, 2, 3, 5, 9, 11, 12, 13, 14, 15, 16], "thing": [15, 16], "third": 3, "those": [1, 3, 6, 16], "threaten": 1, "threshold": [], "through": [1, 8, 14], "tilman": [], "time": [1, 4, 7, 9, 14], "tini": [], "titl": [6, 16], "tm": 16, "tmp": 12, "togeth": [2, 6], "tograi": 8, "tool": 14, "top": [9, 16], "topic": 2, "torch": [3, 8, 11, 13, 15], "torchvis": 8, "total": 11, "toward": [1, 3], "train": [2, 5, 7, 8, 13, 14, 15, 16], "train_it": [5, 14], "train_load": [5, 14], "train_pytorch": 13, "train_set": [5, 14], "train_tensorflow": 13, "trainabl": [4, 7], "tranform": 8, "transcrib": 16, "transfer": [4, 5], "transfo": 8, "transform": [4, 5, 7], "translat": 1, "troll": 1, "true": [5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16], "truth": 9, "tune": 15, "tupl": [5, 6, 8, 9], "two": [6, 12], "txt": 5, "type": [6, 13, 15, 16], "typic": 16, "u": [1, 2], "ucsd": 5, "udac": 2, "uint8": [6, 7, 9, 16], "ukrainian": [], "unaccept": 1, "underli": 14, "underneath": 6, "understand": [4, 5, 16], "unidecod": 9, "uniform": [7, 8], "uniformli": 8, "uninterrupt": [6, 16], "union": 9, "unit": [], "unittest": 2, "unlock": 6, "unoffici": 7, "unprofession": 1, "unsolicit": 1, "unsupervis": 4, "unwelcom": 1, "up": [7, 16], "updat": 9, "upgrad": 2, "upper": [5, 8], "uppercas": 14, "url": 6, "us": [1, 2, 3, 5, 7, 9, 11, 12, 13, 16], "usabl": 16, "usag": [12, 15], "use_broadcast": 9, "use_polygon": [5, 9, 14], "useabl": 16, "user": [3, 4, 6, 10], "utf": 16, "util": 15, "v1": 13, "v3": [7, 13, 16], "valid": 14, "valu": [2, 6, 8, 16], "valuabl": 4, "variabl": 12, "varieti": 5, "veri": 7, "verma": [], "version": [1, 2, 3, 15, 16], "vgg": 7, "vgg16": 13, "vgg16_bn_r": 7, "via": 1, "video": [], "vietnames": 5, "view": [4, 5], "viewpoint": 1, "violat": 1, "visibl": 1, "vision": [4, 5, 7], "visiondataset": 5, "visiontransform": 7, "visual": 4, "visualize_pag": 9, "vit_": 7, "vit_b": 7, "vitstr": [4, 7, 15], "vitstr_bas": [7, 16], "vitstr_smal": [7, 11, 15, 16], "viz": [], "vocab": [11, 13, 14, 16], "vocabulari": [5, 11, 13], "w": [6, 7, 8, 9], "w3": 16, "wa": 1, "wai": [1, 4, 14], "want": [2, 15, 16], "warmup": 16, "wasn": 2, "we": [1, 2, 3, 4, 6, 8, 13, 14, 15, 16], "weasyprint": [], "web": [2, 6], "websit": 5, "weight": 11, "welcom": 1, "well": [1, 15], "were": [1, 6, 16], "what": 1, "when": [1, 2, 7], "whenev": 2, "where": [2, 6, 8, 9], "whether": [2, 5, 6, 8, 9, 14], "which": [1, 7, 12, 14, 16], "whichev": 3, "while": [8, 16], "why": 1, "width": 6, "wiki": 1, "wildreceipt": [], "window": [3, 7, 9], "wish": 2, "within": 1, "without": [1, 5, 7], "wonder": 2, "word": [4, 5, 7, 9, 16], "word_1_1": 16, "word_1_2": 16, "word_1_3": 16, "wordgener": [5, 14], "words_onli": 9, "work": [12, 16], "worker": 5, "workflow": 2, "worklow": 2, "world": [9, 16], "worth": 7, "wrap": 16, "wrapper": [5, 8], "write": 12, "written": [1, 6], "www": [1, 6, 16], "x": [6, 8, 9], "x_ascend": 16, "x_descend": 16, "x_i": 9, "x_size": 16, "x_wconf": 16, "xhtml": 16, "xmax": 6, "xmin": 6, "xml": 16, "xml_bytes_str": 16, "xml_element": 16, "xml_output": 16, "xmln": 16, "y": 9, "y_i": 9, "y_j": 9, "yet": [], "ymax": 6, "ymin": 6, "yolov8": [], "you": [2, 3, 5, 6, 7, 11, 12, 13, 14, 15, 16], "your": [2, 4, 6, 9, 16], "yoursit": 6, "yugesh": [], "zero": [8, 9], "zoo": [], "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 5, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 5, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": [], "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 5, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 5, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": [], "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 5, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 5, "\u00e4\u00f6\u00e4\u00f6": [], "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 5, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": [], "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": [], "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": [], "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 5, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": [], "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": [], "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": [], "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 5, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 5, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 5, "\u067e\u0686\u06a2\u06a4\u06af": 5, "\u0905": [], "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": [], "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": [], "\u0950": [], "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": [], "\u09bd": [], "\u09ce": [], "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": []}, "titles": ["Changelog", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 2, "0": 0, "01": 0, "02": 0, "03": 0, "04": [], "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 1], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 1], "2021": 0, "2022": 0, "2023": [], "2024": [], "21": [], "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 1], "31": 0, "4": [0, 1], "5": 0, "6": 0, "7": [], "8": [], "9": [], "advanc": [], "approach": 16, "architectur": 16, "arg": [], "artefact": 6, "artefactdetect": [], "attribut": 1, "avail": [14, 16], "aw": 12, "ban": 1, "block": 6, "bug": 2, "changelog": 0, "choos": [14, 16], "classif": [7, 13], "code": [1, 2], "codebas": 2, "commit": 2, "commun": 13, "compos": 8, "conda": [], "conduct": 1, "connect": 2, "continu": 2, "contrib": [], "contribut": 2, "contributor": 1, "convent": 13, "correct": 1, "coven": 1, "custom": [5, 11], "data": 14, "dataload": 5, "dataset": [4, 5, 14], "detect": [4, 7, 13, 14, 16], "develop": 2, "do": 16, "doctr": [2, 4, 5, 6, 7, 8, 9, 10, 15], "document": [2, 4, 6], "end": 16, "enforc": 1, "evalu": 9, "export": 15, "factori": 7, "featur": [2, 4], "feedback": 2, "file": 6, "from": 13, "gener": [5, 14], "git": 3, "guidelin": 1, "half": 15, "hub": 13, "huggingfac": 13, "i": 16, "infer": 15, "instal": [2, 3], "integr": 2, "io": 6, "lambda": 12, "let": 2, "line": 6, "linux": [], "load": [11, 13, 14], "loader": 5, "main": 4, "mode": 2, "model": [4, 7, 11, 13, 15, 16], "modifi": 2, "modul": [], "name": 13, "notebook": 10, "object": 14, "ocr": 16, "onli": [], "onnx": 15, "optim": 15, "option": [], "orient": [], "our": 1, "output": 16, "own": [11, 14], "packag": 3, "page": 6, "perman": 1, "pipelin": [], "pledg": 1, "precis": 15, "predictor": 16, "prepar": 15, "prerequisit": 3, "pretrain": 13, "push": 13, "python": 3, "qualiti": 2, "question": 2, "read": 6, "readi": 14, "recognit": [4, 7, 13, 14, 16], "report": 2, "request": 2, "resourc": [], "respons": 1, "return": [], "right": 16, "scope": 1, "share": 13, "should": 16, "stage": 16, "standard": 1, "structur": [2, 6], "style": 2, "support": [4, 5, 8], "synthet": [5, 14], "task": 9, "temporari": 1, "test": 2, "text": [4, 16], "train": 11, "transform": 8, "two": 16, "unit": 2, "us": [14, 15], "util": 9, "v0": 0, "verif": 2, "via": 3, "visual": 9, "vocab": 5, "warn": 1, "what": 16, "word": 6, "your": [11, 13, 14, 15], "zoo": [4, 7]}}) \ No newline at end of file diff --git a/v0.7.0/transforms.html b/v0.7.0/transforms.html deleted file mode 100644 index 85e94d8a76..0000000000 --- a/v0.7.0/transforms.html +++ /dev/null @@ -1,684 +0,0 @@ - - - - - - - - - - - - - doctr.transforms - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    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:

    -
    -
    -class doctr.transforms.Resize(output_size: Tuple[int, int], method: str = 'bilinear', preserve_aspect_ratio: bool = False, symmetric_pad: bool = False)[source]
    -

    Resizes a tensor to a target size

    -
    -
    Example::
    >>> from doctr.transforms import Resize
    ->>> import tensorflow as tf
    ->>> transfo = Resize((32, 32))
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • output_size – expected output size

    • -
    • method – interpolation method

    • -
    • preserve_aspect_ratio – if True, preserve aspect ratio and pad the rest with zeros

    • -
    • symmetric_pad – if True while preserving aspect ratio, the padding will be done symmetrically

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.Normalize(mean: Tuple[float, float, float], std: Tuple[float, float, float])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • mean – average value per channel

    • -
    • std – standard deviation per channel

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.LambdaTransformation(fn: Callable[[Tensor], Tensor])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import LambdaTransformation
    ->>> import tensorflow as tf
    ->>> transfo = LambdaTransformation(lambda x: x/ 255.)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    fn – the function to be applied to the input tensor

    -
    -
    -
    - -
    -
    -class doctr.transforms.ToGray[source]
    -

    Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ToGray()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    - -
    -
    -class doctr.transforms.ColorInversion(min_val: float = 0.5)[source]
    -

    Applies the following tranformation to a tensor (image or batch of images): -convert to grayscale, colorize (shift 0-values randomly), and then invert colors

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ColorInversion(min_val=0.6)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    min_val – range [min_val, 1] to colorize RGB pixels

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomBrightness(max_delta: float = 0.3)[source]
    -

    Randomly adjust brightness of a tensor (batch of images or image) by adding a delta -to all pixels

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Brightness()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    • -
    • p – probability to apply transformation

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomContrast(delta: float = 0.3)[source]
    -

    Randomly adjust contrast of a tensor (batch of images or image) by adjusting -each pixel: (img - mean) * contrast_factor + mean.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Contrast()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomSaturation(delta: float = 0.5)[source]
    -

    Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and -increasing saturation by a factor.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Saturation()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomHue(max_delta: float = 0.3)[source]
    -

    Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Hue()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomGamma(min_gamma: float = 0.5, max_gamma: float = 1.5, min_gain: float = 0.8, max_gain: float = 1.2)[source]
    -

    randomly performs gamma correction for a tensor (batch of images or image)

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Gamma()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_gamma – non-negative real number, lower bound for gamma param

    • -
    • max_gamma – non-negative real number, upper bound for gamma

    • -
    • min_gain – lower bound for constant multiplier

    • -
    • max_gain – upper bound for constant multiplier

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomJpegQuality(min_quality: int = 60, max_quality: int = 100)[source]
    -

    Randomly adjust jpeg quality of a 3 dimensional RGB image

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = JpegQuality()
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_quality – int between [0, 100]

    • -
    • max_quality – int between [0, 100]

    • -
    -
    -
    -
    - -
    -
    -

    Composing transformations

    -

    It is common to require several transformations to be performed consecutively.

    -
    -
    -class doctr.transforms.Compose(transforms: List[Callable[[Any], Any]])[source]
    -

    Implements a wrapper that will apply transformations sequentially

    -
    -
    Example::
    >>> from doctr.transforms import Compose, Resize
    ->>> import tensorflow as tf
    ->>> transfos = Compose([Resize((32, 32))])
    ->>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformation modules

    -
    -
    -
    - -
    -
    -class doctr.transforms.OneOf(transforms: List[Callable[[Any], Any]])[source]
    -

    Randomly apply one of the input transformations

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = OneOf([JpegQuality(), Gamma()])
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformations, one only will be picked

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomApply(transform: Callable[[Any], Any], p: float = 0.5)[source]
    -

    Apply with a probability p the input transformation

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = RandomApply(Gamma(), p=.5)
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • transform – transformation to apply

    • -
    • p – probability to apply

    • -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.7.0/using_doctr/custom_models_training.html b/v0.7.0/using_doctr/custom_models_training.html index 7cb776b28f..6273492fc9 100644 --- a/v0.7.0/using_doctr/custom_models_training.html +++ b/v0.7.0/using_doctr/custom_models_training.html @@ -14,7 +14,7 @@ - + Train your own model - docTR documentation @@ -545,7 +545,7 @@

    Loading your custom trained model - + diff --git a/v0.7.0/using_doctr/running_on_aws.html b/v0.7.0/using_doctr/running_on_aws.html index 207347602a..a52e3bb5f7 100644 --- a/v0.7.0/using_doctr/running_on_aws.html +++ b/v0.7.0/using_doctr/running_on_aws.html @@ -14,7 +14,7 @@ - + AWS Lambda - docTR documentation @@ -356,7 +356,7 @@

    AWS Lambda - + diff --git a/v0.7.0/using_doctr/sharing_models.html b/v0.7.0/using_doctr/sharing_models.html index 45a53f9c5c..6a5ca6611e 100644 --- a/v0.7.0/using_doctr/sharing_models.html +++ b/v0.7.0/using_doctr/sharing_models.html @@ -14,7 +14,7 @@ - + Share your model with the community - docTR documentation @@ -528,7 +528,7 @@

    Recognition - + diff --git a/v0.7.0/using_doctr/using_contrib_modules.html b/v0.7.0/using_doctr/using_contrib_modules.html index 50598dae5d..0c5fffdf9f 100644 --- a/v0.7.0/using_doctr/using_contrib_modules.html +++ b/v0.7.0/using_doctr/using_contrib_modules.html @@ -14,7 +14,7 @@ - + Integrate contributions into your pipeline - docTR documentation @@ -415,7 +415,7 @@

    ArtefactDetection - + diff --git a/v0.7.0/using_doctr/using_datasets.html b/v0.7.0/using_doctr/using_datasets.html index 594b518886..2c62da97af 100644 --- a/v0.7.0/using_doctr/using_datasets.html +++ b/v0.7.0/using_doctr/using_datasets.html @@ -14,7 +14,7 @@ - + Choose a ready to use dataset - docTR documentation @@ -613,7 +613,7 @@

    Data Loading - + diff --git a/v0.7.0/using_doctr/using_model_export.html b/v0.7.0/using_doctr/using_model_export.html index 0129f7c861..5c5cbe84ce 100644 --- a/v0.7.0/using_doctr/using_model_export.html +++ b/v0.7.0/using_doctr/using_model_export.html @@ -14,7 +14,7 @@ - + Preparing your model for inference - docTR documentation @@ -436,7 +436,7 @@

    Using your ONNX exported model in docTR - + diff --git a/v0.7.0/using_doctr/using_models.html b/v0.7.0/using_doctr/using_models.html index 261b6bab62..4e8dd95a8f 100644 --- a/v0.7.0/using_doctr/using_models.html +++ b/v0.7.0/using_doctr/using_models.html @@ -14,7 +14,7 @@ - + Choosing the right model - docTR documentation @@ -1112,7 +1112,7 @@

    What should I do with the output? - + diff --git a/v0.7.0/utils.html b/v0.7.0/utils.html deleted file mode 100644 index e2f223f06a..0000000000 --- a/v0.7.0/utils.html +++ /dev/null @@ -1,574 +0,0 @@ - - - - - - - - - - - - - doctr.utils - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.utils

    -

    This module regroups non-core features that are complementary to the rest of the package.

    -
    -

    Visualization

    -

    Easy-to-use functions to make sense of your model’s predictions.

    -
    -
    -doctr.utils.visualization.visualize_page(page: Dict[str, Any], image: ndarray, words_only: bool = True, display_artefacts: bool = True, scale: float = 10, interactive: bool = True, add_labels: bool = True, **kwargs: Any) Figure[source]
    -

    Visualize a full page with predicted blocks, lines and words

    -
    -
    Example::
    >>> import numpy as np
    ->>> import matplotlib.pyplot as plt
    ->>> from doctr.utils.visualization import visualize_page
    ->>> from doctr.models import ocr_db_crnn
    ->>> model = ocr_db_crnn(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([[input_page]])
    ->>> visualize_page(out[0].pages[0].export(), input_page)
    ->>> plt.show()
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • page – the exported Page of a Document

    • -
    • image – np array of the page, needs to have the same shape than page[‘dimensions’]

    • -
    • words_only – whether only words should be displayed

    • -
    • display_artefacts – whether artefacts should be displayed

    • -
    • scale – figsize of the largest windows side

    • -
    • interactive – whether the plot should be interactive

    • -
    • add_labels – for static plot, adds text labels on top of bounding box

    • -
    -
    -
    -
    - -
    -
    -

    Task evaluation

    -

    Implementations of task-specific metrics to easily assess your model performances.

    -
    -
    -class doctr.utils.metrics.TextMatch[source]
    -

    Implements text match metric (word-level accuracy) for recognition task.

    -

    The raw aggregated metric is computed as follows:

    -
    -
    -\[\forall X, Y \in \mathcal{W}^N, -TextMatch(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N f_{Y_i}(X_i)\]
    -
    -

    with the indicator function \(f_{a}\) defined as:

    -
    -
    -\[\begin{split}\forall a, x \in \mathcal{W}, -f_a(x) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } x = a \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{W}\) is the set of all possible character sequences, -\(N\) is a strictly positive integer.

    -
    -
    Example::
    >>> from doctr.utils import TextMatch
    ->>> metric = TextMatch()
    ->>> metric.update(['Hello', 'world'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    -summary() Dict[str, float][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a dictionary with the exact match score for the raw data, its lower-case counterpart, its unidecode -counterpart and its lower-case unidecode counterpart

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.LocalizationConfusion(iou_thresh: float = 0.5, rotated_bbox: bool = False, mask_shape: Tuple[int, int] = (1024, 1024))[source]
    -

    Implements common confusion metrics and mean IoU for localization evaluation.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall Y \in \mathcal{B}^N, \forall X \in \mathcal{B}^M, \\ -Recall(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -Precision(X, Y) = \frac{1}{M} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -meanIoU(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(X_i, Y_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(g_{X}\) defined as:

    -
    -
    -\[\begin{split}\forall y \in \mathcal{B}, -g_X(y) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } y\mbox{ has been assigned to any }(X_i)_i\mbox{ with an }IoU \geq 0.5 \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import LocalizationConfusion
    ->>> metric = LocalizationConfusion(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]))
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[float | None, float | None, float | None][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall, precision and meanIoU scores

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.OCRMetric(iou_thresh: float = 0.5, rotated_bbox: bool = False, mask_shape: Tuple[int, int] = (1024, 1024))[source]
    -

    Implements end-to-end OCR metric.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall (B, L) \in \mathcal{B}^N \times \mathcal{L}^N, -\forall (\hat{B}, \hat{L}) \in \mathcal{B}^M \times \mathcal{L}^M, \\ -Recall(B, \hat{B}, L, \hat{L}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -Precision(B, \hat{B}, L, \hat{L}) = \frac{1}{M} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(h_{B, L}\) defined as:

    -
    -
    -\[\begin{split}\forall (b, l) \in \mathcal{B} \times \mathcal{L}, -h_{B,L}(b, l) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ - & IoU \geq 0.5 \mbox{ and that for this assignment, } l = L_j\\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(\mathcal{L}\) is the set of possible character sequences, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import OCRMetric
    ->>> metric = OCRMetric(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]),
    -['hello'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[Dict[str, float | None], Dict[str, float | None], float | None][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall & precision for each string comparison flexibility and the mean IoU

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.8.0/_modules/doctr/datasets/cord.html b/v0.8.0/_modules/doctr/datasets/cord.html index 354f0062c2..85f1a47a08 100644 --- a/v0.8.0/_modules/doctr/datasets/cord.html +++ b/v0.8.0/_modules/doctr/datasets/cord.html @@ -13,7 +13,7 @@ - + doctr.datasets.cord - docTR documentation @@ -447,7 +447,7 @@

    Source code for doctr.datasets.cord

         
       
    - + diff --git a/v0.8.0/_modules/doctr/datasets/core.html b/v0.8.0/_modules/doctr/datasets/core.html deleted file mode 100644 index b3dcc29ff9..0000000000 --- a/v0.8.0/_modules/doctr/datasets/core.html +++ /dev/null @@ -1,417 +0,0 @@ - - - - - - - - - - - - doctr.datasets.core - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.core

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from pathlib import Path
    -from zipfile import ZipFile
    -from typing import List, Any, Optional, Tuple
    -import tensorflow as tf
    -
    -from doctr.models.utils import download_from_url
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset:
    -
    -    data: List[Any] = []
    -
    -    def __len__(self):
    -        return len(self.data)
    -
    -    def __getitem__(
    -        self,
    -        index: int
    -    ) -> Tuple[tf.Tensor, Any]:
    -
    -        img_name, target = self.data[index]
    -        # Read image
    -        img = tf.io.read_file(os.path.join(self.root, img_name))
    -        img = tf.image.decode_jpeg(img, channels=3)
    -        if self.sample_transforms is not None:
    -            img = self.sample_transforms(img)
    -
    -        return img, target
    -
    -    def extra_repr(self) -> str:
    -        return ""
    -
    -    def __repr__(self) -> str:
    -        return f"{self.__class__.__name__}({self.extra_repr()})"
    -
    -    @staticmethod
    -    def collate_fn(samples: List[Tuple[tf.Tensor, Any]]) -> Tuple[tf.Tensor, List[Any]]:
    -
    -        images, targets = zip(*samples)
    -        images = tf.stack(images, axis=0)
    -
    -        return images, list(targets)
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset): - """Implements an abstract dataset - - Args: - url: URL of the dataset - file_name: name of the file once downloaded - file_hash: expected SHA256 of the file - extract_archive: whether the downloaded file is an archive to be extracted - download: whether the dataset should be downloaded if not present on disk - overwrite: whether the archive should be re-extracted - """ - - def __init__( - self, - url: str, - file_name: Optional[str] = None, - file_hash: Optional[str] = None, - extract_archive: bool = False, - download: bool = False, - overwrite: bool = False, - ) -> None: - - dataset_cache = os.path.join(os.path.expanduser('~'), '.cache', 'doctr', 'datasets') - - file_name = file_name if isinstance(file_name, str) else os.path.basename(url) - # Download the file if not present - archive_path = os.path.join(dataset_cache, file_name) - - if not os.path.exists(archive_path) and not download: - raise ValueError("the dataset needs to be downloaded first with download=True") - - archive_path = download_from_url(url, file_name, file_hash, cache_subdir='datasets') - - # Extract the archive - if extract_archive: - archive_path = Path(archive_path) - dataset_path = archive_path.parent.joinpath(archive_path.stem) - if not dataset_path.is_dir() or overwrite: - with ZipFile(archive_path, 'r') as f: - f.extractall(path=dataset_path) - - # List images - self._root = dataset_path if extract_archive else archive_path - self.data: List[Any] = []
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.0/_modules/doctr/datasets/datasets/tensorflow.html b/v0.8.0/_modules/doctr/datasets/datasets/tensorflow.html deleted file mode 100644 index a236abd9fe..0000000000 --- a/v0.8.0/_modules/doctr/datasets/datasets/tensorflow.html +++ /dev/null @@ -1,356 +0,0 @@ - - - - - - - - - - - - doctr.datasets.datasets.tensorflow - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.datasets.tensorflow

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from typing import List, Any, Tuple
    -import tensorflow as tf
    -
    -from .base import _AbstractDataset, _VisionDataset
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset(_AbstractDataset):
    -
    -    def _read_sample(self, index: int) -> Tuple[tf.Tensor, Any]:
    -        img_name, target = self.data[index]
    -        # Read image
    -        img = tf.io.read_file(os.path.join(self.root, img_name))
    -        img = tf.image.decode_jpeg(img, channels=3)
    -
    -        return img, target
    -
    -    @staticmethod
    -    def collate_fn(samples: List[Tuple[tf.Tensor, Any]]) -> Tuple[tf.Tensor, List[Any]]:
    -
    -        images, targets = zip(*samples)
    -        images = tf.stack(images, axis=0)
    -
    -        return images, list(targets)
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset, _VisionDataset): - pass
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.0/_modules/doctr/datasets/detection.html b/v0.8.0/_modules/doctr/datasets/detection.html index faf9256c89..706b89a562 100644 --- a/v0.8.0/_modules/doctr/datasets/detection.html +++ b/v0.8.0/_modules/doctr/datasets/detection.html @@ -13,7 +13,7 @@ - + doctr.datasets.detection - docTR documentation @@ -424,7 +424,7 @@

    Source code for doctr.datasets.detection

         
       
    - + diff --git a/v0.8.0/_modules/doctr/datasets/doc_artefacts.html b/v0.8.0/_modules/doctr/datasets/doc_artefacts.html index 886999868b..dc8e8f9c29 100644 --- a/v0.8.0/_modules/doctr/datasets/doc_artefacts.html +++ b/v0.8.0/_modules/doctr/datasets/doc_artefacts.html @@ -13,7 +13,7 @@ - + doctr.datasets.doc_artefacts - docTR documentation @@ -408,7 +408,7 @@

    Source code for doctr.datasets.doc_artefacts

       
    - + diff --git a/v0.8.0/_modules/doctr/datasets/funsd.html b/v0.8.0/_modules/doctr/datasets/funsd.html index 60f7e51592..6f7ab121f0 100644 --- a/v0.8.0/_modules/doctr/datasets/funsd.html +++ b/v0.8.0/_modules/doctr/datasets/funsd.html @@ -13,7 +13,7 @@ - + doctr.datasets.funsd - docTR documentation @@ -438,7 +438,7 @@

    Source code for doctr.datasets.funsd

         
       
    - + diff --git a/v0.8.0/_modules/doctr/datasets/generator/tensorflow.html b/v0.8.0/_modules/doctr/datasets/generator/tensorflow.html index fecf8b2d82..814dc0822d 100644 --- a/v0.8.0/_modules/doctr/datasets/generator/tensorflow.html +++ b/v0.8.0/_modules/doctr/datasets/generator/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.datasets.generator.tensorflow - docTR documentation @@ -389,7 +389,7 @@

    Source code for doctr.datasets.generator.tensorflow

    - + diff --git a/v0.8.0/_modules/doctr/datasets/ic03.html b/v0.8.0/_modules/doctr/datasets/ic03.html index 83f7bcddf0..cf8999d751 100644 --- a/v0.8.0/_modules/doctr/datasets/ic03.html +++ b/v0.8.0/_modules/doctr/datasets/ic03.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic03 - docTR documentation @@ -452,7 +452,7 @@

    Source code for doctr.datasets.ic03

         
       
    - + diff --git a/v0.8.0/_modules/doctr/datasets/ic13.html b/v0.8.0/_modules/doctr/datasets/ic13.html index 1d92d10349..7650af381c 100644 --- a/v0.8.0/_modules/doctr/datasets/ic13.html +++ b/v0.8.0/_modules/doctr/datasets/ic13.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic13 - docTR documentation @@ -425,7 +425,7 @@

    Source code for doctr.datasets.ic13

         
       
    - + diff --git a/v0.8.0/_modules/doctr/datasets/iiit5k.html b/v0.8.0/_modules/doctr/datasets/iiit5k.html index 14ab1db716..b4a54e7e22 100644 --- a/v0.8.0/_modules/doctr/datasets/iiit5k.html +++ b/v0.8.0/_modules/doctr/datasets/iiit5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiit5k - docTR documentation @@ -429,7 +429,7 @@

    Source code for doctr.datasets.iiit5k

         
       
    - + diff --git a/v0.8.0/_modules/doctr/datasets/iiithws.html b/v0.8.0/_modules/doctr/datasets/iiithws.html index e7c0d4e8dd..052a85cd56 100644 --- a/v0.8.0/_modules/doctr/datasets/iiithws.html +++ b/v0.8.0/_modules/doctr/datasets/iiithws.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiithws - docTR documentation @@ -401,7 +401,7 @@

    Source code for doctr.datasets.iiithws

         
       
    - + diff --git a/v0.8.0/_modules/doctr/datasets/imgur5k.html b/v0.8.0/_modules/doctr/datasets/imgur5k.html index eb12e48784..f6c1a4692c 100644 --- a/v0.8.0/_modules/doctr/datasets/imgur5k.html +++ b/v0.8.0/_modules/doctr/datasets/imgur5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.imgur5k - docTR documentation @@ -473,7 +473,7 @@

    Source code for doctr.datasets.imgur5k

         
       
    - + diff --git a/v0.8.0/_modules/doctr/datasets/loader.html b/v0.8.0/_modules/doctr/datasets/loader.html index cdaec1bb70..9b2b3126de 100644 --- a/v0.8.0/_modules/doctr/datasets/loader.html +++ b/v0.8.0/_modules/doctr/datasets/loader.html @@ -13,7 +13,7 @@ - + doctr.datasets.loader - docTR documentation @@ -428,7 +428,7 @@

    Source code for doctr.datasets.loader

         
       
    - + diff --git a/v0.8.0/_modules/doctr/datasets/mjsynth.html b/v0.8.0/_modules/doctr/datasets/mjsynth.html index d7a7e66e35..c95f99e6d5 100644 --- a/v0.8.0/_modules/doctr/datasets/mjsynth.html +++ b/v0.8.0/_modules/doctr/datasets/mjsynth.html @@ -13,7 +13,7 @@ - + doctr.datasets.mjsynth - docTR documentation @@ -432,7 +432,7 @@

    Source code for doctr.datasets.mjsynth

         
       
    - + diff --git a/v0.8.0/_modules/doctr/datasets/ocr.html b/v0.8.0/_modules/doctr/datasets/ocr.html index c6e09faee3..a1a249b259 100644 --- a/v0.8.0/_modules/doctr/datasets/ocr.html +++ b/v0.8.0/_modules/doctr/datasets/ocr.html @@ -13,7 +13,7 @@ - + doctr.datasets.ocr - docTR documentation @@ -397,7 +397,7 @@

    Source code for doctr.datasets.ocr

         
       
    - + diff --git a/v0.8.0/_modules/doctr/datasets/recognition.html b/v0.8.0/_modules/doctr/datasets/recognition.html index 1e14da06a9..95612cdadb 100644 --- a/v0.8.0/_modules/doctr/datasets/recognition.html +++ b/v0.8.0/_modules/doctr/datasets/recognition.html @@ -13,7 +13,7 @@ - + doctr.datasets.recognition - docTR documentation @@ -382,7 +382,7 @@

    Source code for doctr.datasets.recognition

         
       
    - + diff --git a/v0.8.0/_modules/doctr/datasets/sroie.html b/v0.8.0/_modules/doctr/datasets/sroie.html index f3ac7b9547..32b4b17983 100644 --- a/v0.8.0/_modules/doctr/datasets/sroie.html +++ b/v0.8.0/_modules/doctr/datasets/sroie.html @@ -13,7 +13,7 @@ - + doctr.datasets.sroie - docTR documentation @@ -429,7 +429,7 @@

    Source code for doctr.datasets.sroie

         
       
    - + diff --git a/v0.8.0/_modules/doctr/datasets/svhn.html b/v0.8.0/_modules/doctr/datasets/svhn.html index f10a8cfd8e..5633dcfd6c 100644 --- a/v0.8.0/_modules/doctr/datasets/svhn.html +++ b/v0.8.0/_modules/doctr/datasets/svhn.html @@ -13,7 +13,7 @@ - + doctr.datasets.svhn - docTR documentation @@ -457,7 +457,7 @@

    Source code for doctr.datasets.svhn

         
       
    - + diff --git a/v0.8.0/_modules/doctr/datasets/svt.html b/v0.8.0/_modules/doctr/datasets/svt.html index 0d64efedf4..0ed4482c50 100644 --- a/v0.8.0/_modules/doctr/datasets/svt.html +++ b/v0.8.0/_modules/doctr/datasets/svt.html @@ -13,7 +13,7 @@ - + doctr.datasets.svt - docTR documentation @@ -443,7 +443,7 @@

    Source code for doctr.datasets.svt

         
       
    - + diff --git a/v0.8.0/_modules/doctr/datasets/synthtext.html b/v0.8.0/_modules/doctr/datasets/synthtext.html index 333de06da8..edd5c63c80 100644 --- a/v0.8.0/_modules/doctr/datasets/synthtext.html +++ b/v0.8.0/_modules/doctr/datasets/synthtext.html @@ -13,7 +13,7 @@ - + doctr.datasets.synthtext - docTR documentation @@ -454,7 +454,7 @@

    Source code for doctr.datasets.synthtext

         
       
    - + diff --git a/v0.8.0/_modules/doctr/datasets/utils.html b/v0.8.0/_modules/doctr/datasets/utils.html index 6e90a6400d..eeee0b2654 100644 --- a/v0.8.0/_modules/doctr/datasets/utils.html +++ b/v0.8.0/_modules/doctr/datasets/utils.html @@ -13,7 +13,7 @@ - + doctr.datasets.utils - docTR documentation @@ -542,7 +542,7 @@

    Source code for doctr.datasets.utils

         
       
    - + diff --git a/v0.8.0/_modules/doctr/datasets/wildreceipt.html b/v0.8.0/_modules/doctr/datasets/wildreceipt.html index 2b386ae694..6b5a52a10e 100644 --- a/v0.8.0/_modules/doctr/datasets/wildreceipt.html +++ b/v0.8.0/_modules/doctr/datasets/wildreceipt.html @@ -13,7 +13,7 @@ - + doctr.datasets.wildreceipt - docTR documentation @@ -437,7 +437,7 @@

    Source code for doctr.datasets.wildreceipt

         
       
    - + diff --git a/v0.8.0/_modules/doctr/documents/elements.html b/v0.8.0/_modules/doctr/documents/elements.html deleted file mode 100644 index 10c1e142d2..0000000000 --- a/v0.8.0/_modules/doctr/documents/elements.html +++ /dev/null @@ -1,577 +0,0 @@ - - - - - - - - - - - - doctr.documents.elements - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.elements

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import matplotlib.pyplot as plt
    -from typing import Tuple, Dict, List, Any, Optional, Union
    -
    -from doctr.utils.geometry import resolve_enclosing_bbox, resolve_enclosing_rbbox
    -from doctr.utils.visualization import visualize_page
    -from doctr.utils.common_types import BoundingBox, RotatedBbox
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['Element', 'Word', 'Artefact', 'Line', 'Block', 'Page', 'Document']
    -
    -
    -class Element(NestedObject):
    -    """Implements an abstract document element with exporting and text rendering capabilities"""
    -
    -    _exported_keys: List[str] = []
    -
    -    def __init__(self, **kwargs: Any) -> None:
    -        self._children_names: List[str] = []
    -        for k, v in kwargs.items():
    -            setattr(self, k, v)
    -            self._children_names.append(k)
    -
    -    def export(self) -> Dict[str, Any]:
    -        """Exports the object into a nested dict format"""
    -
    -        export_dict = {k: getattr(self, k) for k in self._exported_keys}
    -        for children_name in self._children_names:
    -            export_dict[children_name] = [c.export() for c in getattr(self, children_name)]
    -
    -        return export_dict
    -
    -    def render(self) -> str:
    -        raise NotImplementedError
    -
    -
    -
    -[docs] -class Word(Element): - """Implements a word element - - Args: - value: the text string of the word - confidence: the confidence associated with the text prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size - """ - - _exported_keys: List[str] = ["value", "confidence", "geometry"] - - def __init__(self, value: str, confidence: float, geometry: Union[BoundingBox, RotatedBbox]) -> None: - super().__init__() - self.value = value - self.confidence = confidence - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return self.value - - def extra_repr(self) -> str: - return f"value='{self.value}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Artefact(Element): - """Implements a non-textual element - - Args: - artefact_type: the type of artefact - confidence: the confidence of the type prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. - """ - - _exported_keys: List[str] = ["geometry", "type", "confidence"] - - def __init__(self, artefact_type: str, confidence: float, geometry: BoundingBox) -> None: - super().__init__() - self.geometry = geometry - self.type = artefact_type - self.confidence = confidence - - def render(self) -> str: - """Renders the full text of the element""" - return f"[{self.type.upper()}]" - - def extra_repr(self) -> str: - return f"type='{self.type}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Line(Element): - """Implements a line element as a collection of words - - Args: - words: list of word elements - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all words in it. - """ - - _exported_keys: List[str] = ["geometry"] - words: List[Word] = [] - - def __init__( - self, - words: List[Word], - geometry: Optional[Union[BoundingBox, RotatedBbox]] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - # Check whether this is a rotated or straight box - box_resolution_fn = resolve_enclosing_rbbox if len(words[0].geometry) == 5 else resolve_enclosing_bbox - geometry = box_resolution_fn([w.geometry for w in words]) # type: ignore[operator, misc] - - super().__init__(words=words) - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return " ".join(w.render() for w in self.words)
    - - - -
    -[docs] -class Block(Element): - """Implements a block element as a collection of lines and artefacts - - Args: - lines: list of line elements - artefacts: list of artefacts - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all lines and artefacts in it. - """ - - _exported_keys: List[str] = ["geometry"] - lines: List[Line] = [] - artefacts: List[Artefact] = [] - - def __init__( - self, - lines: List[Line] = [], - artefacts: List[Artefact] = [], - geometry: Optional[Union[BoundingBox, RotatedBbox]] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - line_boxes = [word.geometry for line in lines for word in line.words] - artefact_boxes = [artefact.geometry for artefact in artefacts] - box_resolution_fn = resolve_enclosing_rbbox if len(lines[0].geometry) == 5 else resolve_enclosing_bbox - geometry = box_resolution_fn(line_boxes + artefact_boxes) # type: ignore[operator, arg-type] - - super().__init__(lines=lines, artefacts=artefacts) - self.geometry = geometry - - def render(self, line_break: str = '\n') -> str: - """Renders the full text of the element""" - return line_break.join(line.render() for line in self.lines)
    - - - -
    -[docs] -class Page(Element): - """Implements a page element as a collection of blocks - - Args: - blocks: list of block elements - page_idx: the index of the page in the input raw document - dimensions: the page size in pixels in format (width, height) - orientation: a dictionary with the value of the rotation angle in degress and confidence of the prediction - language: a dictionary with the language value and confidence of the prediction - """ - - _exported_keys: List[str] = ["page_idx", "dimensions", "orientation", "language"] - blocks: List[Block] = [] - - def __init__( - self, - blocks: List[Block], - page_idx: int, - dimensions: Tuple[int, int], - orientation: Optional[Dict[str, Any]] = None, - language: Optional[Dict[str, Any]] = None, - ) -> None: - super().__init__(blocks=blocks) - self.page_idx = page_idx - self.dimensions = dimensions - self.orientation = orientation if isinstance(orientation, dict) else dict(value=None, confidence=None) - self.language = language if isinstance(language, dict) else dict(value=None, confidence=None) - - def render(self, block_break: str = '\n\n') -> str: - """Renders the full text of the element""" - return block_break.join(b.render() for b in self.blocks) - - def extra_repr(self) -> str: - return f"dimensions={self.dimensions}" - -
    -[docs] - def show( - self, page: np.ndarray, interactive: bool = True, **kwargs - ) -> None: - """Overlay the result on a given image - - Args: - page: image encoded as a numpy array in uint8 - interactive: whether the display should be interactive - """ - visualize_page(self.export(), page, interactive=interactive) - plt.show(**kwargs)
    -
    - - - -
    -[docs] -class Document(Element): - """Implements a document element as a collection of pages - - Args: - pages: list of page elements - """ - - pages: List[Page] = [] - - def __init__( - self, - pages: List[Page], - ) -> None: - super().__init__(pages=pages) - - def render(self, page_break: str = '\n\n\n\n') -> str: - """Renders the full text of the element""" - return page_break.join(p.render() for p in self.pages) - -
    -[docs] - def show(self, pages: List[np.ndarray], **kwargs) -> None: - """Overlay the result on a given image - - Args: - pages: list of images encoded as numpy arrays in uint8 - """ - for img, result in zip(pages, self.pages): - result.show(img, **kwargs)
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.0/_modules/doctr/documents/reader.html b/v0.8.0/_modules/doctr/documents/reader.html deleted file mode 100644 index cdcd814b6c..0000000000 --- a/v0.8.0/_modules/doctr/documents/reader.html +++ /dev/null @@ -1,612 +0,0 @@ - - - - - - - - - - - - doctr.documents.reader - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.reader

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import cv2
    -from pathlib import Path
    -import fitz
    -from weasyprint import HTML
    -from typing import List, Tuple, Optional, Any, Union, Sequence, Dict
    -
    -__all__ = ['read_pdf', 'read_img', 'read_html', 'DocumentFile', 'PDF']
    -
    -
    -AbstractPath = Union[str, Path]
    -AbstractFile = Union[AbstractPath, bytes]
    -Bbox = Tuple[float, float, float, float]
    -
    -
    -
    -[docs] -def read_img( - file: AbstractFile, - output_size: Optional[Tuple[int, int]] = None, - rgb_output: bool = True, -) -> np.ndarray: - """Read an image file into numpy format - - Example:: - >>> from doctr.documents import read_img - >>> page = read_img("path/to/your/doc.jpg") - - Args: - file: the path to the image file - output_size: the expected output size of each page in format H x W - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - Returns: - the page decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)): - if not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - img = cv2.imread(str(file), cv2.IMREAD_COLOR) - elif isinstance(file, bytes): - file = np.frombuffer(file, np.uint8) - img = cv2.imdecode(file, cv2.IMREAD_COLOR) - else: - raise TypeError("unsupported object type for argument 'file'") - - # Validity check - if img is None: - raise ValueError("unable to read file.") - # Resizing - if isinstance(output_size, tuple): - img = cv2.resize(img, output_size[::-1], interpolation=cv2.INTER_LINEAR) - # Switch the channel order - if rgb_output: - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - return img
    - - - -
    -[docs] -def read_pdf(file: AbstractFile, **kwargs: Any) -> fitz.Document: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_pdf - >>> doc = read_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)) and not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - - fitz_args: Dict[str, AbstractFile] = {} - - if isinstance(file, (str, Path)): - fitz_args['filename'] = file - elif isinstance(file, bytes): - fitz_args['stream'] = file - else: - raise TypeError("unsupported object type for argument 'file'") - - # Read pages with fitz and convert them to numpy ndarrays - return fitz.open(**fitz_args, filetype="pdf", **kwargs)
    - - - -def convert_page_to_numpy( - page: fitz.fitz.Page, - output_size: Optional[Tuple[int, int]] = None, - bgr_output: bool = False, - default_scales: Tuple[float, float] = (2, 2), -) -> np.ndarray: - """Convert a fitz page to a numpy-formatted image - - Args: - page: the page of a file read with PyMuPDF - output_size: the expected output size of each page in format H x W. Default goes to 840 x 595 for A4 pdf, - if you want to increase the resolution while preserving the original A4 aspect ratio can pass (1024, 726) - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - default_scales: spatial scaling to be applied when output_size is not specified where (1, 1) - corresponds to 72 dpi rendering. - - Returns: - the rendered image in numpy format - """ - - # If no output size is specified, keep the origin one - if output_size is not None: - scales = (output_size[1] / page.MediaBox[2], output_size[0] / page.MediaBox[3]) - else: - # Default 72 DPI (scales of (1, 1)) is unnecessarily low - scales = default_scales - - transform_matrix = fitz.Matrix(*scales) - - # Generate the pixel map using the transformation matrix - pixmap = page.getPixmap(matrix=transform_matrix) - # Decode it into a numpy - img = np.frombuffer(pixmap.samples, dtype=np.uint8).reshape(pixmap.height, pixmap.width, 3) - - # Switch the channel order - if bgr_output: - img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) - - return img - - -
    -[docs] -def read_html(url: str, **kwargs: Any) -> bytes: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_html - >>> doc = read_html("https://www.yoursite.com") - - Args: - url: URL of the target web page - Returns: - decoded PDF file as a bytes stream - """ - - return HTML(url, **kwargs).write_pdf()
    - - - -
    -[docs] -class PDF: - """PDF document template - - Args: - doc: input PDF document - """ - def __init__(self, doc: fitz.Document) -> None: - self.doc = doc - -
    -[docs] - def as_images(self, **kwargs) -> List[np.ndarray]: - """Convert all document pages to images - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images() - - Args: - kwargs: keyword arguments of `convert_page_to_numpy` - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - return [convert_page_to_numpy(page, **kwargs) for page in self.doc]
    - - - def get_page_words(self, idx, **kwargs) -> List[Tuple[Bbox, str]]: - """Get the annotations for all words of a given page""" - - # xmin, ymin, xmax, ymax, value, block_idx, line_idx, word_idx - return [(info[:4], info[4]) for info in self.doc[idx].getTextWords(**kwargs)] - -
    -[docs] - def get_words(self, **kwargs) -> List[List[Tuple[Bbox, str]]]: - """Get the annotations for all words in the document - - Example:: - >>> from doctr.documents import DocumentFile - >>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words() - - Args: - kwargs: keyword arguments of `fitz.Page.getTextWords` - Returns: - the list of pages annotations, represented as a list of tuple (bounding box, value) - """ - return [self.get_page_words(idx, **kwargs) for idx in range(len(self.doc))]
    - - - def get_page_artefacts(self, idx) -> List[Tuple[float, float, float, float]]: - return [tuple(self.doc[idx].getImageBbox(artefact)) # type: ignore[misc] - for artefact in self.doc[idx].get_images(full=True)] - -
    -[docs] - def get_artefacts(self) -> List[List[Tuple[float, float, float, float]]]: - """Get the artefacts for the entire document - - Example:: - >>> from doctr.documents import DocumentFile - >>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts() - - Returns: - the list of pages artefacts, represented as a list of bounding boxes - """ - - return [self.get_page_artefacts(idx) for idx in range(len(self.doc))]
    -
    - - - -
    -[docs] -class DocumentFile: - """Read a document from multiple extensions""" - -
    -[docs] - @classmethod - def from_pdf(cls, file: AbstractFile, **kwargs) -> PDF: - """Read a PDF file - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file or a binary stream - Returns: - a PDF document - """ - - doc = read_pdf(file, **kwargs) - - return PDF(doc)
    - - -
    -[docs] - @classmethod - def from_url(cls, url: str, **kwargs) -> PDF: - """Interpret a web page as a PDF document - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_url("https://www.yoursite.com") - - Args: - url: the URL of the target web page - Returns: - a PDF document - """ - pdf_stream = read_html(url) - return cls.from_pdf(pdf_stream, **kwargs)
    - - -
    -[docs] - @classmethod - def from_images(cls, files: Union[Sequence[AbstractFile], AbstractFile], **kwargs) -> List[np.ndarray]: - """Read an image file (or a collection of image files) and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"]) - - Args: - files: the path to the image file or a binary stream, or a collection of those - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - if isinstance(files, (str, Path, bytes)): - files = [files] - - return [read_img(file, **kwargs) for file in files]
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.0/_modules/doctr/io/elements.html b/v0.8.0/_modules/doctr/io/elements.html index 78ea4cc7cf..a8d52c457f 100644 --- a/v0.8.0/_modules/doctr/io/elements.html +++ b/v0.8.0/_modules/doctr/io/elements.html @@ -13,7 +13,7 @@ - + doctr.io.elements - docTR documentation @@ -960,7 +960,7 @@

    Source code for doctr.io.elements

         
       
    - + diff --git a/v0.8.0/_modules/doctr/io/html.html b/v0.8.0/_modules/doctr/io/html.html index a1eb075da0..34a60da286 100644 --- a/v0.8.0/_modules/doctr/io/html.html +++ b/v0.8.0/_modules/doctr/io/html.html @@ -13,7 +13,7 @@ - + doctr.io.html - docTR documentation @@ -354,7 +354,7 @@

    Source code for doctr.io.html

         
       
    - + diff --git a/v0.8.0/_modules/doctr/io/image/base.html b/v0.8.0/_modules/doctr/io/image/base.html index 1b42de0506..54663fa868 100644 --- a/v0.8.0/_modules/doctr/io/image/base.html +++ b/v0.8.0/_modules/doctr/io/image/base.html @@ -13,7 +13,7 @@ - + doctr.io.image.base - docTR documentation @@ -382,7 +382,7 @@

    Source code for doctr.io.image.base

         
       
    - + diff --git a/v0.8.0/_modules/doctr/io/image/tensorflow.html b/v0.8.0/_modules/doctr/io/image/tensorflow.html index 02325e0630..cf030207d4 100644 --- a/v0.8.0/_modules/doctr/io/image/tensorflow.html +++ b/v0.8.0/_modules/doctr/io/image/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.io.image.tensorflow - docTR documentation @@ -439,7 +439,7 @@

    Source code for doctr.io.image.tensorflow

         
       
    - + diff --git a/v0.8.0/_modules/doctr/io/pdf.html b/v0.8.0/_modules/doctr/io/pdf.html index 7d82b6573c..7dcb3e2381 100644 --- a/v0.8.0/_modules/doctr/io/pdf.html +++ b/v0.8.0/_modules/doctr/io/pdf.html @@ -13,7 +13,7 @@ - + doctr.io.pdf - docTR documentation @@ -368,7 +368,7 @@

    Source code for doctr.io.pdf

         
       
    - + diff --git a/v0.8.0/_modules/doctr/io/reader.html b/v0.8.0/_modules/doctr/io/reader.html index 5a8c87d168..5568ce7e0f 100644 --- a/v0.8.0/_modules/doctr/io/reader.html +++ b/v0.8.0/_modules/doctr/io/reader.html @@ -13,7 +13,7 @@ - + doctr.io.reader - docTR documentation @@ -414,7 +414,7 @@

    Source code for doctr.io.reader

         
       
    - + diff --git a/v0.8.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html b/v0.8.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html index 1b97d83911..4dd332b464 100644 --- a/v0.8.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html +++ b/v0.8.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.magc_resnet.tensorflow - docTR documentation @@ -518,7 +518,7 @@

    Source code for doctr.models.classification.magc_resnet.tensorflow

    - + diff --git a/v0.8.0/_modules/doctr/models/classification/mobilenet/tensorflow.html b/v0.8.0/_modules/doctr/models/classification/mobilenet/tensorflow.html index b583e184fa..7dbc971810 100644 --- a/v0.8.0/_modules/doctr/models/classification/mobilenet/tensorflow.html +++ b/v0.8.0/_modules/doctr/models/classification/mobilenet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.mobilenet.tensorflow - docTR documentation @@ -747,7 +747,7 @@

    Source code for doctr.models.classification.mobilenet.tensorflow

    - + diff --git a/v0.8.0/_modules/doctr/models/classification/resnet/tensorflow.html b/v0.8.0/_modules/doctr/models/classification/resnet/tensorflow.html index 67c7ede371..77a5747d8b 100644 --- a/v0.8.0/_modules/doctr/models/classification/resnet/tensorflow.html +++ b/v0.8.0/_modules/doctr/models/classification/resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.resnet.tensorflow - docTR documentation @@ -730,7 +730,7 @@

    Source code for doctr.models.classification.resnet.tensorflow

    - + diff --git a/v0.8.0/_modules/doctr/models/classification/textnet/tensorflow.html b/v0.8.0/_modules/doctr/models/classification/textnet/tensorflow.html index a36ebab4f6..45bcea9658 100644 --- a/v0.8.0/_modules/doctr/models/classification/textnet/tensorflow.html +++ b/v0.8.0/_modules/doctr/models/classification/textnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.textnet.tensorflow - docTR documentation @@ -599,7 +599,7 @@

    Source code for doctr.models.classification.textnet.tensorflow

    - + diff --git a/v0.8.0/_modules/doctr/models/classification/vgg/tensorflow.html b/v0.8.0/_modules/doctr/models/classification/vgg/tensorflow.html index 57e34af78f..8dc381674b 100644 --- a/v0.8.0/_modules/doctr/models/classification/vgg/tensorflow.html +++ b/v0.8.0/_modules/doctr/models/classification/vgg/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vgg.tensorflow - docTR documentation @@ -439,7 +439,7 @@

    Source code for doctr.models.classification.vgg.tensorflow

    - + diff --git a/v0.8.0/_modules/doctr/models/classification/vit/tensorflow.html b/v0.8.0/_modules/doctr/models/classification/vit/tensorflow.html index 717a6d1649..84d68b5388 100644 --- a/v0.8.0/_modules/doctr/models/classification/vit/tensorflow.html +++ b/v0.8.0/_modules/doctr/models/classification/vit/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vit.tensorflow - docTR documentation @@ -521,7 +521,7 @@

    Source code for doctr.models.classification.vit.tensorflow

    - + diff --git a/v0.8.0/_modules/doctr/models/classification/zoo.html b/v0.8.0/_modules/doctr/models/classification/zoo.html index 87f2d2956d..d1f749776e 100644 --- a/v0.8.0/_modules/doctr/models/classification/zoo.html +++ b/v0.8.0/_modules/doctr/models/classification/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.classification.zoo - docTR documentation @@ -400,7 +400,7 @@

    Source code for doctr.models.classification.zoo

    <
    - + diff --git a/v0.8.0/_modules/doctr/models/detection/differentiable_binarization.html b/v0.8.0/_modules/doctr/models/detection/differentiable_binarization.html deleted file mode 100644 index 38e9b36ec2..0000000000 --- a/v0.8.0/_modules/doctr/models/detection/differentiable_binarization.html +++ /dev/null @@ -1,879 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.differentiable_binarization - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.differentiable_binarization

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -import cv2
    -from copy import deepcopy
    -import numpy as np
    -from shapely.geometry import Polygon
    -import pyclipper
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers
    -from typing import Union, List, Tuple, Optional, Any, Dict
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..utils import IntermediateLayerGetter, load_pretrained_params, conv_sequence
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['DBPostProcessor', 'DBNet', 'db_resnet50']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'db_resnet50': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'backbone': 'ResNet50',
    -        'fpn_layers': ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"],
    -        'fpn_channels': 128,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'DBPostProcessor',
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.2.0/db_resnet50-adcafc63.zip',
    -    },
    -}
    -
    -
    -class DBPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for DBNet adapted from the implementation of `xuannianz
    -    <https://github.com/xuannianz/DifferentiableBinarization>`_.
    -
    -    Args:
    -        unclip ratio: ratio used to unshrink polygons
    -        min_size_box: minimal length (pix) to keep a box
    -        max_candidates: maximum boxes to consider in a single page
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        unclip_ratio: Union[float, int] = 1.5,
    -        max_candidates: int = 1000,
    -        box_thresh: float = 0.1,
    -        bin_thresh: float = 0.3,
    -    ) -> None:
    -
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -        self.unclip_ratio = unclip_ratio
    -        self.max_candidates = max_candidates
    -
    -    def polygon_to_box(
    -        self,
    -        points: np.ndarray,
    -    ) -> Optional[Tuple[int, int, int, int]]:
    -        """Expand a polygon (points) by a factor unclip_ratio, and returns a 4-points box
    -
    -        Args:
    -            points: The first parameter.
    -
    -        Returns:
    -            a box in absolute coordinates (x, y, w, h)
    -        """
    -        poly = Polygon(points)
    -        distance = poly.area * self.unclip_ratio / poly.length  # compute distance to expand polygon
    -        offset = pyclipper.PyclipperOffset()
    -        offset.AddPath(points, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        _points = offset.Execute(distance)
    -        # Take biggest stack of points
    -        idx = 0
    -        if len(_points) > 1:
    -            max_size = 0
    -            for _idx, p in enumerate(_points):
    -                if len(p) > max_size:
    -                    idx = _idx
    -                    max_size = len(p)
    -            # We ensure that _points can be correctly casted to a ndarray
    -            _points = [_points[idx]]
    -        expanded_points = np.asarray(_points)  # expand polygon
    -        if len(expanded_points) < 1:
    -            return None
    -        x, y, w, h = cv2.boundingRect(expanded_points)  # compute a 4-points box from expanded polygon
    -        return x, y, w, h
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map
    -
    -        Args:
    -            pred: Pred map from differentiable binarization output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        # get contours from connected components on the bitmap
    -        contours, _ = cv2.findContours(bitmap.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    -        for contour in contours[:self.max_candidates]:
    -            # Check whether smallest enclosing bounding box is not too small
    -            if np.any(contour[:, 0].max(axis=0) - contour[:, 0].min(axis=0) < min_size_box):
    -                continue
    -            x, y, w, h = cv2.boundingRect(contour)
    -            points = np.array([[x, y], [x, y + h], [x + w, y + h], [x + w, y]])
    -            # Compute objectness
    -            score = self.box_score(pred, points)
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            _box = self.polygon_to_box(points)
    -
    -            if _box is None or _box[2] < min_size_box or _box[3] < min_size_box:  # remove to small boxes
    -                continue
    -            x, y, w, h = _box
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -class FeaturePyramidNetwork(layers.Layer, NestedObject):
    -    """Feature Pyramid Network as described in `"Feature Pyramid Networks for Object Detection"
    -    <https://arxiv.org/pdf/1612.03144.pdf>`_.
    -
    -    Args:
    -        channels: number of channel to output
    -    """
    -
    -    def __init__(
    -        self,
    -        channels: int,
    -    ) -> None:
    -        super().__init__()
    -        self.channels = channels
    -        self.upsample = layers.UpSampling2D(size=(2, 2), interpolation='nearest')
    -        self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer='he_normal') for _ in range(4)]
    -        self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2 ** idx) for idx in range(4)]
    -
    -    @staticmethod
    -    def build_upsampling(
    -        channels: int,
    -        dilation_factor: int = 1,
    -    ) -> layers.Layer:
    -        """Module which performs a 3x3 convolution followed by up-sampling
    -
    -        Args:
    -            channels: number of output channels
    -            dilation_factor (int): dilation factor to scale the convolution output before concatenation
    -
    -        Returns:
    -            a keras.layers.Layer object, wrapping these operations in a sequential module
    -
    -        """
    -
    -        _layers = conv_sequence(channels, 'relu', True, kernel_size=3)
    -
    -        if dilation_factor > 1:
    -            _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation='nearest'))
    -
    -        module = keras.Sequential(_layers)
    -
    -        return module
    -
    -    def extra_repr(self) -> str:
    -        return f"channels={self.channels}"
    -
    -    def call(
    -        self,
    -        x: List[tf.Tensor],
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # Channel mapping
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.inner_blocks, x)]
    -        # Upsample & sum
    -        for idx in range(len(results) - 1, -1):
    -            results[idx] += self.upsample(results[idx + 1])
    -        # Conv & upsample
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.layer_blocks, results)]
    -
    -        return layers.concatenate(results)
    -
    -
    -class DBNet(DetectionModel, NestedObject):
    -    """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization"
    -    <https://arxiv.org/pdf/1911.08947.pdf>`_.
    -
    -    Args:
    -        feature extractor: the backbone serving as feature extractor
    -        fpn_channels: number of channels each extracted feature maps is mapped to
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'fpn', 'probability_head', 'threshold_head', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: IntermediateLayerGetter,
    -        fpn_channels: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(cfg=cfg)
    -
    -        self.shrink_ratio = 0.4
    -        self.thresh_min = 0.3
    -        self.thresh_max = 0.7
    -        self.min_size_box = 3
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.fpn = FeaturePyramidNetwork(channels=fpn_channels)
    -        # Initialize kernels
    -        _inputs = [layers.Input(shape=in_shape[1:]) for in_shape in self.feat_extractor.output_shape]
    -        output_shape = tuple(self.fpn(_inputs).shape)
    -
    -        self.probability_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -        self.threshold_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -
    -        self.postprocessor = DBPostProcessor()
    -
    -    @staticmethod
    -    def compute_distance(
    -        xs: np.array,
    -        ys: np.array,
    -        a: np.array,
    -        b: np.array,
    -        eps: float = 1e-7,
    -    ) -> float:
    -        """Compute the distance for each point of the map (xs, ys) to the (a, b) segment
    -
    -        Args:
    -            xs : map of x coordinates (height, width)
    -            ys : map of y coordinates (height, width)
    -            a: first point defining the [ab] segment
    -            b: second point defining the [ab] segment
    -
    -        Returns:
    -            The computed distance
    -
    -        """
    -        square_dist_1 = np.square(xs - a[0]) + np.square(ys - a[1])
    -        square_dist_2 = np.square(xs - b[0]) + np.square(ys - b[1])
    -        square_dist = np.square(a[0] - b[0]) + np.square(a[1] - b[1])
    -        cosin = (square_dist - square_dist_1 - square_dist_2) / (2 * np.sqrt(square_dist_1 * square_dist_2) + eps)
    -        square_sin = 1 - np.square(cosin)
    -        square_sin = np.nan_to_num(square_sin)
    -        result = np.sqrt(square_dist_1 * square_dist_2 * square_sin / square_dist)
    -        result[cosin < 0] = np.sqrt(np.fmin(square_dist_1, square_dist_2))[cosin < 0]
    -        return result
    -
    -    def draw_thresh_map(
    -        self,
    -        polygon: np.array,
    -        canvas: np.array,
    -        mask: np.array,
    -    ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    -        """Draw a polygon treshold map on a canvas, as described in the DB paper
    -
    -        Args:
    -            polygon : array of coord., to draw the boundary of the polygon
    -            canvas : threshold map to fill with polygons
    -            mask : mask for training on threshold polygons
    -        """
    -        if polygon.ndim != 2 or polygon.shape[1] != 2:
    -            raise AttributeError("polygon should be a 2 dimensional array of coords")
    -
    -        # Augment polygon by shrink_ratio
    -        polygon_shape = Polygon(polygon)
    -        distance = polygon_shape.area * (1 - np.power(self.shrink_ratio, 2)) / polygon_shape.length
    -        subject = [tuple(coor) for coor in polygon]  # Get coord as list of tuples
    -        padding = pyclipper.PyclipperOffset()
    -        padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        padded_polygon = np.array(padding.Execute(distance)[0])
    -
    -        # Fill the mask with 1 on the new padded polygon
    -        cv2.fillPoly(mask, [padded_polygon.astype(np.int32)], 1.0)
    -
    -        # Get min/max to recover polygon after distance computation
    -        xmin = padded_polygon[:, 0].min()
    -        xmax = padded_polygon[:, 0].max()
    -        ymin = padded_polygon[:, 1].min()
    -        ymax = padded_polygon[:, 1].max()
    -        width = xmax - xmin + 1
    -        height = ymax - ymin + 1
    -        # Get absolute polygon for distance computation
    -        polygon[:, 0] = polygon[:, 0] - xmin
    -        polygon[:, 1] = polygon[:, 1] - ymin
    -        # Get absolute padded polygon
    -        xs = np.broadcast_to(np.linspace(0, width - 1, num=width).reshape(1, width), (height, width))
    -        ys = np.broadcast_to(np.linspace(0, height - 1, num=height).reshape(height, 1), (height, width))
    -
    -        # Compute distance map to fill the padded polygon
    -        distance_map = np.zeros((polygon.shape[0], height, width), dtype=np.float32)
    -        for i in range(polygon.shape[0]):
    -            j = (i + 1) % polygon.shape[0]
    -            absolute_distance = self.compute_distance(xs, ys, polygon[i], polygon[j])
    -            distance_map[i] = np.clip(absolute_distance / distance, 0, 1)
    -        distance_map = np.min(distance_map, axis=0)
    -
    -        # Clip the padded polygon inside the canvas
    -        xmin_valid = min(max(0, xmin), canvas.shape[1] - 1)
    -        xmax_valid = min(max(0, xmax), canvas.shape[1] - 1)
    -        ymin_valid = min(max(0, ymin), canvas.shape[0] - 1)
    -        ymax_valid = min(max(0, ymax), canvas.shape[0] - 1)
    -
    -        # Fill the canvas with the distances computed inside the valid padded polygon
    -        canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1] = np.fmax(
    -            1 - distance_map[
    -                ymin_valid - ymin:ymax_valid - ymin + 1,
    -                xmin_valid - xmin:xmax_valid - xmin + 1
    -            ],
    -            canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1]
    -        )
    -
    -        return polygon, canvas, mask
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor, tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.uint8)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -        thresh_target = np.zeros(output_shape, dtype=np.uint8)
    -        thresh_mask = np.ones(output_shape, dtype=np.uint8)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            polys = np.stack([
    -                abs_boxes[:, [0, 1]],
    -                abs_boxes[:, [0, 3]],
    -                abs_boxes[:, [2, 3]],
    -                abs_boxes[:, [2, 1]],
    -            ], axis=1)
    -
    -            for box, box_size, poly, is_ambiguous in zip(abs_boxes, boxes_size, polys, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -
    -                # Negative shrink for gt, as described in paper
    -                polygon = Polygon(poly)
    -                distance = polygon.area * (1 - np.power(self.shrink_ratio, 2)) / polygon.length
    -                subject = [tuple(coor) for coor in poly]
    -                padding = pyclipper.PyclipperOffset()
    -                padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -                shrinked = padding.Execute(-distance)
    -
    -                # Draw polygon on gt if it is valid
    -                if len(shrinked) == 0:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                shrinked = np.array(shrinked[0]).reshape(-1, 2)
    -                if shrinked.shape[0] <= 2 or not Polygon(shrinked).is_valid:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                cv2.fillPoly(seg_target[idx], [shrinked.astype(np.int32)], 1)
    -
    -                # Draw on both thresh map and thresh mask
    -                poly, thresh_target[idx], thresh_mask[idx] = self.draw_thresh_map(poly, thresh_target[idx],
    -                                                                                  thresh_mask[idx])
    -
    -        thresh_target = thresh_target.astype(np.float32) * (self.thresh_max - self.thresh_min) + self.thresh_min
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -        thresh_target = tf.convert_to_tensor(thresh_target, dtype=tf.float32)
    -        thresh_mask = tf.convert_to_tensor(thresh_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask, thresh_target, thresh_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        thresh_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts, masks, thresh_gts, thresh_masks from a list of boxes
    -        and a list of masks for each image. From there it computes the loss with the model output
    -
    -        Args:
    -            out_map: output feature map of the model of shape (N, H, W, C)
    -            thresh_map: threshold map of shape (N, H, W, C)
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -
    -        prob_map = tf.math.sigmoid(tf.squeeze(out_map, axis=[-1]))
    -        thresh_map = tf.math.sigmoid(tf.squeeze(thresh_map, axis=[-1]))
    -
    -        seg_target, seg_mask, thresh_target, thresh_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute balanced BCE loss for proba_map
    -        bce_scale = 5.
    -        bce_loss = tf.keras.losses.binary_crossentropy(seg_target[..., None], out_map, from_logits=True)[seg_mask]
    -
    -        neg_target = 1 - seg_target[seg_mask]
    -        positive_count = tf.math.reduce_sum(seg_target[seg_mask])
    -        negative_count = tf.math.reduce_min([tf.math.reduce_sum(neg_target), 3. * positive_count])
    -        negative_loss = bce_loss * neg_target
    -        negative_loss, _ = tf.nn.top_k(negative_loss, tf.cast(negative_count, tf.int32))
    -        sum_losses = tf.math.reduce_sum(bce_loss * seg_target[seg_mask]) + tf.math.reduce_sum(negative_loss)
    -        balanced_bce_loss = sum_losses / (positive_count + negative_count + 1e-6)
    -
    -        # Compute dice loss for approxbin_map
    -        bin_map = 1 / (1 + tf.exp(-50. * (prob_map[seg_mask] - thresh_map[seg_mask])))
    -
    -        bce_min = tf.math.reduce_min(bce_loss)
    -        weights = (bce_loss - bce_min) / (tf.math.reduce_max(bce_loss) - bce_min) + 1.
    -        inter = tf.math.reduce_sum(bin_map * seg_target[seg_mask] * weights)
    -        union = tf.math.reduce_sum(bin_map) + tf.math.reduce_sum(seg_target[seg_mask]) + 1e-8
    -        dice_loss = 1 - 2.0 * inter / union
    -
    -        # Compute l1 loss for thresh_map
    -        l1_scale = 10.
    -        if tf.reduce_any(thresh_mask):
    -            l1_loss = tf.math.reduce_mean(tf.math.abs(thresh_map[thresh_mask] - thresh_target[thresh_mask]))
    -        else:
    -            l1_loss = tf.constant(0.)
    -
    -        return l1_scale * l1_loss + bce_scale * balanced_bce_loss + dice_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        feat_maps = self.feat_extractor(x, **kwargs)
    -        feat_concat = self.fpn(feat_maps, **kwargs)
    -        logits = self.probability_head(feat_concat, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            thresh_map = self.threshold_head(feat_concat, **kwargs)
    -            loss = self.compute_loss(logits, thresh_map, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _db_resnet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> DBNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['fpn_channels'] = kwargs.get('fpn_channels', _cfg['fpn_channels'])
    -
    -    # Feature extractor
    -    resnet = tf.keras.applications.__dict__[_cfg['backbone']](
    -        include_top=False,
    -        weights=None,
    -        input_shape=_cfg['input_shape'],
    -        pooling=None,
    -    )
    -
    -    feat_extractor = IntermediateLayerGetter(
    -        resnet,
    -        _cfg['fpn_layers'],
    -    )
    -
    -    kwargs['fpn_channels'] = _cfg['fpn_channels']
    -
    -    # Build the model
    -    model = DBNet(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def db_resnet50(pretrained: bool = False, **kwargs: Any) -> DBNet: - """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" - <https://arxiv.org/pdf/1911.08947.pdf>`_, using a ResNet-50 backbone. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _db_resnet('db_resnet50', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html b/v0.8.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html index d8f6168c33..bbc634a899 100644 --- a/v0.8.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html +++ b/v0.8.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.differentiable_binarization.tensorflow - docTR documentation @@ -735,7 +735,7 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo

    - + diff --git a/v0.8.0/_modules/doctr/models/detection/fast/tensorflow.html b/v0.8.0/_modules/doctr/models/detection/fast/tensorflow.html index 5b84d2dea1..65e1a77af8 100644 --- a/v0.8.0/_modules/doctr/models/detection/fast/tensorflow.html +++ b/v0.8.0/_modules/doctr/models/detection/fast/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.fast.tensorflow - docTR documentation @@ -769,7 +769,7 @@

    Source code for doctr.models.detection.fast.tensorflow

    - + diff --git a/v0.8.0/_modules/doctr/models/detection/linknet.html b/v0.8.0/_modules/doctr/models/detection/linknet.html deleted file mode 100644 index 129cfdce8b..0000000000 --- a/v0.8.0/_modules/doctr/models/detection/linknet.html +++ /dev/null @@ -1,644 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.linknet - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.linknet

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -import numpy as np
    -import cv2
    -from tensorflow.keras import layers, Sequential
    -from typing import Dict, Any, Tuple, Optional, List
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..backbones import ResnetStage
    -from ..utils import conv_sequence, load_pretrained_params
    -from ...utils.repr import NestedObject
    -
    -__all__ = ['LinkNet', 'linknet', 'LinkNetPostProcessor']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'linknet': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'out_chan': 1,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'LinkNetPostProcessor',
    -        'url': None,
    -    },
    -}
    -
    -
    -class LinkNetPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for LinkNet model.
    -
    -    Args:
    -        min_size_box: minimal length (pix) to keep a box
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        min_size_box: int = 3,
    -        bin_thresh: float = 0.15,
    -        box_thresh: float = 0.1,
    -    ) -> None:
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map: find connected components then filter boxes
    -
    -        Args:
    -            pred: Pred map from differentiable linknet output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        label_num, labelimage = cv2.connectedComponents(bitmap.astype(np.uint8), connectivity=4)
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        for label in range(1, label_num + 1):
    -            points = np.array(np.where(labelimage == label)[::-1]).T
    -            if points.shape[0] < 4:  # remove polygons with 3 points or less
    -                continue
    -            score = self.box_score(pred, points.reshape(-1, 2))
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            x, y, w, h = cv2.boundingRect(points)
    -            if min(w, h) < min_size_box:  # filter too small boxes
    -                continue
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -def decoder_block(in_chan: int, out_chan: int) -> Sequential:
    -    """Creates a LinkNet decoder block"""
    -
    -    return Sequential([
    -        *conv_sequence(in_chan // 4, 'relu', True, kernel_size=1),
    -        layers.Conv2DTranspose(
    -            filters=in_chan // 4,
    -            kernel_size=3,
    -            strides=2,
    -            padding="same",
    -            use_bias=False,
    -            kernel_initializer='he_normal'
    -        ),
    -        layers.BatchNormalization(),
    -        layers.Activation('relu'),
    -        *conv_sequence(out_chan, 'relu', True, kernel_size=1),
    -    ])
    -
    -
    -class LinkNetFPN(layers.Layer, NestedObject):
    -    """LinkNet Encoder-Decoder module
    -
    -    """
    -
    -    def __init__(
    -        self,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.encoder_1 = ResnetStage(num_blocks=2, output_channels=64, downsample=True)
    -        self.encoder_2 = ResnetStage(num_blocks=2, output_channels=128, downsample=True)
    -        self.encoder_3 = ResnetStage(num_blocks=2, output_channels=256, downsample=True)
    -        self.encoder_4 = ResnetStage(num_blocks=2, output_channels=512, downsample=True)
    -        self.decoder_1 = decoder_block(in_chan=64, out_chan=64)
    -        self.decoder_2 = decoder_block(in_chan=128, out_chan=64)
    -        self.decoder_3 = decoder_block(in_chan=256, out_chan=128)
    -        self.decoder_4 = decoder_block(in_chan=512, out_chan=256)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor
    -    ) -> tf.Tensor:
    -        x_1 = self.encoder_1(x)
    -        x_2 = self.encoder_2(x_1)
    -        x_3 = self.encoder_3(x_2)
    -        x_4 = self.encoder_4(x_3)
    -        y_4 = self.decoder_4(x_4)
    -        y_3 = self.decoder_3(y_4 + x_3)
    -        y_2 = self.decoder_2(y_3 + x_2)
    -        y_1 = self.decoder_1(y_2 + x_1)
    -        return y_1
    -
    -
    -class LinkNet(DetectionModel, NestedObject):
    -    """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation"
    -    <https://arxiv.org/pdf/1707.03718.pdf>`_.
    -
    -    Args:
    -        out_chan: number of channels for the output
    -    """
    -
    -    _children_names: List[str] = ['stem', 'fpn', 'classifier', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        out_chan: int = 1,
    -        input_shape: Tuple[int, int, int] = (512, 512, 3),
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(cfg=cfg)
    -
    -        self.stem = Sequential([
    -            *conv_sequence(64, 'relu', True, strides=2, kernel_size=7, input_shape=input_shape),
    -            layers.MaxPool2D(pool_size=(3, 3), strides=2, padding='same'),
    -        ])
    -
    -        self.fpn = LinkNetFPN()
    -
    -        self.classifier = Sequential([
    -            layers.Conv2DTranspose(
    -                filters=32,
    -                kernel_size=3,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -            layers.BatchNormalization(),
    -            layers.Activation('relu'),
    -            *conv_sequence(32, 'relu', True, strides=1, kernel_size=3),
    -            layers.Conv2DTranspose(
    -                filters=out_chan,
    -                kernel_size=2,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -        ])
    -
    -        self.min_size_box = 3
    -
    -        self.postprocessor = LinkNetPostProcessor()
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.bool)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            for box, box_size, is_ambiguous in zip(abs_boxes, boxes_size, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Fill polygon with 1
    -                seg_target[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = True
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts and masks from a list of boxes and a list of masks for each image
    -        Then, it computes the loss function with proba_map, gts and masks
    -
    -        Args:
    -            out_map: output feature map of the model of shape N x H x W x 1
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -        seg_target, seg_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute BCE loss
    -        return tf.math.reduce_mean(tf.keras.losses.binary_crossentropy(
    -            seg_target[seg_mask],
    -            tf.squeeze(out_map, axis=[-1])[seg_mask],
    -            from_logits=True
    -        ))
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        logits = self.stem(x)
    -        logits = self.fpn(logits)
    -        logits = self.classifier(logits)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            loss = self.compute_loss(logits, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _linknet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> LinkNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['out_chan'] = kwargs.get('out_chan', _cfg['out_chan'])
    -
    -    kwargs['out_chan'] = _cfg['out_chan']
    -    kwargs['input_shape'] = _cfg['input_shape']
    -    # Build the model
    -    model = LinkNet(cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def linknet(pretrained: bool = False, **kwargs: Any) -> LinkNet: - """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" - <https://arxiv.org/pdf/1707.03718.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import linknet - >>> model = linknet(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _linknet('linknet', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.0/_modules/doctr/models/detection/linknet/tensorflow.html b/v0.8.0/_modules/doctr/models/detection/linknet/tensorflow.html index c5fd053513..d374bb6d1e 100644 --- a/v0.8.0/_modules/doctr/models/detection/linknet/tensorflow.html +++ b/v0.8.0/_modules/doctr/models/detection/linknet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.linknet.tensorflow - docTR documentation @@ -698,7 +698,7 @@

    Source code for doctr.models.detection.linknet.tensorflow

    - + diff --git a/v0.8.0/_modules/doctr/models/detection/zoo.html b/v0.8.0/_modules/doctr/models/detection/zoo.html index 00783fcab6..dd9da6dc8e 100644 --- a/v0.8.0/_modules/doctr/models/detection/zoo.html +++ b/v0.8.0/_modules/doctr/models/detection/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.detection.zoo - docTR documentation @@ -412,7 +412,7 @@

    Source code for doctr.models.detection.zoo

         
       
    - + diff --git a/v0.8.0/_modules/doctr/models/export.html b/v0.8.0/_modules/doctr/models/export.html deleted file mode 100644 index f25a81aa21..0000000000 --- a/v0.8.0/_modules/doctr/models/export.html +++ /dev/null @@ -1,411 +0,0 @@ - - - - - - - - - - - - doctr.models.export - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.export

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import logging
    -import numpy as np
    -import tensorflow as tf
    -from tensorflow.keras import Model
    -from typing import Tuple
    -
    -logging.getLogger("tensorflow").setLevel(logging.DEBUG)
    -
    -
    -__all__ = ['convert_to_tflite', 'convert_to_fp16', 'quantize_model']
    -
    -
    -
    -[docs] -def convert_to_tflite(tf_model: Model) -> bytes: - """Converts a model to TFLite format - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_tflite, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_tflite(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - return converter.convert()
    - - - -
    -[docs] -def convert_to_fp16(tf_model: Model) -> bytes: - """Converts a model to half precision - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_fp16, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_fp16(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the serialized FP16 model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - converter.target_spec.supported_types = [tf.float16] - return converter.convert()
    - - - -
    -[docs] -def quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) -> bytes: - """Quantize a Tensorflow model - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import quantize_model, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = quantize_model(model, (224, 224, 3)) - - Args: - tf_model: a keras model - input_shape: shape of the expected input tensor (excluding batch dimension) with channel last order - - Returns: - bytes: the serialized quantized model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - - # Float fallback for operators that do not have an integer implementation - def representative_dataset(): - for _ in range(100): - data = np.random.rand(1, *input_shape) - yield [data.astype(np.float32)] - - converter.representative_dataset = representative_dataset - converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] - converter.inference_input_type = tf.int8 - converter.inference_output_type = tf.int8 - - return converter.convert()
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.0/_modules/doctr/models/factory/hub.html b/v0.8.0/_modules/doctr/models/factory/hub.html index a49f4ebde7..93aa0aa8f3 100644 --- a/v0.8.0/_modules/doctr/models/factory/hub.html +++ b/v0.8.0/_modules/doctr/models/factory/hub.html @@ -13,7 +13,7 @@ - + doctr.models.factory.hub - docTR documentation @@ -572,7 +572,7 @@

    Source code for doctr.models.factory.hub

         
       
    - + diff --git a/v0.8.0/_modules/doctr/models/recognition/crnn.html b/v0.8.0/_modules/doctr/models/recognition/crnn.html deleted file mode 100644 index daa2393439..0000000000 --- a/v0.8.0/_modules/doctr/models/recognition/crnn.html +++ /dev/null @@ -1,565 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.crnn - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.crnn

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import layers
    -from tensorflow.keras.models import Sequential
    -from typing import Tuple, Dict, Any, Optional, List
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel, RecognitionPostProcessor
    -
    -__all__ = ['CRNN', 'crnn_vgg16_bn', 'crnn_resnet31', 'CTCPostProcessor']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'crnn_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/crnn_vgg16_bn-748c855f.zip',
    -    },
    -    'crnn_resnet31': {
    -        'mean': (0.694, 0.695, 0.693),
    -        'std': (0.299, 0.296, 0.301),
    -        'backbone': 'resnet31', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.1/crnn_resnet31-69ab71db.zip',
    -    },
    -}
    -
    -
    -class CTCPostProcessor(RecognitionPostProcessor):
    -    """
    -    Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor
    -    ) -> List[Tuple[str, float]]:
    -        """
    -        Performs decoding of raw output with CTC and decoding of CTC predictions
    -        with label_to_idx mapping dictionnary
    -
    -        Args:
    -            logits: raw output of the model, shape BATCH_SIZE X SEQ_LEN X NUM_CLASSES + 1
    -
    -        Returns:
    -            A list of decoded words of length BATCH_SIZE
    -
    -        """
    -        # Decode CTC
    -        _decoded, _log_prob = tf.nn.ctc_beam_search_decoder(
    -            tf.transpose(logits, perm=[1, 0, 2]),
    -            tf.fill(logits.shape[0], logits.shape[1]),
    -            beam_width=1, top_paths=1,
    -        )
    -        out_idxs = tf.sparse.to_dense(_decoded[0], default_value=len(self.vocab))
    -        probs = tf.math.exp(tf.squeeze(_log_prob, axis=1))
    -
    -        # Map it to characters
    -        _decoded_strings_pred = tf.strings.reduce_join(
    -            inputs=tf.nn.embedding_lookup(self._embedding, out_idxs),
    -            axis=-1
    -        )
    -        _decoded_strings_pred = tf.strings.split(_decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -class CRNN(RecognitionModel):
    -    """Implements a CRNN architecture as described in `"An End-to-End Trainable Neural Network for Image-based
    -    Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of units in the LSTM layers
    -        cfg: configuration dictionary
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: tf.keras.Model,
    -        vocab: str,
    -        rnn_units: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(vocab=vocab, cfg=cfg)
    -        self.feat_extractor = feature_extractor
    -
    -        # Initialize kernels
    -        h, w, c = self.feat_extractor.output_shape[1:]
    -        self.max_length = w
    -
    -        self.decoder = Sequential(
    -            [
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Dense(units=len(vocab) + 1)
    -            ]
    -        )
    -        self.decoder.build(input_shape=(None, w, h * c))
    -
    -        self.postprocessor = CTCPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        target: List[str],
    -    ) -> tf.Tensor:
    -        """Compute CTC loss for the model.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        gt, seq_len = self.compute_target(target)
    -        batch_len = model_output.shape[0]
    -        input_length = model_output.shape[1] * tf.ones(shape=(batch_len))
    -        ctc_loss = tf.nn.ctc_loss(
    -            gt, model_output, seq_len, input_length, logits_time_major=False, blank_index=len(self.vocab)
    -        )
    -        return ctc_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        # B x H x W x C --> B x W x H x C
    -        transposed_feat = tf.transpose(features, perm=[0, 2, 1, 3])
    -        w, h, c = transposed_feat.get_shape().as_list()[1:]
    -        # B x W x H x C --> B x W x H * C
    -        features_seq = tf.reshape(transposed_feat, shape=(-1, w, h * c))
    -        logits = self.decoder(features_seq, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = logits
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(logits)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(logits, target)
    -
    -        return out
    -
    -
    -def _crnn(arch: str, pretrained: bool, input_shape: Optional[Tuple[int, int, int]] = None, **kwargs: Any) -> CRNN:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[_cfg['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -
    -    # Build the model
    -    model = CRNN(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a VGG-16 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_vgg16_bn - >>> model = crnn_vgg16_bn(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_vgg16_bn', pretrained, **kwargs)
    - - - -def crnn_resnet31(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a resnet31 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_resnet31 - >>> model = crnn_resnet31(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_resnet31', pretrained, **kwargs) -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.0/_modules/doctr/models/recognition/crnn/tensorflow.html b/v0.8.0/_modules/doctr/models/recognition/crnn/tensorflow.html index a00647e1b2..a8a19605ba 100644 --- a/v0.8.0/_modules/doctr/models/recognition/crnn/tensorflow.html +++ b/v0.8.0/_modules/doctr/models/recognition/crnn/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.crnn.tensorflow - docTR documentation @@ -650,7 +650,7 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    - + diff --git a/v0.8.0/_modules/doctr/models/recognition/master/tensorflow.html b/v0.8.0/_modules/doctr/models/recognition/master/tensorflow.html index 446786da5f..fa02c4de73 100644 --- a/v0.8.0/_modules/doctr/models/recognition/master/tensorflow.html +++ b/v0.8.0/_modules/doctr/models/recognition/master/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.master.tensorflow - docTR documentation @@ -644,7 +644,7 @@

    Source code for doctr.models.recognition.master.tensorflow

    - + diff --git a/v0.8.0/_modules/doctr/models/recognition/parseq/tensorflow.html b/v0.8.0/_modules/doctr/models/recognition/parseq/tensorflow.html index bd56053be1..d06bbd51e6 100644 --- a/v0.8.0/_modules/doctr/models/recognition/parseq/tensorflow.html +++ b/v0.8.0/_modules/doctr/models/recognition/parseq/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.parseq.tensorflow - docTR documentation @@ -837,7 +837,7 @@

    Source code for doctr.models.recognition.parseq.tensorflow

    - + diff --git a/v0.8.0/_modules/doctr/models/recognition/sar.html b/v0.8.0/_modules/doctr/models/recognition/sar.html deleted file mode 100644 index 2482e9f156..0000000000 --- a/v0.8.0/_modules/doctr/models/recognition/sar.html +++ /dev/null @@ -1,712 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.sar - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.sar

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import Sequential, layers
    -from typing import Tuple, Dict, List, Any, Optional
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel
    -from .core import RecognitionPostProcessor
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['SAR', 'SARPostProcessor', 'sar_vgg16_bn', 'sar_resnet31']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'sar_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1-models/sar_vgg16bn-0d7e2c26.zip',
    -    },
    -    'sar_resnet31': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'resnet31', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/sar_resnet31-ea202587.zip',
    -    },
    -}
    -
    -
    -class AttentionModule(layers.Layer, NestedObject):
    -    """Implements attention module of the SAR model
    -
    -    Args:
    -        attention_units: number of hidden attention units
    -
    -    """
    -    def __init__(
    -        self,
    -        attention_units: int
    -    ) -> None:
    -
    -        super().__init__()
    -        self.hidden_state_projector = layers.Conv2D(
    -            attention_units, 1, strides=1, use_bias=False, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.features_projector = layers.Conv2D(
    -            attention_units, 3, strides=1, use_bias=True, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.attention_projector = layers.Conv2D(
    -            1, 1, strides=1, use_bias=False, padding="same", kernel_initializer='he_normal',
    -        )
    -        self.flatten = layers.Flatten()
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        hidden_state: tf.Tensor,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        [H, W] = features.get_shape().as_list()[1:3]
    -        # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units)
    -        hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs)
    -        # shape (N, H, W, vgg_units) -> (N, H, W, attention_units)
    -        features_projection = self.features_projector(features, **kwargs)
    -        projection = tf.math.tanh(hidden_state_projection + features_projection)
    -        # shape (N, H, W, attention_units) -> (N, H, W, 1)
    -        attention = self.attention_projector(projection, **kwargs)
    -        # shape (N, H, W, 1) -> (N, H * W)
    -        attention = self.flatten(attention)
    -        attention = tf.nn.softmax(attention)
    -        # shape (N, H * W) -> (N, H, W, 1)
    -        attention_map = tf.reshape(attention, [-1, H, W, 1])
    -        glimpse = tf.math.multiply(features, attention_map)
    -        # shape (N, H * W) -> (N, 1)
    -        glimpse = tf.reduce_sum(glimpse, axis=[1, 2])
    -        return glimpse
    -
    -
    -class SARDecoder(layers.Layer, NestedObject):
    -    """Implements decoder module of the SAR model
    -
    -    Args:
    -        rnn_units: number of hidden units in recurrent cells
    -        max_length: maximum length of a sequence
    -        vocab_size: number of classes in the model alphabet
    -        embedding_units: number of hidden embedding units
    -        attention_units: number of hidden attention units
    -        num_decoder_layers: number of LSTM layers to stack
    -
    -    """
    -    def __init__(
    -        self,
    -        rnn_units: int,
    -        max_length: int,
    -        vocab_size: int,
    -        embedding_units: int,
    -        attention_units: int,
    -        num_decoder_layers: int = 2,
    -        input_shape: Optional[List[Tuple[Optional[int]]]] = None,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.vocab_size = vocab_size
    -        self.lstm_decoder = layers.StackedRNNCells(
    -            [layers.LSTMCell(rnn_units, dtype=tf.float32, implementation=1) for _ in range(num_decoder_layers)]
    -        )
    -        self.embed = layers.Dense(embedding_units, use_bias=False, input_shape=(None, self.vocab_size + 1))
    -        self.attention_module = AttentionModule(attention_units)
    -        self.output_dense = layers.Dense(vocab_size + 1, use_bias=True, input_shape=(None, 2 * rnn_units))
    -        self.max_length = max_length
    -
    -        # Initialize kernels
    -        if input_shape is not None:
    -            self.attention_module.call(layers.Input(input_shape[0][1:]), layers.Input((1, 1, rnn_units)))
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        holistic: tf.Tensor,
    -        gt: Optional[tf.Tensor] = None,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # initialize states (each of shape (N, rnn_units))
    -        states = self.lstm_decoder.get_initial_state(
    -            inputs=None, batch_size=features.shape[0], dtype=tf.float32
    -        )
    -        # run first step of lstm
    -        # holistic: shape (N, rnn_units)
    -        _, states = self.lstm_decoder(holistic, states, **kwargs)
    -        # Initialize with the index of virtual START symbol (placed after <eos>)
    -        symbol = tf.fill(features.shape[0], self.vocab_size + 1)
    -        logits_list = []
    -        if kwargs.get('training') and gt is None:
    -            raise ValueError('Need to provide labels during training for teacher forcing')
    -        for t in range(self.max_length + 1):  # keep 1 step for <eos>
    -            # one-hot symbol with depth vocab_size + 1
    -            # embeded_symbol: shape (N, embedding_units)
    -            embeded_symbol = self.embed(tf.one_hot(symbol, depth=self.vocab_size + 1), **kwargs)
    -            logits, states = self.lstm_decoder(embeded_symbol, states, **kwargs)
    -            glimpse = self.attention_module(
    -                features, tf.expand_dims(tf.expand_dims(logits, axis=1), axis=1), **kwargs,
    -            )
    -            # logits: shape (N, rnn_units), glimpse: shape (N, 1)
    -            logits = tf.concat([logits, glimpse], axis=-1)
    -            # shape (N, rnn_units + 1) -> (N, vocab_size + 1)
    -            logits = self.output_dense(logits, **kwargs)
    -            # update symbol with predicted logits for t+1 step
    -            if kwargs.get('training'):
    -                symbol = gt[:, t]
    -            else:
    -                symbol = tf.argmax(logits, axis=-1)
    -            logits_list.append(logits)
    -        outputs = tf.stack(logits_list, axis=1)  # shape (N, max_length + 1, vocab_size + 1)
    -
    -        return outputs
    -
    -
    -class SAR(RecognitionModel):
    -    """Implements a SAR architecture as described in `"Show, Attend and Read:A Simple and Strong Baseline for
    -    Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of hidden units in both encoder and decoder LSTM
    -        embedding_units: number of embedding units
    -        attention_units: number of hidden units in attention module
    -        max_length: maximum word length handled by the model
    -        num_decoders: number of LSTM to stack in decoder layer
    -
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'encoder', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor,
    -        vocab: str,
    -        rnn_units: int = 512,
    -        embedding_units: int = 512,
    -        attention_units: int = 512,
    -        max_length: int = 30,
    -        num_decoders: int = 2,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(vocab=vocab, cfg=cfg)
    -
    -        self.max_length = max_length + 1  # Add 1 timestep for EOS after the longest word
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.encoder = Sequential(
    -            [
    -                layers.LSTM(units=rnn_units, return_sequences=True),
    -                layers.LSTM(units=rnn_units, return_sequences=False)
    -            ]
    -        )
    -        # Initialize the kernels (watch out for reduce_max)
    -        self.encoder.build(input_shape=(None,) + self.feat_extractor.output_shape[2:])
    -
    -        self.decoder = SARDecoder(
    -            rnn_units, max_length, len(vocab), embedding_units, attention_units, num_decoders,
    -            input_shape=[self.feat_extractor.output_shape, self.encoder.output_shape]
    -        )
    -
    -        self.postprocessor = SARPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        gt: tf.Tensor,
    -        seq_len: tf.Tensor,
    -    ) -> tf.Tensor:
    -        """Compute categorical cross-entropy loss for the model.
    -        Sequences are masked after the EOS character.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        # Input length : number of timesteps
    -        input_len = tf.shape(model_output)[1]
    -        # Add one for additional <eos> token
    -        seq_len = seq_len + 1
    -        # One-hot gt labels
    -        oh_gt = tf.one_hot(gt, depth=model_output.shape[2])
    -        # Compute loss
    -        cce = tf.nn.softmax_cross_entropy_with_logits(oh_gt, model_output)
    -        # Compute mask
    -        mask_values = tf.zeros_like(cce)
    -        mask_2d = tf.sequence_mask(seq_len, input_len)
    -        masked_loss = tf.where(mask_2d, cce, mask_values)
    -        ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32))
    -        return tf.expand_dims(ce_loss, axis=1)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        pooled_features = tf.reduce_max(features, axis=1)  # vertical max pooling
    -        encoded = self.encoder(pooled_features, **kwargs)
    -        if target is not None:
    -            gt, seq_len = self.compute_target(target)
    -        decoded_features = self.decoder(features, encoded, gt=None if target is None else gt, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = decoded_features
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(decoded_features)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(decoded_features, gt, seq_len)
    -
    -        return out
    -
    -
    -class SARPostProcessor(RecognitionPostProcessor):
    -    """Post processor for SAR architectures
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor,
    -    ) -> List[Tuple[str, float]]:
    -        # compute pred with argmax for attention models
    -        out_idxs = tf.math.argmax(logits, axis=2)
    -        # N x L
    -        probs = tf.gather(tf.nn.softmax(logits, axis=-1), out_idxs, axis=-1, batch_dims=2)
    -        # Take the minimum confidence of the sequence
    -        probs = tf.math.reduce_min(probs, axis=1)
    -
    -        # decode raw output of the model with tf_label_to_idx
    -        out_idxs = tf.cast(out_idxs, dtype='int32')
    -        decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(self._embedding, out_idxs), axis=-1)
    -        decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -def _sar(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> SAR:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -    _cfg['embedding_units'] = kwargs.get('embedding_units', _cfg['rnn_units'])
    -    _cfg['attention_units'] = kwargs.get('attention_units', _cfg['rnn_units'])
    -    _cfg['max_length'] = kwargs.get('max_length', _cfg['max_length'])
    -    _cfg['num_decoders'] = kwargs.get('num_decoders', _cfg['num_decoders'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[default_cfgs[arch]['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -    kwargs['embedding_units'] = _cfg['embedding_units']
    -    kwargs['attention_units'] = _cfg['attention_units']
    -    kwargs['max_length'] = _cfg['max_length']
    -    kwargs['num_decoders'] = _cfg['num_decoders']
    -
    -    # Build the model
    -    model = SAR(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, default_cfgs[arch]['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a VGG16 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import sar_vgg16_bn - >>> model = sar_vgg16_bn(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_vgg16_bn', pretrained, **kwargs)
    - - - -
    -[docs] -def sar_resnet31(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a resnet-31 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example: - >>> import tensorflow as tf - >>> from doctr.models import sar_resnet31 - >>> model = sar_resnet31(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_resnet31', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.0/_modules/doctr/models/recognition/sar/tensorflow.html b/v0.8.0/_modules/doctr/models/recognition/sar/tensorflow.html index 6a44c6d2f4..9bbcdfbf81 100644 --- a/v0.8.0/_modules/doctr/models/recognition/sar/tensorflow.html +++ b/v0.8.0/_modules/doctr/models/recognition/sar/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.sar.tensorflow - docTR documentation @@ -753,7 +753,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    - + diff --git a/v0.8.0/_modules/doctr/models/recognition/vitstr/tensorflow.html b/v0.8.0/_modules/doctr/models/recognition/vitstr/tensorflow.html index 1a97114efa..7131ac4a5b 100644 --- a/v0.8.0/_modules/doctr/models/recognition/vitstr/tensorflow.html +++ b/v0.8.0/_modules/doctr/models/recognition/vitstr/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.vitstr.tensorflow - docTR documentation @@ -610,7 +610,7 @@

    Source code for doctr.models.recognition.vitstr.tensorflow

    - + diff --git a/v0.8.0/_modules/doctr/models/recognition/zoo.html b/v0.8.0/_modules/doctr/models/recognition/zoo.html index 4c61c0e058..b6896dd45c 100644 --- a/v0.8.0/_modules/doctr/models/recognition/zoo.html +++ b/v0.8.0/_modules/doctr/models/recognition/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.zoo - docTR documentation @@ -401,7 +401,7 @@

    Source code for doctr.models.recognition.zoo

       
    - + diff --git a/v0.8.0/_modules/doctr/models/zoo.html b/v0.8.0/_modules/doctr/models/zoo.html index 7d5510b773..a964ff6aff 100644 --- a/v0.8.0/_modules/doctr/models/zoo.html +++ b/v0.8.0/_modules/doctr/models/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.zoo - docTR documentation @@ -570,7 +570,7 @@

    Source code for doctr.models.zoo

         
       
    - + diff --git a/v0.8.0/_modules/doctr/transforms/modules.html b/v0.8.0/_modules/doctr/transforms/modules.html deleted file mode 100644 index ba8269e7ef..0000000000 --- a/v0.8.0/_modules/doctr/transforms/modules.html +++ /dev/null @@ -1,734 +0,0 @@ - - - - - - - - - - - - doctr.transforms.modules - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.transforms.modules

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import random
    -import tensorflow as tf
    -from typing import List, Any, Tuple, Callable
    -
    -from doctr.utils.repr import NestedObject
    -from . import functional as F
    -
    -
    -__all__ = ['Compose', 'Resize', 'Normalize', 'LambdaTransformation', 'ToGray', 'ColorInversion',
    -           'RandomBrightness', 'RandomContrast', 'RandomSaturation', 'RandomHue', 'RandomGamma', 'RandomJpegQuality',
    -           'OneOf', 'RandomApply']
    -
    -
    -
    -[docs] -class Compose(NestedObject): - """Implements a wrapper that will apply transformations sequentially - - Example:: - >>> from doctr.transforms import Compose, Resize - >>> import tensorflow as tf - >>> transfos = Compose([Resize((32, 32))]) - >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformation modules - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, x: Any) -> Any: - for t in self.transforms: - x = t(x) - - return x
    - - - -
    -[docs] -class Resize(NestedObject): - """Resizes a tensor to a target size - - Example:: - >>> from doctr.transforms import Resize - >>> import tensorflow as tf - >>> transfo = Resize((32, 32)) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - output_size: expected output size - method: interpolation method - preserve_aspect_ratio: if `True`, preserve aspect ratio and pad the rest with zeros - symmetric_pad: if `True` while preserving aspect ratio, the padding will be done symmetrically - """ - def __init__( - self, - output_size: Tuple[int, int], - method: str = 'bilinear', - preserve_aspect_ratio: bool = False, - symmetric_pad: bool = False, - ) -> None: - self.output_size = output_size - self.method = method - self.preserve_aspect_ratio = preserve_aspect_ratio - self.symmetric_pad = symmetric_pad - - def extra_repr(self) -> str: - _repr = f"output_size={self.output_size}, method='{self.method}'" - if self.preserve_aspect_ratio: - _repr += f", preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}" - return _repr - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img = tf.image.resize(img, self.output_size, self.method, self.preserve_aspect_ratio) - if self.preserve_aspect_ratio: - # pad width - if not self.symmetric_pad: - offset = (0, 0) - elif self.output_size[0] == img.shape[0]: - offset = (0, int((self.output_size[1] - img.shape[1]) / 2)) - else: - offset = (int((self.output_size[0] - img.shape[0]) / 2), 0) - img = tf.image.pad_to_bounding_box(img, *offset, *self.output_size) - return img
    - - - -
    -[docs] -class Normalize(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - mean: average value per channel - std: standard deviation per channel - """ - def __init__(self, mean: Tuple[float, float, float], std: Tuple[float, float, float]) -> None: - self.mean = tf.constant(mean, dtype=tf.float32) - self.std = tf.constant(std, dtype=tf.float32) - - def extra_repr(self) -> str: - return f"mean={self.mean.numpy().tolist()}, std={self.std.numpy().tolist()}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img -= self.mean - img /= self.std - return img
    - - - -
    -[docs] -class LambdaTransformation(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import LambdaTransformation - >>> import tensorflow as tf - >>> transfo = LambdaTransformation(lambda x: x/ 255.) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - fn: the function to be applied to the input tensor - """ - def __init__(self, fn: Callable[[tf.Tensor], tf.Tensor]) -> None: - self.fn = fn - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return self.fn(img)
    - - - -
    -[docs] -class ToGray(NestedObject): - """Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ToGray() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - """ - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.rgb_to_grayscale(img)
    - - - -
    -[docs] -class ColorInversion(NestedObject): - """Applies the following tranformation to a tensor (image or batch of images): - convert to grayscale, colorize (shift 0-values randomly), and then invert colors - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ColorInversion(min_val=0.6) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_val: range [min_val, 1] to colorize RGB pixels - """ - def __init__(self, min_val: float = 0.6) -> None: - self.min_val = min_val - - def extra_repr(self) -> str: - return f"min_val={self.min_val}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return F.invert_colors(img, self.min_val)
    - - - -
    -[docs] -class RandomBrightness(NestedObject): - """Randomly adjust brightness of a tensor (batch of images or image) by adding a delta - to all pixels - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Brightness() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - p: probability to apply transformation - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_brightness(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomContrast(NestedObject): - """Randomly adjust contrast of a tensor (batch of images or image) by adjusting - each pixel: (img - mean) * contrast_factor + mean. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Contrast() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1) - """ - def __init__(self, delta: float = .3) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_contrast(img, lower=1 - self.delta, upper=1 / (1 - self.delta))
    - - - -
    -[docs] -class RandomSaturation(NestedObject): - """Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and - increasing saturation by a factor. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Saturation() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1) - """ - def __init__(self, delta: float = .5) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_saturation(img, lower=1 - self.delta, upper=1 + self.delta)
    - - - -
    -[docs] -class RandomHue(NestedObject): - """Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Hue() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_hue(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomGamma(NestedObject): - """randomly performs gamma correction for a tensor (batch of images or image) - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Gamma() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_gamma: non-negative real number, lower bound for gamma param - max_gamma: non-negative real number, upper bound for gamma - min_gain: lower bound for constant multiplier - max_gain: upper bound for constant multiplier - """ - def __init__( - self, - min_gamma: float = 0.5, - max_gamma: float = 1.5, - min_gain: float = 0.8, - max_gain: float = 1.2, - ) -> None: - self.min_gamma = min_gamma - self.max_gamma = max_gamma - self.min_gain = min_gain - self.max_gain = max_gain - - def extra_repr(self) -> str: - return f"""gamma_range=({self.min_gamma}, {self.max_gamma}), - gain_range=({self.min_gain}, {self.max_gain})""" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - gamma = random.uniform(self.min_gamma, self.max_gamma) - gain = random.uniform(self.min_gain, self.max_gain) - return tf.image.adjust_gamma(img, gamma=gamma, gain=gain)
    - - - -
    -[docs] -class RandomJpegQuality(NestedObject): - """Randomly adjust jpeg quality of a 3 dimensional RGB image - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = JpegQuality() - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - min_quality: int between [0, 100] - max_quality: int between [0, 100] - """ - def __init__(self, min_quality: int = 60, max_quality: int = 100) -> None: - self.min_quality = min_quality - self.max_quality = max_quality - - def extra_repr(self) -> str: - return f"min_quality={self.min_quality}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_jpeg_quality( - img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality - )
    - - - -
    -[docs] -class OneOf(NestedObject): - """Randomly apply one of the input transformations - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = OneOf([JpegQuality(), Gamma()]) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformations, one only will be picked - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - # Pick transformation - transfo = self.transforms[int(random.random() * len(self.transforms))] - # Apply - return transfo(img)
    - - - -
    -[docs] -class RandomApply(NestedObject): - """Apply with a probability p the input transformation - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = RandomApply(Gamma(), p=.5) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transform: transformation to apply - p: probability to apply - """ - def __init__(self, transform: NestedObject, p: float = .5) -> None: - self.transform = transform - self.p = p - - def extra_repr(self) -> str: - return f"transform={self.transform}, p={self.p}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - if random.random() < self.p: - return self.transform(img) - return img
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.0/_modules/doctr/transforms/modules/base.html b/v0.8.0/_modules/doctr/transforms/modules/base.html index 42e8b8d2b1..087636ae0d 100644 --- a/v0.8.0/_modules/doctr/transforms/modules/base.html +++ b/v0.8.0/_modules/doctr/transforms/modules/base.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.base - docTR documentation @@ -615,7 +615,7 @@

    Source code for doctr.transforms.modules.base

    - + diff --git a/v0.8.0/_modules/doctr/transforms/modules/tensorflow.html b/v0.8.0/_modules/doctr/transforms/modules/tensorflow.html index 5e85447d5c..9ef65dafc0 100644 --- a/v0.8.0/_modules/doctr/transforms/modules/tensorflow.html +++ b/v0.8.0/_modules/doctr/transforms/modules/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.tensorflow - docTR documentation @@ -888,7 +888,7 @@

    Source code for doctr.transforms.modules.tensorflow

    - + diff --git a/v0.8.0/_modules/doctr/utils/metrics.html b/v0.8.0/_modules/doctr/utils/metrics.html index 5190fb3dd2..bec0aee3f4 100644 --- a/v0.8.0/_modules/doctr/utils/metrics.html +++ b/v0.8.0/_modules/doctr/utils/metrics.html @@ -13,7 +13,7 @@ - + doctr.utils.metrics - docTR documentation @@ -1071,7 +1071,7 @@

    Source code for doctr.utils.metrics

         
       
    - + diff --git a/v0.8.0/_modules/doctr/utils/visualization.html b/v0.8.0/_modules/doctr/utils/visualization.html index 9094dda132..d7c33dc75a 100644 --- a/v0.8.0/_modules/doctr/utils/visualization.html +++ b/v0.8.0/_modules/doctr/utils/visualization.html @@ -13,7 +13,7 @@ - + doctr.utils.visualization - docTR documentation @@ -830,7 +830,7 @@

    Source code for doctr.utils.visualization

         
       
    - + diff --git a/v0.8.0/_modules/index.html b/v0.8.0/_modules/index.html index 4f958d8657..ce2cfeee25 100644 --- a/v0.8.0/_modules/index.html +++ b/v0.8.0/_modules/index.html @@ -13,7 +13,7 @@ - + Overview: module code - docTR documentation @@ -371,7 +371,7 @@

    All modules for which code is available

    - + diff --git a/v0.8.0/_sources/datasets.rst.txt b/v0.8.0/_sources/datasets.rst.txt deleted file mode 100644 index 354122f1e5..0000000000 --- a/v0.8.0/_sources/datasets.rst.txt +++ /dev/null @@ -1,68 +0,0 @@ -doctr.datasets -============== - -.. currentmodule:: doctr.datasets - -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. - - -.. _datasets: - -Available Datasets ------------------- -The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL. - -.. autoclass:: doctr.datasets.datasets.VisionDataset - - -Here are all datasets that are available through DocTR: - -.. autoclass:: FUNSD -.. autoclass:: SROIE -.. autoclass:: CORD -.. autoclass:: OCRDataset - - -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. - -.. autoclass:: doctr.datasets.loader.DataLoader - - -.. _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 - * - digits - - 10 - - 0123456789 - * - ascii_letters - - 52 - - abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ - * - punctuation - - 32 - - !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ - * - currency - - 5 - - £€¥¢฿ - * - latin - - 96 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~° - * - french - - 154 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿ - -.. autofunction:: encode_sequences diff --git a/v0.8.0/_sources/documents.rst.txt b/v0.8.0/_sources/documents.rst.txt deleted file mode 100644 index 655730073e..0000000000 --- a/v0.8.0/_sources/documents.rst.txt +++ /dev/null @@ -1,87 +0,0 @@ -doctr.documents -=============== - - -.. currentmodule:: doctr.documents - -The documents module enables users to easily access content from documents and export analysis -results to structured formats. - - -Document structure ------------------- - -Structural organization of the documents. - -Word -^^^^ -A Word is an uninterrupted sequence of characters. - -.. autoclass:: Word - -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 - -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 - - -Document -^^^^^^^^ - -A Document is a collection of Pages. - -.. autoclass:: Document - - .. automethod:: show - - -File reading ------------- - -High-performance file reading and conversion to processable structured data. - -.. autofunction:: read_pdf - -.. autofunction:: read_img - -.. autofunction:: read_html - - -.. autoclass:: DocumentFile - - .. automethod:: from_pdf - - .. automethod:: from_url - - .. automethod:: from_images - -.. autoclass:: PDF - - .. automethod:: as_images - - .. automethod:: get_words - - .. automethod:: get_artefacts diff --git a/v0.8.0/_sources/installing.rst.txt b/v0.8.0/_sources/installing.rst.txt deleted file mode 100644 index 5c8779dc1c..0000000000 --- a/v0.8.0/_sources/installing.rst.txt +++ /dev/null @@ -1,46 +0,0 @@ - -************ -Installation -************ - -This library requires Python 3.6 or higher. - - -Prerequisites -============= - -Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so: - -* TensorFlow: `installation page `_. -* PyTorch: `installation page `_. - -If you are running another OS than Linux, you will need a few extra dependencies. - -For MacOS users, you can install them as follows: - -.. code:: shell - - brew install cairo pango gdk-pixbuf libffi - -For Windows users, those dependencies are included in GTK. You can find the latest installer over `here `_. - - -Via Python Package -================== - -Install the last stable release of the package using pip: - -.. code:: bash - - pip install python-doctr - - -Via Git -======= - -Install the library in developper mode: - -.. code:: bash - - git clone https://github.com/mindee/doctr.git - pip install -e doctr/. diff --git a/v0.8.0/_sources/models.rst.txt b/v0.8.0/_sources/models.rst.txt deleted file mode 100644 index 9830c6c153..0000000000 --- a/v0.8.0/_sources/models.rst.txt +++ /dev/null @@ -1,215 +0,0 @@ -doctr.models -============ - -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. - -.. currentmodule:: doctr.models - -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 TensorFlow model. -* Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable. - - -Text Detection --------------- -Localizing text elements in images - -+---------------------------------------------------+----------------------------+----------------------------+---------+ -| | FUNSD | CORD | | -+==================+=================+==============+============+===============+============+===============+=========+ -| **Architecture** | **Input shape** | **# params** | **Recall** | **Precision** | **Recall** | **Precision** | **FPS** | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ -| db_resnet50 | (1024, 1024, 3) | 25.2 M | 82.14 | 87.64 | 92.49 | 89.66 | 2.1 | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ - -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 combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for detection -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for detection is the following: - -1. resize each input image to the target size (bilinear interpolation by default) with potential deformation. -2. batch images together -3. normalize the batch using the training data statistics - - -Detection models -^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - -.. autofunction:: doctr.models.detection.db_resnet50 -.. autofunction:: doctr.models.detection.linknet16 - -Detection predictors -^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information. - -.. autofunction:: doctr.models.detection.detection_predictor - - -Text Recognition ----------------- -Identifying strings in images - -.. list-table:: Text recognition model zoo - :widths: 20 20 15 10 10 10 - :header-rows: 1 - - * - Architecture - - Input shape - - # params - - FUNSD - - CORD - - FPS - * - crnn_vgg16_bn - - (32, 128, 3) - - 15.8M - - 86.02 - - 91.3 - - 12.8 - * - sar_vgg16_bn - - (32, 128, 3) - - 21.5M - - 86.2 - - 91.7 - - 3.3 - * - sar_resnet31 - - (32, 128, 3) - - 53.1M - - **86.3** - - **92.1** - - 2.7 - -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 metrics being used are available in :ref:`metrics`. - -All these recognition models are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for recognition -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for recognition is the following: - -1. resize each input image to the target size (bilinear interpolation by default) without deformation. -2. pad the image to the target size (with zeros by default) -3. batch images together -4. normalize the batch using the training data statistics - -Recognition models -^^^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - - -.. autofunction:: doctr.models.recognition.crnn_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_resnet31 -.. autofunction:: doctr.models.recognition.master - - -Recognition predictors -^^^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage. - -.. autofunction:: doctr.models.recognition.recognition_predictor - - -End-to-End OCR --------------- -Predictors that localize and identify text elements in images - -+-----------------------------+--------------------------------------+--------------------------------------+ -| | FUNSD | CORD | -+=============================+============+===============+=========+============+===============+=========+ -| **Architecture** | **Recall** | **Precision** | **FPS** | **Recall** | **Precision** | **FPS** | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + crnn_vgg16_bn | 70.08 | 74.77 | 0.85 | 82.19 | **79.67** | 1.6 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_vgg16_bn | N/A | N/A | 0.49 | N/A | N/A | 1.0 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_resnet31 | N/A | N/A | 0.27 | N/A | N/A | 0.83 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision text detection | 59.50 | 62.50 | | 75.30 | 70.00 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision doc. text detection | 64.00 | 53.30 | | 68.90 | 61.10 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| AWS textract | **78.10** | **83.00** | | **87.50** | 66.00 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ - -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`. - -All recognition models of predictors are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Results on private ocr datasets - -+------------------------------------+----------------------------+----------------------------+----------------------------+ -| | Receipts | Invoices | IDs | -+====================================+============+===============+============+===============+============+===============+ -| **Architecture** | **Recall** | **Precision** | **Recall** | **Precision** | **Recall** | **Precision** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| db_resnet50 + crnn_vgg16_bn (ours) | **78.90** | **81.01** | 65.68 | **69.86** | **49.48** | **50.46** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| Gvision doc. text detection | 68.91 | 59.89 | 63.20 | 52.85 | 43.70 | 29.21 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| AWS textract | 75.77 | 77.70 | **70.47** | 69.13 | 46.39 | 43.32 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ - - -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. - -.. autofunction:: doctr.models.zoo.ocr_predictor - - -Model export ------------- -Utility functions to make the most of document analysis models. - -.. currentmodule:: doctr.models.export - -Model compression -^^^^^^^^^^^^^^^^^ - -.. autofunction:: convert_to_tflite - -.. autofunction:: convert_to_fp16 - -.. autofunction:: quantize_model - -Using SavedModel -^^^^^^^^^^^^^^^^ - -Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -`SavedModel `_ format as follows: - - - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> _ = model(input_t, training=False) - >>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/') - -And loaded just as easily: - - - >>> import tensorflow as tf - >>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/') diff --git a/v0.8.0/_sources/transforms.rst.txt b/v0.8.0/_sources/transforms.rst.txt deleted file mode 100644 index 0230fe75f5..0000000000 --- a/v0.8.0/_sources/transforms.rst.txt +++ /dev/null @@ -1,32 +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: - -.. autoclass:: Resize -.. autoclass:: Normalize -.. autoclass:: LambdaTransformation -.. autoclass:: ToGray -.. autoclass:: ColorInversion -.. autoclass:: RandomBrightness -.. autoclass:: RandomContrast -.. autoclass:: RandomSaturation -.. autoclass:: RandomHue -.. autoclass:: RandomGamma -.. autoclass:: RandomJpegQuality - - -Composing transformations ---------------------------------------------- -It is common to require several transformations to be performed consecutively. - -.. autoclass:: Compose -.. autoclass:: OneOf -.. autoclass:: RandomApply diff --git a/v0.8.0/_sources/utils.rst.txt b/v0.8.0/_sources/utils.rst.txt deleted file mode 100644 index 69c1abe0eb..0000000000 --- a/v0.8.0/_sources/utils.rst.txt +++ /dev/null @@ -1,36 +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 - - -.. _metrics: - -Task evaluation ---------------- -Implementations of task-specific metrics to easily assess your model performances. - -.. currentmodule:: doctr.utils.metrics - -.. autoclass:: TextMatch - - .. automethod:: summary - -.. autoclass:: LocalizationConfusion - - .. automethod:: summary - -.. autoclass:: OCRMetric - - .. automethod:: summary diff --git a/v0.8.0/_static/basic.css b/v0.8.0/_static/basic.css index f316efcb47..7ebbd6d07b 100644 --- a/v0.8.0/_static/basic.css +++ b/v0.8.0/_static/basic.css @@ -1,12 +1,5 @@ /* - * basic.css - * ~~~~~~~~~ - * * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ /* -- main layout ----------------------------------------------------------- */ @@ -115,15 +108,11 @@ img { /* -- search page ----------------------------------------------------------- */ ul.search { - margin: 10px 0 0 20px; - padding: 0; + margin-top: 10px; } ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; + padding: 5px 0; } ul.search li a { diff --git a/v0.8.0/_static/doctools.js b/v0.8.0/_static/doctools.js index 4d67807d17..0398ebb9f0 100644 --- a/v0.8.0/_static/doctools.js +++ b/v0.8.0/_static/doctools.js @@ -1,12 +1,5 @@ /* - * doctools.js - * ~~~~~~~~~~~ - * * Base JavaScript utilities for all Sphinx HTML documentation. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; diff --git a/v0.8.0/_static/language_data.js b/v0.8.0/_static/language_data.js index 367b8ed81b..c7fe6c6faf 100644 --- a/v0.8.0/_static/language_data.js +++ b/v0.8.0/_static/language_data.js @@ -1,13 +1,6 @@ /* - * language_data.js - * ~~~~~~~~~~~~~~~~ - * * This script contains the language-specific data used by searchtools.js, * namely the list of stopwords, stemmer, scorer and splitter. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; diff --git a/v0.8.0/_static/searchtools.js b/v0.8.0/_static/searchtools.js index b08d58c9b9..2c774d17af 100644 --- a/v0.8.0/_static/searchtools.js +++ b/v0.8.0/_static/searchtools.js @@ -1,12 +1,5 @@ /* - * searchtools.js - * ~~~~~~~~~~~~~~~~ - * * Sphinx JavaScript utilities for the full-text search. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; @@ -20,7 +13,7 @@ if (typeof Scorer === "undefined") { // and returns the new score. /* score: result => { - const [docname, title, anchor, descr, score, filename] = result + const [docname, title, anchor, descr, score, filename, kind] = result return score }, */ @@ -47,6 +40,14 @@ if (typeof Scorer === "undefined") { }; } +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + const _removeChildren = (element) => { while (element && element.lastChild) element.removeChild(element.lastChild); }; @@ -64,9 +65,13 @@ const _displayItem = (item, searchTerms, highlightTerms) => { const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; const contentRoot = document.documentElement.dataset.content_root; - const [docName, title, anchor, descr, score, _filename] = item; + const [docName, title, anchor, descr, score, _filename, kind] = item; let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); let requestUrl; let linkUrl; if (docBuilder === "dirhtml") { @@ -115,8 +120,10 @@ const _finishSearch = (resultCount) => { "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." ); else - Search.status.innerText = _( - "Search finished, found ${resultCount} page(s) matching the search query." + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, ).replace('${resultCount}', resultCount); }; const _displayNextItem = ( @@ -138,7 +145,7 @@ const _displayNextItem = ( else _finishSearch(resultCount); }; // Helper function used by query() to order search results. -// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. // Order the results by score (in opposite order of appearance, since the // `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. const _orderResultsByScoreThenName = (a, b) => { @@ -248,6 +255,7 @@ const Search = { searchSummary.classList.add("search-summary"); searchSummary.innerText = ""; const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); searchList.classList.add("search"); const out = document.getElementById("search-results"); @@ -318,7 +326,7 @@ const Search = { const indexEntries = Search._index.indexentries; // Collect multiple result groups to be sorted separately and then ordered. - // Each is an array of [docname, title, anchor, descr, score, filename]. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. const normalResults = []; const nonMainIndexResults = []; @@ -337,6 +345,7 @@ const Search = { null, score + boost, filenames[file], + SearchResultKind.title, ]); } } @@ -354,6 +363,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.index, ]; if (isMain) { normalResults.push(result); @@ -475,6 +485,7 @@ const Search = { descr, score, filenames[match[0]], + SearchResultKind.object, ]); }; Object.keys(objects).forEach((prefix) => @@ -585,6 +596,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.text, ]); } return results; diff --git a/v0.8.0/changelog.html b/v0.8.0/changelog.html index 8285a5e380..9c80410651 100644 --- a/v0.8.0/changelog.html +++ b/v0.8.0/changelog.html @@ -14,7 +14,7 @@ - + Changelog - docTR documentation @@ -420,7 +420,7 @@

    v0.1.0 (2021-03-05) - + diff --git a/v0.8.0/community/resources.html b/v0.8.0/community/resources.html index 2564037893..9a1988258c 100644 --- a/v0.8.0/community/resources.html +++ b/v0.8.0/community/resources.html @@ -14,7 +14,7 @@ - + Community resources - docTR documentation @@ -389,7 +389,7 @@

    Community resources - + diff --git a/v0.8.0/contributing/code_of_conduct.html b/v0.8.0/contributing/code_of_conduct.html index b27650ed66..0c4d9db2bb 100644 --- a/v0.8.0/contributing/code_of_conduct.html +++ b/v0.8.0/contributing/code_of_conduct.html @@ -14,7 +14,7 @@ - + Contributor Covenant Code of Conduct - docTR documentation @@ -498,7 +498,7 @@

    Attribution - + diff --git a/v0.8.0/contributing/contributing.html b/v0.8.0/contributing/contributing.html index a878d4a467..12c9ec5fd1 100644 --- a/v0.8.0/contributing/contributing.html +++ b/v0.8.0/contributing/contributing.html @@ -14,7 +14,7 @@ - + Contributing to docTR - docTR documentation @@ -475,7 +475,7 @@

    Let’s connect - + diff --git a/v0.8.0/datasets.html b/v0.8.0/datasets.html deleted file mode 100644 index 193e576c57..0000000000 --- a/v0.8.0/datasets.html +++ /dev/null @@ -1,578 +0,0 @@ - - - - - - - - - - - - - doctr.datasets - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.datasets

    -

    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.

    -
    -

    Available Datasets

    -

    The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL.

    -
    -
    -class doctr.datasets.datasets.VisionDataset(url: str, file_name: str | None = None, file_hash: str | None = None, extract_archive: bool = False, download: bool = False, overwrite: bool = False)[source]
    -
    - -

    Here are all datasets that are available through DocTR:

    -
    -
    -class doctr.datasets.FUNSD(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    FUNSD dataset from “FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents”.

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD
    ->>> train_set = FUNSD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.SROIE(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    SROIE dataset from “ICDAR2019 Competition on Scanned Receipt OCR and Information Extraction”.

    -
    -
    Example::
    >>> from doctr.datasets import SROIE
    ->>> train_set = SROIE(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.CORD(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    CORD dataset from “CORD: A Consolidated Receipt Dataset forPost-OCR Parsing”.

    -
    -
    Example::
    >>> from doctr.datasets import CORD
    ->>> train_set = CORD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.OCRDataset(img_folder: str, label_file: str, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    Implements an OCR dataset

    -
    -
    Parameters:
    -
      -
    • img_folder – local path to image folder (all jpg at the root)

    • -
    • label_file – local path to the label file

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -

    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.

    -
    -
    -class doctr.datasets.loader.DataLoader(dataset, shuffle: bool = True, batch_size: int = 1, drop_last: bool = False, workers: int | None = None)[source]
    -

    Implements a dataset wrapper for fast data loading

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD, 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)
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • dataset – the dataset

    • -
    • shuffle – whether the samples should be shuffled before passing it to the iterator

    • -
    • batch_size – number of elements in each batch

    • -
    • drop_last – if True, drops the last batch if it isn’t full

    • -
    • workers – number of workers to use for data loading

    • -
    -
    -
    -
    - -
    -
    -

    Supported Vocabs

    -

    Since textual content has to be encoded properly for models to interpret them efficiently, DocTR supports multiple sets -of vocabs.

    -
    - - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    DocTR Vocabs

    Name

    size

    characters

    digits

    10

    0123456789

    ascii_letters

    52

    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

    punctuation

    32

    !”#$%&'()*+,-./:;<=>?@[\]^_`{|}~

    currency

    5

    £€¥¢฿

    latin

    96

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°

    french

    154

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿

    -
    -
    -
    -doctr.datasets.encode_sequences(sequences: List[str], vocab: str, target_size: int | None = None, eos: int = -1, sos: int | None = None, pad: int | None = None, **kwargs: Any) ndarray[source]
    -

    Encode character sequences using a given vocab as mapping

    -
    -
    Parameters:
    -
      -
    • sequences – the list of character sequences of size N

    • -
    • vocab – the ordered vocab to use for encoding

    • -
    • target_size – maximum length of the encoded data

    • -
    • eos – encoding of End Of String

    • -
    • sos – optional encoding of Start Of String

    • -
    • pad – optional encoding for padding. In case of padding, all sequences are followed by 1 EOS then PAD

    • -
    -
    -
    Returns:
    -

    the padded encoded data as a tensor

    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.0/documents.html b/v0.8.0/documents.html deleted file mode 100644 index 98cbb2c5ef..0000000000 --- a/v0.8.0/documents.html +++ /dev/null @@ -1,772 +0,0 @@ - - - - - - - - - - - - - doctr.documents - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.documents

    -

    The documents module enables users to easily access content from documents and export analysis -results to structured formats.

    -
    -

    Document structure

    -

    Structural organization of the documents.

    -
    -

    Word

    -

    A Word is an uninterrupted sequence of characters.

    -
    -
    -class doctr.documents.Word(value: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float])[source]
    -

    Implements a word element

    -
    -
    Parameters:
    -
      -
    • value – the text string of the word

    • -
    • confidence – the confidence associated with the text prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to

    • -
    • size (the page's)

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Line(words: List[Word], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float] | None = None)[source]
    -

    Implements a line element as a collection of words

    -
    -
    Parameters:
    -
      -
    • words – list of word elements

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all words in it.

    • -
    -
    -
    -
    - -
    -
    -

    Artefact

    -

    An Artefact is a non-textual element (e.g. QR code, picture, chart, signature, logo, etc.).

    -
    -
    -class doctr.documents.Artefact(artefact_type: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]])[source]
    -

    Implements a non-textual element

    -
    -
    Parameters:
    -
      -
    • artefact_type – the type of artefact

    • -
    • confidence – the confidence of the type prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size.

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Block(lines: List[Line] = [], artefacts: List[Artefact] = [], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float] | None = None)[source]
    -

    Implements a block element as a collection of lines and artefacts

    -
    -
    Parameters:
    -
      -
    • lines – list of line elements

    • -
    • artefacts – list of artefacts

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all lines and artefacts in it.

    • -
    -
    -
    -
    - -
    -
    -

    Page

    -

    A Page is a collection of Blocks that were on the same physical page.

    -
    -
    -class doctr.documents.Page(blocks: List[Block], page_idx: int, dimensions: Tuple[int, int], orientation: Dict[str, Any] | None = None, language: Dict[str, Any] | None = None)[source]
    -

    Implements a page element as a collection of blocks

    -
    -
    Parameters:
    -
      -
    • blocks – list of block elements

    • -
    • page_idx – the index of the page in the input raw document

    • -
    • dimensions – the page size in pixels in format (width, height)

    • -
    • orientation – a dictionary with the value of the rotation angle in degress and confidence of the prediction

    • -
    • language – a dictionary with the language value and confidence of the prediction

    • -
    -
    -
    -
    -
    -show(page: ndarray, interactive: bool = True, **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -
      -
    • page – image encoded as a numpy array in uint8

    • -
    • interactive – whether the display should be interactive

    • -
    -
    -
    -
    - -
    - -
    -
    -

    Document

    -

    A Document is a collection of Pages.

    -
    -
    -class doctr.documents.Document(pages: List[Page])[source]
    -

    Implements a document element as a collection of pages

    -
    -
    Parameters:
    -

    pages – list of page elements

    -
    -
    -
    -
    -show(pages: List[ndarray], **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -

    pages – list of images encoded as numpy arrays in uint8

    -
    -
    -
    - -
    - -
    -
    -
    -

    File reading

    -

    High-performance file reading and conversion to processable structured data.

    -
    -
    -doctr.documents.read_pdf(file: str | Path | bytes, **kwargs: Any) Document[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_pdf
    ->>> doc = read_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_img(file: str | Path | bytes, output_size: Tuple[int, int] | None = None, rgb_output: bool = True) ndarray[source]
    -

    Read an image file into numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_img
    ->>> page = read_img("path/to/your/doc.jpg")
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • file – the path to the image file

    • -
    • output_size – the expected output size of each page in format H x W

    • -
    • rgb_output – whether the output ndarray channel order should be RGB instead of BGR.

    • -
    -
    -
    Returns:
    -

    the page decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_html(url: str, **kwargs: Any) bytes[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_html
    ->>> doc = read_html("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – URL of the target web page

    -
    -
    Returns:
    -

    decoded PDF file as a bytes stream

    -
    -
    -
    - -
    -
    -class doctr.documents.DocumentFile[source]
    -

    Read a document from multiple extensions

    -
    -
    -classmethod from_pdf(file: str | Path | bytes, **kwargs) PDF[source]
    -

    Read a PDF file

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file or a binary stream

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_url(url: str, **kwargs) PDF[source]
    -

    Interpret a web page as a PDF document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_url("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – the URL of the target web page

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_images(files: Sequence[str | Path | bytes] | str | Path | bytes, **kwargs) List[ndarray][source]
    -

    Read an image file (or a collection of image files) and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"])
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    files – the path to the image file or a binary stream, or a collection of those

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    - -
    -
    -class doctr.documents.PDF(doc: Document)[source]
    -

    PDF document template

    -
    -
    Parameters:
    -

    doc – input PDF document

    -
    -
    -
    -
    -as_images(**kwargs) List[ndarray][source]
    -

    Convert all document pages to images

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of convert_page_to_numpy

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -get_words(**kwargs) List[List[Tuple[Tuple[float, float, float, float], str]]][source]
    -

    Get the annotations for all words in the document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of fitz.Page.getTextWords

    -
    -
    Returns:
    -

    the list of pages annotations, represented as a list of tuple (bounding box, value)

    -
    -
    -
    - -
    -
    -get_artefacts() List[List[Tuple[float, float, float, float]]][source]
    -

    Get the artefacts for the entire document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts()
    -
    -
    -
    -
    -
    -
    Returns:
    -

    the list of pages artefacts, represented as a list of bounding boxes

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.0/genindex.html b/v0.8.0/genindex.html index 60b8645d9f..4e4a97a0d4 100644 --- a/v0.8.0/genindex.html +++ b/v0.8.0/genindex.html @@ -13,7 +13,7 @@ - Index - docTR documentation + Index - docTR documentation @@ -740,7 +740,7 @@

    W

    - + diff --git a/v0.8.0/getting_started/installing.html b/v0.8.0/getting_started/installing.html index 3d28b70e68..ee89a6da08 100644 --- a/v0.8.0/getting_started/installing.html +++ b/v0.8.0/getting_started/installing.html @@ -14,7 +14,7 @@ - + Installation - docTR documentation @@ -422,7 +422,7 @@

    Via Git - + diff --git a/v0.8.0/index.html b/v0.8.0/index.html index 0c21ab0888..b2b248cd9c 100644 --- a/v0.8.0/index.html +++ b/v0.8.0/index.html @@ -14,7 +14,7 @@ - + docTR documentation @@ -436,7 +436,7 @@

    Supported datasets - + diff --git a/v0.8.0/installing.html b/v0.8.0/installing.html deleted file mode 100644 index b61c60134b..0000000000 --- a/v0.8.0/installing.html +++ /dev/null @@ -1,395 +0,0 @@ - - - - - - - - - - - - - Installation - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    Installation

    -

    This library requires Python 3.6 or higher.

    -
    -

    Prerequisites

    -

    Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so:

    - -

    If you are running another OS than Linux, you will need a few extra dependencies.

    -

    For MacOS users, you can install them as follows:

    -
    brew install cairo pango gdk-pixbuf libffi
    -
    -
    -

    For Windows users, those dependencies are included in GTK. You can find the latest installer over here.

    -
    -
    -

    Via Python Package

    -

    Install the last stable release of the package using pip:

    -
    pip install python-doctr
    -
    -
    -
    -
    -

    Via Git

    -

    Install the library in developper mode:

    -
    git clone https://github.com/mindee/doctr.git
    -pip install -e doctr/.
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.0/models.html b/v0.8.0/models.html deleted file mode 100644 index b5cd44c9fa..0000000000 --- a/v0.8.0/models.html +++ /dev/null @@ -1,1002 +0,0 @@ - - - - - - - - - - - - - doctr.models - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.models

    -

    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 TensorFlow model.

    • -
    • Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable.

    • -
    -
    -

    Text Detection

    -

    Localizing text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Input shape

    # params

    Recall

    Precision

    Recall

    Precision

    FPS

    db_resnet50

    (1024, 1024, 3)

    25.2 M

    82.14

    87.64

    92.49

    89.66

    2.1

    -
    -

    All text detection models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for detection

    -

    In DocTR, the pre-processing scheme for detection is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) with potential deformation.

    2. -
    3. batch images together

    4. -
    5. normalize the batch using the training data statistics

    6. -
    -
    -
    -

    Detection models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.detection.db_resnet50(pretrained: bool = False, **kwargs: Any) DBNet[source]
    -

    DBNet as described in “Real-time Scene Text Detection with Differentiable Binarization”, using a ResNet-50 backbone.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -doctr.models.detection.linknet16(pretrained: bool = False, **kwargs: Any) LinkNet[source]
    -

    LinkNet as described in “LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import linknet16
    ->>> model = linknet16(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -

    Detection predictors

    -

    Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information.

    -
    -
    -doctr.models.detection.detection_predictor(arch: str = 'db_resnet50', pretrained: bool = False, **kwargs: Any) DetectionPredictor[source]
    -

    Text detection architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import detection_predictor
    ->>> model = detection_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_resnet50’)

    • -
    • pretrained – If True, returns a model pre-trained on our text detection dataset

    • -
    -
    -
    Returns:
    -

    Detection predictor

    -
    -
    -
    - -
    -
    -
    -

    Text Recognition

    -

    Identifying strings in images

    -
    - - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Text recognition model zoo

    Architecture

    Input shape

    # params

    FUNSD

    CORD

    FPS

    crnn_vgg16_bn

    (32, 128, 3)

    15.8M

    86.02

    91.3

    12.8

    sar_vgg16_bn

    (32, 128, 3)

    21.5M

    86.2

    91.7

    3.3

    sar_resnet31

    (32, 128, 3)

    53.1M

    86.3

    92.1

    2.7

    -
    -

    All text recognition models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All these recognition models are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for recognition

    -

    In DocTR, the pre-processing scheme for recognition is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) without deformation.

    2. -
    3. pad the image to the target size (with zeros by default)

    4. -
    5. batch images together

    6. -
    7. normalize the batch using the training data statistics

    8. -
    -
    -
    -

    Recognition models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.recognition.crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) CRNN[source]
    -

    CRNN with a VGG-16 backbone as described in “An End-to-End Trainable Neural Network for Image-based -Sequence Recognition and Its Application to Scene Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import crnn_vgg16_bn
    ->>> model = crnn_vgg16_bn(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a VGG16 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_vgg16_bn
    ->>> model = sar_vgg16_bn(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_resnet31(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a resnet-31 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -

    Example

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_resnet31
    ->>> model = sar_resnet31(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.master(pretrained: bool = False, **kwargs: Any) MASTER[source]
    -

    MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_. -Example:

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import master
    ->>> model = master(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 48, 160, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -

    Recognition predictors

    -

    Combining the right components around a given architecture for easier usage.

    -
    -
    -doctr.models.recognition.recognition_predictor(arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) RecognitionPredictor[source]
    -

    Text recognition architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import recognition_predictor
    ->>> model = recognition_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(32, 128, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘crnn_vgg16_bn’, ‘crnn_resnet31’, ‘sar_vgg16_bn’, ‘sar_resnet31’)

    • -
    • pretrained – If True, returns a model pre-trained on our text recognition dataset

    • -
    -
    -
    Returns:
    -

    Recognition predictor

    -
    -
    -
    - -
    -
    -
    -

    End-to-End OCR

    -

    Predictors that localize and identify text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Recall

    Precision

    FPS

    Recall

    Precision

    FPS

    db_resnet50 + crnn_vgg16_bn

    70.08

    74.77

    0.85

    82.19

    79.67

    1.6

    db_resnet50 + sar_vgg16_bn

    N/A

    N/A

    0.49

    N/A

    N/A

    1.0

    db_resnet50 + sar_resnet31

    N/A

    N/A

    0.27

    N/A

    N/A

    0.83

    Gvision text detection

    59.50

    62.50

    75.30

    70.00

    Gvision doc. text detection

    64.00

    53.30

    68.90

    61.10

    AWS textract

    78.10

    83.00

    87.50

    66.00

    -
    -

    All OCR models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All recognition models of predictors are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -

    Results on private ocr datasets

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Receipts

    Invoices

    IDs

    Architecture

    Recall

    Precision

    Recall

    Precision

    Recall

    Precision

    db_resnet50 + crnn_vgg16_bn (ours)

    78.90

    81.01

    65.68

    69.86

    49.48

    50.46

    Gvision doc. text detection

    68.91

    59.89

    63.20

    52.85

    43.70

    29.21

    AWS textract

    75.77

    77.70

    70.47

    69.13

    46.39

    43.32

    -
    -
    -

    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.

    -
    -
    -doctr.models.zoo.ocr_predictor(det_arch: str = 'db_resnet50', reco_arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) OCRPredictor[source]
    -

    End-to-end OCR architecture using one model for localization, and another for text recognition.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import ocr_predictor
    ->>> model = ocr_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_sar_vgg’, ‘db_sar_resnet’, ‘db_crnn_vgg’, ‘db_crnn_resnet’)

    • -
    • pretrained – If True, returns a model pre-trained on our OCR dataset

    • -
    -
    -
    Returns:
    -

    OCR predictor

    -
    -
    -
    - -
    -
    -
    -

    Model export

    -

    Utility functions to make the most of document analysis models.

    -
    -

    Model compression

    -
    -
    -doctr.models.export.convert_to_tflite(tf_model: Model) bytes[source]
    -

    Converts a model to TFLite format

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_tflite, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_tflite(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.convert_to_fp16(tf_model: Model) bytes[source]
    -

    Converts a model to half precision

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_fp16, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_fp16(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the serialized FP16 model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) bytes[source]
    -

    Quantize a Tensorflow model

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import quantize_model, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = quantize_model(model, (224, 224, 3))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • tf_model – a keras model

    • -
    • input_shape – shape of the expected input tensor (excluding batch dimension) with channel last order

    • -
    -
    -
    Returns:
    -

    the serialized quantized model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -

    Using SavedModel

    -

    Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -SavedModel format as follows:

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> _ = model(input_t, training=False)
    ->>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/')
    -
    -
    -

    And loaded just as easily:

    -
    >>> import tensorflow as tf
    ->>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/')
    -
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.0/modules/contrib.html b/v0.8.0/modules/contrib.html index e99f6b3f74..7fb86b8b38 100644 --- a/v0.8.0/modules/contrib.html +++ b/v0.8.0/modules/contrib.html @@ -14,7 +14,7 @@ - + doctr.contrib - docTR documentation @@ -380,7 +380,7 @@

    Supported contribution modules - + diff --git a/v0.8.0/modules/datasets.html b/v0.8.0/modules/datasets.html index 8594fcc430..ff7215e911 100644 --- a/v0.8.0/modules/datasets.html +++ b/v0.8.0/modules/datasets.html @@ -14,7 +14,7 @@ - + doctr.datasets - docTR documentation @@ -1049,7 +1049,7 @@

    Returns: - + diff --git a/v0.8.0/modules/io.html b/v0.8.0/modules/io.html index 7e2cc4acdf..5042fa9a8f 100644 --- a/v0.8.0/modules/io.html +++ b/v0.8.0/modules/io.html @@ -14,7 +14,7 @@ - + doctr.io - docTR documentation @@ -752,7 +752,7 @@

    Returns: - + diff --git a/v0.8.0/modules/models.html b/v0.8.0/modules/models.html index 5583787c99..218467341e 100644 --- a/v0.8.0/modules/models.html +++ b/v0.8.0/modules/models.html @@ -14,7 +14,7 @@ - + doctr.models - docTR documentation @@ -1459,7 +1459,7 @@

    Args: - + diff --git a/v0.8.0/modules/transforms.html b/v0.8.0/modules/transforms.html index 07708fb0fa..867b50df22 100644 --- a/v0.8.0/modules/transforms.html +++ b/v0.8.0/modules/transforms.html @@ -14,7 +14,7 @@ - + doctr.transforms - docTR documentation @@ -804,7 +804,7 @@

    Args:< - + diff --git a/v0.8.0/modules/utils.html b/v0.8.0/modules/utils.html index a9cb4fd561..08fb937d9e 100644 --- a/v0.8.0/modules/utils.html +++ b/v0.8.0/modules/utils.html @@ -14,7 +14,7 @@ - + doctr.utils - docTR documentation @@ -737,7 +737,7 @@

    Args:< - + diff --git a/v0.8.0/notebooks.html b/v0.8.0/notebooks.html index 2548d1b73c..2c16094e3b 100644 --- a/v0.8.0/notebooks.html +++ b/v0.8.0/notebooks.html @@ -14,7 +14,7 @@ - + docTR Notebooks - docTR documentation @@ -379,7 +379,7 @@

    docTR Notebooks - + diff --git a/v0.8.0/py-modindex.html b/v0.8.0/py-modindex.html deleted file mode 100644 index c1569be607..0000000000 --- a/v0.8.0/py-modindex.html +++ /dev/null @@ -1,330 +0,0 @@ - - - - - - - - - - - Python Module Index - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    - -
    -

    Python Module Index

    - -
    - - - - - - - - - - - -
     
    d
    - doctr -
    - -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.8.0/search.html b/v0.8.0/search.html index f4af66c58d..2d08f1f54b 100644 --- a/v0.8.0/search.html +++ b/v0.8.0/search.html @@ -14,7 +14,7 @@ - + Search - docTR documentation @@ -334,7 +334,7 @@ - + diff --git a/v0.8.0/searchindex.js b/v0.8.0/searchindex.js index aed6427a1d..989c1b03d6 100644 --- a/v0.8.0/searchindex.js +++ b/v0.8.0/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles": {"1. Correction": [[1, "correction"]], "2. Warning": [[1, "warning"]], "3. Temporary Ban": [[1, "temporary-ban"]], "4. Permanent Ban": [[1, "permanent-ban"]], "AWS Lambda": [[12, null]], "Advanced options": [[16, "advanced-options"]], "Args:": [[5, "args"], [5, "id4"], [5, "id7"], [5, "id10"], [5, "id13"], [5, "id16"], [5, "id19"], [5, "id22"], [5, "id25"], [5, "id29"], [5, "id32"], [5, "id37"], [5, "id40"], [5, "id46"], [5, "id49"], [5, "id50"], [5, "id51"], [5, "id54"], [5, "id57"], [5, "id60"], [5, "id61"], [6, "args"], [6, "id2"], [6, "id3"], [6, "id4"], [6, "id5"], [6, "id6"], [6, "id7"], [6, "id10"], [6, "id12"], [6, "id14"], [6, "id16"], [6, "id20"], [6, "id24"], [6, "id28"], [7, "args"], [7, "id3"], [7, "id8"], [7, "id13"], [7, "id17"], [7, "id21"], [7, "id26"], [7, "id31"], [7, "id36"], [7, "id41"], [7, "id45"], [7, "id49"], [7, "id54"], [7, "id58"], [7, "id63"], [7, "id68"], [7, "id72"], [7, "id76"], [7, "id81"], [7, "id86"], [7, "id90"], [7, "id95"], [7, "id99"], [7, "id103"], [7, "id108"], [7, "id113"], [7, "id118"], [7, "id122"], [7, "id126"], [7, "id131"], [7, "id135"], [7, "id139"], [7, "id143"], [7, "id145"], [7, "id147"], [7, "id149"], [8, "args"], [8, "id1"], [8, "id2"], [8, "id3"], [8, "id4"], [8, "id5"], [8, "id6"], [8, "id7"], [8, "id8"], [8, "id9"], [8, "id10"], [8, "id11"], [8, "id12"], [8, "id13"], [8, "id14"], [8, "id15"], [8, "id16"], [8, "id17"], [8, "id18"], [9, "args"], [9, "id3"], [9, "id5"], [9, "id6"], [9, "id7"], [9, "id8"], [9, "id9"], [9, "id10"], [9, "id11"]], "Artefact": [[6, "artefact"]], "Attribution": [[1, "attribution"]], "Available Datasets": [[14, "available-datasets"]], "Available architectures": [[16, "available-architectures"], [16, "id1"], [16, "id2"]], "Block": [[6, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[14, null]], "Choosing the right model": [[16, null]], "Classification": [[13, "classification"]], "Code quality": [[2, "code-quality"]], "Code style verification": [[2, "code-style-verification"]], "Codebase structure": [[2, "codebase-structure"]], "Commits": [[2, "commits"]], "Composing transformations": [[8, "composing-transformations"]], "Continuous Integration": [[2, "continuous-integration"]], "Contributing to docTR": [[2, null]], "Contributor Covenant Code of Conduct": [[1, null]], "Custom dataset loader": [[5, "custom-dataset-loader"]], "Data Loading": [[14, "data-loading"]], "Dataloader": [[5, "dataloader"]], "Detection": [[13, "detection"], [14, "detection"]], "Detection predictors": [[16, "detection-predictors"]], "Developer mode installation": [[2, "developer-mode-installation"]], "Developing docTR": [[2, "developing-doctr"]], "Document": [[6, "document"]], "Document structure": [[6, "document-structure"]], "End-to-End OCR": [[16, "end-to-end-ocr"]], "Enforcement": [[1, "enforcement"]], "Enforcement Guidelines": [[1, "enforcement-guidelines"]], "Enforcement Responsibilities": [[1, "enforcement-responsibilities"]], "Export to ONNX": [[15, "export-to-onnx"]], "Feature requests & bug report": [[2, "feature-requests-bug-report"]], "Feedback": [[2, "feedback"]], "File reading": [[6, "file-reading"]], "Half-precision": [[15, "half-precision"]], "Installation": [[3, null]], "Let\u2019s connect": [[2, "let-s-connect"]], "Line": [[6, "line"]], "Loading from Huggingface Hub": [[13, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[11, "loading-your-custom-trained-model"]], "Main Features": [[4, "main-features"]], "Model optimization": [[15, "model-optimization"]], "Model zoo": [[4, "model-zoo"]], "Modifying the documentation": [[2, "modifying-the-documentation"]], "Naming conventions": [[13, "naming-conventions"]], "Object Detection": [[14, "object-detection"]], "Our Pledge": [[1, "our-pledge"]], "Our Standards": [[1, "our-standards"]], "Page": [[6, "page"]], "Preparing your model for inference": [[15, null]], "Prerequisites": [[3, "prerequisites"]], "Pretrained community models": [[13, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[13, "pushing-to-the-huggingface-hub"]], "Questions": [[2, "questions"]], "Recognition": [[13, "recognition"], [14, "recognition"]], "Recognition predictors": [[16, "recognition-predictors"]], "Returns:": [[5, "returns"], [6, "returns"], [6, "id11"], [6, "id13"], [6, "id15"], [6, "id19"], [6, "id23"], [6, "id27"], [6, "id31"], [7, "returns"], [7, "id6"], [7, "id11"], [7, "id16"], [7, "id20"], [7, "id24"], [7, "id29"], [7, "id34"], [7, "id39"], [7, "id44"], [7, "id48"], [7, "id52"], [7, "id57"], [7, "id61"], [7, "id66"], [7, "id71"], [7, "id75"], [7, "id79"], [7, "id84"], [7, "id89"], [7, "id93"], [7, "id98"], [7, "id102"], [7, "id106"], [7, "id111"], [7, "id116"], [7, "id121"], [7, "id125"], [7, "id129"], [7, "id134"], [7, "id138"], [7, "id142"], [7, "id144"], [7, "id146"], [7, "id148"], [9, "returns"], [9, "id4"]], "Scope": [[1, "scope"]], "Share your model with the community": [[13, null]], "Supported Vocabs": [[5, "supported-vocabs"]], "Supported datasets": [[4, "supported-datasets"]], "Supported transformations": [[8, "supported-transformations"]], "Synthetic dataset generator": [[5, "synthetic-dataset-generator"], [14, "synthetic-dataset-generator"]], "Task evaluation": [[9, "task-evaluation"]], "Text Detection": [[16, "text-detection"]], "Text Recognition": [[16, "text-recognition"]], "Text detection models": [[4, "text-detection-models"]], "Text recognition models": [[4, "text-recognition-models"]], "Train your own model": [[11, null]], "Two-stage approaches": [[16, "two-stage-approaches"]], "Unit tests": [[2, "unit-tests"]], "Use your own datasets": [[14, "use-your-own-datasets"]], "Using your ONNX exported model in docTR": [[15, "using-your-onnx-exported-model-in-doctr"]], "Via Git": [[3, "via-git"]], "Via Python Package": [[3, "via-python-package"]], "Visualization": [[9, "visualization"]], "What should I do with the output?": [[16, "what-should-i-do-with-the-output"]], "Word": [[6, "word"]], "docTR Notebooks": [[10, null]], "docTR Vocabs": [[5, "id62"]], "docTR: Document Text Recognition": [[4, null]], "doctr.datasets": [[5, null], [5, "datasets"]], "doctr.io": [[6, null]], "doctr.models": [[7, null]], "doctr.models.classification": [[7, "doctr-models-classification"]], "doctr.models.detection": [[7, "doctr-models-detection"]], "doctr.models.factory": [[7, "doctr-models-factory"]], "doctr.models.recognition": [[7, "doctr-models-recognition"]], "doctr.models.zoo": [[7, "doctr-models-zoo"]], "doctr.transforms": [[8, null]], "doctr.utils": [[9, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]], "v0.7.0 (2024-09-09)": [[0, "v0-7-0-2024-09-09"]]}, "docnames": ["changelog", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[6, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[6, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[8, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[5, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[8, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[8, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[5, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[7, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[5, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[7, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[7, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[6, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[7, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[5, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[5, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[6, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[6, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[5, "doctr.datasets.encode_sequences", false]], "from_hub() (in module doctr.models.factory)": [[7, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[6, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[6, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[6, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[5, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[8, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[8, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[5, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[5, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[5, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[5, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[5, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[7, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[8, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[6, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[7, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet34() (in module doctr.models.detection)": [[7, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[7, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[7, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[7, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[5, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_orientation() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_small_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[8, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[7, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[5, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[8, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[6, "doctr.io.Page", false]], "parseq() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[7, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[8, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[8, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[8, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[8, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[8, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[8, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[8, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[8, "doctr.transforms.RandomJpegQuality", false]], "randomrotate (class in doctr.transforms)": [[8, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[8, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[8, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[6, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[6, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[6, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[6, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[5, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[8, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[6, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[6, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[5, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[9, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[9, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[9, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[9, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[5, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[5, "doctr.datasets.SVT", false]], "synthesize_page() (in module doctr.utils.visualization)": [[9, "doctr.utils.visualization.synthesize_page", false]], "synthtext (class in doctr.datasets)": [[5, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.TextMatch", false]], "textnet_base() (in module doctr.models.classification)": [[7, "doctr.models.classification.textnet_base", false]], "textnet_small() (in module doctr.models.classification)": [[7, "doctr.models.classification.textnet_small", false]], "textnet_tiny() (in module doctr.models.classification)": [[7, "doctr.models.classification.textnet_tiny", false]], "togray (class in doctr.transforms)": [[8, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[9, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[9, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[9, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[9, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[7, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[9, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[7, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[7, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.vitstr_small", false]], "wildreceipt (class in doctr.datasets)": [[5, "doctr.datasets.WILDRECEIPT", false]], "word (class in doctr.io)": [[6, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[5, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[5, 0, 1, "", "CORD"], [5, 0, 1, "", "CharacterGenerator"], [5, 0, 1, "", "DetectionDataset"], [5, 0, 1, "", "DocArtefacts"], [5, 0, 1, "", "FUNSD"], [5, 0, 1, "", "IC03"], [5, 0, 1, "", "IC13"], [5, 0, 1, "", "IIIT5K"], [5, 0, 1, "", "IIITHWS"], [5, 0, 1, "", "IMGUR5K"], [5, 0, 1, "", "MJSynth"], [5, 0, 1, "", "OCRDataset"], [5, 0, 1, "", "RecognitionDataset"], [5, 0, 1, "", "SROIE"], [5, 0, 1, "", "SVHN"], [5, 0, 1, "", "SVT"], [5, 0, 1, "", "SynthText"], [5, 0, 1, "", "WILDRECEIPT"], [5, 0, 1, "", "WordGenerator"], [5, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[5, 0, 1, "", "DataLoader"]], "doctr.io": [[6, 0, 1, "", "Artefact"], [6, 0, 1, "", "Block"], [6, 0, 1, "", "Document"], [6, 0, 1, "", "DocumentFile"], [6, 0, 1, "", "Line"], [6, 0, 1, "", "Page"], [6, 0, 1, "", "Word"], [6, 1, 1, "", "decode_img_as_tensor"], [6, 1, 1, "", "read_html"], [6, 1, 1, "", "read_img_as_numpy"], [6, 1, 1, "", "read_img_as_tensor"], [6, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[6, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[6, 2, 1, "", "from_images"], [6, 2, 1, "", "from_pdf"], [6, 2, 1, "", "from_url"]], "doctr.io.Page": [[6, 2, 1, "", "show"]], "doctr.models": [[7, 1, 1, "", "kie_predictor"], [7, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[7, 1, 1, "", "crop_orientation_predictor"], [7, 1, 1, "", "magc_resnet31"], [7, 1, 1, "", "mobilenet_v3_large"], [7, 1, 1, "", "mobilenet_v3_large_r"], [7, 1, 1, "", "mobilenet_v3_small"], [7, 1, 1, "", "mobilenet_v3_small_orientation"], [7, 1, 1, "", "mobilenet_v3_small_r"], [7, 1, 1, "", "resnet18"], [7, 1, 1, "", "resnet31"], [7, 1, 1, "", "resnet34"], [7, 1, 1, "", "resnet50"], [7, 1, 1, "", "textnet_base"], [7, 1, 1, "", "textnet_small"], [7, 1, 1, "", "textnet_tiny"], [7, 1, 1, "", "vgg16_bn_r"], [7, 1, 1, "", "vit_b"], [7, 1, 1, "", "vit_s"]], "doctr.models.detection": [[7, 1, 1, "", "db_mobilenet_v3_large"], [7, 1, 1, "", "db_resnet50"], [7, 1, 1, "", "detection_predictor"], [7, 1, 1, "", "linknet_resnet18"], [7, 1, 1, "", "linknet_resnet34"], [7, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[7, 1, 1, "", "from_hub"], [7, 1, 1, "", "login_to_hub"], [7, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[7, 1, 1, "", "crnn_mobilenet_v3_large"], [7, 1, 1, "", "crnn_mobilenet_v3_small"], [7, 1, 1, "", "crnn_vgg16_bn"], [7, 1, 1, "", "master"], [7, 1, 1, "", "parseq"], [7, 1, 1, "", "recognition_predictor"], [7, 1, 1, "", "sar_resnet31"], [7, 1, 1, "", "vitstr_base"], [7, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[8, 0, 1, "", "ChannelShuffle"], [8, 0, 1, "", "ColorInversion"], [8, 0, 1, "", "Compose"], [8, 0, 1, "", "GaussianBlur"], [8, 0, 1, "", "GaussianNoise"], [8, 0, 1, "", "LambdaTransformation"], [8, 0, 1, "", "Normalize"], [8, 0, 1, "", "OneOf"], [8, 0, 1, "", "RandomApply"], [8, 0, 1, "", "RandomBrightness"], [8, 0, 1, "", "RandomContrast"], [8, 0, 1, "", "RandomCrop"], [8, 0, 1, "", "RandomGamma"], [8, 0, 1, "", "RandomHorizontalFlip"], [8, 0, 1, "", "RandomHue"], [8, 0, 1, "", "RandomJpegQuality"], [8, 0, 1, "", "RandomRotate"], [8, 0, 1, "", "RandomSaturation"], [8, 0, 1, "", "RandomShadow"], [8, 0, 1, "", "Resize"], [8, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[9, 0, 1, "", "DetectionMetric"], [9, 0, 1, "", "LocalizationConfusion"], [9, 0, 1, "", "OCRMetric"], [9, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.visualization": [[9, 1, 1, "", "synthesize_page"], [9, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [1, 6, 7, 9, 13], "0": [1, 3, 5, 8, 9, 11, 14, 16], "00": 16, "01": 16, "0123456789": 5, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 5, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 5, "02": [], "02562": 7, "03": 16, "035": 16, "0361328125": 16, "04": [], "05": 16, "06": 16, "06640625": 16, "07": 16, "08": [8, 16], "09": 16, "0966796875": 16, "1": [3, 5, 6, 7, 8, 9, 11, 14, 16], "10": [5, 9, 16], "100": [5, 8, 9, 14, 16], "1000": 16, "101": 5, "1024": [7, 9, 11, 16], "104": 5, "106": 5, "108": 5, "1095": 14, "11": 16, "110": 9, "1107": 14, "114": 5, "115": [], "1156": 14, "116": 5, "118": 5, "11800h": 16, "11th": 16, "12": [3, 16], "120": 5, "123": 5, "126": 5, "1268": 14, "128": [7, 11, 15, 16], "13": [9, 16], "130": 5, "13068": 14, "131": 5, "1337891": 14, "1357421875": 16, "1396484375": 16, "14": 16, "1420": 16, "14470v1": 5, "149": 14, "15": 16, "150": [9, 16], "154": [], "1552": 16, "16": [7, 15, 16], "160": [], "1630859375": 16, "1684": 16, "16x16": 7, "17": 16, "1778": 16, "1782": 16, "18": 7, "185546875": 16, "19": [], "1900": 16, "1910": 7, "19342": 14, "19370": 14, "195": 5, "19598": 14, "199": 16, "1999": 16, "1m": [], "2": [3, 4, 5, 6, 8, 16], "20": 16, "200": 9, "2000": 14, "2003": [4, 5], "2012": 5, "2013": [4, 5], "2015": 5, "2019": 4, "2021": [], "2023": [], "207901": 14, "21": 16, "2103": 5, "2186": 14, "21888": 14, "22": 16, "224": [7, 8], "225": 8, "22672": 14, "229": [8, 14], "23": 16, "233": 14, "234": 5, "236": [], "24": 16, "246": 14, "249": 14, "25": 16, "2504": 16, "255": [6, 7, 8, 9, 16], "256": 7, "257": 14, "26": 16, "26032": 14, "264": 11, "27": 16, "2700": 14, "2710": 16, "2749": 11, "28": 16, "287": 11, "29": 16, "296": 11, "299": 11, "2d": 16, "3": [3, 4, 6, 7, 8, 9, 15, 16], "30": 16, "300": 14, "3000": 14, "301": 11, "30595": 16, "30ghz": 16, "31": 7, "32": [5, 7, 8, 11, 14, 15, 16], "3232421875": 16, "33": [8, 16], "33402": 14, "33608": 14, "34": [7, 16], "340": 16, "3456": 16, "35": [], "3515625": 16, "36": [], "360": 14, "37": [5, 16], "38": 16, "39": 16, "4": [7, 8, 9, 16], "40": 16, "406": 8, "41": 16, "42": 16, "43": 16, "44": 16, "45": 16, "456": 8, "46": 16, "47": 16, "472": 14, "48": [5, 16], "485": 8, "49": 16, "49377": 14, "5": [5, 8, 9, 16], "50": [7, 14, 16], "51": 16, "51171875": 16, "512": 7, "52": [5, 16], "529": 16, "53": 16, "533": [], "54": 16, "540": 16, "5478515625": 16, "55": 16, "56": 16, "57": 16, "58": 16, "580": 16, "5810546875": 16, "583": 16, "59": 16, "595": [], "597": 16, "5k": [4, 5], "5m": [], "6": [8, 16], "60": 8, "600": [7, 9, 16], "61": 16, "611": [], "62": 16, "625": [], "626": 14, "629": [], "63": 16, "630": [], "64": [7, 8, 16], "640": [], "641": 16, "647": 14, "65": 16, "66": 16, "660": [], "664": [], "666": [], "67": 16, "672": [], "68": 16, "689": [], "69": 16, "693": 11, "694": 11, "695": 11, "6m": [], "7": 16, "70": [9, 16], "700": [], "701": [], "702": [], "707470": 14, "71": 16, "7100000": 14, "713": [], "7141797": 14, "7149": 14, "72": 16, "72dpi": 6, "73": 16, "73257": 14, "733": [], "74": 16, "745": [], "75": [8, 16], "753": [], "7581382": 14, "76": 16, "77": 16, "772": 11, "772875": 14, "78": 16, "780": [], "781": [], "783": [], "785": 11, "789": [], "79": 16, "793533": 14, "796": 14, "798": 11, "7m": [], "8": [3, 7, 8, 16], "80": 16, "800": [7, 9, 14, 16], "81": 16, "817": [], "82": 16, "8275l": [], "83": 16, "830": [], "84": 16, "849": 14, "85": 16, "8564453125": 16, "857": 16, "85875": 14, "86": 16, "860": [], "8603515625": 16, "862": [], "863": [], "87": 16, "8707": 14, "875": [], "88": 16, "89": 16, "8m": [], "9": 16, "90": 16, "90k": 5, "90kdict32px": 5, "91": 16, "913": [], "914085328578949": 16, "917": [], "92": 16, "921": [], "93": 16, "94": [5, 16], "95": [9, 16], "9578408598899841": 16, "96": 16, "97": [], "98": 16, "99": 16, "9949972033500671": 16, "A": [1, 2, 4, 5, 6, 7, 10, 15], "And": [], "As": 2, "Be": 16, "Being": 1, "By": 12, "For": [1, 2, 3, 11, 16], "If": [2, 3, 6, 7, 11, 16], "In": [2, 5, 14], "It": [8, 13, 15], "Its": [4, 7], "No": [1, 16], "Of": 5, "Or": [], "The": [1, 2, 5, 6, 9, 12, 16], "Then": 7, "To": [2, 3, 12, 13, 16], "_": [1, 5, 7], "__call__": 16, "_build": 2, "_i": 9, "ab": 5, "abc": [], "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 5, "abdef": [5, 14], "abl": [14, 16], "about": [1, 14, 16], "abov": 16, "abstract": [], "abstractdataset": 5, "abus": 1, "accent": [], "accept": 1, "access": [4, 6, 14, 16], "account": [1, 13], "accur": 16, "accuraci": 9, "achiev": 15, "act": 1, "action": 1, "activ": 4, "ad": [2, 7, 8], "adapt": 1, "add": [8, 9, 13, 16], "add_hook": 16, "add_label": 9, "addit": [2, 3, 6], "addition": [2, 16], "address": [1, 6], "adjust": 8, "advanc": 1, "advantag": 15, "advis": 2, "aesthet": [4, 5], "affect": 1, "after": [13, 16], "ag": 1, "again": 7, "aggreg": [9, 14], "aggress": 1, "align": [1, 6], "all": [1, 2, 5, 6, 8, 9, 14, 16], "allow": 1, "along": 16, "alreadi": 2, "also": [1, 7, 13, 14, 16], "alwai": 14, "an": [1, 2, 4, 5, 6, 7, 9, 15, 16], "analysi": 6, "ancient_greek": 5, "andrej": [], "angl": [6, 8], "ani": [1, 5, 6, 7, 8, 9, 16], "annot": 5, "anot": 14, "anoth": [3, 7, 11, 14], "answer": 1, "anyascii": [], "anyon": 4, "anyth": [], "api": [2, 4], "apolog": 1, "apologi": 1, "app": 2, "appear": 1, "appli": [1, 5, 8], "applic": [4, 7], "appoint": 1, "appreci": 13, "appropri": [1, 2, 16], "ar": [1, 2, 3, 5, 6, 8, 9, 10, 14, 16], "arab": 5, "arabic_diacrit": 5, "arabic_lett": 5, "arabic_punctu": 5, "arbitrarili": 7, "arch": [7, 13], "architectur": [4, 7, 13], "archiv": [], "area": 16, "arg": [], "argument": [5, 6, 7, 9, 16], "around": 1, "arrai": [6, 8, 9], "art": 4, "artefact": [9, 10, 16], "artefact_typ": 6, "articl": [], "artifici": [4, 5], "arxiv": [5, 7], "as_imag": [], "asarrai": 9, "ascii_lett": 5, "aspect": [4, 7, 8, 16], "assess": 9, "assign": 9, "associ": 6, "assum": 7, "assume_straight_pag": [7, 16], "astyp": [7, 9, 16], "attack": 1, "attend": [4, 7], "attent": [1, 7], "autoclass": [], "autom": 4, "automat": 16, "autoregress": [4, 7], "avail": [1, 4, 8], "averag": [8, 16], "avoid": [1, 3], "aw": [4, 16], "awar": 16, "azur": 16, "b": [7, 9, 16], "b_j": 9, "back": 2, "backbon": 7, "backend": 16, "background": 14, "bangla": [], "bar": [], "bar_cod": 14, "baranovskij": [], "base": [4, 7], "baselin": [4, 7, 16], "batch": [5, 7, 8, 14, 16], "batch_siz": [5, 11, 14, 15], "bblanchon": [], "bbox": 16, "becaus": 12, "been": [2, 9, 14, 16], "befor": [5, 7, 8, 16], "begin": 9, "behavior": [1, 16], "being": [9, 16], "belong": 16, "benchmark": 16, "best": 1, "beta": [], "better": [10, 16], "between": [8, 9, 16], "bgr": 6, "bilinear": 8, "bin_thresh": 16, "binar": [4, 7, 16], "binari": [6, 15, 16], "bit": 15, "blank": 9, "block": [9, 16], "block_1_1": 16, "blue": 9, "blur": 8, "bmvc": 5, "bn": 13, "bodi": [1, 16], "bool": [5, 6, 7, 8, 9], "boolean": [7, 16], "both": [4, 5, 8, 14, 16], "bottom": [7, 16], "bound": [5, 6, 7, 8, 9, 16], "box": [5, 6, 7, 8, 9, 14, 16], "box_thresh": 16, "brew": 3, "bright": 8, "broadcast": 9, "browser": [2, 4], "build": [2, 3], "built": 2, "byte": [6, 16], "c": [6, 9], "c5": [], "c_j": 9, "cach": [2, 5, 12], "cache_sampl": 5, "cairo": 3, "call": [], "callabl": [5, 8], "can": [2, 3, 11, 12, 13, 14, 16], "capabl": [2, 10, 16], "case": [5, 9], "cf": 16, "cfg": 16, "challeng": 5, "challenge2_test_task12_imag": 5, "challenge2_test_task1_gt": 5, "challenge2_training_task12_imag": 5, "challenge2_training_task1_gt": 5, "chang": 12, "changelog": [], "channel": [1, 2, 6, 8], "channel_prior": [], "channelshuffl": 8, "charact": [4, 5, 6, 9, 14, 16], "charactergener": [5, 14], "characterist": 1, "charg": 16, "charset": 16, "chart": 6, "check": [2, 13, 16], "checkpoint": 7, "chip": 3, "christian": [], "ci": 2, "clarifi": 1, "clariti": 1, "class": [1, 5, 6, 8, 9, 16], "class_nam": 11, "classif": 14, "classif_mobilenet_v3_smal": 7, "classmethod": 6, "clear": 2, "clone": 3, "close": 2, "co": 13, "code": [4, 6], "codecov": 2, "colab": 10, "collate_fn": 5, "collect": 6, "color": [8, 9], "colorinvers": 8, "column": 6, "com": [1, 3, 6, 7, 13], "combin": 16, "come": 15, "command": 2, "comment": 1, "commit": 1, "common": [1, 8, 9, 15], "commun": 1, "compar": 4, "comparison": [9, 16], "competit": 5, "compil": [10, 16], "complaint": 1, "complementari": 9, "complet": 2, "compon": 16, "compos": [5, 16], "comprehens": 16, "comput": [5, 9, 15, 16], "conf_threshold": [], "confid": [6, 9, 16], "config": 7, "configur": 7, "confus": 9, "consecut": [8, 16], "consequ": 1, "consid": [1, 2, 5, 6, 9, 16], "consist": 16, "consolid": [4, 5], "constant": 8, "construct": 1, "consum": 9, "contact": 1, "contain": [5, 14], "content": [5, 6, 9, 16], "context": 7, "contib": [], "continu": 1, "contrast": 8, "contrast_factor": 8, "contrib": [], "contribut": 1, "contributor": 2, "conv_sequ": [], "convers": 6, "convert": [6, 8], "convert_page_to_numpi": [], "convert_to_fp16": [], "convert_to_tflit": [], "convolut": 7, "cool": [], "coordin": [6, 16], "cord": [4, 5, 14, 16], "core": [9, 16], "corner": 16, "correct": 8, "correspond": [3, 6, 16], "could": 1, "counterpart": 9, "cover": 2, "coverag": 2, "cpu": [4, 11], "creat": 13, "crnn": [4, 7, 13], "crnn_mobilenet_v3_larg": [7, 13, 16], "crnn_mobilenet_v3_smal": [7, 15, 16], "crnn_resnet31": [], "crnn_vgg16_bn": [7, 11, 13, 16], "crop": [7, 8, 14, 16], "crop_orient": [], "crop_orientation_predictor": 7, "crop_param": [], "croporientationpredictor": 7, "cuda": 15, "currenc": 5, "current": [2, 16], "custom": [13, 16], "custom_crop_orientation_model": [], "custom_page_orientation_model": [], "customhook": 16, "cvit": 4, "czczup": 7, "czech": 5, "d": [5, 14], "daili": [], "danish": 5, "data": [4, 5, 6, 8, 9, 11, 13], "dataload": 14, "dataset": [7, 11, 16], "dataset_info": 5, "date": [11, 16], "db": 13, "db_crnn_resnet": [], "db_crnn_vgg": [], "db_mobilenet_v3_larg": [7, 13, 16], "db_resnet34": 16, "db_resnet50": [7, 11, 13, 16], "db_resnet50_rot": [], "db_sar_resnet": [], "db_sar_vgg": [], "dbnet": [4, 7], "deal": [], "decis": 1, "decod": 6, "decode_img_as_tensor": 6, "dedic": [], "deem": 1, "deep": [7, 16], "def": 16, "default": [6, 9, 11, 12, 16], "defer": 14, "defin": [9, 15], "deform": [], "degre": 8, "degress": 6, "delet": 2, "delimit": 16, "delta": 8, "demo": [2, 4], "demonstr": 1, "depend": [2, 3, 4], "deploi": 2, "deploy": 4, "derogatori": 1, "describ": [7, 9], "descript": 10, "design": 8, "desir": 6, "det_arch": [7, 11, 13, 15], "det_b": [], "det_model": [11, 13], "det_param": 11, "det_predictor": [11, 16], "detail": [11, 16], "detect": [5, 9, 10, 11], "detect_languag": 7, "detect_orient": 7, "detection_predictor": [7, 16], "detection_task": [], "detectiondataset": [5, 14], "detectionmetr": 9, "detectionpredictor": [7, 11], "detector": 7, "deterior": 7, "determin": 1, "dev": [2, 12], "develop": 3, "developp": [], "deviat": 8, "devic": 15, "dict": [6, 9, 16], "dictionari": [6, 9], "differ": 1, "differenti": [4, 7], "digit": [4, 5, 14], "dimens": [6, 9, 16], "dimension": 8, "direct": 5, "directli": [13, 16], "directori": [2, 12], "disabl": [1, 12, 16], "disable_crop_orient": [], "disable_page_orient": [], "disclaim": 16, "discuss": 2, "disk": [], "disparag": 1, "displai": [6, 9], "display_artefact": 9, "distanc": [], "distribut": 8, "div": 16, "divers": 1, "divid": 6, "do": [2, 3, 7], "doc": [2, 6, 15, 16], "docartefact": [5, 14], "docstr": 2, "doctr": [3, 11, 12, 13, 14, 16], "doctr_cache_dir": 12, "doctr_multiprocessing_dis": 12, "document": [5, 7, 9, 10, 14, 16], "documentbuild": 16, "documentfil": [6, 13], "doesn": [], "don": [11, 16], "done": 8, "download": [5, 14], "downsiz": 7, "draw": [8, 9], "draw_proba": 9, "drop": 5, "drop_last": 5, "dtype": [6, 7, 8, 9, 15], "dual": [4, 5], "dummi": 13, "dummy_img": 16, "dummy_input": 15, "dure": 1, "dutch": 5, "dynam": 5, "dynamic_seq_length": 5, "e": [1, 2, 3, 6, 7], "each": [4, 5, 6, 7, 8, 9, 14, 16], "eas": 2, "easi": [4, 9, 13], "easier": [], "easili": [6, 9, 11, 13, 14, 16], "econom": 1, "edit": 1, "educ": 1, "effect": [], "effici": [2, 4, 5, 7], "either": [9, 16], "element": [5, 6, 7, 9, 16], "els": 2, "email": 1, "empathi": 1, "en": 16, "enabl": [5, 6], "enclos": 6, "encod": [4, 5, 6, 7, 16], "encode_sequ": 5, "encount": 2, "encrypt": 6, "end": [4, 5, 7, 9], "english": [5, 14], "enough": [2, 16], "ensur": 2, "entir": [], "entri": 5, "environ": [1, 12], "eo": 5, "equiv": 16, "error": [], "estim": 7, "etc": 6, "ethnic": 1, "evalu": [14, 16], "event": 1, "everyon": 1, "everyth": [2, 16], "exact": [9, 16], "exactmatch": [], "exampl": [1, 2, 4, 5, 7, 13, 16], "exchang": 15, "exclud": [], "execut": 16, "exist": 13, "expand": 8, "expect": [6, 8, 9], "experi": 1, "explan": [1, 16], "explicit": 1, "exploit": [4, 7], "export": [6, 7, 9, 10, 16], "export_as_straight_box": [7, 16], "export_as_xml": 16, "export_model_to_onnx": 15, "express": [1, 8], "extens": 6, "extern": [1, 14], "extra": 3, "extract": [4, 5], "extract_arch": [], "extractor": 7, "f_": 9, "f_a": 9, "factor": 8, "fair": 1, "fairli": 1, "fals": [5, 6, 7, 8, 9, 11, 16], "famili": 9, "faq": 1, "fascan": 13, "fast": [4, 5, 7], "fast_bas": [], "fast_smal": [], "fast_tini": [], "faster": [7, 15], "fasterrcnn_mobilenet_v3_large_fpn": 7, "favorit": 16, "featur": [3, 7, 9, 10], "feed": [], "feedback": 1, "feel": [2, 13], "felix92": 13, "few": [3, 15, 16], "figsiz": 9, "figur": 9, "file": [2, 5], "file_hash": [], "file_nam": [], "final": 7, "find": [2, 3, 14], "fine": [], "finnish": 5, "first": [2, 5], "firsthand": 5, "fit": [7, 16], "fitz": [], "flag": 16, "flexibl": [], "flip": 8, "float": [6, 8, 9, 15], "float32": [6, 7, 8, 15], "fn": 8, "focu": 13, "focus": [1, 5], "folder": 5, "follow": [1, 2, 3, 5, 8, 9, 11, 12, 13, 16], "font": [5, 9], "font_famili": [5, 9], "font_siz": 9, "foral": 9, "forc": 2, "forg": [], "form": [4, 5, 16], "format": [6, 9, 11, 14, 15, 16], "forpost": [4, 5], "forum": 2, "found": [], "fp": [], "fp16": 15, "frac": 9, "frame": [], "framework": [3, 13, 14, 16], "free": [1, 2, 13], "french": [5, 11, 13, 16], "friendli": 4, "from": [1, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16], "from_hub": [7, 13], "from_imag": [6, 13], "from_pdf": 6, "from_url": 6, "full": [5, 9, 16], "fulli": [], "function": [5, 8, 9], "funsd": [4, 5, 14, 16], "further": 14, "futur": 5, "g": [6, 7], "g_": 9, "g_x": 9, "gallagh": [], "gamma": 8, "gaussian": 8, "gaussianblur": 8, "gaussiannois": 8, "gdk": 3, "gen": 16, "gender": 1, "gener": [2, 4, 7], "generic_cyrillic_lett": [], "geometri": [4, 6, 16], "geq": 9, "german": [5, 11, 13], "get": 16, "get_artefact": [], "get_word": [], "gettextword": [], "git": 13, "github": [2, 3, 7, 13], "give": 1, "given": [5, 6, 8, 9, 16], "global": 7, "go": 16, "good": 15, "googl": 2, "googlevis": 4, "gpu": [4, 15], "gracefulli": 1, "graph": [4, 5, 6], "grayscal": 8, "ground": 9, "groung": 9, "group": [4, 16], "gt": 9, "gt_box": 9, "gt_label": 9, "gtk": 3, "guid": 2, "guidanc": 14, "gvision": 16, "h": [6, 7, 8], "h_": 9, "ha": [2, 5, 9, 14], "half": [], "handl": [14, 16], "handwrit": 5, "handwritten": 14, "harass": 1, "hardwar": [], "harm": 1, "hat": 9, "have": [1, 2, 9, 11, 13, 14, 16], "head": [7, 16], "healthi": 1, "hebrew": 5, "height": 6, "hello": [9, 16], "help": 15, "here": [3, 8, 10, 14, 16], "hf": 7, "hf_hub_download": 7, "high": 6, "higher": [3, 5, 16], "hindi": [], "hindi_digit": 5, "hocr": 16, "homebrew": 3, "hook": 16, "horizont": [6, 8], "hous": 5, "how": [2, 11, 13, 14], "howev": 14, "hsv": 8, "html": [1, 2, 6, 16], "http": [1, 3, 5, 6, 7, 13, 16], "hub": 7, "hue": 8, "huggingfac": 7, "hw": 5, "i": [1, 2, 5, 6, 7, 8, 9, 12, 13, 14, 15], "i7": 16, "ibrahimov": [], "ic03": [4, 5, 14], "ic13": [4, 5, 14], "icdar": [4, 5], "icdar2019": 5, "id": 16, "ident": 1, "identifi": 4, "ignor": [], "ignore_acc": [], "ignore_cas": [], "iiit": [4, 5], "iiit5k": [5, 14], "iiithw": [4, 5, 14], "imag": [4, 5, 6, 7, 8, 9, 13, 14, 16], "imagenet": 7, "imageri": 1, "images_90k_norm": 5, "img": [5, 8, 14], "img_cont": 6, "img_fold": [5, 14], "img_path": 6, "img_transform": 5, "imgur5k": [4, 5, 14], "imgur5k_annot": 5, "imlist": 5, "impact": 1, "implement": [5, 6, 7, 8, 9, 16], "import": [5, 6, 7, 8, 9, 11, 13, 14, 15, 16], "improv": 7, "inappropri": 1, "incid": 1, "includ": [1, 3, 5, 14, 15], "inclus": 1, "increas": 8, "independ": [], "index": [2, 6], "indic": 9, "individu": 1, "infer": [4, 7, 8], "inform": [1, 2, 4, 5, 14], "inherit": [], "input": [2, 6, 7, 8, 15, 16], "input_crop": 7, "input_pag": [7, 9, 16], "input_shap": 15, "input_t": [], "input_tensor": 7, "inspir": [1, 8], "instal": 13, "instanc": [1, 16], "instanti": [7, 16], "instead": [5, 6, 7], "insult": 1, "int": [5, 6, 8, 9], "int64": [8, 9], "integ": 9, "integr": [4, 13, 14], "intel": 16, "interact": [1, 6, 9], "interfac": 13, "interoper": 15, "interpol": 8, "interpret": [5, 6], "intersect": 9, "invert": 8, "investig": 1, "invis": 1, "invoic": [], "involv": [1, 16], "io": 13, "iou": 9, "iou_thresh": 9, "iou_threshold": [], "irregular": [4, 7, 14], "isn": 5, "issu": [1, 2, 13], "italian": 5, "iter": [5, 8, 14, 16], "its": [6, 7, 8, 9, 14, 16], "itself": [7, 13], "j": 9, "jame": [], "job": 2, "join": 2, "jpeg": 8, "jpegqual": 8, "jpg": [5, 6, 13], "json": [5, 14, 16], "json_output": 16, "jump": 2, "just": 1, "kei": [4, 5], "kera": [7, 15], "kernel": [7, 8], "kernel_s": [], "kernel_shap": 8, "keywoard": 7, "keyword": [5, 6, 7, 9], "kie": [7, 11], "kie_predictor": [7, 11], "kiepredictor": 7, "kind": 1, "know": 2, "kwarg": [5, 6, 7, 9], "l": 9, "l_j": 9, "label": [5, 8, 9, 14], "label_fil": [5, 14], "label_fold": 5, "label_path": [5, 14], "labels_path": [5, 14], "ladder": 1, "lambda": 8, "lambdatransform": 8, "lang": 16, "languag": [1, 4, 5, 6, 7, 13, 16], "larg": [7, 13], "largest": 9, "last": [3, 5], "latenc": 7, "later": 2, "latest": [3, 16], "latin": 5, "layer": 15, "layout": 16, "lead": 1, "leader": 1, "learn": [1, 4, 7, 15, 16], "least": 3, "left": [9, 16], "legacy_french": 5, "length": [5, 16], "less": [15, 16], "let": [], "letter": [], "level": [1, 5, 9, 16], "levenshtein": [], "leverag": 10, "lf": 13, "libffi": 3, "librari": [2, 3, 10, 11], "light": 4, "lightweight": [], "like": 1, "limits_": 9, "line": [4, 7, 9, 16], "line_1_1": 16, "link": 11, "linknet": [4, 7], "linknet16": [], "linknet_resnet18": [7, 11, 16], "linknet_resnet18_rot": [], "linknet_resnet34": [7, 15, 16], "linknet_resnet50": [7, 16], "linux": 3, "list": [5, 6, 8, 9, 13], "ll": 9, "load": [4, 5, 7], "load_state_dict": 11, "load_weight": 11, "loader": [], "loc_pr": 16, "local": [2, 4, 5, 7, 9, 14, 16], "localis": 5, "localizationconfus": 9, "locat": [2, 6, 16], "login": 7, "login_to_hub": [7, 13], "logo": [6, 14], "love": 13, "lower": [8, 9, 16], "m": [2, 9, 16], "m1": 3, "macbook": 3, "machin": 15, "maco": 3, "made": 4, "magc_resnet31": 7, "mai": [1, 2], "mail": 1, "main": 10, "maintain": 4, "mainten": 2, "make": [1, 2, 9, 12, 13, 15, 16], "mani": [14, 16], "manipul": 16, "map": [5, 7], "map_loc": 11, "mask_shap": 9, "master": [4, 7, 16], "match": [9, 16], "mathcal": 9, "matplotlib": [6, 9], "max": [5, 8, 9], "max_angl": 8, "max_area": 8, "max_char": [5, 14], "max_delta": 8, "max_dist": [], "max_gain": 8, "max_gamma": 8, "max_qual": 8, "max_ratio": 8, "maximum": [5, 8], "maxval": [7, 8], "mbox": 9, "mean": [8, 9, 11], "meaniou": 9, "meant": [6, 15], "measur": 16, "media": 1, "median": 7, "meet": 11, "member": 1, "memori": [9, 12, 15], "mention": 16, "merg": 5, "messag": 2, "meta": 16, "metadata": 15, "metal": 3, "method": [6, 8, 16], "metric": [9, 16], "middl": 16, "might": [15, 16], "min": 8, "min_area": 8, "min_char": [5, 14], "min_gain": 8, "min_gamma": 8, "min_qual": 8, "min_ratio": 8, "min_val": 8, "minde": [1, 3, 4, 7], "minim": [2, 4], "minimalist": 7, "minimum": [3, 5, 8, 9, 16], "minval": 8, "miss": 3, "mistak": 1, "mix": [], "mixed_float16": 15, "mixed_precis": 15, "mjsynth": [4, 5, 14], "mnt": 5, "mobilenet": [7, 13], "mobilenet_v3_larg": 7, "mobilenet_v3_large_r": 7, "mobilenet_v3_smal": 7, "mobilenet_v3_small_crop_orient": [], "mobilenet_v3_small_orient": 7, "mobilenet_v3_small_page_orient": [], "mobilenet_v3_small_r": 7, "mobilenetv3": 7, "modal": [4, 5], "mode": 3, "model": [5, 9, 12, 14], "model_nam": [7, 13, 15], "model_path": 15, "moder": 1, "modif": 2, "modifi": [7, 12, 16], "modul": [6, 7, 8, 9, 16], "moment": [], "more": [2, 9, 14, 16], "moscardi": [], "most": 16, "mozilla": 1, "multi": [4, 7], "multilingu": [5, 13], "multipl": [5, 6, 8, 16], "multipli": 8, "multiprocess": 12, "my": 7, "my_awesome_model": 13, "my_hook": 16, "n": [5, 9], "na": [], "name": [5, 7, 15, 16], "nation": 1, "natur": [1, 4, 5], "nb": [], "ndarrai": [5, 6, 8, 9], "necessari": [3, 11, 12], "need": [2, 3, 5, 9, 11, 12, 13, 16], "neg": 8, "nest": 16, "nestedobject": [], "netraj": [], "network": [4, 5, 7, 15], "neural": [4, 5, 7, 15], "new": [2, 9], "newer": [], "next": [5, 14], "nois": 8, "noisi": [4, 5], "non": [4, 5, 6, 7, 8, 9], "none": [5, 6, 7, 8, 9, 16], "normal": [7, 8], "norwegian": 5, "note": [0, 2, 5, 7, 13, 15], "now": 2, "np": [7, 8, 9, 16], "num_output_channel": 8, "num_sampl": [5, 14], "num_work": 5, "number": [5, 8, 9, 16], "numpi": [6, 7, 9, 16], "o": 3, "obb": [], "obj_detect": 13, "object": [5, 9, 10, 16], "objectness_scor": [], "oblig": 1, "obtain": 16, "occupi": 15, "ocr": [4, 5, 7, 9, 13, 14], "ocr_carea": 16, "ocr_db_crnn": 9, "ocr_lin": 16, "ocr_pag": 16, "ocr_par": 16, "ocr_predictor": [7, 11, 13, 15, 16], "ocrdataset": [5, 14], "ocrmetr": 9, "ocrpredictor": [7, 11], "ocrx_word": 16, "offens": 1, "offici": [1, 7], "offlin": 1, "offset": 8, "onc": 16, "one": [2, 5, 7, 8, 11, 13, 16], "oneof": 8, "ones": [5, 8, 9], "onli": [2, 7, 8, 9, 13, 14, 15, 16], "onlin": 1, "onnx": [], "onnxruntim": [], "onnxtr": [], "opac": 8, "opacity_rang": 8, "open": [1, 2, 13, 15], "opinion": 1, "optic": [4, 16], "optim": 4, "option": [5, 7, 11], "order": [2, 5, 6, 8], "org": [1, 5, 7, 16], "organ": 6, "orient": [1, 6, 7, 16], "orientationpredictor": [], "other": [1, 2], "otherwis": [1, 6, 9], "our": [2, 7, 16], "out": [2, 7, 8, 9, 16], "outpout": 16, "output": [6, 8, 15], "output_s": [6, 8], "outsid": 12, "over": [3, 5, 9, 16], "overal": [1, 7], "overlai": 6, "overview": [], "overwrit": [], "overwritten": 13, "own": 4, "p": [8, 9, 16], "packag": [2, 4, 9, 12, 14], "pad": [5, 7, 8, 16], "page": [3, 5, 7, 9, 16], "page1": 6, "page2": 6, "page_1": 16, "page_idx": [6, 16], "page_orientation_predictor": [], "page_param": [], "pair": 9, "pango": 3, "paper": 7, "par_1_1": 16, "paragraph": 16, "paragraph_break": 16, "parallel": [], "param": [8, 16], "paramet": [4, 6, 7, 15], "pars": [4, 5], "parseq": [4, 7, 13, 16], "part": [5, 8, 16], "parti": 3, "partial": 16, "particip": 1, "pass": [5, 6, 7, 16], "password": 6, "patch": [7, 9], "path": [5, 6, 14], "path_to_checkpoint": 11, "path_to_custom_model": [], "path_to_pt": 11, "patil": [], "pattern": 1, "pdf": [6, 7, 10], "pdfpage": 6, "peopl": 1, "per": [8, 16], "perform": [4, 6, 7, 8, 9, 12, 15, 16], "period": 1, "permiss": 1, "permut": [4, 7], "persian_lett": 5, "person": [1, 14], "phase": 16, "photo": 14, "physic": [1, 6], "pick": 8, "pictur": 6, "pip": [2, 3], "pipelin": 16, "pixbuf": 3, "pixel": [6, 8, 16], "platinum": [], "pleas": 2, "plot": 9, "plt": 9, "plug": 13, "plugin": 3, "png": 6, "point": 15, "polici": 12, "polish": 5, "polit": 1, "polygon": [5, 9, 16], "pool": 7, "portugues": 5, "posit": [1, 9], "possibl": [2, 9, 13, 16], "post": [1, 16], "postprocessor": 16, "potenti": 7, "power": 4, "ppageno": 16, "pre": [2, 7], "precis": [9, 16], "pred": 9, "pred_box": 9, "pred_label": 9, "predefin": 14, "predict": [6, 7, 9, 16], "predictor": [4, 6, 7, 11, 13, 15], "prefer": 14, "preinstal": [], "preprocessor": [11, 16], "prerequisit": 13, "present": 10, "preserv": [7, 8, 16], "preserve_aspect_ratio": [6, 7, 8, 11, 16], "pretrain": [4, 7, 9, 11, 15, 16], "pretrained_backbon": [7, 11], "print": 16, "prior": 5, "privaci": 1, "privat": 1, "probabl": 8, "problem": 2, "procedur": 8, "process": [2, 4, 6, 11, 16], "processor": 16, "produc": [10, 16], "product": 15, "profession": 1, "project": [2, 14], "promptli": 1, "proper": 2, "properli": 5, "properti": [], "provid": [1, 2, 4, 13, 14, 16], "public": [1, 4], "publicli": 16, "publish": 1, "pull": 13, "punctuat": 5, "pure": 5, "purpos": 2, "push_to_hf_hub": [7, 13], "py": 13, "pypdfium2": 6, "pyplot": [6, 9], "python": 2, "python3": 13, "pytorch": [3, 4, 7, 8, 11, 13, 15, 16], "q": 2, "qr": 6, "qr_code": 14, "qualiti": 8, "quantiz": [], "quantize_model": [], "question": 1, "quickli": 4, "quicktour": 10, "r": 16, "race": 1, "ramdisk": 5, "rand": [7, 8, 9, 15, 16], "random": [7, 8, 9, 16], "randomappli": 8, "randombright": 8, "randomcontrast": 8, "randomcrop": 8, "randomgamma": 8, "randomhorizontalflip": 8, "randomhu": 8, "randomjpegqu": 8, "randomli": 8, "randomres": [], "randomrot": 8, "randomsatur": 8, "randomshadow": 8, "rang": 8, "rassi": 13, "ratio": [7, 8, 16], "raw": [6, 9], "re": 15, "read": [4, 5, 7], "read_html": 6, "read_img": [], "read_img_as_numpi": 6, "read_img_as_tensor": 6, "read_pdf": 6, "readi": 15, "real": [4, 7, 8], "realli": [], "reason": [1, 4, 5], "rebuild": 2, "rebuilt": 2, "recal": [9, 16], "receipt": [4, 5, 16], "reco_arch": [7, 11, 13, 15], "reco_b": [], "reco_model": [11, 13], "reco_param": 11, "reco_predictor": 11, "recogn": 16, "recognit": [5, 9, 11], "recognition_predictor": [7, 16], "recognition_task": [5, 14], "recognitiondataset": [5, 14], "recognitionpredictor": [7, 11], "rectangular": 7, "recurr": [], "red": 9, "reduc": [3, 8], "refer": [2, 3, 11, 13, 14, 16], "regardless": 1, "region": 16, "regroup": 9, "regular": 14, "reject": 1, "rel": [6, 8, 9, 16], "relat": 6, "releas": [0, 3], "relev": [], "religion": 1, "relu": [], "remov": 1, "render": [6, 16], "repo": 7, "repo_id": [7, 13], "report": 1, "repositori": [5, 7, 13], "repres": [1, 9, 15, 16], "represent": [4, 7], "request": [1, 13], "requir": [3, 8], "research": 4, "residu": 7, "resiz": [8, 16], "resnet": 7, "resnet18": [7, 13], "resnet31": 7, "resnet34": 7, "resnet50": [7, 13], "resolv": 6, "resolve_block": 16, "resolve_lin": 16, "resourc": 14, "respect": 1, "respons": 9, "rest": [2, 8, 9], "restrict": 12, "result": [2, 5, 6, 10, 13, 16], "return": 16, "reusabl": 16, "review": 1, "rgb": [6, 8], "rgb_mode": 6, "rgb_output": 6, "right": [1, 7, 9], "roboflow": [], "robust": [4, 5], "root": 5, "rotat": [5, 6, 7, 8, 9, 14, 16], "rotated_bbox": [], "run": [2, 3, 7], "same": [2, 6, 9, 14, 16], "sampl": [5, 14, 16], "sample_transform": 5, "sanjin": [], "sar": [4, 7], "sar_resnet31": [7, 16], "sar_vgg16_bn": [], "satur": 8, "save": [7, 14], "saved_model": [], "scale": [6, 7, 8, 9], "scale_rang": [], "scan": [4, 5], "scene": [4, 5, 7], "scheme": [], "score": 9, "scratch": [], "script": [2, 14], "seamless": 4, "seamlessli": [4, 16], "search": 7, "searchabl": 10, "sec": 16, "second": 16, "section": [11, 13, 15, 16], "secur": [1, 12], "see": [1, 2], "seemlessli": [], "seen": 16, "segment": [4, 7, 16], "self": 16, "semant": [4, 7], "send": 16, "sens": 9, "sensit": 14, "separ": 16, "sequenc": [4, 5, 6, 7, 9, 16], "sequenti": [8, 16], "seri": 1, "serial": [], "serialized_model": [], "seriou": 1, "set": [1, 5, 7, 9, 12, 16], "set_global_polici": 15, "sever": [6, 8, 16], "sex": 1, "sexual": 1, "sha256": [], "shade": 8, "shape": [6, 7, 8, 9, 16], "share": [12, 14], "shift": 8, "shm": 12, "should": [2, 5, 6, 8, 9], "show": [4, 6, 7, 9, 11, 13], "showcas": 2, "shuffl": [5, 8], "side": 9, "signatur": 6, "signific": 14, "simpl": [4, 7], "simpler": 7, "sinc": [5, 14], "singl": [1, 2, 4, 5], "single_img_doc": [], "size": [1, 5, 6, 8, 9, 16], "skew": 16, "slack": 2, "slightli": 7, "small": [2, 7], "smallest": 6, "snapshot_download": 7, "snippet": 16, "so": [2, 3, 5, 7, 13, 14], "social": 1, "socio": 1, "some": [3, 10, 13, 14], "someth": 2, "somewher": 2, "soon": 15, "sort": 1, "sourc": [5, 6, 7, 8, 9, 13], "space": [1, 16], "span": 16, "spanish": 5, "spatial": [4, 5, 6, 9], "special": [], "specif": [2, 3, 9, 11, 14, 16], "specifi": [1, 5, 6], "speed": [4, 7], "sphinx": 2, "sroie": [4, 5, 14], "stabl": 3, "stackoverflow": 2, "stage": 4, "standalon": [], "standard": 8, "start": 5, "state": [4, 9], "static": 9, "statist": [], "statu": 1, "std": [8, 11], "step": 12, "still": 16, "str": [5, 6, 7, 8, 9], "straight": [5, 7, 14, 16], "straighten": [], "straighten_pag": 7, "straigten_pag": [], "stream": 6, "street": [4, 5], "strict": [], "strictli": 9, "string": [5, 6, 9, 16], "strive": 3, "strong": [4, 7], "structur": [15, 16], "subset": [5, 16], "suggest": [2, 13], "sum": 9, "summari": 9, "support": [15, 16], "sustain": 1, "svhn": [4, 5, 14], "svt": [5, 14], "swedish": 5, "symbol": [], "symmetr": [7, 8, 16], "symmetric_pad": [7, 8, 16], "synthes": 9, "synthesize_pag": 9, "synthet": 4, "synthtext": [4, 5, 14], "system": 16, "t": [2, 5, 11, 16], "tabl": 13, "take": [1, 5, 16], "target": [5, 6, 8, 9, 14], "target_s": 5, "task": [4, 5, 7, 13, 14, 16], "task2": 5, "team": [], "techminde": [], "templat": [2, 4], "tensor": [5, 6, 8, 16], "tensorflow": [3, 4, 6, 7, 8, 11, 13, 15, 16], "tensorspec": 15, "term": 1, "test": [5, 14], "test_set": 5, "text": [5, 6, 7, 9, 14], "text_output": 16, "textmatch": 9, "textnet": 7, "textnet_bas": 7, "textnet_smal": 7, "textnet_tini": 7, "textract": [4, 16], "textstylebrush": [4, 5], "textual": [4, 5, 6, 7, 16], "tf": [3, 6, 7, 8, 13, 15], "tf_model": [], "tflite": [], "than": [2, 3, 9, 13], "thank": 2, "thei": [1, 9], "them": [3, 5, 16], "thi": [1, 2, 3, 5, 9, 11, 12, 13, 14, 15, 16], "thing": [15, 16], "third": 3, "those": [1, 3, 6, 16], "threaten": 1, "threshold": 16, "through": [1, 8, 14], "tilman": 13, "time": [1, 4, 7, 9, 14], "tini": 7, "titl": [6, 16], "tm": 16, "tmp": 12, "togeth": [2, 6], "tograi": 8, "tool": 14, "top": [9, 16], "topic": 2, "torch": [3, 8, 11, 13, 15], "torchvis": 8, "total": 11, "toward": [1, 3], "train": [2, 5, 7, 8, 13, 14, 15, 16], "train_it": [5, 14], "train_load": [5, 14], "train_pytorch": 13, "train_set": [5, 14], "train_tensorflow": 13, "trainabl": [4, 7], "tranform": 8, "transcrib": 16, "transfer": [4, 5], "transfo": 8, "transform": [4, 5, 7], "translat": 1, "troll": 1, "true": [5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16], "truth": 9, "tune": 15, "tupl": [5, 6, 8, 9], "turn": [], "two": [6, 12], "txt": 5, "type": [6, 9, 13, 15, 16], "typic": 16, "u": [1, 2], "ucsd": 5, "udac": 2, "uint8": [6, 7, 9, 16], "ukrainian": [], "unaccept": 1, "underli": [14, 16], "underneath": 6, "understand": [4, 5, 16], "unidecod": 9, "uniform": [7, 8], "uniformli": 8, "uninterrupt": [6, 16], "union": 9, "unit": [], "unittest": 2, "unlock": 6, "unoffici": 7, "unprofession": 1, "unsolicit": 1, "unsupervis": 4, "unwelcom": 1, "up": [7, 16], "updat": 9, "upgrad": 2, "upper": [5, 8], "uppercas": 14, "url": 6, "us": [1, 2, 3, 5, 7, 9, 11, 12, 13, 16], "usabl": 16, "usag": [12, 15], "use_broadcast": 9, "use_polygon": [5, 9, 14], "useabl": 16, "user": [3, 4, 6, 10], "utf": 16, "util": 15, "v0": [], "v1": 13, "v3": [7, 13, 16], "valid": 14, "valu": [2, 6, 8, 16], "valuabl": 4, "variabl": 12, "varieti": 5, "veri": 7, "verifi": [], "verma": [], "version": [1, 2, 3, 15, 16], "vgg": 7, "vgg16": 13, "vgg16_bn_r": 7, "via": 1, "video": [], "vietnames": 5, "view": [4, 5], "viewpoint": 1, "violat": 1, "visibl": 1, "vision": [4, 5, 7], "visiondataset": 5, "visiontransform": 7, "visual": 4, "visualize_pag": 9, "vit_": 7, "vit_b": 7, "vitstr": [4, 7, 15], "vitstr_bas": [7, 16], "vitstr_smal": [7, 11, 15, 16], "viz": [], "vocab": [11, 13, 14, 16], "vocabulari": [5, 11, 13], "w": [6, 7, 8, 9], "w3": 16, "wa": 1, "wai": [1, 4, 14], "want": [2, 15, 16], "warm": [], "warmup": 16, "wasn": 2, "we": [1, 2, 3, 4, 6, 8, 13, 14, 15, 16], "weasyprint": 6, "web": [2, 6], "websit": 5, "weight": 11, "welcom": 1, "well": [1, 15], "were": [1, 6, 16], "what": 1, "when": [1, 2, 7], "whenev": 2, "where": [2, 6, 8, 9], "whether": [2, 5, 6, 8, 9, 14, 16], "which": [1, 7, 12, 14, 16], "whichev": 3, "while": [8, 16], "why": 1, "width": 6, "wiki": 1, "wildreceipt": [4, 5, 14], "window": [3, 7, 9], "wish": 2, "within": 1, "without": [1, 5, 7], "wonder": 2, "word": [4, 5, 7, 9, 16], "word_1_1": 16, "word_1_2": 16, "word_1_3": 16, "wordgener": [5, 14], "words_onli": 9, "work": [12, 16], "worker": 5, "workflow": 2, "worklow": 2, "world": [9, 16], "worth": 7, "wrap": 16, "wrapper": [5, 8], "write": 12, "written": [1, 6], "www": [1, 6, 16], "x": [6, 8, 9], "x12larg": [], "x_ascend": 16, "x_descend": 16, "x_i": 9, "x_size": 16, "x_wconf": 16, "xeon": [], "xhtml": 16, "xmax": 6, "xmin": 6, "xml": 16, "xml_bytes_str": 16, "xml_element": 16, "xml_output": 16, "xmln": 16, "y": 9, "y_i": 9, "y_j": 9, "yet": [], "ymax": 6, "ymin": 6, "yolov8": [], "you": [2, 3, 5, 6, 7, 11, 12, 13, 14, 15, 16], "your": [2, 4, 6, 9, 16], "yoursit": 6, "yugesh": [], "zero": [8, 9], "zoo": [], "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 5, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 5, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": 5, "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 5, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 5, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": [], "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 5, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 5, "\u00e4\u00f6\u00e4\u00f6": 5, "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 5, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": 5, "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": 5, "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": 5, "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 5, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": [], "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": [], "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": 5, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 5, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 5, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 5, "\u067e\u0686\u06a2\u06a4\u06af": 5, "\u0905": [], "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": [], "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": [], "\u0950": [], "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": [], "\u09bd": [], "\u09ce": [], "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": []}, "titles": ["Changelog", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 2, "0": 0, "01": 0, "02": 0, "03": 0, "04": [], "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 1], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 1], "2021": 0, "2022": 0, "2023": [], "2024": 0, "21": [], "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 1], "31": 0, "4": [0, 1], "5": 0, "6": 0, "7": 0, "8": [], "9": [], "advanc": 16, "approach": 16, "architectur": 16, "arg": [5, 6, 7, 8, 9], "artefact": 6, "artefactdetect": [], "attribut": 1, "avail": [14, 16], "aw": 12, "ban": 1, "block": 6, "bug": 2, "build": [], "changelog": 0, "choos": [14, 16], "classif": [7, 13], "code": [1, 2], "codebas": 2, "commit": 2, "commun": 13, "compos": 8, "compress": [], "conda": [], "conduct": 1, "connect": 2, "content": [], "continu": 2, "contrib": [], "contribut": 2, "contributor": 1, "convent": 13, "correct": 1, "coven": 1, "custom": [5, 11], "data": 14, "dataload": 5, "dataset": [4, 5, 14], "detect": [4, 7, 13, 14, 16], "develop": 2, "do": 16, "doctr": [2, 4, 5, 6, 7, 8, 9, 10, 15], "document": [2, 4, 6], "end": 16, "enforc": 1, "evalu": 9, "export": 15, "factori": 7, "featur": [2, 4], "feedback": 2, "file": 6, "from": 13, "gener": [5, 14], "get": [], "git": 3, "guidelin": 1, "half": 15, "hub": 13, "huggingfac": 13, "i": 16, "implement": [], "infer": 15, "instal": [2, 3], "integr": 2, "io": 6, "lambda": 12, "let": 2, "line": 6, "linux": [], "load": [11, 13, 14], "loader": 5, "main": 4, "mode": 2, "model": [4, 7, 11, 13, 15, 16], "modifi": 2, "modul": [], "name": 13, "note": [], "notebook": 10, "object": 14, "ocr": 16, "onli": [], "onnx": 15, "optim": 15, "option": 16, "orient": [], "our": 1, "output": 16, "own": [11, 14], "packag": 3, "page": 6, "perman": 1, "pipelin": [], "pledg": 1, "post": [], "pre": [], "precis": 15, "predictor": 16, "prepar": 15, "prerequisit": 3, "pretrain": 13, "process": [], "push": 13, "python": 3, "qualiti": 2, "question": 2, "read": 6, "readi": 14, "recognit": [4, 7, 13, 14, 16], "refer": [], "report": 2, "request": 2, "resourc": [], "respons": 1, "return": [5, 6, 7, 9], "right": 16, "savedmodel": [], "scope": 1, "share": 13, "should": 16, "stage": 16, "standard": 1, "start": [], "structur": [2, 6], "style": 2, "support": [4, 5, 8], "synthet": [5, 14], "task": 9, "temporari": 1, "test": 2, "text": [4, 16], "train": 11, "transform": 8, "two": 16, "unit": 2, "us": [14, 15], "util": 9, "v0": 0, "verif": 2, "via": 3, "visual": 9, "vocab": 5, "warn": 1, "what": 16, "word": 6, "your": [11, 13, 14, 15], "zoo": [4, 7]}}) \ No newline at end of file +Search.setIndex({"alltitles": {"1. Correction": [[1, "correction"]], "2. Warning": [[1, "warning"]], "3. Temporary Ban": [[1, "temporary-ban"]], "4. Permanent Ban": [[1, "permanent-ban"]], "AWS Lambda": [[12, null]], "Advanced options": [[16, "advanced-options"]], "Args:": [[5, "args"], [5, "id4"], [5, "id7"], [5, "id10"], [5, "id13"], [5, "id16"], [5, "id19"], [5, "id22"], [5, "id25"], [5, "id29"], [5, "id32"], [5, "id37"], [5, "id40"], [5, "id46"], [5, "id49"], [5, "id50"], [5, "id51"], [5, "id54"], [5, "id57"], [5, "id60"], [5, "id61"], [6, "args"], [6, "id2"], [6, "id3"], [6, "id4"], [6, "id5"], [6, "id6"], [6, "id7"], [6, "id10"], [6, "id12"], [6, "id14"], [6, "id16"], [6, "id20"], [6, "id24"], [6, "id28"], [7, "args"], [7, "id3"], [7, "id8"], [7, "id13"], [7, "id17"], [7, "id21"], [7, "id26"], [7, "id31"], [7, "id36"], [7, "id41"], [7, "id45"], [7, "id49"], [7, "id54"], [7, "id58"], [7, "id63"], [7, "id68"], [7, "id72"], [7, "id76"], [7, "id81"], [7, "id86"], [7, "id90"], [7, "id95"], [7, "id99"], [7, "id103"], [7, "id108"], [7, "id113"], [7, "id118"], [7, "id122"], [7, "id126"], [7, "id131"], [7, "id135"], [7, "id139"], [7, "id143"], [7, "id145"], [7, "id147"], [7, "id149"], [8, "args"], [8, "id1"], [8, "id2"], [8, "id3"], [8, "id4"], [8, "id5"], [8, "id6"], [8, "id7"], [8, "id8"], [8, "id9"], [8, "id10"], [8, "id11"], [8, "id12"], [8, "id13"], [8, "id14"], [8, "id15"], [8, "id16"], [8, "id17"], [8, "id18"], [9, "args"], [9, "id3"], [9, "id5"], [9, "id6"], [9, "id7"], [9, "id8"], [9, "id9"], [9, "id10"], [9, "id11"]], "Artefact": [[6, "artefact"]], "Attribution": [[1, "attribution"]], "Available Datasets": [[14, "available-datasets"]], "Available architectures": [[16, "available-architectures"], [16, "id1"], [16, "id2"]], "Block": [[6, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[14, null]], "Choosing the right model": [[16, null]], "Classification": [[13, "classification"]], "Code quality": [[2, "code-quality"]], "Code style verification": [[2, "code-style-verification"]], "Codebase structure": [[2, "codebase-structure"]], "Commits": [[2, "commits"]], "Composing transformations": [[8, "composing-transformations"]], "Continuous Integration": [[2, "continuous-integration"]], "Contributing to docTR": [[2, null]], "Contributor Covenant Code of Conduct": [[1, null]], "Custom dataset loader": [[5, "custom-dataset-loader"]], "Data Loading": [[14, "data-loading"]], "Dataloader": [[5, "dataloader"]], "Detection": [[13, "detection"], [14, "detection"]], "Detection predictors": [[16, "detection-predictors"]], "Developer mode installation": [[2, "developer-mode-installation"]], "Developing docTR": [[2, "developing-doctr"]], "Document": [[6, "document"]], "Document structure": [[6, "document-structure"]], "End-to-End OCR": [[16, "end-to-end-ocr"]], "Enforcement": [[1, "enforcement"]], "Enforcement Guidelines": [[1, "enforcement-guidelines"]], "Enforcement Responsibilities": [[1, "enforcement-responsibilities"]], "Export to ONNX": [[15, "export-to-onnx"]], "Feature requests & bug report": [[2, "feature-requests-bug-report"]], "Feedback": [[2, "feedback"]], "File reading": [[6, "file-reading"]], "Half-precision": [[15, "half-precision"]], "Installation": [[3, null]], "Let\u2019s connect": [[2, "let-s-connect"]], "Line": [[6, "line"]], "Loading from Huggingface Hub": [[13, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[11, "loading-your-custom-trained-model"]], "Main Features": [[4, "main-features"]], "Model optimization": [[15, "model-optimization"]], "Model zoo": [[4, "model-zoo"]], "Modifying the documentation": [[2, "modifying-the-documentation"]], "Naming conventions": [[13, "naming-conventions"]], "Object Detection": [[14, "object-detection"]], "Our Pledge": [[1, "our-pledge"]], "Our Standards": [[1, "our-standards"]], "Page": [[6, "page"]], "Preparing your model for inference": [[15, null]], "Prerequisites": [[3, "prerequisites"]], "Pretrained community models": [[13, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[13, "pushing-to-the-huggingface-hub"]], "Questions": [[2, "questions"]], "Recognition": [[13, "recognition"], [14, "recognition"]], "Recognition predictors": [[16, "recognition-predictors"]], "Returns:": [[5, "returns"], [6, "returns"], [6, "id11"], [6, "id13"], [6, "id15"], [6, "id19"], [6, "id23"], [6, "id27"], [6, "id31"], [7, "returns"], [7, "id6"], [7, "id11"], [7, "id16"], [7, "id20"], [7, "id24"], [7, "id29"], [7, "id34"], [7, "id39"], [7, "id44"], [7, "id48"], [7, "id52"], [7, "id57"], [7, "id61"], [7, "id66"], [7, "id71"], [7, "id75"], [7, "id79"], [7, "id84"], [7, "id89"], [7, "id93"], [7, "id98"], [7, "id102"], [7, "id106"], [7, "id111"], [7, "id116"], [7, "id121"], [7, "id125"], [7, "id129"], [7, "id134"], [7, "id138"], [7, "id142"], [7, "id144"], [7, "id146"], [7, "id148"], [9, "returns"], [9, "id4"]], "Scope": [[1, "scope"]], "Share your model with the community": [[13, null]], "Supported Vocabs": [[5, "supported-vocabs"]], "Supported datasets": [[4, "supported-datasets"]], "Supported transformations": [[8, "supported-transformations"]], "Synthetic dataset generator": [[5, "synthetic-dataset-generator"], [14, "synthetic-dataset-generator"]], "Task evaluation": [[9, "task-evaluation"]], "Text Detection": [[16, "text-detection"]], "Text Recognition": [[16, "text-recognition"]], "Text detection models": [[4, "text-detection-models"]], "Text recognition models": [[4, "text-recognition-models"]], "Train your own model": [[11, null]], "Two-stage approaches": [[16, "two-stage-approaches"]], "Unit tests": [[2, "unit-tests"]], "Use your own datasets": [[14, "use-your-own-datasets"]], "Using your ONNX exported model in docTR": [[15, "using-your-onnx-exported-model-in-doctr"]], "Via Git": [[3, "via-git"]], "Via Python Package": [[3, "via-python-package"]], "Visualization": [[9, "visualization"]], "What should I do with the output?": [[16, "what-should-i-do-with-the-output"]], "Word": [[6, "word"]], "docTR Notebooks": [[10, null]], "docTR Vocabs": [[5, "id62"]], "docTR: Document Text Recognition": [[4, null]], "doctr.datasets": [[5, null], [5, "datasets"]], "doctr.io": [[6, null]], "doctr.models": [[7, null]], "doctr.models.classification": [[7, "doctr-models-classification"]], "doctr.models.detection": [[7, "doctr-models-detection"]], "doctr.models.factory": [[7, "doctr-models-factory"]], "doctr.models.recognition": [[7, "doctr-models-recognition"]], "doctr.models.zoo": [[7, "doctr-models-zoo"]], "doctr.transforms": [[8, null]], "doctr.utils": [[9, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]], "v0.7.0 (2024-09-09)": [[0, "v0-7-0-2024-09-09"]]}, "docnames": ["changelog", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[6, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[6, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[8, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[5, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[8, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[8, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[5, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[7, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[5, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[7, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[7, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[6, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[7, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[5, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[5, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[6, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[6, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[5, "doctr.datasets.encode_sequences", false]], "from_hub() (in module doctr.models.factory)": [[7, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[6, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[6, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[6, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[5, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[8, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[8, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[5, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[5, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[5, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[5, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[5, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[7, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[8, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[6, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[7, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet34() (in module doctr.models.detection)": [[7, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[7, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[7, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[7, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[5, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_orientation() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_small_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[8, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[7, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[5, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[8, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[6, "doctr.io.Page", false]], "parseq() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[7, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[8, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[8, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[8, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[8, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[8, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[8, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[8, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[8, "doctr.transforms.RandomJpegQuality", false]], "randomrotate (class in doctr.transforms)": [[8, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[8, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[8, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[6, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[6, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[6, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[6, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[5, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[8, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[6, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[6, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[5, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[9, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[9, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[9, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[9, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[5, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[5, "doctr.datasets.SVT", false]], "synthesize_page() (in module doctr.utils.visualization)": [[9, "doctr.utils.visualization.synthesize_page", false]], "synthtext (class in doctr.datasets)": [[5, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.TextMatch", false]], "textnet_base() (in module doctr.models.classification)": [[7, "doctr.models.classification.textnet_base", false]], "textnet_small() (in module doctr.models.classification)": [[7, "doctr.models.classification.textnet_small", false]], "textnet_tiny() (in module doctr.models.classification)": [[7, "doctr.models.classification.textnet_tiny", false]], "togray (class in doctr.transforms)": [[8, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[9, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[9, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[9, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[9, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[7, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[9, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[7, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[7, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.vitstr_small", false]], "wildreceipt (class in doctr.datasets)": [[5, "doctr.datasets.WILDRECEIPT", false]], "word (class in doctr.io)": [[6, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[5, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[5, 0, 1, "", "CORD"], [5, 0, 1, "", "CharacterGenerator"], [5, 0, 1, "", "DetectionDataset"], [5, 0, 1, "", "DocArtefacts"], [5, 0, 1, "", "FUNSD"], [5, 0, 1, "", "IC03"], [5, 0, 1, "", "IC13"], [5, 0, 1, "", "IIIT5K"], [5, 0, 1, "", "IIITHWS"], [5, 0, 1, "", "IMGUR5K"], [5, 0, 1, "", "MJSynth"], [5, 0, 1, "", "OCRDataset"], [5, 0, 1, "", "RecognitionDataset"], [5, 0, 1, "", "SROIE"], [5, 0, 1, "", "SVHN"], [5, 0, 1, "", "SVT"], [5, 0, 1, "", "SynthText"], [5, 0, 1, "", "WILDRECEIPT"], [5, 0, 1, "", "WordGenerator"], [5, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[5, 0, 1, "", "DataLoader"]], "doctr.io": [[6, 0, 1, "", "Artefact"], [6, 0, 1, "", "Block"], [6, 0, 1, "", "Document"], [6, 0, 1, "", "DocumentFile"], [6, 0, 1, "", "Line"], [6, 0, 1, "", "Page"], [6, 0, 1, "", "Word"], [6, 1, 1, "", "decode_img_as_tensor"], [6, 1, 1, "", "read_html"], [6, 1, 1, "", "read_img_as_numpy"], [6, 1, 1, "", "read_img_as_tensor"], [6, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[6, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[6, 2, 1, "", "from_images"], [6, 2, 1, "", "from_pdf"], [6, 2, 1, "", "from_url"]], "doctr.io.Page": [[6, 2, 1, "", "show"]], "doctr.models": [[7, 1, 1, "", "kie_predictor"], [7, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[7, 1, 1, "", "crop_orientation_predictor"], [7, 1, 1, "", "magc_resnet31"], [7, 1, 1, "", "mobilenet_v3_large"], [7, 1, 1, "", "mobilenet_v3_large_r"], [7, 1, 1, "", "mobilenet_v3_small"], [7, 1, 1, "", "mobilenet_v3_small_orientation"], [7, 1, 1, "", "mobilenet_v3_small_r"], [7, 1, 1, "", "resnet18"], [7, 1, 1, "", "resnet31"], [7, 1, 1, "", "resnet34"], [7, 1, 1, "", "resnet50"], [7, 1, 1, "", "textnet_base"], [7, 1, 1, "", "textnet_small"], [7, 1, 1, "", "textnet_tiny"], [7, 1, 1, "", "vgg16_bn_r"], [7, 1, 1, "", "vit_b"], [7, 1, 1, "", "vit_s"]], "doctr.models.detection": [[7, 1, 1, "", "db_mobilenet_v3_large"], [7, 1, 1, "", "db_resnet50"], [7, 1, 1, "", "detection_predictor"], [7, 1, 1, "", "linknet_resnet18"], [7, 1, 1, "", "linknet_resnet34"], [7, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[7, 1, 1, "", "from_hub"], [7, 1, 1, "", "login_to_hub"], [7, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[7, 1, 1, "", "crnn_mobilenet_v3_large"], [7, 1, 1, "", "crnn_mobilenet_v3_small"], [7, 1, 1, "", "crnn_vgg16_bn"], [7, 1, 1, "", "master"], [7, 1, 1, "", "parseq"], [7, 1, 1, "", "recognition_predictor"], [7, 1, 1, "", "sar_resnet31"], [7, 1, 1, "", "vitstr_base"], [7, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[8, 0, 1, "", "ChannelShuffle"], [8, 0, 1, "", "ColorInversion"], [8, 0, 1, "", "Compose"], [8, 0, 1, "", "GaussianBlur"], [8, 0, 1, "", "GaussianNoise"], [8, 0, 1, "", "LambdaTransformation"], [8, 0, 1, "", "Normalize"], [8, 0, 1, "", "OneOf"], [8, 0, 1, "", "RandomApply"], [8, 0, 1, "", "RandomBrightness"], [8, 0, 1, "", "RandomContrast"], [8, 0, 1, "", "RandomCrop"], [8, 0, 1, "", "RandomGamma"], [8, 0, 1, "", "RandomHorizontalFlip"], [8, 0, 1, "", "RandomHue"], [8, 0, 1, "", "RandomJpegQuality"], [8, 0, 1, "", "RandomRotate"], [8, 0, 1, "", "RandomSaturation"], [8, 0, 1, "", "RandomShadow"], [8, 0, 1, "", "Resize"], [8, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[9, 0, 1, "", "DetectionMetric"], [9, 0, 1, "", "LocalizationConfusion"], [9, 0, 1, "", "OCRMetric"], [9, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.visualization": [[9, 1, 1, "", "synthesize_page"], [9, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [1, 6, 7, 9, 13], "0": [1, 3, 5, 8, 9, 11, 14, 16], "00": 16, "01": 16, "0123456789": 5, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 5, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 5, "02": [], "02562": 7, "03": 16, "035": 16, "0361328125": 16, "04": [], "05": 16, "06": 16, "06640625": 16, "07": 16, "08": [8, 16], "09": 16, "0966796875": 16, "1": [3, 5, 6, 7, 8, 9, 11, 14, 16], "10": [5, 9, 16], "100": [5, 8, 9, 14, 16], "1000": 16, "101": 5, "1024": [7, 9, 11, 16], "104": 5, "106": 5, "108": 5, "1095": 14, "11": 16, "110": 9, "1107": 14, "114": 5, "115": [], "1156": 14, "116": 5, "118": 5, "11800h": 16, "11th": 16, "12": [3, 16], "120": 5, "123": 5, "126": 5, "1268": 14, "128": [7, 11, 15, 16], "13": [9, 16], "130": 5, "13068": 14, "131": 5, "1337891": 14, "1357421875": 16, "1396484375": 16, "14": 16, "1420": 16, "14470v1": 5, "149": 14, "15": 16, "150": [9, 16], "1552": 16, "16": [7, 15, 16], "1630859375": 16, "1684": 16, "16x16": 7, "17": 16, "1778": 16, "1782": 16, "18": 7, "185546875": 16, "1900": 16, "1910": 7, "19342": 14, "19370": 14, "195": 5, "19598": 14, "199": 16, "1999": 16, "2": [3, 4, 5, 6, 8, 16], "20": 16, "200": 9, "2000": 14, "2003": [4, 5], "2012": 5, "2013": [4, 5], "2015": 5, "2019": 4, "2023": [], "207901": 14, "21": 16, "2103": 5, "2186": 14, "21888": 14, "22": 16, "224": [7, 8], "225": 8, "22672": 14, "229": [8, 14], "23": 16, "233": 14, "234": 5, "236": [], "24": 16, "246": 14, "249": 14, "25": 16, "2504": 16, "255": [6, 7, 8, 9, 16], "256": 7, "257": 14, "26": 16, "26032": 14, "264": 11, "27": 16, "2700": 14, "2710": 16, "2749": 11, "28": 16, "287": 11, "29": 16, "296": 11, "299": 11, "2d": 16, "3": [3, 4, 6, 7, 8, 9, 15, 16], "30": 16, "300": 14, "3000": 14, "301": 11, "30595": 16, "30ghz": 16, "31": 7, "32": [5, 7, 8, 11, 14, 15, 16], "3232421875": 16, "33": [8, 16], "33402": 14, "33608": 14, "34": [7, 16], "340": 16, "3456": 16, "35": [], "3515625": 16, "36": [], "360": 14, "37": [5, 16], "38": 16, "39": 16, "4": [7, 8, 9, 16], "40": 16, "406": 8, "41": 16, "42": 16, "43": 16, "44": 16, "45": 16, "456": 8, "46": 16, "47": 16, "472": 14, "48": [5, 16], "485": 8, "49": 16, "49377": 14, "5": [5, 8, 9, 16], "50": [7, 14, 16], "51": 16, "51171875": 16, "512": 7, "52": [5, 16], "529": 16, "53": 16, "54": 16, "540": 16, "5478515625": 16, "55": 16, "56": 16, "57": 16, "58": 16, "580": 16, "5810546875": 16, "583": 16, "59": 16, "597": 16, "5k": [4, 5], "5m": [], "6": [8, 16], "60": 8, "600": [7, 9, 16], "61": 16, "62": 16, "626": 14, "63": 16, "64": [7, 8, 16], "641": 16, "647": 14, "65": 16, "66": 16, "67": 16, "68": 16, "69": 16, "693": 11, "694": 11, "695": 11, "6m": [], "7": 16, "70": [9, 16], "707470": 14, "71": 16, "7100000": 14, "7141797": 14, "7149": 14, "72": 16, "72dpi": 6, "73": 16, "73257": 14, "74": 16, "75": [8, 16], "7581382": 14, "76": 16, "77": 16, "772": 11, "772875": 14, "78": 16, "785": 11, "79": 16, "793533": 14, "796": 14, "798": 11, "7m": [], "8": [3, 7, 8, 16], "80": 16, "800": [7, 9, 14, 16], "81": 16, "82": 16, "83": 16, "84": 16, "849": 14, "85": 16, "8564453125": 16, "857": 16, "85875": 14, "86": 16, "8603515625": 16, "87": 16, "8707": 14, "88": 16, "89": 16, "9": 16, "90": 16, "90k": 5, "90kdict32px": 5, "91": 16, "914085328578949": 16, "92": 16, "93": 16, "94": [5, 16], "95": [9, 16], "9578408598899841": 16, "96": 16, "97": [], "98": 16, "99": 16, "9949972033500671": 16, "A": [1, 2, 4, 5, 6, 7, 10, 15], "As": 2, "Be": 16, "Being": 1, "By": 12, "For": [1, 2, 3, 11, 16], "If": [2, 3, 6, 7, 11, 16], "In": [2, 5, 14], "It": [8, 13, 15], "Its": [4, 7], "No": [1, 16], "Of": 5, "Or": [], "The": [1, 2, 5, 6, 9, 12, 16], "Then": 7, "To": [2, 3, 12, 13, 16], "_": [1, 5, 7], "__call__": 16, "_build": 2, "_i": 9, "ab": 5, "abc": [], "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 5, "abdef": [5, 14], "abl": [14, 16], "about": [1, 14, 16], "abov": 16, "abstract": [], "abstractdataset": 5, "abus": 1, "accept": 1, "access": [4, 6, 14, 16], "account": [1, 13], "accur": 16, "accuraci": 9, "achiev": 15, "act": 1, "action": 1, "activ": 4, "ad": [2, 7, 8], "adapt": 1, "add": [8, 9, 13, 16], "add_hook": 16, "add_label": 9, "addit": [2, 3, 6], "addition": [2, 16], "address": [1, 6], "adjust": 8, "advanc": 1, "advantag": 15, "advis": 2, "aesthet": [4, 5], "affect": 1, "after": [13, 16], "ag": 1, "again": 7, "aggreg": [9, 14], "aggress": 1, "align": [1, 6], "all": [1, 2, 5, 6, 8, 9, 14, 16], "allow": 1, "along": 16, "alreadi": 2, "also": [1, 7, 13, 14, 16], "alwai": 14, "an": [1, 2, 4, 5, 6, 7, 9, 15, 16], "analysi": 6, "ancient_greek": 5, "andrej": [], "angl": [6, 8], "ani": [1, 5, 6, 7, 8, 9, 16], "annot": 5, "anot": 14, "anoth": [3, 7, 11, 14], "answer": 1, "anyascii": [], "anyon": 4, "anyth": [], "api": [2, 4], "apolog": 1, "apologi": 1, "app": 2, "appear": 1, "appli": [1, 5, 8], "applic": [4, 7], "appoint": 1, "appreci": 13, "appropri": [1, 2, 16], "ar": [1, 2, 3, 5, 6, 8, 9, 10, 14, 16], "arab": 5, "arabic_diacrit": 5, "arabic_lett": 5, "arabic_punctu": 5, "arbitrarili": 7, "arch": [7, 13], "architectur": [4, 7, 13], "area": 16, "arg": [], "argument": [5, 6, 7, 9, 16], "around": 1, "arrai": [6, 8, 9], "art": 4, "artefact": [9, 10, 16], "artefact_typ": 6, "articl": [], "artifici": [4, 5], "arxiv": [5, 7], "asarrai": 9, "ascii_lett": 5, "aspect": [4, 7, 8, 16], "assess": 9, "assign": 9, "associ": 6, "assum": 7, "assume_straight_pag": [7, 16], "astyp": [7, 9, 16], "attack": 1, "attend": [4, 7], "attent": [1, 7], "autom": 4, "automat": 16, "autoregress": [4, 7], "avail": [1, 4, 8], "averag": [8, 16], "avoid": [1, 3], "aw": [4, 16], "awar": 16, "azur": 16, "b": [7, 9, 16], "b_j": 9, "back": 2, "backbon": 7, "backend": 16, "background": 14, "bangla": [], "bar": [], "bar_cod": 14, "baranovskij": [], "base": [4, 7], "baselin": [4, 7, 16], "batch": [5, 7, 8, 14, 16], "batch_siz": [5, 11, 14, 15], "bblanchon": [], "bbox": 16, "becaus": 12, "been": [2, 9, 14, 16], "befor": [5, 7, 8, 16], "begin": 9, "behavior": [1, 16], "being": [9, 16], "belong": 16, "benchmark": 16, "best": 1, "better": [10, 16], "between": [8, 9, 16], "bgr": 6, "bilinear": 8, "bin_thresh": 16, "binar": [4, 7, 16], "binari": [6, 15, 16], "bit": 15, "blank": 9, "block": [9, 16], "block_1_1": 16, "blue": 9, "blur": 8, "bmvc": 5, "bn": 13, "bodi": [1, 16], "bool": [5, 6, 7, 8, 9], "boolean": [7, 16], "both": [4, 5, 8, 14, 16], "bottom": [7, 16], "bound": [5, 6, 7, 8, 9, 16], "box": [5, 6, 7, 8, 9, 14, 16], "box_thresh": 16, "brew": 3, "bright": 8, "broadcast": 9, "browser": [2, 4], "build": [2, 3], "built": 2, "byte": [6, 16], "c": [6, 9], "c_j": 9, "cach": [2, 5, 12], "cache_sampl": 5, "cairo": 3, "call": [], "callabl": [5, 8], "can": [2, 3, 11, 12, 13, 14, 16], "capabl": [2, 10, 16], "case": [5, 9], "cf": 16, "cfg": 16, "challeng": 5, "challenge2_test_task12_imag": 5, "challenge2_test_task1_gt": 5, "challenge2_training_task12_imag": 5, "challenge2_training_task1_gt": 5, "chang": 12, "channel": [1, 2, 6, 8], "channel_prior": [], "channelshuffl": 8, "charact": [4, 5, 6, 9, 14, 16], "charactergener": [5, 14], "characterist": 1, "charg": 16, "charset": 16, "chart": 6, "check": [2, 13, 16], "checkpoint": 7, "chip": 3, "christian": [], "ci": 2, "clarifi": 1, "clariti": 1, "class": [1, 5, 6, 8, 9, 16], "class_nam": 11, "classif": 14, "classif_mobilenet_v3_smal": 7, "classmethod": 6, "clear": 2, "clone": 3, "close": 2, "co": 13, "code": [4, 6], "codecov": 2, "colab": 10, "collate_fn": 5, "collect": 6, "color": [8, 9], "colorinvers": 8, "column": 6, "com": [1, 3, 6, 7, 13], "combin": 16, "come": 15, "command": 2, "comment": 1, "commit": 1, "common": [1, 8, 9, 15], "commun": 1, "compar": 4, "comparison": [9, 16], "competit": 5, "compil": [10, 16], "complaint": 1, "complementari": 9, "complet": 2, "compon": 16, "compos": [5, 16], "comprehens": 16, "comput": [5, 9, 15, 16], "conf_threshold": [], "confid": [6, 9, 16], "config": 7, "configur": 7, "confus": 9, "consecut": [8, 16], "consequ": 1, "consid": [1, 2, 5, 6, 9, 16], "consist": 16, "consolid": [4, 5], "constant": 8, "construct": 1, "consum": 9, "contact": 1, "contain": [5, 14], "content": [5, 6, 9, 16], "context": 7, "contib": [], "continu": 1, "contrast": 8, "contrast_factor": 8, "contrib": [], "contribut": 1, "contributor": 2, "convers": 6, "convert": [6, 8], "convolut": 7, "cool": [], "coordin": [6, 16], "cord": [4, 5, 14, 16], "core": [9, 16], "corner": 16, "correct": 8, "correspond": [3, 6, 16], "could": 1, "counterpart": 9, "cover": 2, "coverag": 2, "cpu": [4, 11], "creat": 13, "crnn": [4, 7, 13], "crnn_mobilenet_v3_larg": [7, 13, 16], "crnn_mobilenet_v3_smal": [7, 15, 16], "crnn_vgg16_bn": [7, 11, 13, 16], "crop": [7, 8, 14, 16], "crop_orient": [], "crop_orientation_predictor": 7, "crop_param": [], "croporientationpredictor": 7, "cuda": 15, "currenc": 5, "current": [2, 16], "custom": [13, 16], "custom_crop_orientation_model": [], "custom_page_orientation_model": [], "customhook": 16, "cvit": 4, "czczup": 7, "czech": 5, "d": [5, 14], "danish": 5, "data": [4, 5, 6, 8, 9, 11, 13], "dataload": 14, "dataset": [7, 11, 16], "dataset_info": 5, "date": [11, 16], "db": 13, "db_mobilenet_v3_larg": [7, 13, 16], "db_resnet34": 16, "db_resnet50": [7, 11, 13, 16], "db_resnet50_rot": [], "dbnet": [4, 7], "deal": [], "decis": 1, "decod": 6, "decode_img_as_tensor": 6, "dedic": [], "deem": 1, "deep": [7, 16], "def": 16, "default": [6, 9, 11, 12, 16], "defer": 14, "defin": [9, 15], "degre": 8, "degress": 6, "delet": 2, "delimit": 16, "delta": 8, "demo": [2, 4], "demonstr": 1, "depend": [2, 3, 4], "deploi": 2, "deploy": 4, "derogatori": 1, "describ": [7, 9], "descript": 10, "design": 8, "desir": 6, "det_arch": [7, 11, 13, 15], "det_b": [], "det_model": [11, 13], "det_param": 11, "det_predictor": [11, 16], "detail": [11, 16], "detect": [5, 9, 10, 11], "detect_languag": 7, "detect_orient": 7, "detection_predictor": [7, 16], "detection_task": [], "detectiondataset": [5, 14], "detectionmetr": 9, "detectionpredictor": [7, 11], "detector": 7, "deterior": 7, "determin": 1, "dev": [2, 12], "develop": 3, "deviat": 8, "devic": 15, "dict": [6, 9, 16], "dictionari": [6, 9], "differ": 1, "differenti": [4, 7], "digit": [4, 5, 14], "dimens": [6, 9, 16], "dimension": 8, "direct": 5, "directli": [13, 16], "directori": [2, 12], "disabl": [1, 12, 16], "disable_crop_orient": [], "disable_page_orient": [], "disclaim": 16, "discuss": 2, "disparag": 1, "displai": [6, 9], "display_artefact": 9, "distribut": 8, "div": 16, "divers": 1, "divid": 6, "do": [2, 3, 7], "doc": [2, 6, 15, 16], "docartefact": [5, 14], "docstr": 2, "doctr": [3, 11, 12, 13, 14, 16], "doctr_cache_dir": 12, "doctr_multiprocessing_dis": 12, "document": [5, 7, 9, 10, 14, 16], "documentbuild": 16, "documentfil": [6, 13], "doesn": [], "don": [11, 16], "done": 8, "download": [5, 14], "downsiz": 7, "draw": [8, 9], "draw_proba": 9, "drop": 5, "drop_last": 5, "dtype": [6, 7, 8, 9, 15], "dual": [4, 5], "dummi": 13, "dummy_img": 16, "dummy_input": 15, "dure": 1, "dutch": 5, "dynam": 5, "dynamic_seq_length": 5, "e": [1, 2, 3, 6, 7], "each": [4, 5, 6, 7, 8, 9, 14, 16], "eas": 2, "easi": [4, 9, 13], "easili": [6, 9, 11, 13, 14, 16], "econom": 1, "edit": 1, "educ": 1, "effect": [], "effici": [2, 4, 5, 7], "either": [9, 16], "element": [5, 6, 7, 9, 16], "els": 2, "email": 1, "empathi": 1, "en": 16, "enabl": [5, 6], "enclos": 6, "encod": [4, 5, 6, 7, 16], "encode_sequ": 5, "encount": 2, "encrypt": 6, "end": [4, 5, 7, 9], "english": [5, 14], "enough": [2, 16], "ensur": 2, "entri": 5, "environ": [1, 12], "eo": 5, "equiv": 16, "estim": 7, "etc": 6, "ethnic": 1, "evalu": [14, 16], "event": 1, "everyon": 1, "everyth": [2, 16], "exact": [9, 16], "exampl": [1, 2, 4, 5, 7, 13, 16], "exchang": 15, "execut": 16, "exist": 13, "expand": 8, "expect": [6, 8, 9], "experi": 1, "explan": [1, 16], "explicit": 1, "exploit": [4, 7], "export": [6, 7, 9, 10, 16], "export_as_straight_box": [7, 16], "export_as_xml": 16, "export_model_to_onnx": 15, "express": [1, 8], "extens": 6, "extern": [1, 14], "extra": 3, "extract": [4, 5], "extractor": 7, "f_": 9, "f_a": 9, "factor": 8, "fair": 1, "fairli": 1, "fals": [5, 6, 7, 8, 9, 11, 16], "famili": 9, "faq": 1, "fascan": 13, "fast": [4, 5, 7], "fast_bas": [], "fast_smal": [], "fast_tini": [], "faster": [7, 15], "fasterrcnn_mobilenet_v3_large_fpn": 7, "favorit": 16, "featur": [3, 7, 9, 10], "feedback": 1, "feel": [2, 13], "felix92": 13, "few": [3, 15, 16], "figsiz": 9, "figur": 9, "file": [2, 5], "final": 7, "find": [2, 3, 14], "fine": [], "finnish": 5, "first": [2, 5], "firsthand": 5, "fit": [7, 16], "flag": 16, "flip": 8, "float": [6, 8, 9, 15], "float32": [6, 7, 8, 15], "fn": 8, "focu": 13, "focus": [1, 5], "folder": 5, "follow": [1, 2, 3, 5, 8, 9, 11, 12, 13, 16], "font": [5, 9], "font_famili": [5, 9], "font_siz": 9, "foral": 9, "forc": 2, "forg": [], "form": [4, 5, 16], "format": [6, 9, 11, 14, 15, 16], "forpost": [4, 5], "forum": 2, "found": [], "fp16": 15, "frac": 9, "framework": [3, 13, 14, 16], "free": [1, 2, 13], "french": [5, 11, 13, 16], "friendli": 4, "from": [1, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16], "from_hub": [7, 13], "from_imag": [6, 13], "from_pdf": 6, "from_url": 6, "full": [5, 9, 16], "function": [5, 8, 9], "funsd": [4, 5, 14, 16], "further": 14, "futur": 5, "g": [6, 7], "g_": 9, "g_x": 9, "gallagh": [], "gamma": 8, "gaussian": 8, "gaussianblur": 8, "gaussiannois": 8, "gdk": 3, "gen": 16, "gender": 1, "gener": [2, 4, 7], "generic_cyrillic_lett": [], "geometri": [4, 6, 16], "geq": 9, "german": [5, 11, 13], "get": 16, "git": 13, "github": [2, 3, 7, 13], "give": 1, "given": [5, 6, 8, 9, 16], "global": 7, "go": 16, "good": 15, "googl": 2, "googlevis": 4, "gpu": [4, 15], "gracefulli": 1, "graph": [4, 5, 6], "grayscal": 8, "ground": 9, "groung": 9, "group": [4, 16], "gt": 9, "gt_box": 9, "gt_label": 9, "gtk": 3, "guid": 2, "guidanc": 14, "gvision": 16, "h": [6, 7, 8], "h_": 9, "ha": [2, 5, 9, 14], "handl": [14, 16], "handwrit": 5, "handwritten": 14, "harass": 1, "hardwar": [], "harm": 1, "hat": 9, "have": [1, 2, 9, 11, 13, 14, 16], "head": [7, 16], "healthi": 1, "hebrew": 5, "height": 6, "hello": [9, 16], "help": 15, "here": [3, 8, 10, 14, 16], "hf": 7, "hf_hub_download": 7, "high": 6, "higher": [3, 5, 16], "hindi": [], "hindi_digit": 5, "hocr": 16, "homebrew": 3, "hook": 16, "horizont": [6, 8], "hous": 5, "how": [2, 11, 13, 14], "howev": 14, "hsv": 8, "html": [1, 2, 6, 16], "http": [1, 3, 5, 6, 7, 13, 16], "hub": 7, "hue": 8, "huggingfac": 7, "hw": 5, "i": [1, 2, 5, 6, 7, 8, 9, 12, 13, 14, 15], "i7": 16, "ibrahimov": [], "ic03": [4, 5, 14], "ic13": [4, 5, 14], "icdar": [4, 5], "icdar2019": 5, "id": 16, "ident": 1, "identifi": 4, "iiit": [4, 5], "iiit5k": [5, 14], "iiithw": [4, 5, 14], "imag": [4, 5, 6, 7, 8, 9, 13, 14, 16], "imagenet": 7, "imageri": 1, "images_90k_norm": 5, "img": [5, 8, 14], "img_cont": 6, "img_fold": [5, 14], "img_path": 6, "img_transform": 5, "imgur5k": [4, 5, 14], "imgur5k_annot": 5, "imlist": 5, "impact": 1, "implement": [5, 6, 7, 8, 9, 16], "import": [5, 6, 7, 8, 9, 11, 13, 14, 15, 16], "improv": 7, "inappropri": 1, "incid": 1, "includ": [1, 3, 5, 14, 15], "inclus": 1, "increas": 8, "independ": [], "index": [2, 6], "indic": 9, "individu": 1, "infer": [4, 7, 8], "inform": [1, 2, 4, 5, 14], "input": [2, 6, 7, 8, 15, 16], "input_crop": 7, "input_pag": [7, 9, 16], "input_shap": 15, "input_tensor": 7, "inspir": [1, 8], "instal": 13, "instanc": [1, 16], "instanti": [7, 16], "instead": [5, 6, 7], "insult": 1, "int": [5, 6, 8, 9], "int64": [8, 9], "integ": 9, "integr": [4, 13, 14], "intel": 16, "interact": [1, 6, 9], "interfac": 13, "interoper": 15, "interpol": 8, "interpret": [5, 6], "intersect": 9, "invert": 8, "investig": 1, "invis": 1, "involv": [1, 16], "io": 13, "iou": 9, "iou_thresh": 9, "iou_threshold": [], "irregular": [4, 7, 14], "isn": 5, "issu": [1, 2, 13], "italian": 5, "iter": [5, 8, 14, 16], "its": [6, 7, 8, 9, 14, 16], "itself": [7, 13], "j": 9, "jame": [], "job": 2, "join": 2, "jpeg": 8, "jpegqual": 8, "jpg": [5, 6, 13], "json": [5, 14, 16], "json_output": 16, "jump": 2, "just": 1, "kei": [4, 5], "kera": [7, 15], "kernel": [7, 8], "kernel_shap": 8, "keywoard": 7, "keyword": [5, 6, 7, 9], "kie": [7, 11], "kie_predictor": [7, 11], "kiepredictor": 7, "kind": 1, "know": 2, "kwarg": [5, 6, 7, 9], "l": 9, "l_j": 9, "label": [5, 8, 9, 14], "label_fil": [5, 14], "label_fold": 5, "label_path": [5, 14], "labels_path": [5, 14], "ladder": 1, "lambda": 8, "lambdatransform": 8, "lang": 16, "languag": [1, 4, 5, 6, 7, 13, 16], "larg": [7, 13], "largest": 9, "last": [3, 5], "latenc": 7, "later": 2, "latest": [3, 16], "latin": 5, "layer": 15, "layout": 16, "lead": 1, "leader": 1, "learn": [1, 4, 7, 15, 16], "least": 3, "left": [9, 16], "legacy_french": 5, "length": [5, 16], "less": [15, 16], "level": [1, 5, 9, 16], "leverag": 10, "lf": 13, "libffi": 3, "librari": [2, 3, 10, 11], "light": 4, "lightweight": [], "like": 1, "limits_": 9, "line": [4, 7, 9, 16], "line_1_1": 16, "link": 11, "linknet": [4, 7], "linknet_resnet18": [7, 11, 16], "linknet_resnet18_rot": [], "linknet_resnet34": [7, 15, 16], "linknet_resnet50": [7, 16], "linux": 3, "list": [5, 6, 8, 9, 13], "ll": 9, "load": [4, 5, 7], "load_state_dict": 11, "load_weight": 11, "loc_pr": 16, "local": [2, 4, 5, 7, 9, 14, 16], "localis": 5, "localizationconfus": 9, "locat": [2, 6, 16], "login": 7, "login_to_hub": [7, 13], "logo": [6, 14], "love": 13, "lower": [8, 9, 16], "m": [2, 9, 16], "m1": 3, "macbook": 3, "machin": 15, "maco": 3, "made": 4, "magc_resnet31": 7, "mai": [1, 2], "mail": 1, "main": 10, "maintain": 4, "mainten": 2, "make": [1, 2, 9, 12, 13, 15, 16], "mani": [14, 16], "manipul": 16, "map": [5, 7], "map_loc": 11, "mask_shap": 9, "master": [4, 7, 16], "match": [9, 16], "mathcal": 9, "matplotlib": [6, 9], "max": [5, 8, 9], "max_angl": 8, "max_area": 8, "max_char": [5, 14], "max_delta": 8, "max_gain": 8, "max_gamma": 8, "max_qual": 8, "max_ratio": 8, "maximum": [5, 8], "maxval": [7, 8], "mbox": 9, "mean": [8, 9, 11], "meaniou": 9, "meant": [6, 15], "measur": 16, "media": 1, "median": 7, "meet": 11, "member": 1, "memori": [9, 12, 15], "mention": 16, "merg": 5, "messag": 2, "meta": 16, "metadata": 15, "metal": 3, "method": [6, 8, 16], "metric": [9, 16], "middl": 16, "might": [15, 16], "min": 8, "min_area": 8, "min_char": [5, 14], "min_gain": 8, "min_gamma": 8, "min_qual": 8, "min_ratio": 8, "min_val": 8, "minde": [1, 3, 4, 7], "minim": [2, 4], "minimalist": 7, "minimum": [3, 5, 8, 9, 16], "minval": 8, "miss": 3, "mistak": 1, "mixed_float16": 15, "mixed_precis": 15, "mjsynth": [4, 5, 14], "mnt": 5, "mobilenet": [7, 13], "mobilenet_v3_larg": 7, "mobilenet_v3_large_r": 7, "mobilenet_v3_smal": 7, "mobilenet_v3_small_crop_orient": [], "mobilenet_v3_small_orient": 7, "mobilenet_v3_small_page_orient": [], "mobilenet_v3_small_r": 7, "mobilenetv3": 7, "modal": [4, 5], "mode": 3, "model": [5, 9, 12, 14], "model_nam": [7, 13, 15], "model_path": 15, "moder": 1, "modif": 2, "modifi": [7, 12, 16], "modul": [6, 7, 8, 9, 16], "moment": [], "more": [2, 9, 14, 16], "moscardi": [], "most": 16, "mozilla": 1, "multi": [4, 7], "multilingu": [5, 13], "multipl": [5, 6, 8, 16], "multipli": 8, "multiprocess": 12, "my": 7, "my_awesome_model": 13, "my_hook": 16, "n": [5, 9], "name": [5, 7, 15, 16], "nation": 1, "natur": [1, 4, 5], "nb": [], "ndarrai": [5, 6, 8, 9], "necessari": [3, 11, 12], "need": [2, 3, 5, 9, 11, 12, 13, 16], "neg": 8, "nest": 16, "netraj": [], "network": [4, 5, 7, 15], "neural": [4, 5, 7, 15], "new": [2, 9], "next": [5, 14], "nois": 8, "noisi": [4, 5], "non": [4, 5, 6, 7, 8, 9], "none": [5, 6, 7, 8, 9, 16], "normal": [7, 8], "norwegian": 5, "note": [0, 2, 5, 7, 13, 15], "now": 2, "np": [7, 8, 9, 16], "num_output_channel": 8, "num_sampl": [5, 14], "num_work": 5, "number": [5, 8, 9, 16], "numpi": [6, 7, 9, 16], "o": 3, "obb": [], "obj_detect": 13, "object": [5, 9, 10, 16], "objectness_scor": [], "oblig": 1, "obtain": 16, "occupi": 15, "ocr": [4, 5, 7, 9, 13, 14], "ocr_carea": 16, "ocr_db_crnn": 9, "ocr_lin": 16, "ocr_pag": 16, "ocr_par": 16, "ocr_predictor": [7, 11, 13, 15, 16], "ocrdataset": [5, 14], "ocrmetr": 9, "ocrpredictor": [7, 11], "ocrx_word": 16, "offens": 1, "offici": [1, 7], "offlin": 1, "offset": 8, "onc": 16, "one": [2, 5, 7, 8, 11, 13, 16], "oneof": 8, "ones": [5, 8, 9], "onli": [2, 7, 8, 9, 13, 14, 15, 16], "onlin": 1, "onnx": [], "onnxruntim": [], "onnxtr": [], "opac": 8, "opacity_rang": 8, "open": [1, 2, 13, 15], "opinion": 1, "optic": [4, 16], "optim": 4, "option": [5, 7, 11], "order": [2, 5, 6, 8], "org": [1, 5, 7, 16], "organ": 6, "orient": [1, 6, 7, 16], "orientationpredictor": [], "other": [1, 2], "otherwis": [1, 6, 9], "our": [2, 7, 16], "out": [2, 7, 8, 9, 16], "outpout": 16, "output": [6, 8, 15], "output_s": [6, 8], "outsid": 12, "over": [3, 5, 9, 16], "overal": [1, 7], "overlai": 6, "overview": [], "overwrit": [], "overwritten": 13, "own": 4, "p": [8, 9, 16], "packag": [2, 4, 9, 12, 14], "pad": [5, 7, 8, 16], "page": [3, 5, 7, 9, 16], "page1": 6, "page2": 6, "page_1": 16, "page_idx": [6, 16], "page_orientation_predictor": [], "page_param": [], "pair": 9, "pango": 3, "paper": 7, "par_1_1": 16, "paragraph": 16, "paragraph_break": 16, "parallel": [], "param": [8, 16], "paramet": [4, 6, 7, 15], "pars": [4, 5], "parseq": [4, 7, 13, 16], "part": [5, 8, 16], "parti": 3, "partial": 16, "particip": 1, "pass": [5, 6, 7, 16], "password": 6, "patch": [7, 9], "path": [5, 6, 14], "path_to_checkpoint": 11, "path_to_custom_model": [], "path_to_pt": 11, "patil": [], "pattern": 1, "pdf": [6, 7, 10], "pdfpage": 6, "peopl": 1, "per": [8, 16], "perform": [4, 6, 7, 8, 9, 12, 15, 16], "period": 1, "permiss": 1, "permut": [4, 7], "persian_lett": 5, "person": [1, 14], "phase": 16, "photo": 14, "physic": [1, 6], "pick": 8, "pictur": 6, "pip": [2, 3], "pipelin": 16, "pixbuf": 3, "pixel": [6, 8, 16], "pleas": 2, "plot": 9, "plt": 9, "plug": 13, "plugin": 3, "png": 6, "point": 15, "polici": 12, "polish": 5, "polit": 1, "polygon": [5, 9, 16], "pool": 7, "portugues": 5, "posit": [1, 9], "possibl": [2, 9, 13, 16], "post": [1, 16], "postprocessor": 16, "potenti": 7, "power": 4, "ppageno": 16, "pre": [2, 7], "precis": [9, 16], "pred": 9, "pred_box": 9, "pred_label": 9, "predefin": 14, "predict": [6, 7, 9, 16], "predictor": [4, 6, 7, 11, 13, 15], "prefer": 14, "preinstal": [], "preprocessor": [11, 16], "prerequisit": 13, "present": 10, "preserv": [7, 8, 16], "preserve_aspect_ratio": [6, 7, 8, 11, 16], "pretrain": [4, 7, 9, 11, 15, 16], "pretrained_backbon": [7, 11], "print": 16, "prior": 5, "privaci": 1, "privat": 1, "probabl": 8, "problem": 2, "procedur": 8, "process": [2, 4, 6, 11, 16], "processor": 16, "produc": [10, 16], "product": 15, "profession": 1, "project": [2, 14], "promptli": 1, "proper": 2, "properli": 5, "provid": [1, 2, 4, 13, 14, 16], "public": [1, 4], "publicli": 16, "publish": 1, "pull": 13, "punctuat": 5, "pure": 5, "purpos": 2, "push_to_hf_hub": [7, 13], "py": 13, "pypdfium2": 6, "pyplot": [6, 9], "python": 2, "python3": 13, "pytorch": [3, 4, 7, 8, 11, 13, 15, 16], "q": 2, "qr": 6, "qr_code": 14, "qualiti": 8, "question": 1, "quickli": 4, "quicktour": 10, "r": 16, "race": 1, "ramdisk": 5, "rand": [7, 8, 9, 15, 16], "random": [7, 8, 9, 16], "randomappli": 8, "randombright": 8, "randomcontrast": 8, "randomcrop": 8, "randomgamma": 8, "randomhorizontalflip": 8, "randomhu": 8, "randomjpegqu": 8, "randomli": 8, "randomres": [], "randomrot": 8, "randomsatur": 8, "randomshadow": 8, "rang": 8, "rassi": 13, "ratio": [7, 8, 16], "raw": [6, 9], "re": 15, "read": [4, 5, 7], "read_html": 6, "read_img": [], "read_img_as_numpi": 6, "read_img_as_tensor": 6, "read_pdf": 6, "readi": 15, "real": [4, 7, 8], "realli": [], "reason": [1, 4, 5], "rebuild": 2, "rebuilt": 2, "recal": [9, 16], "receipt": [4, 5, 16], "reco_arch": [7, 11, 13, 15], "reco_b": [], "reco_model": [11, 13], "reco_param": 11, "reco_predictor": 11, "recogn": 16, "recognit": [5, 9, 11], "recognition_predictor": [7, 16], "recognition_task": [5, 14], "recognitiondataset": [5, 14], "recognitionpredictor": [7, 11], "rectangular": 7, "red": 9, "reduc": [3, 8], "refer": [2, 3, 11, 13, 14, 16], "regardless": 1, "region": 16, "regroup": 9, "regular": 14, "reject": 1, "rel": [6, 8, 9, 16], "relat": 6, "releas": [0, 3], "relev": [], "religion": 1, "remov": 1, "render": [6, 16], "repo": 7, "repo_id": [7, 13], "report": 1, "repositori": [5, 7, 13], "repres": [1, 9, 15, 16], "represent": [4, 7], "request": [1, 13], "requir": [3, 8], "research": 4, "residu": 7, "resiz": [8, 16], "resnet": 7, "resnet18": [7, 13], "resnet31": 7, "resnet34": 7, "resnet50": [7, 13], "resolv": 6, "resolve_block": 16, "resolve_lin": 16, "resourc": 14, "respect": 1, "respons": 9, "rest": [2, 8, 9], "restrict": 12, "result": [2, 5, 6, 10, 13, 16], "return": 16, "reusabl": 16, "review": 1, "rgb": [6, 8], "rgb_mode": 6, "rgb_output": 6, "right": [1, 7, 9], "roboflow": [], "robust": [4, 5], "root": 5, "rotat": [5, 6, 7, 8, 9, 14, 16], "run": [2, 3, 7], "same": [2, 6, 9, 14, 16], "sampl": [5, 14, 16], "sample_transform": 5, "sanjin": [], "sar": [4, 7], "sar_resnet31": [7, 16], "satur": 8, "save": [7, 14], "scale": [6, 7, 8, 9], "scale_rang": [], "scan": [4, 5], "scene": [4, 5, 7], "score": 9, "script": [2, 14], "seamless": 4, "seamlessli": [4, 16], "search": 7, "searchabl": 10, "sec": 16, "second": 16, "section": [11, 13, 15, 16], "secur": [1, 12], "see": [1, 2], "seen": 16, "segment": [4, 7, 16], "self": 16, "semant": [4, 7], "send": 16, "sens": 9, "sensit": 14, "separ": 16, "sequenc": [4, 5, 6, 7, 9, 16], "sequenti": [8, 16], "seri": 1, "seriou": 1, "set": [1, 5, 7, 9, 12, 16], "set_global_polici": 15, "sever": [6, 8, 16], "sex": 1, "sexual": 1, "shade": 8, "shape": [6, 7, 8, 9, 16], "share": [12, 14], "shift": 8, "shm": 12, "should": [2, 5, 6, 8, 9], "show": [4, 6, 7, 9, 11, 13], "showcas": 2, "shuffl": [5, 8], "side": 9, "signatur": 6, "signific": 14, "simpl": [4, 7], "simpler": 7, "sinc": [5, 14], "singl": [1, 2, 4, 5], "single_img_doc": [], "size": [1, 5, 6, 8, 9, 16], "skew": 16, "slack": 2, "slightli": 7, "small": [2, 7], "smallest": 6, "snapshot_download": 7, "snippet": 16, "so": [2, 3, 5, 7, 13, 14], "social": 1, "socio": 1, "some": [3, 10, 13, 14], "someth": 2, "somewher": 2, "soon": 15, "sort": 1, "sourc": [5, 6, 7, 8, 9, 13], "space": [1, 16], "span": 16, "spanish": 5, "spatial": [4, 5, 6, 9], "specif": [2, 3, 9, 11, 14, 16], "specifi": [1, 5, 6], "speed": [4, 7], "sphinx": 2, "sroie": [4, 5, 14], "stabl": 3, "stackoverflow": 2, "stage": 4, "standalon": [], "standard": 8, "start": 5, "state": [4, 9], "static": 9, "statist": [], "statu": 1, "std": [8, 11], "step": 12, "still": 16, "str": [5, 6, 7, 8, 9], "straight": [5, 7, 14, 16], "straighten": [], "straighten_pag": 7, "straigten_pag": [], "stream": 6, "street": [4, 5], "strict": [], "strictli": 9, "string": [5, 6, 9, 16], "strive": 3, "strong": [4, 7], "structur": [15, 16], "subset": [5, 16], "suggest": [2, 13], "sum": 9, "summari": 9, "support": [15, 16], "sustain": 1, "svhn": [4, 5, 14], "svt": [5, 14], "swedish": 5, "symmetr": [7, 8, 16], "symmetric_pad": [7, 8, 16], "synthes": 9, "synthesize_pag": 9, "synthet": 4, "synthtext": [4, 5, 14], "system": 16, "t": [2, 5, 11, 16], "tabl": 13, "take": [1, 5, 16], "target": [5, 6, 8, 9, 14], "target_s": 5, "task": [4, 5, 7, 13, 14, 16], "task2": 5, "team": [], "techminde": [], "templat": [2, 4], "tensor": [5, 6, 8, 16], "tensorflow": [3, 4, 6, 7, 8, 11, 13, 15, 16], "tensorspec": 15, "term": 1, "test": [5, 14], "test_set": 5, "text": [5, 6, 7, 9, 14], "text_output": 16, "textmatch": 9, "textnet": 7, "textnet_bas": 7, "textnet_smal": 7, "textnet_tini": 7, "textract": [4, 16], "textstylebrush": [4, 5], "textual": [4, 5, 6, 7, 16], "tf": [3, 6, 7, 8, 13, 15], "than": [2, 3, 9, 13], "thank": 2, "thei": [1, 9], "them": [3, 5, 16], "thi": [1, 2, 3, 5, 9, 11, 12, 13, 14, 15, 16], "thing": [15, 16], "third": 3, "those": [1, 3, 6, 16], "threaten": 1, "threshold": 16, "through": [1, 8, 14], "tilman": 13, "time": [1, 4, 7, 9, 14], "tini": 7, "titl": [6, 16], "tm": 16, "tmp": 12, "togeth": [2, 6], "tograi": 8, "tool": 14, "top": [9, 16], "topic": 2, "torch": [3, 8, 11, 13, 15], "torchvis": 8, "total": 11, "toward": [1, 3], "train": [2, 5, 7, 8, 13, 14, 15, 16], "train_it": [5, 14], "train_load": [5, 14], "train_pytorch": 13, "train_set": [5, 14], "train_tensorflow": 13, "trainabl": [4, 7], "tranform": 8, "transcrib": 16, "transfer": [4, 5], "transfo": 8, "transform": [4, 5, 7], "translat": 1, "troll": 1, "true": [5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16], "truth": 9, "tune": 15, "tupl": [5, 6, 8, 9], "two": [6, 12], "txt": 5, "type": [6, 9, 13, 15, 16], "typic": 16, "u": [1, 2], "ucsd": 5, "udac": 2, "uint8": [6, 7, 9, 16], "ukrainian": [], "unaccept": 1, "underli": [14, 16], "underneath": 6, "understand": [4, 5, 16], "unidecod": 9, "uniform": [7, 8], "uniformli": 8, "uninterrupt": [6, 16], "union": 9, "unit": [], "unittest": 2, "unlock": 6, "unoffici": 7, "unprofession": 1, "unsolicit": 1, "unsupervis": 4, "unwelcom": 1, "up": [7, 16], "updat": 9, "upgrad": 2, "upper": [5, 8], "uppercas": 14, "url": 6, "us": [1, 2, 3, 5, 7, 9, 11, 12, 13, 16], "usabl": 16, "usag": [12, 15], "use_broadcast": 9, "use_polygon": [5, 9, 14], "useabl": 16, "user": [3, 4, 6, 10], "utf": 16, "util": 15, "v1": 13, "v3": [7, 13, 16], "valid": 14, "valu": [2, 6, 8, 16], "valuabl": 4, "variabl": 12, "varieti": 5, "veri": 7, "verma": [], "version": [1, 2, 3, 15, 16], "vgg": 7, "vgg16": 13, "vgg16_bn_r": 7, "via": 1, "video": [], "vietnames": 5, "view": [4, 5], "viewpoint": 1, "violat": 1, "visibl": 1, "vision": [4, 5, 7], "visiondataset": 5, "visiontransform": 7, "visual": 4, "visualize_pag": 9, "vit_": 7, "vit_b": 7, "vitstr": [4, 7, 15], "vitstr_bas": [7, 16], "vitstr_smal": [7, 11, 15, 16], "viz": [], "vocab": [11, 13, 14, 16], "vocabulari": [5, 11, 13], "w": [6, 7, 8, 9], "w3": 16, "wa": 1, "wai": [1, 4, 14], "want": [2, 15, 16], "warmup": 16, "wasn": 2, "we": [1, 2, 3, 4, 6, 8, 13, 14, 15, 16], "weasyprint": 6, "web": [2, 6], "websit": 5, "weight": 11, "welcom": 1, "well": [1, 15], "were": [1, 6, 16], "what": 1, "when": [1, 2, 7], "whenev": 2, "where": [2, 6, 8, 9], "whether": [2, 5, 6, 8, 9, 14, 16], "which": [1, 7, 12, 14, 16], "whichev": 3, "while": [8, 16], "why": 1, "width": 6, "wiki": 1, "wildreceipt": [4, 5, 14], "window": [3, 7, 9], "wish": 2, "within": 1, "without": [1, 5, 7], "wonder": 2, "word": [4, 5, 7, 9, 16], "word_1_1": 16, "word_1_2": 16, "word_1_3": 16, "wordgener": [5, 14], "words_onli": 9, "work": [12, 16], "worker": 5, "workflow": 2, "worklow": 2, "world": [9, 16], "worth": 7, "wrap": 16, "wrapper": [5, 8], "write": 12, "written": [1, 6], "www": [1, 6, 16], "x": [6, 8, 9], "x_ascend": 16, "x_descend": 16, "x_i": 9, "x_size": 16, "x_wconf": 16, "xhtml": 16, "xmax": 6, "xmin": 6, "xml": 16, "xml_bytes_str": 16, "xml_element": 16, "xml_output": 16, "xmln": 16, "y": 9, "y_i": 9, "y_j": 9, "yet": [], "ymax": 6, "ymin": 6, "yolov8": [], "you": [2, 3, 5, 6, 7, 11, 12, 13, 14, 15, 16], "your": [2, 4, 6, 9, 16], "yoursit": 6, "yugesh": [], "zero": [8, 9], "zoo": [], "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 5, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 5, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": 5, "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 5, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 5, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": [], "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 5, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 5, "\u00e4\u00f6\u00e4\u00f6": 5, "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 5, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": 5, "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": 5, "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": 5, "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 5, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": [], "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": [], "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": 5, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 5, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 5, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 5, "\u067e\u0686\u06a2\u06a4\u06af": 5, "\u0905": [], "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": [], "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": [], "\u0950": [], "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": [], "\u09bd": [], "\u09ce": [], "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": []}, "titles": ["Changelog", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 2, "0": 0, "01": 0, "02": 0, "03": 0, "04": [], "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 1], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 1], "2021": 0, "2022": 0, "2023": [], "2024": 0, "21": [], "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 1], "31": 0, "4": [0, 1], "5": 0, "6": 0, "7": 0, "8": [], "9": [], "advanc": 16, "approach": 16, "architectur": 16, "arg": [5, 6, 7, 8, 9], "artefact": 6, "artefactdetect": [], "attribut": 1, "avail": [14, 16], "aw": 12, "ban": 1, "block": 6, "bug": 2, "changelog": 0, "choos": [14, 16], "classif": [7, 13], "code": [1, 2], "codebas": 2, "commit": 2, "commun": 13, "compos": 8, "conda": [], "conduct": 1, "connect": 2, "continu": 2, "contrib": [], "contribut": 2, "contributor": 1, "convent": 13, "correct": 1, "coven": 1, "custom": [5, 11], "data": 14, "dataload": 5, "dataset": [4, 5, 14], "detect": [4, 7, 13, 14, 16], "develop": 2, "do": 16, "doctr": [2, 4, 5, 6, 7, 8, 9, 10, 15], "document": [2, 4, 6], "end": 16, "enforc": 1, "evalu": 9, "export": 15, "factori": 7, "featur": [2, 4], "feedback": 2, "file": 6, "from": 13, "gener": [5, 14], "git": 3, "guidelin": 1, "half": 15, "hub": 13, "huggingfac": 13, "i": 16, "infer": 15, "instal": [2, 3], "integr": 2, "io": 6, "lambda": 12, "let": 2, "line": 6, "linux": [], "load": [11, 13, 14], "loader": 5, "main": 4, "mode": 2, "model": [4, 7, 11, 13, 15, 16], "modifi": 2, "modul": [], "name": 13, "notebook": 10, "object": 14, "ocr": 16, "onli": [], "onnx": 15, "optim": 15, "option": 16, "orient": [], "our": 1, "output": 16, "own": [11, 14], "packag": 3, "page": 6, "perman": 1, "pipelin": [], "pledg": 1, "precis": 15, "predictor": 16, "prepar": 15, "prerequisit": 3, "pretrain": 13, "push": 13, "python": 3, "qualiti": 2, "question": 2, "read": 6, "readi": 14, "recognit": [4, 7, 13, 14, 16], "report": 2, "request": 2, "resourc": [], "respons": 1, "return": [5, 6, 7, 9], "right": 16, "scope": 1, "share": 13, "should": 16, "stage": 16, "standard": 1, "structur": [2, 6], "style": 2, "support": [4, 5, 8], "synthet": [5, 14], "task": 9, "temporari": 1, "test": 2, "text": [4, 16], "train": 11, "transform": 8, "two": 16, "unit": 2, "us": [14, 15], "util": 9, "v0": 0, "verif": 2, "via": 3, "visual": 9, "vocab": 5, "warn": 1, "what": 16, "word": 6, "your": [11, 13, 14, 15], "zoo": [4, 7]}}) \ No newline at end of file diff --git a/v0.8.0/transforms.html b/v0.8.0/transforms.html deleted file mode 100644 index 85e94d8a76..0000000000 --- a/v0.8.0/transforms.html +++ /dev/null @@ -1,684 +0,0 @@ - - - - - - - - - - - - - doctr.transforms - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    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:

    -
    -
    -class doctr.transforms.Resize(output_size: Tuple[int, int], method: str = 'bilinear', preserve_aspect_ratio: bool = False, symmetric_pad: bool = False)[source]
    -

    Resizes a tensor to a target size

    -
    -
    Example::
    >>> from doctr.transforms import Resize
    ->>> import tensorflow as tf
    ->>> transfo = Resize((32, 32))
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • output_size – expected output size

    • -
    • method – interpolation method

    • -
    • preserve_aspect_ratio – if True, preserve aspect ratio and pad the rest with zeros

    • -
    • symmetric_pad – if True while preserving aspect ratio, the padding will be done symmetrically

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.Normalize(mean: Tuple[float, float, float], std: Tuple[float, float, float])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • mean – average value per channel

    • -
    • std – standard deviation per channel

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.LambdaTransformation(fn: Callable[[Tensor], Tensor])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import LambdaTransformation
    ->>> import tensorflow as tf
    ->>> transfo = LambdaTransformation(lambda x: x/ 255.)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    fn – the function to be applied to the input tensor

    -
    -
    -
    - -
    -
    -class doctr.transforms.ToGray[source]
    -

    Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ToGray()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    - -
    -
    -class doctr.transforms.ColorInversion(min_val: float = 0.5)[source]
    -

    Applies the following tranformation to a tensor (image or batch of images): -convert to grayscale, colorize (shift 0-values randomly), and then invert colors

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ColorInversion(min_val=0.6)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    min_val – range [min_val, 1] to colorize RGB pixels

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomBrightness(max_delta: float = 0.3)[source]
    -

    Randomly adjust brightness of a tensor (batch of images or image) by adding a delta -to all pixels

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Brightness()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    • -
    • p – probability to apply transformation

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomContrast(delta: float = 0.3)[source]
    -

    Randomly adjust contrast of a tensor (batch of images or image) by adjusting -each pixel: (img - mean) * contrast_factor + mean.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Contrast()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomSaturation(delta: float = 0.5)[source]
    -

    Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and -increasing saturation by a factor.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Saturation()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomHue(max_delta: float = 0.3)[source]
    -

    Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Hue()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomGamma(min_gamma: float = 0.5, max_gamma: float = 1.5, min_gain: float = 0.8, max_gain: float = 1.2)[source]
    -

    randomly performs gamma correction for a tensor (batch of images or image)

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Gamma()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_gamma – non-negative real number, lower bound for gamma param

    • -
    • max_gamma – non-negative real number, upper bound for gamma

    • -
    • min_gain – lower bound for constant multiplier

    • -
    • max_gain – upper bound for constant multiplier

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomJpegQuality(min_quality: int = 60, max_quality: int = 100)[source]
    -

    Randomly adjust jpeg quality of a 3 dimensional RGB image

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = JpegQuality()
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_quality – int between [0, 100]

    • -
    • max_quality – int between [0, 100]

    • -
    -
    -
    -
    - -
    -
    -

    Composing transformations

    -

    It is common to require several transformations to be performed consecutively.

    -
    -
    -class doctr.transforms.Compose(transforms: List[Callable[[Any], Any]])[source]
    -

    Implements a wrapper that will apply transformations sequentially

    -
    -
    Example::
    >>> from doctr.transforms import Compose, Resize
    ->>> import tensorflow as tf
    ->>> transfos = Compose([Resize((32, 32))])
    ->>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformation modules

    -
    -
    -
    - -
    -
    -class doctr.transforms.OneOf(transforms: List[Callable[[Any], Any]])[source]
    -

    Randomly apply one of the input transformations

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = OneOf([JpegQuality(), Gamma()])
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformations, one only will be picked

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomApply(transform: Callable[[Any], Any], p: float = 0.5)[source]
    -

    Apply with a probability p the input transformation

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = RandomApply(Gamma(), p=.5)
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • transform – transformation to apply

    • -
    • p – probability to apply

    • -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.0/using_doctr/custom_models_training.html b/v0.8.0/using_doctr/custom_models_training.html index 9d5e2b2767..cae5a30ecd 100644 --- a/v0.8.0/using_doctr/custom_models_training.html +++ b/v0.8.0/using_doctr/custom_models_training.html @@ -14,7 +14,7 @@ - + Train your own model - docTR documentation @@ -545,7 +545,7 @@

    Loading your custom trained model - + diff --git a/v0.8.0/using_doctr/running_on_aws.html b/v0.8.0/using_doctr/running_on_aws.html index 87855efb54..86eb396241 100644 --- a/v0.8.0/using_doctr/running_on_aws.html +++ b/v0.8.0/using_doctr/running_on_aws.html @@ -14,7 +14,7 @@ - + AWS Lambda - docTR documentation @@ -356,7 +356,7 @@

    AWS Lambda - + diff --git a/v0.8.0/using_doctr/sharing_models.html b/v0.8.0/using_doctr/sharing_models.html index 27592853b0..94bfbf9a72 100644 --- a/v0.8.0/using_doctr/sharing_models.html +++ b/v0.8.0/using_doctr/sharing_models.html @@ -14,7 +14,7 @@ - + Share your model with the community - docTR documentation @@ -538,7 +538,7 @@

    Recognition - + diff --git a/v0.8.0/using_doctr/using_contrib_modules.html b/v0.8.0/using_doctr/using_contrib_modules.html index 50598dae5d..0c5fffdf9f 100644 --- a/v0.8.0/using_doctr/using_contrib_modules.html +++ b/v0.8.0/using_doctr/using_contrib_modules.html @@ -14,7 +14,7 @@ - + Integrate contributions into your pipeline - docTR documentation @@ -415,7 +415,7 @@

    ArtefactDetection - + diff --git a/v0.8.0/using_doctr/using_datasets.html b/v0.8.0/using_doctr/using_datasets.html index 99aca264ac..51ce87dd4d 100644 --- a/v0.8.0/using_doctr/using_datasets.html +++ b/v0.8.0/using_doctr/using_datasets.html @@ -14,7 +14,7 @@ - + Choose a ready to use dataset - docTR documentation @@ -623,7 +623,7 @@

    Data Loading - + diff --git a/v0.8.0/using_doctr/using_model_export.html b/v0.8.0/using_doctr/using_model_export.html index e0554e6718..0983118f82 100644 --- a/v0.8.0/using_doctr/using_model_export.html +++ b/v0.8.0/using_doctr/using_model_export.html @@ -14,7 +14,7 @@ - + Preparing your model for inference - docTR documentation @@ -436,7 +436,7 @@

    Using your ONNX exported model in docTR - + diff --git a/v0.8.0/using_doctr/using_models.html b/v0.8.0/using_doctr/using_models.html index e7751545e8..7f81e649f0 100644 --- a/v0.8.0/using_doctr/using_models.html +++ b/v0.8.0/using_doctr/using_models.html @@ -14,7 +14,7 @@ - + Choosing the right model - docTR documentation @@ -1135,7 +1135,7 @@

    Advanced options - + diff --git a/v0.8.0/utils.html b/v0.8.0/utils.html deleted file mode 100644 index e2f223f06a..0000000000 --- a/v0.8.0/utils.html +++ /dev/null @@ -1,574 +0,0 @@ - - - - - - - - - - - - - doctr.utils - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.utils

    -

    This module regroups non-core features that are complementary to the rest of the package.

    -
    -

    Visualization

    -

    Easy-to-use functions to make sense of your model’s predictions.

    -
    -
    -doctr.utils.visualization.visualize_page(page: Dict[str, Any], image: ndarray, words_only: bool = True, display_artefacts: bool = True, scale: float = 10, interactive: bool = True, add_labels: bool = True, **kwargs: Any) Figure[source]
    -

    Visualize a full page with predicted blocks, lines and words

    -
    -
    Example::
    >>> import numpy as np
    ->>> import matplotlib.pyplot as plt
    ->>> from doctr.utils.visualization import visualize_page
    ->>> from doctr.models import ocr_db_crnn
    ->>> model = ocr_db_crnn(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([[input_page]])
    ->>> visualize_page(out[0].pages[0].export(), input_page)
    ->>> plt.show()
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • page – the exported Page of a Document

    • -
    • image – np array of the page, needs to have the same shape than page[‘dimensions’]

    • -
    • words_only – whether only words should be displayed

    • -
    • display_artefacts – whether artefacts should be displayed

    • -
    • scale – figsize of the largest windows side

    • -
    • interactive – whether the plot should be interactive

    • -
    • add_labels – for static plot, adds text labels on top of bounding box

    • -
    -
    -
    -
    - -
    -
    -

    Task evaluation

    -

    Implementations of task-specific metrics to easily assess your model performances.

    -
    -
    -class doctr.utils.metrics.TextMatch[source]
    -

    Implements text match metric (word-level accuracy) for recognition task.

    -

    The raw aggregated metric is computed as follows:

    -
    -
    -\[\forall X, Y \in \mathcal{W}^N, -TextMatch(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N f_{Y_i}(X_i)\]
    -
    -

    with the indicator function \(f_{a}\) defined as:

    -
    -
    -\[\begin{split}\forall a, x \in \mathcal{W}, -f_a(x) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } x = a \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{W}\) is the set of all possible character sequences, -\(N\) is a strictly positive integer.

    -
    -
    Example::
    >>> from doctr.utils import TextMatch
    ->>> metric = TextMatch()
    ->>> metric.update(['Hello', 'world'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    -summary() Dict[str, float][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a dictionary with the exact match score for the raw data, its lower-case counterpart, its unidecode -counterpart and its lower-case unidecode counterpart

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.LocalizationConfusion(iou_thresh: float = 0.5, rotated_bbox: bool = False, mask_shape: Tuple[int, int] = (1024, 1024))[source]
    -

    Implements common confusion metrics and mean IoU for localization evaluation.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall Y \in \mathcal{B}^N, \forall X \in \mathcal{B}^M, \\ -Recall(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -Precision(X, Y) = \frac{1}{M} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -meanIoU(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(X_i, Y_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(g_{X}\) defined as:

    -
    -
    -\[\begin{split}\forall y \in \mathcal{B}, -g_X(y) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } y\mbox{ has been assigned to any }(X_i)_i\mbox{ with an }IoU \geq 0.5 \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import LocalizationConfusion
    ->>> metric = LocalizationConfusion(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]))
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[float | None, float | None, float | None][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall, precision and meanIoU scores

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.OCRMetric(iou_thresh: float = 0.5, rotated_bbox: bool = False, mask_shape: Tuple[int, int] = (1024, 1024))[source]
    -

    Implements end-to-end OCR metric.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall (B, L) \in \mathcal{B}^N \times \mathcal{L}^N, -\forall (\hat{B}, \hat{L}) \in \mathcal{B}^M \times \mathcal{L}^M, \\ -Recall(B, \hat{B}, L, \hat{L}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -Precision(B, \hat{B}, L, \hat{L}) = \frac{1}{M} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(h_{B, L}\) defined as:

    -
    -
    -\[\begin{split}\forall (b, l) \in \mathcal{B} \times \mathcal{L}, -h_{B,L}(b, l) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ - & IoU \geq 0.5 \mbox{ and that for this assignment, } l = L_j\\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(\mathcal{L}\) is the set of possible character sequences, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import OCRMetric
    ->>> metric = OCRMetric(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]),
    -['hello'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[Dict[str, float | None], Dict[str, float | None], float | None][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall & precision for each string comparison flexibility and the mean IoU

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.8.1/_modules/doctr/datasets/cord.html b/v0.8.1/_modules/doctr/datasets/cord.html index 354f0062c2..85f1a47a08 100644 --- a/v0.8.1/_modules/doctr/datasets/cord.html +++ b/v0.8.1/_modules/doctr/datasets/cord.html @@ -13,7 +13,7 @@ - + doctr.datasets.cord - docTR documentation @@ -447,7 +447,7 @@

    Source code for doctr.datasets.cord

         
       
    - + diff --git a/v0.8.1/_modules/doctr/datasets/core.html b/v0.8.1/_modules/doctr/datasets/core.html deleted file mode 100644 index b3dcc29ff9..0000000000 --- a/v0.8.1/_modules/doctr/datasets/core.html +++ /dev/null @@ -1,417 +0,0 @@ - - - - - - - - - - - - doctr.datasets.core - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.core

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from pathlib import Path
    -from zipfile import ZipFile
    -from typing import List, Any, Optional, Tuple
    -import tensorflow as tf
    -
    -from doctr.models.utils import download_from_url
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset:
    -
    -    data: List[Any] = []
    -
    -    def __len__(self):
    -        return len(self.data)
    -
    -    def __getitem__(
    -        self,
    -        index: int
    -    ) -> Tuple[tf.Tensor, Any]:
    -
    -        img_name, target = self.data[index]
    -        # Read image
    -        img = tf.io.read_file(os.path.join(self.root, img_name))
    -        img = tf.image.decode_jpeg(img, channels=3)
    -        if self.sample_transforms is not None:
    -            img = self.sample_transforms(img)
    -
    -        return img, target
    -
    -    def extra_repr(self) -> str:
    -        return ""
    -
    -    def __repr__(self) -> str:
    -        return f"{self.__class__.__name__}({self.extra_repr()})"
    -
    -    @staticmethod
    -    def collate_fn(samples: List[Tuple[tf.Tensor, Any]]) -> Tuple[tf.Tensor, List[Any]]:
    -
    -        images, targets = zip(*samples)
    -        images = tf.stack(images, axis=0)
    -
    -        return images, list(targets)
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset): - """Implements an abstract dataset - - Args: - url: URL of the dataset - file_name: name of the file once downloaded - file_hash: expected SHA256 of the file - extract_archive: whether the downloaded file is an archive to be extracted - download: whether the dataset should be downloaded if not present on disk - overwrite: whether the archive should be re-extracted - """ - - def __init__( - self, - url: str, - file_name: Optional[str] = None, - file_hash: Optional[str] = None, - extract_archive: bool = False, - download: bool = False, - overwrite: bool = False, - ) -> None: - - dataset_cache = os.path.join(os.path.expanduser('~'), '.cache', 'doctr', 'datasets') - - file_name = file_name if isinstance(file_name, str) else os.path.basename(url) - # Download the file if not present - archive_path = os.path.join(dataset_cache, file_name) - - if not os.path.exists(archive_path) and not download: - raise ValueError("the dataset needs to be downloaded first with download=True") - - archive_path = download_from_url(url, file_name, file_hash, cache_subdir='datasets') - - # Extract the archive - if extract_archive: - archive_path = Path(archive_path) - dataset_path = archive_path.parent.joinpath(archive_path.stem) - if not dataset_path.is_dir() or overwrite: - with ZipFile(archive_path, 'r') as f: - f.extractall(path=dataset_path) - - # List images - self._root = dataset_path if extract_archive else archive_path - self.data: List[Any] = []
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.1/_modules/doctr/datasets/datasets/tensorflow.html b/v0.8.1/_modules/doctr/datasets/datasets/tensorflow.html deleted file mode 100644 index a236abd9fe..0000000000 --- a/v0.8.1/_modules/doctr/datasets/datasets/tensorflow.html +++ /dev/null @@ -1,356 +0,0 @@ - - - - - - - - - - - - doctr.datasets.datasets.tensorflow - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.datasets.tensorflow

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from typing import List, Any, Tuple
    -import tensorflow as tf
    -
    -from .base import _AbstractDataset, _VisionDataset
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset(_AbstractDataset):
    -
    -    def _read_sample(self, index: int) -> Tuple[tf.Tensor, Any]:
    -        img_name, target = self.data[index]
    -        # Read image
    -        img = tf.io.read_file(os.path.join(self.root, img_name))
    -        img = tf.image.decode_jpeg(img, channels=3)
    -
    -        return img, target
    -
    -    @staticmethod
    -    def collate_fn(samples: List[Tuple[tf.Tensor, Any]]) -> Tuple[tf.Tensor, List[Any]]:
    -
    -        images, targets = zip(*samples)
    -        images = tf.stack(images, axis=0)
    -
    -        return images, list(targets)
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset, _VisionDataset): - pass
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.1/_modules/doctr/datasets/detection.html b/v0.8.1/_modules/doctr/datasets/detection.html index faf9256c89..706b89a562 100644 --- a/v0.8.1/_modules/doctr/datasets/detection.html +++ b/v0.8.1/_modules/doctr/datasets/detection.html @@ -13,7 +13,7 @@ - + doctr.datasets.detection - docTR documentation @@ -424,7 +424,7 @@

    Source code for doctr.datasets.detection

         
       
    - + diff --git a/v0.8.1/_modules/doctr/datasets/doc_artefacts.html b/v0.8.1/_modules/doctr/datasets/doc_artefacts.html index 886999868b..dc8e8f9c29 100644 --- a/v0.8.1/_modules/doctr/datasets/doc_artefacts.html +++ b/v0.8.1/_modules/doctr/datasets/doc_artefacts.html @@ -13,7 +13,7 @@ - + doctr.datasets.doc_artefacts - docTR documentation @@ -408,7 +408,7 @@

    Source code for doctr.datasets.doc_artefacts

       
    - + diff --git a/v0.8.1/_modules/doctr/datasets/funsd.html b/v0.8.1/_modules/doctr/datasets/funsd.html index 60f7e51592..6f7ab121f0 100644 --- a/v0.8.1/_modules/doctr/datasets/funsd.html +++ b/v0.8.1/_modules/doctr/datasets/funsd.html @@ -13,7 +13,7 @@ - + doctr.datasets.funsd - docTR documentation @@ -438,7 +438,7 @@

    Source code for doctr.datasets.funsd

         
       
    - + diff --git a/v0.8.1/_modules/doctr/datasets/generator/tensorflow.html b/v0.8.1/_modules/doctr/datasets/generator/tensorflow.html index fecf8b2d82..814dc0822d 100644 --- a/v0.8.1/_modules/doctr/datasets/generator/tensorflow.html +++ b/v0.8.1/_modules/doctr/datasets/generator/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.datasets.generator.tensorflow - docTR documentation @@ -389,7 +389,7 @@

    Source code for doctr.datasets.generator.tensorflow

    - + diff --git a/v0.8.1/_modules/doctr/datasets/ic03.html b/v0.8.1/_modules/doctr/datasets/ic03.html index 83f7bcddf0..cf8999d751 100644 --- a/v0.8.1/_modules/doctr/datasets/ic03.html +++ b/v0.8.1/_modules/doctr/datasets/ic03.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic03 - docTR documentation @@ -452,7 +452,7 @@

    Source code for doctr.datasets.ic03

         
       
    - + diff --git a/v0.8.1/_modules/doctr/datasets/ic13.html b/v0.8.1/_modules/doctr/datasets/ic13.html index 1d92d10349..7650af381c 100644 --- a/v0.8.1/_modules/doctr/datasets/ic13.html +++ b/v0.8.1/_modules/doctr/datasets/ic13.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic13 - docTR documentation @@ -425,7 +425,7 @@

    Source code for doctr.datasets.ic13

         
       
    - + diff --git a/v0.8.1/_modules/doctr/datasets/iiit5k.html b/v0.8.1/_modules/doctr/datasets/iiit5k.html index 14ab1db716..b4a54e7e22 100644 --- a/v0.8.1/_modules/doctr/datasets/iiit5k.html +++ b/v0.8.1/_modules/doctr/datasets/iiit5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiit5k - docTR documentation @@ -429,7 +429,7 @@

    Source code for doctr.datasets.iiit5k

         
       
    - + diff --git a/v0.8.1/_modules/doctr/datasets/iiithws.html b/v0.8.1/_modules/doctr/datasets/iiithws.html index e7c0d4e8dd..052a85cd56 100644 --- a/v0.8.1/_modules/doctr/datasets/iiithws.html +++ b/v0.8.1/_modules/doctr/datasets/iiithws.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiithws - docTR documentation @@ -401,7 +401,7 @@

    Source code for doctr.datasets.iiithws

         
       
    - + diff --git a/v0.8.1/_modules/doctr/datasets/imgur5k.html b/v0.8.1/_modules/doctr/datasets/imgur5k.html index eb12e48784..f6c1a4692c 100644 --- a/v0.8.1/_modules/doctr/datasets/imgur5k.html +++ b/v0.8.1/_modules/doctr/datasets/imgur5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.imgur5k - docTR documentation @@ -473,7 +473,7 @@

    Source code for doctr.datasets.imgur5k

         
       
    - + diff --git a/v0.8.1/_modules/doctr/datasets/loader.html b/v0.8.1/_modules/doctr/datasets/loader.html index cdaec1bb70..9b2b3126de 100644 --- a/v0.8.1/_modules/doctr/datasets/loader.html +++ b/v0.8.1/_modules/doctr/datasets/loader.html @@ -13,7 +13,7 @@ - + doctr.datasets.loader - docTR documentation @@ -428,7 +428,7 @@

    Source code for doctr.datasets.loader

         
       
    - + diff --git a/v0.8.1/_modules/doctr/datasets/mjsynth.html b/v0.8.1/_modules/doctr/datasets/mjsynth.html index d7a7e66e35..c95f99e6d5 100644 --- a/v0.8.1/_modules/doctr/datasets/mjsynth.html +++ b/v0.8.1/_modules/doctr/datasets/mjsynth.html @@ -13,7 +13,7 @@ - + doctr.datasets.mjsynth - docTR documentation @@ -432,7 +432,7 @@

    Source code for doctr.datasets.mjsynth

         
       
    - + diff --git a/v0.8.1/_modules/doctr/datasets/ocr.html b/v0.8.1/_modules/doctr/datasets/ocr.html index c6e09faee3..a1a249b259 100644 --- a/v0.8.1/_modules/doctr/datasets/ocr.html +++ b/v0.8.1/_modules/doctr/datasets/ocr.html @@ -13,7 +13,7 @@ - + doctr.datasets.ocr - docTR documentation @@ -397,7 +397,7 @@

    Source code for doctr.datasets.ocr

         
       
    - + diff --git a/v0.8.1/_modules/doctr/datasets/recognition.html b/v0.8.1/_modules/doctr/datasets/recognition.html index 1e14da06a9..95612cdadb 100644 --- a/v0.8.1/_modules/doctr/datasets/recognition.html +++ b/v0.8.1/_modules/doctr/datasets/recognition.html @@ -13,7 +13,7 @@ - + doctr.datasets.recognition - docTR documentation @@ -382,7 +382,7 @@

    Source code for doctr.datasets.recognition

         
       
    - + diff --git a/v0.8.1/_modules/doctr/datasets/sroie.html b/v0.8.1/_modules/doctr/datasets/sroie.html index f3ac7b9547..32b4b17983 100644 --- a/v0.8.1/_modules/doctr/datasets/sroie.html +++ b/v0.8.1/_modules/doctr/datasets/sroie.html @@ -13,7 +13,7 @@ - + doctr.datasets.sroie - docTR documentation @@ -429,7 +429,7 @@

    Source code for doctr.datasets.sroie

         
       
    - + diff --git a/v0.8.1/_modules/doctr/datasets/svhn.html b/v0.8.1/_modules/doctr/datasets/svhn.html index f10a8cfd8e..5633dcfd6c 100644 --- a/v0.8.1/_modules/doctr/datasets/svhn.html +++ b/v0.8.1/_modules/doctr/datasets/svhn.html @@ -13,7 +13,7 @@ - + doctr.datasets.svhn - docTR documentation @@ -457,7 +457,7 @@

    Source code for doctr.datasets.svhn

         
       
    - + diff --git a/v0.8.1/_modules/doctr/datasets/svt.html b/v0.8.1/_modules/doctr/datasets/svt.html index 0d64efedf4..0ed4482c50 100644 --- a/v0.8.1/_modules/doctr/datasets/svt.html +++ b/v0.8.1/_modules/doctr/datasets/svt.html @@ -13,7 +13,7 @@ - + doctr.datasets.svt - docTR documentation @@ -443,7 +443,7 @@

    Source code for doctr.datasets.svt

         
       
    - + diff --git a/v0.8.1/_modules/doctr/datasets/synthtext.html b/v0.8.1/_modules/doctr/datasets/synthtext.html index 333de06da8..edd5c63c80 100644 --- a/v0.8.1/_modules/doctr/datasets/synthtext.html +++ b/v0.8.1/_modules/doctr/datasets/synthtext.html @@ -13,7 +13,7 @@ - + doctr.datasets.synthtext - docTR documentation @@ -454,7 +454,7 @@

    Source code for doctr.datasets.synthtext

         
       
    - + diff --git a/v0.8.1/_modules/doctr/datasets/utils.html b/v0.8.1/_modules/doctr/datasets/utils.html index 6e90a6400d..eeee0b2654 100644 --- a/v0.8.1/_modules/doctr/datasets/utils.html +++ b/v0.8.1/_modules/doctr/datasets/utils.html @@ -13,7 +13,7 @@ - + doctr.datasets.utils - docTR documentation @@ -542,7 +542,7 @@

    Source code for doctr.datasets.utils

         
       
    - + diff --git a/v0.8.1/_modules/doctr/datasets/wildreceipt.html b/v0.8.1/_modules/doctr/datasets/wildreceipt.html index 2b386ae694..6b5a52a10e 100644 --- a/v0.8.1/_modules/doctr/datasets/wildreceipt.html +++ b/v0.8.1/_modules/doctr/datasets/wildreceipt.html @@ -13,7 +13,7 @@ - + doctr.datasets.wildreceipt - docTR documentation @@ -437,7 +437,7 @@

    Source code for doctr.datasets.wildreceipt

         
       
    - + diff --git a/v0.8.1/_modules/doctr/documents/elements.html b/v0.8.1/_modules/doctr/documents/elements.html deleted file mode 100644 index 10c1e142d2..0000000000 --- a/v0.8.1/_modules/doctr/documents/elements.html +++ /dev/null @@ -1,577 +0,0 @@ - - - - - - - - - - - - doctr.documents.elements - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.elements

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import matplotlib.pyplot as plt
    -from typing import Tuple, Dict, List, Any, Optional, Union
    -
    -from doctr.utils.geometry import resolve_enclosing_bbox, resolve_enclosing_rbbox
    -from doctr.utils.visualization import visualize_page
    -from doctr.utils.common_types import BoundingBox, RotatedBbox
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['Element', 'Word', 'Artefact', 'Line', 'Block', 'Page', 'Document']
    -
    -
    -class Element(NestedObject):
    -    """Implements an abstract document element with exporting and text rendering capabilities"""
    -
    -    _exported_keys: List[str] = []
    -
    -    def __init__(self, **kwargs: Any) -> None:
    -        self._children_names: List[str] = []
    -        for k, v in kwargs.items():
    -            setattr(self, k, v)
    -            self._children_names.append(k)
    -
    -    def export(self) -> Dict[str, Any]:
    -        """Exports the object into a nested dict format"""
    -
    -        export_dict = {k: getattr(self, k) for k in self._exported_keys}
    -        for children_name in self._children_names:
    -            export_dict[children_name] = [c.export() for c in getattr(self, children_name)]
    -
    -        return export_dict
    -
    -    def render(self) -> str:
    -        raise NotImplementedError
    -
    -
    -
    -[docs] -class Word(Element): - """Implements a word element - - Args: - value: the text string of the word - confidence: the confidence associated with the text prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size - """ - - _exported_keys: List[str] = ["value", "confidence", "geometry"] - - def __init__(self, value: str, confidence: float, geometry: Union[BoundingBox, RotatedBbox]) -> None: - super().__init__() - self.value = value - self.confidence = confidence - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return self.value - - def extra_repr(self) -> str: - return f"value='{self.value}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Artefact(Element): - """Implements a non-textual element - - Args: - artefact_type: the type of artefact - confidence: the confidence of the type prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. - """ - - _exported_keys: List[str] = ["geometry", "type", "confidence"] - - def __init__(self, artefact_type: str, confidence: float, geometry: BoundingBox) -> None: - super().__init__() - self.geometry = geometry - self.type = artefact_type - self.confidence = confidence - - def render(self) -> str: - """Renders the full text of the element""" - return f"[{self.type.upper()}]" - - def extra_repr(self) -> str: - return f"type='{self.type}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Line(Element): - """Implements a line element as a collection of words - - Args: - words: list of word elements - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all words in it. - """ - - _exported_keys: List[str] = ["geometry"] - words: List[Word] = [] - - def __init__( - self, - words: List[Word], - geometry: Optional[Union[BoundingBox, RotatedBbox]] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - # Check whether this is a rotated or straight box - box_resolution_fn = resolve_enclosing_rbbox if len(words[0].geometry) == 5 else resolve_enclosing_bbox - geometry = box_resolution_fn([w.geometry for w in words]) # type: ignore[operator, misc] - - super().__init__(words=words) - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return " ".join(w.render() for w in self.words)
    - - - -
    -[docs] -class Block(Element): - """Implements a block element as a collection of lines and artefacts - - Args: - lines: list of line elements - artefacts: list of artefacts - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all lines and artefacts in it. - """ - - _exported_keys: List[str] = ["geometry"] - lines: List[Line] = [] - artefacts: List[Artefact] = [] - - def __init__( - self, - lines: List[Line] = [], - artefacts: List[Artefact] = [], - geometry: Optional[Union[BoundingBox, RotatedBbox]] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - line_boxes = [word.geometry for line in lines for word in line.words] - artefact_boxes = [artefact.geometry for artefact in artefacts] - box_resolution_fn = resolve_enclosing_rbbox if len(lines[0].geometry) == 5 else resolve_enclosing_bbox - geometry = box_resolution_fn(line_boxes + artefact_boxes) # type: ignore[operator, arg-type] - - super().__init__(lines=lines, artefacts=artefacts) - self.geometry = geometry - - def render(self, line_break: str = '\n') -> str: - """Renders the full text of the element""" - return line_break.join(line.render() for line in self.lines)
    - - - -
    -[docs] -class Page(Element): - """Implements a page element as a collection of blocks - - Args: - blocks: list of block elements - page_idx: the index of the page in the input raw document - dimensions: the page size in pixels in format (width, height) - orientation: a dictionary with the value of the rotation angle in degress and confidence of the prediction - language: a dictionary with the language value and confidence of the prediction - """ - - _exported_keys: List[str] = ["page_idx", "dimensions", "orientation", "language"] - blocks: List[Block] = [] - - def __init__( - self, - blocks: List[Block], - page_idx: int, - dimensions: Tuple[int, int], - orientation: Optional[Dict[str, Any]] = None, - language: Optional[Dict[str, Any]] = None, - ) -> None: - super().__init__(blocks=blocks) - self.page_idx = page_idx - self.dimensions = dimensions - self.orientation = orientation if isinstance(orientation, dict) else dict(value=None, confidence=None) - self.language = language if isinstance(language, dict) else dict(value=None, confidence=None) - - def render(self, block_break: str = '\n\n') -> str: - """Renders the full text of the element""" - return block_break.join(b.render() for b in self.blocks) - - def extra_repr(self) -> str: - return f"dimensions={self.dimensions}" - -
    -[docs] - def show( - self, page: np.ndarray, interactive: bool = True, **kwargs - ) -> None: - """Overlay the result on a given image - - Args: - page: image encoded as a numpy array in uint8 - interactive: whether the display should be interactive - """ - visualize_page(self.export(), page, interactive=interactive) - plt.show(**kwargs)
    -
    - - - -
    -[docs] -class Document(Element): - """Implements a document element as a collection of pages - - Args: - pages: list of page elements - """ - - pages: List[Page] = [] - - def __init__( - self, - pages: List[Page], - ) -> None: - super().__init__(pages=pages) - - def render(self, page_break: str = '\n\n\n\n') -> str: - """Renders the full text of the element""" - return page_break.join(p.render() for p in self.pages) - -
    -[docs] - def show(self, pages: List[np.ndarray], **kwargs) -> None: - """Overlay the result on a given image - - Args: - pages: list of images encoded as numpy arrays in uint8 - """ - for img, result in zip(pages, self.pages): - result.show(img, **kwargs)
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.1/_modules/doctr/documents/reader.html b/v0.8.1/_modules/doctr/documents/reader.html deleted file mode 100644 index cdcd814b6c..0000000000 --- a/v0.8.1/_modules/doctr/documents/reader.html +++ /dev/null @@ -1,612 +0,0 @@ - - - - - - - - - - - - doctr.documents.reader - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.reader

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import cv2
    -from pathlib import Path
    -import fitz
    -from weasyprint import HTML
    -from typing import List, Tuple, Optional, Any, Union, Sequence, Dict
    -
    -__all__ = ['read_pdf', 'read_img', 'read_html', 'DocumentFile', 'PDF']
    -
    -
    -AbstractPath = Union[str, Path]
    -AbstractFile = Union[AbstractPath, bytes]
    -Bbox = Tuple[float, float, float, float]
    -
    -
    -
    -[docs] -def read_img( - file: AbstractFile, - output_size: Optional[Tuple[int, int]] = None, - rgb_output: bool = True, -) -> np.ndarray: - """Read an image file into numpy format - - Example:: - >>> from doctr.documents import read_img - >>> page = read_img("path/to/your/doc.jpg") - - Args: - file: the path to the image file - output_size: the expected output size of each page in format H x W - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - Returns: - the page decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)): - if not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - img = cv2.imread(str(file), cv2.IMREAD_COLOR) - elif isinstance(file, bytes): - file = np.frombuffer(file, np.uint8) - img = cv2.imdecode(file, cv2.IMREAD_COLOR) - else: - raise TypeError("unsupported object type for argument 'file'") - - # Validity check - if img is None: - raise ValueError("unable to read file.") - # Resizing - if isinstance(output_size, tuple): - img = cv2.resize(img, output_size[::-1], interpolation=cv2.INTER_LINEAR) - # Switch the channel order - if rgb_output: - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - return img
    - - - -
    -[docs] -def read_pdf(file: AbstractFile, **kwargs: Any) -> fitz.Document: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_pdf - >>> doc = read_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)) and not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - - fitz_args: Dict[str, AbstractFile] = {} - - if isinstance(file, (str, Path)): - fitz_args['filename'] = file - elif isinstance(file, bytes): - fitz_args['stream'] = file - else: - raise TypeError("unsupported object type for argument 'file'") - - # Read pages with fitz and convert them to numpy ndarrays - return fitz.open(**fitz_args, filetype="pdf", **kwargs)
    - - - -def convert_page_to_numpy( - page: fitz.fitz.Page, - output_size: Optional[Tuple[int, int]] = None, - bgr_output: bool = False, - default_scales: Tuple[float, float] = (2, 2), -) -> np.ndarray: - """Convert a fitz page to a numpy-formatted image - - Args: - page: the page of a file read with PyMuPDF - output_size: the expected output size of each page in format H x W. Default goes to 840 x 595 for A4 pdf, - if you want to increase the resolution while preserving the original A4 aspect ratio can pass (1024, 726) - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - default_scales: spatial scaling to be applied when output_size is not specified where (1, 1) - corresponds to 72 dpi rendering. - - Returns: - the rendered image in numpy format - """ - - # If no output size is specified, keep the origin one - if output_size is not None: - scales = (output_size[1] / page.MediaBox[2], output_size[0] / page.MediaBox[3]) - else: - # Default 72 DPI (scales of (1, 1)) is unnecessarily low - scales = default_scales - - transform_matrix = fitz.Matrix(*scales) - - # Generate the pixel map using the transformation matrix - pixmap = page.getPixmap(matrix=transform_matrix) - # Decode it into a numpy - img = np.frombuffer(pixmap.samples, dtype=np.uint8).reshape(pixmap.height, pixmap.width, 3) - - # Switch the channel order - if bgr_output: - img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) - - return img - - -
    -[docs] -def read_html(url: str, **kwargs: Any) -> bytes: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_html - >>> doc = read_html("https://www.yoursite.com") - - Args: - url: URL of the target web page - Returns: - decoded PDF file as a bytes stream - """ - - return HTML(url, **kwargs).write_pdf()
    - - - -
    -[docs] -class PDF: - """PDF document template - - Args: - doc: input PDF document - """ - def __init__(self, doc: fitz.Document) -> None: - self.doc = doc - -
    -[docs] - def as_images(self, **kwargs) -> List[np.ndarray]: - """Convert all document pages to images - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images() - - Args: - kwargs: keyword arguments of `convert_page_to_numpy` - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - return [convert_page_to_numpy(page, **kwargs) for page in self.doc]
    - - - def get_page_words(self, idx, **kwargs) -> List[Tuple[Bbox, str]]: - """Get the annotations for all words of a given page""" - - # xmin, ymin, xmax, ymax, value, block_idx, line_idx, word_idx - return [(info[:4], info[4]) for info in self.doc[idx].getTextWords(**kwargs)] - -
    -[docs] - def get_words(self, **kwargs) -> List[List[Tuple[Bbox, str]]]: - """Get the annotations for all words in the document - - Example:: - >>> from doctr.documents import DocumentFile - >>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words() - - Args: - kwargs: keyword arguments of `fitz.Page.getTextWords` - Returns: - the list of pages annotations, represented as a list of tuple (bounding box, value) - """ - return [self.get_page_words(idx, **kwargs) for idx in range(len(self.doc))]
    - - - def get_page_artefacts(self, idx) -> List[Tuple[float, float, float, float]]: - return [tuple(self.doc[idx].getImageBbox(artefact)) # type: ignore[misc] - for artefact in self.doc[idx].get_images(full=True)] - -
    -[docs] - def get_artefacts(self) -> List[List[Tuple[float, float, float, float]]]: - """Get the artefacts for the entire document - - Example:: - >>> from doctr.documents import DocumentFile - >>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts() - - Returns: - the list of pages artefacts, represented as a list of bounding boxes - """ - - return [self.get_page_artefacts(idx) for idx in range(len(self.doc))]
    -
    - - - -
    -[docs] -class DocumentFile: - """Read a document from multiple extensions""" - -
    -[docs] - @classmethod - def from_pdf(cls, file: AbstractFile, **kwargs) -> PDF: - """Read a PDF file - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file or a binary stream - Returns: - a PDF document - """ - - doc = read_pdf(file, **kwargs) - - return PDF(doc)
    - - -
    -[docs] - @classmethod - def from_url(cls, url: str, **kwargs) -> PDF: - """Interpret a web page as a PDF document - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_url("https://www.yoursite.com") - - Args: - url: the URL of the target web page - Returns: - a PDF document - """ - pdf_stream = read_html(url) - return cls.from_pdf(pdf_stream, **kwargs)
    - - -
    -[docs] - @classmethod - def from_images(cls, files: Union[Sequence[AbstractFile], AbstractFile], **kwargs) -> List[np.ndarray]: - """Read an image file (or a collection of image files) and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"]) - - Args: - files: the path to the image file or a binary stream, or a collection of those - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - if isinstance(files, (str, Path, bytes)): - files = [files] - - return [read_img(file, **kwargs) for file in files]
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.1/_modules/doctr/io/elements.html b/v0.8.1/_modules/doctr/io/elements.html index 78ea4cc7cf..a8d52c457f 100644 --- a/v0.8.1/_modules/doctr/io/elements.html +++ b/v0.8.1/_modules/doctr/io/elements.html @@ -13,7 +13,7 @@ - + doctr.io.elements - docTR documentation @@ -960,7 +960,7 @@

    Source code for doctr.io.elements

         
       
    - + diff --git a/v0.8.1/_modules/doctr/io/html.html b/v0.8.1/_modules/doctr/io/html.html index a1eb075da0..34a60da286 100644 --- a/v0.8.1/_modules/doctr/io/html.html +++ b/v0.8.1/_modules/doctr/io/html.html @@ -13,7 +13,7 @@ - + doctr.io.html - docTR documentation @@ -354,7 +354,7 @@

    Source code for doctr.io.html

         
       
    - + diff --git a/v0.8.1/_modules/doctr/io/image/base.html b/v0.8.1/_modules/doctr/io/image/base.html index 1b42de0506..54663fa868 100644 --- a/v0.8.1/_modules/doctr/io/image/base.html +++ b/v0.8.1/_modules/doctr/io/image/base.html @@ -13,7 +13,7 @@ - + doctr.io.image.base - docTR documentation @@ -382,7 +382,7 @@

    Source code for doctr.io.image.base

         
       
    - + diff --git a/v0.8.1/_modules/doctr/io/image/tensorflow.html b/v0.8.1/_modules/doctr/io/image/tensorflow.html index 02325e0630..cf030207d4 100644 --- a/v0.8.1/_modules/doctr/io/image/tensorflow.html +++ b/v0.8.1/_modules/doctr/io/image/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.io.image.tensorflow - docTR documentation @@ -439,7 +439,7 @@

    Source code for doctr.io.image.tensorflow

         
       
    - + diff --git a/v0.8.1/_modules/doctr/io/pdf.html b/v0.8.1/_modules/doctr/io/pdf.html index 7d82b6573c..7dcb3e2381 100644 --- a/v0.8.1/_modules/doctr/io/pdf.html +++ b/v0.8.1/_modules/doctr/io/pdf.html @@ -13,7 +13,7 @@ - + doctr.io.pdf - docTR documentation @@ -368,7 +368,7 @@

    Source code for doctr.io.pdf

         
       
    - + diff --git a/v0.8.1/_modules/doctr/io/reader.html b/v0.8.1/_modules/doctr/io/reader.html index 5a8c87d168..5568ce7e0f 100644 --- a/v0.8.1/_modules/doctr/io/reader.html +++ b/v0.8.1/_modules/doctr/io/reader.html @@ -13,7 +13,7 @@ - + doctr.io.reader - docTR documentation @@ -414,7 +414,7 @@

    Source code for doctr.io.reader

         
       
    - + diff --git a/v0.8.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html b/v0.8.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html index 1b97d83911..4dd332b464 100644 --- a/v0.8.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html +++ b/v0.8.1/_modules/doctr/models/classification/magc_resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.magc_resnet.tensorflow - docTR documentation @@ -518,7 +518,7 @@

    Source code for doctr.models.classification.magc_resnet.tensorflow

    - + diff --git a/v0.8.1/_modules/doctr/models/classification/mobilenet/tensorflow.html b/v0.8.1/_modules/doctr/models/classification/mobilenet/tensorflow.html index b583e184fa..7dbc971810 100644 --- a/v0.8.1/_modules/doctr/models/classification/mobilenet/tensorflow.html +++ b/v0.8.1/_modules/doctr/models/classification/mobilenet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.mobilenet.tensorflow - docTR documentation @@ -747,7 +747,7 @@

    Source code for doctr.models.classification.mobilenet.tensorflow

    - + diff --git a/v0.8.1/_modules/doctr/models/classification/resnet/tensorflow.html b/v0.8.1/_modules/doctr/models/classification/resnet/tensorflow.html index 67c7ede371..77a5747d8b 100644 --- a/v0.8.1/_modules/doctr/models/classification/resnet/tensorflow.html +++ b/v0.8.1/_modules/doctr/models/classification/resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.resnet.tensorflow - docTR documentation @@ -730,7 +730,7 @@

    Source code for doctr.models.classification.resnet.tensorflow

    - + diff --git a/v0.8.1/_modules/doctr/models/classification/textnet/tensorflow.html b/v0.8.1/_modules/doctr/models/classification/textnet/tensorflow.html index a36ebab4f6..45bcea9658 100644 --- a/v0.8.1/_modules/doctr/models/classification/textnet/tensorflow.html +++ b/v0.8.1/_modules/doctr/models/classification/textnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.textnet.tensorflow - docTR documentation @@ -599,7 +599,7 @@

    Source code for doctr.models.classification.textnet.tensorflow

    - + diff --git a/v0.8.1/_modules/doctr/models/classification/vgg/tensorflow.html b/v0.8.1/_modules/doctr/models/classification/vgg/tensorflow.html index 57e34af78f..8dc381674b 100644 --- a/v0.8.1/_modules/doctr/models/classification/vgg/tensorflow.html +++ b/v0.8.1/_modules/doctr/models/classification/vgg/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vgg.tensorflow - docTR documentation @@ -439,7 +439,7 @@

    Source code for doctr.models.classification.vgg.tensorflow

    - + diff --git a/v0.8.1/_modules/doctr/models/classification/vit/tensorflow.html b/v0.8.1/_modules/doctr/models/classification/vit/tensorflow.html index 717a6d1649..84d68b5388 100644 --- a/v0.8.1/_modules/doctr/models/classification/vit/tensorflow.html +++ b/v0.8.1/_modules/doctr/models/classification/vit/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vit.tensorflow - docTR documentation @@ -521,7 +521,7 @@

    Source code for doctr.models.classification.vit.tensorflow

    - + diff --git a/v0.8.1/_modules/doctr/models/classification/zoo.html b/v0.8.1/_modules/doctr/models/classification/zoo.html index 87f2d2956d..d1f749776e 100644 --- a/v0.8.1/_modules/doctr/models/classification/zoo.html +++ b/v0.8.1/_modules/doctr/models/classification/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.classification.zoo - docTR documentation @@ -400,7 +400,7 @@

    Source code for doctr.models.classification.zoo

    <
    - + diff --git a/v0.8.1/_modules/doctr/models/detection/differentiable_binarization.html b/v0.8.1/_modules/doctr/models/detection/differentiable_binarization.html deleted file mode 100644 index 38e9b36ec2..0000000000 --- a/v0.8.1/_modules/doctr/models/detection/differentiable_binarization.html +++ /dev/null @@ -1,879 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.differentiable_binarization - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.differentiable_binarization

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -import cv2
    -from copy import deepcopy
    -import numpy as np
    -from shapely.geometry import Polygon
    -import pyclipper
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers
    -from typing import Union, List, Tuple, Optional, Any, Dict
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..utils import IntermediateLayerGetter, load_pretrained_params, conv_sequence
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['DBPostProcessor', 'DBNet', 'db_resnet50']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'db_resnet50': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'backbone': 'ResNet50',
    -        'fpn_layers': ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"],
    -        'fpn_channels': 128,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'DBPostProcessor',
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.2.0/db_resnet50-adcafc63.zip',
    -    },
    -}
    -
    -
    -class DBPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for DBNet adapted from the implementation of `xuannianz
    -    <https://github.com/xuannianz/DifferentiableBinarization>`_.
    -
    -    Args:
    -        unclip ratio: ratio used to unshrink polygons
    -        min_size_box: minimal length (pix) to keep a box
    -        max_candidates: maximum boxes to consider in a single page
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        unclip_ratio: Union[float, int] = 1.5,
    -        max_candidates: int = 1000,
    -        box_thresh: float = 0.1,
    -        bin_thresh: float = 0.3,
    -    ) -> None:
    -
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -        self.unclip_ratio = unclip_ratio
    -        self.max_candidates = max_candidates
    -
    -    def polygon_to_box(
    -        self,
    -        points: np.ndarray,
    -    ) -> Optional[Tuple[int, int, int, int]]:
    -        """Expand a polygon (points) by a factor unclip_ratio, and returns a 4-points box
    -
    -        Args:
    -            points: The first parameter.
    -
    -        Returns:
    -            a box in absolute coordinates (x, y, w, h)
    -        """
    -        poly = Polygon(points)
    -        distance = poly.area * self.unclip_ratio / poly.length  # compute distance to expand polygon
    -        offset = pyclipper.PyclipperOffset()
    -        offset.AddPath(points, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        _points = offset.Execute(distance)
    -        # Take biggest stack of points
    -        idx = 0
    -        if len(_points) > 1:
    -            max_size = 0
    -            for _idx, p in enumerate(_points):
    -                if len(p) > max_size:
    -                    idx = _idx
    -                    max_size = len(p)
    -            # We ensure that _points can be correctly casted to a ndarray
    -            _points = [_points[idx]]
    -        expanded_points = np.asarray(_points)  # expand polygon
    -        if len(expanded_points) < 1:
    -            return None
    -        x, y, w, h = cv2.boundingRect(expanded_points)  # compute a 4-points box from expanded polygon
    -        return x, y, w, h
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map
    -
    -        Args:
    -            pred: Pred map from differentiable binarization output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        # get contours from connected components on the bitmap
    -        contours, _ = cv2.findContours(bitmap.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    -        for contour in contours[:self.max_candidates]:
    -            # Check whether smallest enclosing bounding box is not too small
    -            if np.any(contour[:, 0].max(axis=0) - contour[:, 0].min(axis=0) < min_size_box):
    -                continue
    -            x, y, w, h = cv2.boundingRect(contour)
    -            points = np.array([[x, y], [x, y + h], [x + w, y + h], [x + w, y]])
    -            # Compute objectness
    -            score = self.box_score(pred, points)
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            _box = self.polygon_to_box(points)
    -
    -            if _box is None or _box[2] < min_size_box or _box[3] < min_size_box:  # remove to small boxes
    -                continue
    -            x, y, w, h = _box
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -class FeaturePyramidNetwork(layers.Layer, NestedObject):
    -    """Feature Pyramid Network as described in `"Feature Pyramid Networks for Object Detection"
    -    <https://arxiv.org/pdf/1612.03144.pdf>`_.
    -
    -    Args:
    -        channels: number of channel to output
    -    """
    -
    -    def __init__(
    -        self,
    -        channels: int,
    -    ) -> None:
    -        super().__init__()
    -        self.channels = channels
    -        self.upsample = layers.UpSampling2D(size=(2, 2), interpolation='nearest')
    -        self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer='he_normal') for _ in range(4)]
    -        self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2 ** idx) for idx in range(4)]
    -
    -    @staticmethod
    -    def build_upsampling(
    -        channels: int,
    -        dilation_factor: int = 1,
    -    ) -> layers.Layer:
    -        """Module which performs a 3x3 convolution followed by up-sampling
    -
    -        Args:
    -            channels: number of output channels
    -            dilation_factor (int): dilation factor to scale the convolution output before concatenation
    -
    -        Returns:
    -            a keras.layers.Layer object, wrapping these operations in a sequential module
    -
    -        """
    -
    -        _layers = conv_sequence(channels, 'relu', True, kernel_size=3)
    -
    -        if dilation_factor > 1:
    -            _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation='nearest'))
    -
    -        module = keras.Sequential(_layers)
    -
    -        return module
    -
    -    def extra_repr(self) -> str:
    -        return f"channels={self.channels}"
    -
    -    def call(
    -        self,
    -        x: List[tf.Tensor],
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # Channel mapping
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.inner_blocks, x)]
    -        # Upsample & sum
    -        for idx in range(len(results) - 1, -1):
    -            results[idx] += self.upsample(results[idx + 1])
    -        # Conv & upsample
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.layer_blocks, results)]
    -
    -        return layers.concatenate(results)
    -
    -
    -class DBNet(DetectionModel, NestedObject):
    -    """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization"
    -    <https://arxiv.org/pdf/1911.08947.pdf>`_.
    -
    -    Args:
    -        feature extractor: the backbone serving as feature extractor
    -        fpn_channels: number of channels each extracted feature maps is mapped to
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'fpn', 'probability_head', 'threshold_head', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: IntermediateLayerGetter,
    -        fpn_channels: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(cfg=cfg)
    -
    -        self.shrink_ratio = 0.4
    -        self.thresh_min = 0.3
    -        self.thresh_max = 0.7
    -        self.min_size_box = 3
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.fpn = FeaturePyramidNetwork(channels=fpn_channels)
    -        # Initialize kernels
    -        _inputs = [layers.Input(shape=in_shape[1:]) for in_shape in self.feat_extractor.output_shape]
    -        output_shape = tuple(self.fpn(_inputs).shape)
    -
    -        self.probability_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -        self.threshold_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -
    -        self.postprocessor = DBPostProcessor()
    -
    -    @staticmethod
    -    def compute_distance(
    -        xs: np.array,
    -        ys: np.array,
    -        a: np.array,
    -        b: np.array,
    -        eps: float = 1e-7,
    -    ) -> float:
    -        """Compute the distance for each point of the map (xs, ys) to the (a, b) segment
    -
    -        Args:
    -            xs : map of x coordinates (height, width)
    -            ys : map of y coordinates (height, width)
    -            a: first point defining the [ab] segment
    -            b: second point defining the [ab] segment
    -
    -        Returns:
    -            The computed distance
    -
    -        """
    -        square_dist_1 = np.square(xs - a[0]) + np.square(ys - a[1])
    -        square_dist_2 = np.square(xs - b[0]) + np.square(ys - b[1])
    -        square_dist = np.square(a[0] - b[0]) + np.square(a[1] - b[1])
    -        cosin = (square_dist - square_dist_1 - square_dist_2) / (2 * np.sqrt(square_dist_1 * square_dist_2) + eps)
    -        square_sin = 1 - np.square(cosin)
    -        square_sin = np.nan_to_num(square_sin)
    -        result = np.sqrt(square_dist_1 * square_dist_2 * square_sin / square_dist)
    -        result[cosin < 0] = np.sqrt(np.fmin(square_dist_1, square_dist_2))[cosin < 0]
    -        return result
    -
    -    def draw_thresh_map(
    -        self,
    -        polygon: np.array,
    -        canvas: np.array,
    -        mask: np.array,
    -    ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    -        """Draw a polygon treshold map on a canvas, as described in the DB paper
    -
    -        Args:
    -            polygon : array of coord., to draw the boundary of the polygon
    -            canvas : threshold map to fill with polygons
    -            mask : mask for training on threshold polygons
    -        """
    -        if polygon.ndim != 2 or polygon.shape[1] != 2:
    -            raise AttributeError("polygon should be a 2 dimensional array of coords")
    -
    -        # Augment polygon by shrink_ratio
    -        polygon_shape = Polygon(polygon)
    -        distance = polygon_shape.area * (1 - np.power(self.shrink_ratio, 2)) / polygon_shape.length
    -        subject = [tuple(coor) for coor in polygon]  # Get coord as list of tuples
    -        padding = pyclipper.PyclipperOffset()
    -        padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        padded_polygon = np.array(padding.Execute(distance)[0])
    -
    -        # Fill the mask with 1 on the new padded polygon
    -        cv2.fillPoly(mask, [padded_polygon.astype(np.int32)], 1.0)
    -
    -        # Get min/max to recover polygon after distance computation
    -        xmin = padded_polygon[:, 0].min()
    -        xmax = padded_polygon[:, 0].max()
    -        ymin = padded_polygon[:, 1].min()
    -        ymax = padded_polygon[:, 1].max()
    -        width = xmax - xmin + 1
    -        height = ymax - ymin + 1
    -        # Get absolute polygon for distance computation
    -        polygon[:, 0] = polygon[:, 0] - xmin
    -        polygon[:, 1] = polygon[:, 1] - ymin
    -        # Get absolute padded polygon
    -        xs = np.broadcast_to(np.linspace(0, width - 1, num=width).reshape(1, width), (height, width))
    -        ys = np.broadcast_to(np.linspace(0, height - 1, num=height).reshape(height, 1), (height, width))
    -
    -        # Compute distance map to fill the padded polygon
    -        distance_map = np.zeros((polygon.shape[0], height, width), dtype=np.float32)
    -        for i in range(polygon.shape[0]):
    -            j = (i + 1) % polygon.shape[0]
    -            absolute_distance = self.compute_distance(xs, ys, polygon[i], polygon[j])
    -            distance_map[i] = np.clip(absolute_distance / distance, 0, 1)
    -        distance_map = np.min(distance_map, axis=0)
    -
    -        # Clip the padded polygon inside the canvas
    -        xmin_valid = min(max(0, xmin), canvas.shape[1] - 1)
    -        xmax_valid = min(max(0, xmax), canvas.shape[1] - 1)
    -        ymin_valid = min(max(0, ymin), canvas.shape[0] - 1)
    -        ymax_valid = min(max(0, ymax), canvas.shape[0] - 1)
    -
    -        # Fill the canvas with the distances computed inside the valid padded polygon
    -        canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1] = np.fmax(
    -            1 - distance_map[
    -                ymin_valid - ymin:ymax_valid - ymin + 1,
    -                xmin_valid - xmin:xmax_valid - xmin + 1
    -            ],
    -            canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1]
    -        )
    -
    -        return polygon, canvas, mask
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor, tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.uint8)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -        thresh_target = np.zeros(output_shape, dtype=np.uint8)
    -        thresh_mask = np.ones(output_shape, dtype=np.uint8)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            polys = np.stack([
    -                abs_boxes[:, [0, 1]],
    -                abs_boxes[:, [0, 3]],
    -                abs_boxes[:, [2, 3]],
    -                abs_boxes[:, [2, 1]],
    -            ], axis=1)
    -
    -            for box, box_size, poly, is_ambiguous in zip(abs_boxes, boxes_size, polys, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -
    -                # Negative shrink for gt, as described in paper
    -                polygon = Polygon(poly)
    -                distance = polygon.area * (1 - np.power(self.shrink_ratio, 2)) / polygon.length
    -                subject = [tuple(coor) for coor in poly]
    -                padding = pyclipper.PyclipperOffset()
    -                padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -                shrinked = padding.Execute(-distance)
    -
    -                # Draw polygon on gt if it is valid
    -                if len(shrinked) == 0:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                shrinked = np.array(shrinked[0]).reshape(-1, 2)
    -                if shrinked.shape[0] <= 2 or not Polygon(shrinked).is_valid:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                cv2.fillPoly(seg_target[idx], [shrinked.astype(np.int32)], 1)
    -
    -                # Draw on both thresh map and thresh mask
    -                poly, thresh_target[idx], thresh_mask[idx] = self.draw_thresh_map(poly, thresh_target[idx],
    -                                                                                  thresh_mask[idx])
    -
    -        thresh_target = thresh_target.astype(np.float32) * (self.thresh_max - self.thresh_min) + self.thresh_min
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -        thresh_target = tf.convert_to_tensor(thresh_target, dtype=tf.float32)
    -        thresh_mask = tf.convert_to_tensor(thresh_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask, thresh_target, thresh_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        thresh_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts, masks, thresh_gts, thresh_masks from a list of boxes
    -        and a list of masks for each image. From there it computes the loss with the model output
    -
    -        Args:
    -            out_map: output feature map of the model of shape (N, H, W, C)
    -            thresh_map: threshold map of shape (N, H, W, C)
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -
    -        prob_map = tf.math.sigmoid(tf.squeeze(out_map, axis=[-1]))
    -        thresh_map = tf.math.sigmoid(tf.squeeze(thresh_map, axis=[-1]))
    -
    -        seg_target, seg_mask, thresh_target, thresh_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute balanced BCE loss for proba_map
    -        bce_scale = 5.
    -        bce_loss = tf.keras.losses.binary_crossentropy(seg_target[..., None], out_map, from_logits=True)[seg_mask]
    -
    -        neg_target = 1 - seg_target[seg_mask]
    -        positive_count = tf.math.reduce_sum(seg_target[seg_mask])
    -        negative_count = tf.math.reduce_min([tf.math.reduce_sum(neg_target), 3. * positive_count])
    -        negative_loss = bce_loss * neg_target
    -        negative_loss, _ = tf.nn.top_k(negative_loss, tf.cast(negative_count, tf.int32))
    -        sum_losses = tf.math.reduce_sum(bce_loss * seg_target[seg_mask]) + tf.math.reduce_sum(negative_loss)
    -        balanced_bce_loss = sum_losses / (positive_count + negative_count + 1e-6)
    -
    -        # Compute dice loss for approxbin_map
    -        bin_map = 1 / (1 + tf.exp(-50. * (prob_map[seg_mask] - thresh_map[seg_mask])))
    -
    -        bce_min = tf.math.reduce_min(bce_loss)
    -        weights = (bce_loss - bce_min) / (tf.math.reduce_max(bce_loss) - bce_min) + 1.
    -        inter = tf.math.reduce_sum(bin_map * seg_target[seg_mask] * weights)
    -        union = tf.math.reduce_sum(bin_map) + tf.math.reduce_sum(seg_target[seg_mask]) + 1e-8
    -        dice_loss = 1 - 2.0 * inter / union
    -
    -        # Compute l1 loss for thresh_map
    -        l1_scale = 10.
    -        if tf.reduce_any(thresh_mask):
    -            l1_loss = tf.math.reduce_mean(tf.math.abs(thresh_map[thresh_mask] - thresh_target[thresh_mask]))
    -        else:
    -            l1_loss = tf.constant(0.)
    -
    -        return l1_scale * l1_loss + bce_scale * balanced_bce_loss + dice_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        feat_maps = self.feat_extractor(x, **kwargs)
    -        feat_concat = self.fpn(feat_maps, **kwargs)
    -        logits = self.probability_head(feat_concat, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            thresh_map = self.threshold_head(feat_concat, **kwargs)
    -            loss = self.compute_loss(logits, thresh_map, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _db_resnet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> DBNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['fpn_channels'] = kwargs.get('fpn_channels', _cfg['fpn_channels'])
    -
    -    # Feature extractor
    -    resnet = tf.keras.applications.__dict__[_cfg['backbone']](
    -        include_top=False,
    -        weights=None,
    -        input_shape=_cfg['input_shape'],
    -        pooling=None,
    -    )
    -
    -    feat_extractor = IntermediateLayerGetter(
    -        resnet,
    -        _cfg['fpn_layers'],
    -    )
    -
    -    kwargs['fpn_channels'] = _cfg['fpn_channels']
    -
    -    # Build the model
    -    model = DBNet(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def db_resnet50(pretrained: bool = False, **kwargs: Any) -> DBNet: - """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" - <https://arxiv.org/pdf/1911.08947.pdf>`_, using a ResNet-50 backbone. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _db_resnet('db_resnet50', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html b/v0.8.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html index b3523b2fb5..5cf3b58dbb 100644 --- a/v0.8.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html +++ b/v0.8.1/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.differentiable_binarization.tensorflow - docTR documentation @@ -731,7 +731,7 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo

    - + diff --git a/v0.8.1/_modules/doctr/models/detection/fast/tensorflow.html b/v0.8.1/_modules/doctr/models/detection/fast/tensorflow.html index 73eeecd71f..c383007826 100644 --- a/v0.8.1/_modules/doctr/models/detection/fast/tensorflow.html +++ b/v0.8.1/_modules/doctr/models/detection/fast/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.fast.tensorflow - docTR documentation @@ -760,7 +760,7 @@

    Source code for doctr.models.detection.fast.tensorflow

    - + diff --git a/v0.8.1/_modules/doctr/models/detection/linknet.html b/v0.8.1/_modules/doctr/models/detection/linknet.html deleted file mode 100644 index 129cfdce8b..0000000000 --- a/v0.8.1/_modules/doctr/models/detection/linknet.html +++ /dev/null @@ -1,644 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.linknet - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.linknet

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -import numpy as np
    -import cv2
    -from tensorflow.keras import layers, Sequential
    -from typing import Dict, Any, Tuple, Optional, List
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..backbones import ResnetStage
    -from ..utils import conv_sequence, load_pretrained_params
    -from ...utils.repr import NestedObject
    -
    -__all__ = ['LinkNet', 'linknet', 'LinkNetPostProcessor']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'linknet': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'out_chan': 1,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'LinkNetPostProcessor',
    -        'url': None,
    -    },
    -}
    -
    -
    -class LinkNetPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for LinkNet model.
    -
    -    Args:
    -        min_size_box: minimal length (pix) to keep a box
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        min_size_box: int = 3,
    -        bin_thresh: float = 0.15,
    -        box_thresh: float = 0.1,
    -    ) -> None:
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map: find connected components then filter boxes
    -
    -        Args:
    -            pred: Pred map from differentiable linknet output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        label_num, labelimage = cv2.connectedComponents(bitmap.astype(np.uint8), connectivity=4)
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        for label in range(1, label_num + 1):
    -            points = np.array(np.where(labelimage == label)[::-1]).T
    -            if points.shape[0] < 4:  # remove polygons with 3 points or less
    -                continue
    -            score = self.box_score(pred, points.reshape(-1, 2))
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            x, y, w, h = cv2.boundingRect(points)
    -            if min(w, h) < min_size_box:  # filter too small boxes
    -                continue
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -def decoder_block(in_chan: int, out_chan: int) -> Sequential:
    -    """Creates a LinkNet decoder block"""
    -
    -    return Sequential([
    -        *conv_sequence(in_chan // 4, 'relu', True, kernel_size=1),
    -        layers.Conv2DTranspose(
    -            filters=in_chan // 4,
    -            kernel_size=3,
    -            strides=2,
    -            padding="same",
    -            use_bias=False,
    -            kernel_initializer='he_normal'
    -        ),
    -        layers.BatchNormalization(),
    -        layers.Activation('relu'),
    -        *conv_sequence(out_chan, 'relu', True, kernel_size=1),
    -    ])
    -
    -
    -class LinkNetFPN(layers.Layer, NestedObject):
    -    """LinkNet Encoder-Decoder module
    -
    -    """
    -
    -    def __init__(
    -        self,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.encoder_1 = ResnetStage(num_blocks=2, output_channels=64, downsample=True)
    -        self.encoder_2 = ResnetStage(num_blocks=2, output_channels=128, downsample=True)
    -        self.encoder_3 = ResnetStage(num_blocks=2, output_channels=256, downsample=True)
    -        self.encoder_4 = ResnetStage(num_blocks=2, output_channels=512, downsample=True)
    -        self.decoder_1 = decoder_block(in_chan=64, out_chan=64)
    -        self.decoder_2 = decoder_block(in_chan=128, out_chan=64)
    -        self.decoder_3 = decoder_block(in_chan=256, out_chan=128)
    -        self.decoder_4 = decoder_block(in_chan=512, out_chan=256)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor
    -    ) -> tf.Tensor:
    -        x_1 = self.encoder_1(x)
    -        x_2 = self.encoder_2(x_1)
    -        x_3 = self.encoder_3(x_2)
    -        x_4 = self.encoder_4(x_3)
    -        y_4 = self.decoder_4(x_4)
    -        y_3 = self.decoder_3(y_4 + x_3)
    -        y_2 = self.decoder_2(y_3 + x_2)
    -        y_1 = self.decoder_1(y_2 + x_1)
    -        return y_1
    -
    -
    -class LinkNet(DetectionModel, NestedObject):
    -    """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation"
    -    <https://arxiv.org/pdf/1707.03718.pdf>`_.
    -
    -    Args:
    -        out_chan: number of channels for the output
    -    """
    -
    -    _children_names: List[str] = ['stem', 'fpn', 'classifier', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        out_chan: int = 1,
    -        input_shape: Tuple[int, int, int] = (512, 512, 3),
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(cfg=cfg)
    -
    -        self.stem = Sequential([
    -            *conv_sequence(64, 'relu', True, strides=2, kernel_size=7, input_shape=input_shape),
    -            layers.MaxPool2D(pool_size=(3, 3), strides=2, padding='same'),
    -        ])
    -
    -        self.fpn = LinkNetFPN()
    -
    -        self.classifier = Sequential([
    -            layers.Conv2DTranspose(
    -                filters=32,
    -                kernel_size=3,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -            layers.BatchNormalization(),
    -            layers.Activation('relu'),
    -            *conv_sequence(32, 'relu', True, strides=1, kernel_size=3),
    -            layers.Conv2DTranspose(
    -                filters=out_chan,
    -                kernel_size=2,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -        ])
    -
    -        self.min_size_box = 3
    -
    -        self.postprocessor = LinkNetPostProcessor()
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.bool)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            for box, box_size, is_ambiguous in zip(abs_boxes, boxes_size, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Fill polygon with 1
    -                seg_target[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = True
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts and masks from a list of boxes and a list of masks for each image
    -        Then, it computes the loss function with proba_map, gts and masks
    -
    -        Args:
    -            out_map: output feature map of the model of shape N x H x W x 1
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -        seg_target, seg_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute BCE loss
    -        return tf.math.reduce_mean(tf.keras.losses.binary_crossentropy(
    -            seg_target[seg_mask],
    -            tf.squeeze(out_map, axis=[-1])[seg_mask],
    -            from_logits=True
    -        ))
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        logits = self.stem(x)
    -        logits = self.fpn(logits)
    -        logits = self.classifier(logits)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            loss = self.compute_loss(logits, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _linknet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> LinkNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['out_chan'] = kwargs.get('out_chan', _cfg['out_chan'])
    -
    -    kwargs['out_chan'] = _cfg['out_chan']
    -    kwargs['input_shape'] = _cfg['input_shape']
    -    # Build the model
    -    model = LinkNet(cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def linknet(pretrained: bool = False, **kwargs: Any) -> LinkNet: - """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" - <https://arxiv.org/pdf/1707.03718.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import linknet - >>> model = linknet(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _linknet('linknet', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.1/_modules/doctr/models/detection/linknet/tensorflow.html b/v0.8.1/_modules/doctr/models/detection/linknet/tensorflow.html index c5fd053513..d374bb6d1e 100644 --- a/v0.8.1/_modules/doctr/models/detection/linknet/tensorflow.html +++ b/v0.8.1/_modules/doctr/models/detection/linknet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.linknet.tensorflow - docTR documentation @@ -698,7 +698,7 @@

    Source code for doctr.models.detection.linknet.tensorflow

    - + diff --git a/v0.8.1/_modules/doctr/models/detection/zoo.html b/v0.8.1/_modules/doctr/models/detection/zoo.html index ce4a60785f..43326bb2a1 100644 --- a/v0.8.1/_modules/doctr/models/detection/zoo.html +++ b/v0.8.1/_modules/doctr/models/detection/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.detection.zoo - docTR documentation @@ -424,7 +424,7 @@

    Source code for doctr.models.detection.zoo

         
       
    - + diff --git a/v0.8.1/_modules/doctr/models/export.html b/v0.8.1/_modules/doctr/models/export.html deleted file mode 100644 index f25a81aa21..0000000000 --- a/v0.8.1/_modules/doctr/models/export.html +++ /dev/null @@ -1,411 +0,0 @@ - - - - - - - - - - - - doctr.models.export - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.export

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import logging
    -import numpy as np
    -import tensorflow as tf
    -from tensorflow.keras import Model
    -from typing import Tuple
    -
    -logging.getLogger("tensorflow").setLevel(logging.DEBUG)
    -
    -
    -__all__ = ['convert_to_tflite', 'convert_to_fp16', 'quantize_model']
    -
    -
    -
    -[docs] -def convert_to_tflite(tf_model: Model) -> bytes: - """Converts a model to TFLite format - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_tflite, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_tflite(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - return converter.convert()
    - - - -
    -[docs] -def convert_to_fp16(tf_model: Model) -> bytes: - """Converts a model to half precision - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_fp16, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_fp16(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the serialized FP16 model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - converter.target_spec.supported_types = [tf.float16] - return converter.convert()
    - - - -
    -[docs] -def quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) -> bytes: - """Quantize a Tensorflow model - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import quantize_model, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = quantize_model(model, (224, 224, 3)) - - Args: - tf_model: a keras model - input_shape: shape of the expected input tensor (excluding batch dimension) with channel last order - - Returns: - bytes: the serialized quantized model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - - # Float fallback for operators that do not have an integer implementation - def representative_dataset(): - for _ in range(100): - data = np.random.rand(1, *input_shape) - yield [data.astype(np.float32)] - - converter.representative_dataset = representative_dataset - converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] - converter.inference_input_type = tf.int8 - converter.inference_output_type = tf.int8 - - return converter.convert()
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.1/_modules/doctr/models/factory/hub.html b/v0.8.1/_modules/doctr/models/factory/hub.html index a49f4ebde7..93aa0aa8f3 100644 --- a/v0.8.1/_modules/doctr/models/factory/hub.html +++ b/v0.8.1/_modules/doctr/models/factory/hub.html @@ -13,7 +13,7 @@ - + doctr.models.factory.hub - docTR documentation @@ -572,7 +572,7 @@

    Source code for doctr.models.factory.hub

         
       
    - + diff --git a/v0.8.1/_modules/doctr/models/recognition/crnn.html b/v0.8.1/_modules/doctr/models/recognition/crnn.html deleted file mode 100644 index daa2393439..0000000000 --- a/v0.8.1/_modules/doctr/models/recognition/crnn.html +++ /dev/null @@ -1,565 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.crnn - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.crnn

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import layers
    -from tensorflow.keras.models import Sequential
    -from typing import Tuple, Dict, Any, Optional, List
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel, RecognitionPostProcessor
    -
    -__all__ = ['CRNN', 'crnn_vgg16_bn', 'crnn_resnet31', 'CTCPostProcessor']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'crnn_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/crnn_vgg16_bn-748c855f.zip',
    -    },
    -    'crnn_resnet31': {
    -        'mean': (0.694, 0.695, 0.693),
    -        'std': (0.299, 0.296, 0.301),
    -        'backbone': 'resnet31', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.1/crnn_resnet31-69ab71db.zip',
    -    },
    -}
    -
    -
    -class CTCPostProcessor(RecognitionPostProcessor):
    -    """
    -    Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor
    -    ) -> List[Tuple[str, float]]:
    -        """
    -        Performs decoding of raw output with CTC and decoding of CTC predictions
    -        with label_to_idx mapping dictionnary
    -
    -        Args:
    -            logits: raw output of the model, shape BATCH_SIZE X SEQ_LEN X NUM_CLASSES + 1
    -
    -        Returns:
    -            A list of decoded words of length BATCH_SIZE
    -
    -        """
    -        # Decode CTC
    -        _decoded, _log_prob = tf.nn.ctc_beam_search_decoder(
    -            tf.transpose(logits, perm=[1, 0, 2]),
    -            tf.fill(logits.shape[0], logits.shape[1]),
    -            beam_width=1, top_paths=1,
    -        )
    -        out_idxs = tf.sparse.to_dense(_decoded[0], default_value=len(self.vocab))
    -        probs = tf.math.exp(tf.squeeze(_log_prob, axis=1))
    -
    -        # Map it to characters
    -        _decoded_strings_pred = tf.strings.reduce_join(
    -            inputs=tf.nn.embedding_lookup(self._embedding, out_idxs),
    -            axis=-1
    -        )
    -        _decoded_strings_pred = tf.strings.split(_decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -class CRNN(RecognitionModel):
    -    """Implements a CRNN architecture as described in `"An End-to-End Trainable Neural Network for Image-based
    -    Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of units in the LSTM layers
    -        cfg: configuration dictionary
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: tf.keras.Model,
    -        vocab: str,
    -        rnn_units: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(vocab=vocab, cfg=cfg)
    -        self.feat_extractor = feature_extractor
    -
    -        # Initialize kernels
    -        h, w, c = self.feat_extractor.output_shape[1:]
    -        self.max_length = w
    -
    -        self.decoder = Sequential(
    -            [
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Dense(units=len(vocab) + 1)
    -            ]
    -        )
    -        self.decoder.build(input_shape=(None, w, h * c))
    -
    -        self.postprocessor = CTCPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        target: List[str],
    -    ) -> tf.Tensor:
    -        """Compute CTC loss for the model.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        gt, seq_len = self.compute_target(target)
    -        batch_len = model_output.shape[0]
    -        input_length = model_output.shape[1] * tf.ones(shape=(batch_len))
    -        ctc_loss = tf.nn.ctc_loss(
    -            gt, model_output, seq_len, input_length, logits_time_major=False, blank_index=len(self.vocab)
    -        )
    -        return ctc_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        # B x H x W x C --> B x W x H x C
    -        transposed_feat = tf.transpose(features, perm=[0, 2, 1, 3])
    -        w, h, c = transposed_feat.get_shape().as_list()[1:]
    -        # B x W x H x C --> B x W x H * C
    -        features_seq = tf.reshape(transposed_feat, shape=(-1, w, h * c))
    -        logits = self.decoder(features_seq, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = logits
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(logits)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(logits, target)
    -
    -        return out
    -
    -
    -def _crnn(arch: str, pretrained: bool, input_shape: Optional[Tuple[int, int, int]] = None, **kwargs: Any) -> CRNN:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[_cfg['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -
    -    # Build the model
    -    model = CRNN(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a VGG-16 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_vgg16_bn - >>> model = crnn_vgg16_bn(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_vgg16_bn', pretrained, **kwargs)
    - - - -def crnn_resnet31(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a resnet31 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_resnet31 - >>> model = crnn_resnet31(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_resnet31', pretrained, **kwargs) -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.1/_modules/doctr/models/recognition/crnn/tensorflow.html b/v0.8.1/_modules/doctr/models/recognition/crnn/tensorflow.html index a00647e1b2..a8a19605ba 100644 --- a/v0.8.1/_modules/doctr/models/recognition/crnn/tensorflow.html +++ b/v0.8.1/_modules/doctr/models/recognition/crnn/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.crnn.tensorflow - docTR documentation @@ -650,7 +650,7 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    - + diff --git a/v0.8.1/_modules/doctr/models/recognition/master/tensorflow.html b/v0.8.1/_modules/doctr/models/recognition/master/tensorflow.html index 446786da5f..fa02c4de73 100644 --- a/v0.8.1/_modules/doctr/models/recognition/master/tensorflow.html +++ b/v0.8.1/_modules/doctr/models/recognition/master/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.master.tensorflow - docTR documentation @@ -644,7 +644,7 @@

    Source code for doctr.models.recognition.master.tensorflow

    - + diff --git a/v0.8.1/_modules/doctr/models/recognition/parseq/tensorflow.html b/v0.8.1/_modules/doctr/models/recognition/parseq/tensorflow.html index bd56053be1..d06bbd51e6 100644 --- a/v0.8.1/_modules/doctr/models/recognition/parseq/tensorflow.html +++ b/v0.8.1/_modules/doctr/models/recognition/parseq/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.parseq.tensorflow - docTR documentation @@ -837,7 +837,7 @@

    Source code for doctr.models.recognition.parseq.tensorflow

    - + diff --git a/v0.8.1/_modules/doctr/models/recognition/sar.html b/v0.8.1/_modules/doctr/models/recognition/sar.html deleted file mode 100644 index 2482e9f156..0000000000 --- a/v0.8.1/_modules/doctr/models/recognition/sar.html +++ /dev/null @@ -1,712 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.sar - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.sar

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import Sequential, layers
    -from typing import Tuple, Dict, List, Any, Optional
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel
    -from .core import RecognitionPostProcessor
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['SAR', 'SARPostProcessor', 'sar_vgg16_bn', 'sar_resnet31']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'sar_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1-models/sar_vgg16bn-0d7e2c26.zip',
    -    },
    -    'sar_resnet31': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'resnet31', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/sar_resnet31-ea202587.zip',
    -    },
    -}
    -
    -
    -class AttentionModule(layers.Layer, NestedObject):
    -    """Implements attention module of the SAR model
    -
    -    Args:
    -        attention_units: number of hidden attention units
    -
    -    """
    -    def __init__(
    -        self,
    -        attention_units: int
    -    ) -> None:
    -
    -        super().__init__()
    -        self.hidden_state_projector = layers.Conv2D(
    -            attention_units, 1, strides=1, use_bias=False, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.features_projector = layers.Conv2D(
    -            attention_units, 3, strides=1, use_bias=True, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.attention_projector = layers.Conv2D(
    -            1, 1, strides=1, use_bias=False, padding="same", kernel_initializer='he_normal',
    -        )
    -        self.flatten = layers.Flatten()
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        hidden_state: tf.Tensor,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        [H, W] = features.get_shape().as_list()[1:3]
    -        # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units)
    -        hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs)
    -        # shape (N, H, W, vgg_units) -> (N, H, W, attention_units)
    -        features_projection = self.features_projector(features, **kwargs)
    -        projection = tf.math.tanh(hidden_state_projection + features_projection)
    -        # shape (N, H, W, attention_units) -> (N, H, W, 1)
    -        attention = self.attention_projector(projection, **kwargs)
    -        # shape (N, H, W, 1) -> (N, H * W)
    -        attention = self.flatten(attention)
    -        attention = tf.nn.softmax(attention)
    -        # shape (N, H * W) -> (N, H, W, 1)
    -        attention_map = tf.reshape(attention, [-1, H, W, 1])
    -        glimpse = tf.math.multiply(features, attention_map)
    -        # shape (N, H * W) -> (N, 1)
    -        glimpse = tf.reduce_sum(glimpse, axis=[1, 2])
    -        return glimpse
    -
    -
    -class SARDecoder(layers.Layer, NestedObject):
    -    """Implements decoder module of the SAR model
    -
    -    Args:
    -        rnn_units: number of hidden units in recurrent cells
    -        max_length: maximum length of a sequence
    -        vocab_size: number of classes in the model alphabet
    -        embedding_units: number of hidden embedding units
    -        attention_units: number of hidden attention units
    -        num_decoder_layers: number of LSTM layers to stack
    -
    -    """
    -    def __init__(
    -        self,
    -        rnn_units: int,
    -        max_length: int,
    -        vocab_size: int,
    -        embedding_units: int,
    -        attention_units: int,
    -        num_decoder_layers: int = 2,
    -        input_shape: Optional[List[Tuple[Optional[int]]]] = None,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.vocab_size = vocab_size
    -        self.lstm_decoder = layers.StackedRNNCells(
    -            [layers.LSTMCell(rnn_units, dtype=tf.float32, implementation=1) for _ in range(num_decoder_layers)]
    -        )
    -        self.embed = layers.Dense(embedding_units, use_bias=False, input_shape=(None, self.vocab_size + 1))
    -        self.attention_module = AttentionModule(attention_units)
    -        self.output_dense = layers.Dense(vocab_size + 1, use_bias=True, input_shape=(None, 2 * rnn_units))
    -        self.max_length = max_length
    -
    -        # Initialize kernels
    -        if input_shape is not None:
    -            self.attention_module.call(layers.Input(input_shape[0][1:]), layers.Input((1, 1, rnn_units)))
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        holistic: tf.Tensor,
    -        gt: Optional[tf.Tensor] = None,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # initialize states (each of shape (N, rnn_units))
    -        states = self.lstm_decoder.get_initial_state(
    -            inputs=None, batch_size=features.shape[0], dtype=tf.float32
    -        )
    -        # run first step of lstm
    -        # holistic: shape (N, rnn_units)
    -        _, states = self.lstm_decoder(holistic, states, **kwargs)
    -        # Initialize with the index of virtual START symbol (placed after <eos>)
    -        symbol = tf.fill(features.shape[0], self.vocab_size + 1)
    -        logits_list = []
    -        if kwargs.get('training') and gt is None:
    -            raise ValueError('Need to provide labels during training for teacher forcing')
    -        for t in range(self.max_length + 1):  # keep 1 step for <eos>
    -            # one-hot symbol with depth vocab_size + 1
    -            # embeded_symbol: shape (N, embedding_units)
    -            embeded_symbol = self.embed(tf.one_hot(symbol, depth=self.vocab_size + 1), **kwargs)
    -            logits, states = self.lstm_decoder(embeded_symbol, states, **kwargs)
    -            glimpse = self.attention_module(
    -                features, tf.expand_dims(tf.expand_dims(logits, axis=1), axis=1), **kwargs,
    -            )
    -            # logits: shape (N, rnn_units), glimpse: shape (N, 1)
    -            logits = tf.concat([logits, glimpse], axis=-1)
    -            # shape (N, rnn_units + 1) -> (N, vocab_size + 1)
    -            logits = self.output_dense(logits, **kwargs)
    -            # update symbol with predicted logits for t+1 step
    -            if kwargs.get('training'):
    -                symbol = gt[:, t]
    -            else:
    -                symbol = tf.argmax(logits, axis=-1)
    -            logits_list.append(logits)
    -        outputs = tf.stack(logits_list, axis=1)  # shape (N, max_length + 1, vocab_size + 1)
    -
    -        return outputs
    -
    -
    -class SAR(RecognitionModel):
    -    """Implements a SAR architecture as described in `"Show, Attend and Read:A Simple and Strong Baseline for
    -    Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of hidden units in both encoder and decoder LSTM
    -        embedding_units: number of embedding units
    -        attention_units: number of hidden units in attention module
    -        max_length: maximum word length handled by the model
    -        num_decoders: number of LSTM to stack in decoder layer
    -
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'encoder', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor,
    -        vocab: str,
    -        rnn_units: int = 512,
    -        embedding_units: int = 512,
    -        attention_units: int = 512,
    -        max_length: int = 30,
    -        num_decoders: int = 2,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(vocab=vocab, cfg=cfg)
    -
    -        self.max_length = max_length + 1  # Add 1 timestep for EOS after the longest word
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.encoder = Sequential(
    -            [
    -                layers.LSTM(units=rnn_units, return_sequences=True),
    -                layers.LSTM(units=rnn_units, return_sequences=False)
    -            ]
    -        )
    -        # Initialize the kernels (watch out for reduce_max)
    -        self.encoder.build(input_shape=(None,) + self.feat_extractor.output_shape[2:])
    -
    -        self.decoder = SARDecoder(
    -            rnn_units, max_length, len(vocab), embedding_units, attention_units, num_decoders,
    -            input_shape=[self.feat_extractor.output_shape, self.encoder.output_shape]
    -        )
    -
    -        self.postprocessor = SARPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        gt: tf.Tensor,
    -        seq_len: tf.Tensor,
    -    ) -> tf.Tensor:
    -        """Compute categorical cross-entropy loss for the model.
    -        Sequences are masked after the EOS character.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        # Input length : number of timesteps
    -        input_len = tf.shape(model_output)[1]
    -        # Add one for additional <eos> token
    -        seq_len = seq_len + 1
    -        # One-hot gt labels
    -        oh_gt = tf.one_hot(gt, depth=model_output.shape[2])
    -        # Compute loss
    -        cce = tf.nn.softmax_cross_entropy_with_logits(oh_gt, model_output)
    -        # Compute mask
    -        mask_values = tf.zeros_like(cce)
    -        mask_2d = tf.sequence_mask(seq_len, input_len)
    -        masked_loss = tf.where(mask_2d, cce, mask_values)
    -        ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32))
    -        return tf.expand_dims(ce_loss, axis=1)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        pooled_features = tf.reduce_max(features, axis=1)  # vertical max pooling
    -        encoded = self.encoder(pooled_features, **kwargs)
    -        if target is not None:
    -            gt, seq_len = self.compute_target(target)
    -        decoded_features = self.decoder(features, encoded, gt=None if target is None else gt, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = decoded_features
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(decoded_features)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(decoded_features, gt, seq_len)
    -
    -        return out
    -
    -
    -class SARPostProcessor(RecognitionPostProcessor):
    -    """Post processor for SAR architectures
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor,
    -    ) -> List[Tuple[str, float]]:
    -        # compute pred with argmax for attention models
    -        out_idxs = tf.math.argmax(logits, axis=2)
    -        # N x L
    -        probs = tf.gather(tf.nn.softmax(logits, axis=-1), out_idxs, axis=-1, batch_dims=2)
    -        # Take the minimum confidence of the sequence
    -        probs = tf.math.reduce_min(probs, axis=1)
    -
    -        # decode raw output of the model with tf_label_to_idx
    -        out_idxs = tf.cast(out_idxs, dtype='int32')
    -        decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(self._embedding, out_idxs), axis=-1)
    -        decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -def _sar(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> SAR:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -    _cfg['embedding_units'] = kwargs.get('embedding_units', _cfg['rnn_units'])
    -    _cfg['attention_units'] = kwargs.get('attention_units', _cfg['rnn_units'])
    -    _cfg['max_length'] = kwargs.get('max_length', _cfg['max_length'])
    -    _cfg['num_decoders'] = kwargs.get('num_decoders', _cfg['num_decoders'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[default_cfgs[arch]['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -    kwargs['embedding_units'] = _cfg['embedding_units']
    -    kwargs['attention_units'] = _cfg['attention_units']
    -    kwargs['max_length'] = _cfg['max_length']
    -    kwargs['num_decoders'] = _cfg['num_decoders']
    -
    -    # Build the model
    -    model = SAR(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, default_cfgs[arch]['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a VGG16 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import sar_vgg16_bn - >>> model = sar_vgg16_bn(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_vgg16_bn', pretrained, **kwargs)
    - - - -
    -[docs] -def sar_resnet31(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a resnet-31 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example: - >>> import tensorflow as tf - >>> from doctr.models import sar_resnet31 - >>> model = sar_resnet31(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_resnet31', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.1/_modules/doctr/models/recognition/sar/tensorflow.html b/v0.8.1/_modules/doctr/models/recognition/sar/tensorflow.html index 6a44c6d2f4..9bbcdfbf81 100644 --- a/v0.8.1/_modules/doctr/models/recognition/sar/tensorflow.html +++ b/v0.8.1/_modules/doctr/models/recognition/sar/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.sar.tensorflow - docTR documentation @@ -753,7 +753,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    - + diff --git a/v0.8.1/_modules/doctr/models/recognition/vitstr/tensorflow.html b/v0.8.1/_modules/doctr/models/recognition/vitstr/tensorflow.html index 1a97114efa..7131ac4a5b 100644 --- a/v0.8.1/_modules/doctr/models/recognition/vitstr/tensorflow.html +++ b/v0.8.1/_modules/doctr/models/recognition/vitstr/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.vitstr.tensorflow - docTR documentation @@ -610,7 +610,7 @@

    Source code for doctr.models.recognition.vitstr.tensorflow

    - + diff --git a/v0.8.1/_modules/doctr/models/recognition/zoo.html b/v0.8.1/_modules/doctr/models/recognition/zoo.html index 4c61c0e058..b6896dd45c 100644 --- a/v0.8.1/_modules/doctr/models/recognition/zoo.html +++ b/v0.8.1/_modules/doctr/models/recognition/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.zoo - docTR documentation @@ -401,7 +401,7 @@

    Source code for doctr.models.recognition.zoo

       
    - + diff --git a/v0.8.1/_modules/doctr/models/zoo.html b/v0.8.1/_modules/doctr/models/zoo.html index 7d5510b773..a964ff6aff 100644 --- a/v0.8.1/_modules/doctr/models/zoo.html +++ b/v0.8.1/_modules/doctr/models/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.zoo - docTR documentation @@ -570,7 +570,7 @@

    Source code for doctr.models.zoo

         
       
    - + diff --git a/v0.8.1/_modules/doctr/transforms/modules.html b/v0.8.1/_modules/doctr/transforms/modules.html deleted file mode 100644 index ba8269e7ef..0000000000 --- a/v0.8.1/_modules/doctr/transforms/modules.html +++ /dev/null @@ -1,734 +0,0 @@ - - - - - - - - - - - - doctr.transforms.modules - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.transforms.modules

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import random
    -import tensorflow as tf
    -from typing import List, Any, Tuple, Callable
    -
    -from doctr.utils.repr import NestedObject
    -from . import functional as F
    -
    -
    -__all__ = ['Compose', 'Resize', 'Normalize', 'LambdaTransformation', 'ToGray', 'ColorInversion',
    -           'RandomBrightness', 'RandomContrast', 'RandomSaturation', 'RandomHue', 'RandomGamma', 'RandomJpegQuality',
    -           'OneOf', 'RandomApply']
    -
    -
    -
    -[docs] -class Compose(NestedObject): - """Implements a wrapper that will apply transformations sequentially - - Example:: - >>> from doctr.transforms import Compose, Resize - >>> import tensorflow as tf - >>> transfos = Compose([Resize((32, 32))]) - >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformation modules - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, x: Any) -> Any: - for t in self.transforms: - x = t(x) - - return x
    - - - -
    -[docs] -class Resize(NestedObject): - """Resizes a tensor to a target size - - Example:: - >>> from doctr.transforms import Resize - >>> import tensorflow as tf - >>> transfo = Resize((32, 32)) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - output_size: expected output size - method: interpolation method - preserve_aspect_ratio: if `True`, preserve aspect ratio and pad the rest with zeros - symmetric_pad: if `True` while preserving aspect ratio, the padding will be done symmetrically - """ - def __init__( - self, - output_size: Tuple[int, int], - method: str = 'bilinear', - preserve_aspect_ratio: bool = False, - symmetric_pad: bool = False, - ) -> None: - self.output_size = output_size - self.method = method - self.preserve_aspect_ratio = preserve_aspect_ratio - self.symmetric_pad = symmetric_pad - - def extra_repr(self) -> str: - _repr = f"output_size={self.output_size}, method='{self.method}'" - if self.preserve_aspect_ratio: - _repr += f", preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}" - return _repr - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img = tf.image.resize(img, self.output_size, self.method, self.preserve_aspect_ratio) - if self.preserve_aspect_ratio: - # pad width - if not self.symmetric_pad: - offset = (0, 0) - elif self.output_size[0] == img.shape[0]: - offset = (0, int((self.output_size[1] - img.shape[1]) / 2)) - else: - offset = (int((self.output_size[0] - img.shape[0]) / 2), 0) - img = tf.image.pad_to_bounding_box(img, *offset, *self.output_size) - return img
    - - - -
    -[docs] -class Normalize(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - mean: average value per channel - std: standard deviation per channel - """ - def __init__(self, mean: Tuple[float, float, float], std: Tuple[float, float, float]) -> None: - self.mean = tf.constant(mean, dtype=tf.float32) - self.std = tf.constant(std, dtype=tf.float32) - - def extra_repr(self) -> str: - return f"mean={self.mean.numpy().tolist()}, std={self.std.numpy().tolist()}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img -= self.mean - img /= self.std - return img
    - - - -
    -[docs] -class LambdaTransformation(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import LambdaTransformation - >>> import tensorflow as tf - >>> transfo = LambdaTransformation(lambda x: x/ 255.) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - fn: the function to be applied to the input tensor - """ - def __init__(self, fn: Callable[[tf.Tensor], tf.Tensor]) -> None: - self.fn = fn - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return self.fn(img)
    - - - -
    -[docs] -class ToGray(NestedObject): - """Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ToGray() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - """ - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.rgb_to_grayscale(img)
    - - - -
    -[docs] -class ColorInversion(NestedObject): - """Applies the following tranformation to a tensor (image or batch of images): - convert to grayscale, colorize (shift 0-values randomly), and then invert colors - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ColorInversion(min_val=0.6) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_val: range [min_val, 1] to colorize RGB pixels - """ - def __init__(self, min_val: float = 0.6) -> None: - self.min_val = min_val - - def extra_repr(self) -> str: - return f"min_val={self.min_val}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return F.invert_colors(img, self.min_val)
    - - - -
    -[docs] -class RandomBrightness(NestedObject): - """Randomly adjust brightness of a tensor (batch of images or image) by adding a delta - to all pixels - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Brightness() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - p: probability to apply transformation - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_brightness(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomContrast(NestedObject): - """Randomly adjust contrast of a tensor (batch of images or image) by adjusting - each pixel: (img - mean) * contrast_factor + mean. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Contrast() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1) - """ - def __init__(self, delta: float = .3) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_contrast(img, lower=1 - self.delta, upper=1 / (1 - self.delta))
    - - - -
    -[docs] -class RandomSaturation(NestedObject): - """Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and - increasing saturation by a factor. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Saturation() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1) - """ - def __init__(self, delta: float = .5) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_saturation(img, lower=1 - self.delta, upper=1 + self.delta)
    - - - -
    -[docs] -class RandomHue(NestedObject): - """Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Hue() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_hue(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomGamma(NestedObject): - """randomly performs gamma correction for a tensor (batch of images or image) - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Gamma() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_gamma: non-negative real number, lower bound for gamma param - max_gamma: non-negative real number, upper bound for gamma - min_gain: lower bound for constant multiplier - max_gain: upper bound for constant multiplier - """ - def __init__( - self, - min_gamma: float = 0.5, - max_gamma: float = 1.5, - min_gain: float = 0.8, - max_gain: float = 1.2, - ) -> None: - self.min_gamma = min_gamma - self.max_gamma = max_gamma - self.min_gain = min_gain - self.max_gain = max_gain - - def extra_repr(self) -> str: - return f"""gamma_range=({self.min_gamma}, {self.max_gamma}), - gain_range=({self.min_gain}, {self.max_gain})""" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - gamma = random.uniform(self.min_gamma, self.max_gamma) - gain = random.uniform(self.min_gain, self.max_gain) - return tf.image.adjust_gamma(img, gamma=gamma, gain=gain)
    - - - -
    -[docs] -class RandomJpegQuality(NestedObject): - """Randomly adjust jpeg quality of a 3 dimensional RGB image - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = JpegQuality() - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - min_quality: int between [0, 100] - max_quality: int between [0, 100] - """ - def __init__(self, min_quality: int = 60, max_quality: int = 100) -> None: - self.min_quality = min_quality - self.max_quality = max_quality - - def extra_repr(self) -> str: - return f"min_quality={self.min_quality}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_jpeg_quality( - img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality - )
    - - - -
    -[docs] -class OneOf(NestedObject): - """Randomly apply one of the input transformations - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = OneOf([JpegQuality(), Gamma()]) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformations, one only will be picked - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - # Pick transformation - transfo = self.transforms[int(random.random() * len(self.transforms))] - # Apply - return transfo(img)
    - - - -
    -[docs] -class RandomApply(NestedObject): - """Apply with a probability p the input transformation - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = RandomApply(Gamma(), p=.5) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transform: transformation to apply - p: probability to apply - """ - def __init__(self, transform: NestedObject, p: float = .5) -> None: - self.transform = transform - self.p = p - - def extra_repr(self) -> str: - return f"transform={self.transform}, p={self.p}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - if random.random() < self.p: - return self.transform(img) - return img
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.1/_modules/doctr/transforms/modules/base.html b/v0.8.1/_modules/doctr/transforms/modules/base.html index 42e8b8d2b1..087636ae0d 100644 --- a/v0.8.1/_modules/doctr/transforms/modules/base.html +++ b/v0.8.1/_modules/doctr/transforms/modules/base.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.base - docTR documentation @@ -615,7 +615,7 @@

    Source code for doctr.transforms.modules.base

    - + diff --git a/v0.8.1/_modules/doctr/transforms/modules/tensorflow.html b/v0.8.1/_modules/doctr/transforms/modules/tensorflow.html index 5e85447d5c..9ef65dafc0 100644 --- a/v0.8.1/_modules/doctr/transforms/modules/tensorflow.html +++ b/v0.8.1/_modules/doctr/transforms/modules/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.tensorflow - docTR documentation @@ -888,7 +888,7 @@

    Source code for doctr.transforms.modules.tensorflow

    - + diff --git a/v0.8.1/_modules/doctr/utils/metrics.html b/v0.8.1/_modules/doctr/utils/metrics.html index 5190fb3dd2..bec0aee3f4 100644 --- a/v0.8.1/_modules/doctr/utils/metrics.html +++ b/v0.8.1/_modules/doctr/utils/metrics.html @@ -13,7 +13,7 @@ - + doctr.utils.metrics - docTR documentation @@ -1071,7 +1071,7 @@

    Source code for doctr.utils.metrics

         
       
    - + diff --git a/v0.8.1/_modules/doctr/utils/visualization.html b/v0.8.1/_modules/doctr/utils/visualization.html index 9094dda132..d7c33dc75a 100644 --- a/v0.8.1/_modules/doctr/utils/visualization.html +++ b/v0.8.1/_modules/doctr/utils/visualization.html @@ -13,7 +13,7 @@ - + doctr.utils.visualization - docTR documentation @@ -830,7 +830,7 @@

    Source code for doctr.utils.visualization

         
       
    - + diff --git a/v0.8.1/_modules/index.html b/v0.8.1/_modules/index.html index db6e0f4507..0c3394d1db 100644 --- a/v0.8.1/_modules/index.html +++ b/v0.8.1/_modules/index.html @@ -13,7 +13,7 @@ - + Overview: module code - docTR documentation @@ -372,7 +372,7 @@

    All modules for which code is available

    - + diff --git a/v0.8.1/_sources/datasets.rst.txt b/v0.8.1/_sources/datasets.rst.txt deleted file mode 100644 index 354122f1e5..0000000000 --- a/v0.8.1/_sources/datasets.rst.txt +++ /dev/null @@ -1,68 +0,0 @@ -doctr.datasets -============== - -.. currentmodule:: doctr.datasets - -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. - - -.. _datasets: - -Available Datasets ------------------- -The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL. - -.. autoclass:: doctr.datasets.datasets.VisionDataset - - -Here are all datasets that are available through DocTR: - -.. autoclass:: FUNSD -.. autoclass:: SROIE -.. autoclass:: CORD -.. autoclass:: OCRDataset - - -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. - -.. autoclass:: doctr.datasets.loader.DataLoader - - -.. _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 - * - digits - - 10 - - 0123456789 - * - ascii_letters - - 52 - - abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ - * - punctuation - - 32 - - !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ - * - currency - - 5 - - £€¥¢฿ - * - latin - - 96 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~° - * - french - - 154 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿ - -.. autofunction:: encode_sequences diff --git a/v0.8.1/_sources/documents.rst.txt b/v0.8.1/_sources/documents.rst.txt deleted file mode 100644 index 655730073e..0000000000 --- a/v0.8.1/_sources/documents.rst.txt +++ /dev/null @@ -1,87 +0,0 @@ -doctr.documents -=============== - - -.. currentmodule:: doctr.documents - -The documents module enables users to easily access content from documents and export analysis -results to structured formats. - - -Document structure ------------------- - -Structural organization of the documents. - -Word -^^^^ -A Word is an uninterrupted sequence of characters. - -.. autoclass:: Word - -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 - -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 - - -Document -^^^^^^^^ - -A Document is a collection of Pages. - -.. autoclass:: Document - - .. automethod:: show - - -File reading ------------- - -High-performance file reading and conversion to processable structured data. - -.. autofunction:: read_pdf - -.. autofunction:: read_img - -.. autofunction:: read_html - - -.. autoclass:: DocumentFile - - .. automethod:: from_pdf - - .. automethod:: from_url - - .. automethod:: from_images - -.. autoclass:: PDF - - .. automethod:: as_images - - .. automethod:: get_words - - .. automethod:: get_artefacts diff --git a/v0.8.1/_sources/installing.rst.txt b/v0.8.1/_sources/installing.rst.txt deleted file mode 100644 index 5c8779dc1c..0000000000 --- a/v0.8.1/_sources/installing.rst.txt +++ /dev/null @@ -1,46 +0,0 @@ - -************ -Installation -************ - -This library requires Python 3.6 or higher. - - -Prerequisites -============= - -Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so: - -* TensorFlow: `installation page `_. -* PyTorch: `installation page `_. - -If you are running another OS than Linux, you will need a few extra dependencies. - -For MacOS users, you can install them as follows: - -.. code:: shell - - brew install cairo pango gdk-pixbuf libffi - -For Windows users, those dependencies are included in GTK. You can find the latest installer over `here `_. - - -Via Python Package -================== - -Install the last stable release of the package using pip: - -.. code:: bash - - pip install python-doctr - - -Via Git -======= - -Install the library in developper mode: - -.. code:: bash - - git clone https://github.com/mindee/doctr.git - pip install -e doctr/. diff --git a/v0.8.1/_sources/models.rst.txt b/v0.8.1/_sources/models.rst.txt deleted file mode 100644 index 9830c6c153..0000000000 --- a/v0.8.1/_sources/models.rst.txt +++ /dev/null @@ -1,215 +0,0 @@ -doctr.models -============ - -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. - -.. currentmodule:: doctr.models - -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 TensorFlow model. -* Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable. - - -Text Detection --------------- -Localizing text elements in images - -+---------------------------------------------------+----------------------------+----------------------------+---------+ -| | FUNSD | CORD | | -+==================+=================+==============+============+===============+============+===============+=========+ -| **Architecture** | **Input shape** | **# params** | **Recall** | **Precision** | **Recall** | **Precision** | **FPS** | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ -| db_resnet50 | (1024, 1024, 3) | 25.2 M | 82.14 | 87.64 | 92.49 | 89.66 | 2.1 | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ - -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 combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for detection -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for detection is the following: - -1. resize each input image to the target size (bilinear interpolation by default) with potential deformation. -2. batch images together -3. normalize the batch using the training data statistics - - -Detection models -^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - -.. autofunction:: doctr.models.detection.db_resnet50 -.. autofunction:: doctr.models.detection.linknet16 - -Detection predictors -^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information. - -.. autofunction:: doctr.models.detection.detection_predictor - - -Text Recognition ----------------- -Identifying strings in images - -.. list-table:: Text recognition model zoo - :widths: 20 20 15 10 10 10 - :header-rows: 1 - - * - Architecture - - Input shape - - # params - - FUNSD - - CORD - - FPS - * - crnn_vgg16_bn - - (32, 128, 3) - - 15.8M - - 86.02 - - 91.3 - - 12.8 - * - sar_vgg16_bn - - (32, 128, 3) - - 21.5M - - 86.2 - - 91.7 - - 3.3 - * - sar_resnet31 - - (32, 128, 3) - - 53.1M - - **86.3** - - **92.1** - - 2.7 - -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 metrics being used are available in :ref:`metrics`. - -All these recognition models are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for recognition -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for recognition is the following: - -1. resize each input image to the target size (bilinear interpolation by default) without deformation. -2. pad the image to the target size (with zeros by default) -3. batch images together -4. normalize the batch using the training data statistics - -Recognition models -^^^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - - -.. autofunction:: doctr.models.recognition.crnn_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_resnet31 -.. autofunction:: doctr.models.recognition.master - - -Recognition predictors -^^^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage. - -.. autofunction:: doctr.models.recognition.recognition_predictor - - -End-to-End OCR --------------- -Predictors that localize and identify text elements in images - -+-----------------------------+--------------------------------------+--------------------------------------+ -| | FUNSD | CORD | -+=============================+============+===============+=========+============+===============+=========+ -| **Architecture** | **Recall** | **Precision** | **FPS** | **Recall** | **Precision** | **FPS** | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + crnn_vgg16_bn | 70.08 | 74.77 | 0.85 | 82.19 | **79.67** | 1.6 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_vgg16_bn | N/A | N/A | 0.49 | N/A | N/A | 1.0 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_resnet31 | N/A | N/A | 0.27 | N/A | N/A | 0.83 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision text detection | 59.50 | 62.50 | | 75.30 | 70.00 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision doc. text detection | 64.00 | 53.30 | | 68.90 | 61.10 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| AWS textract | **78.10** | **83.00** | | **87.50** | 66.00 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ - -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`. - -All recognition models of predictors are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Results on private ocr datasets - -+------------------------------------+----------------------------+----------------------------+----------------------------+ -| | Receipts | Invoices | IDs | -+====================================+============+===============+============+===============+============+===============+ -| **Architecture** | **Recall** | **Precision** | **Recall** | **Precision** | **Recall** | **Precision** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| db_resnet50 + crnn_vgg16_bn (ours) | **78.90** | **81.01** | 65.68 | **69.86** | **49.48** | **50.46** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| Gvision doc. text detection | 68.91 | 59.89 | 63.20 | 52.85 | 43.70 | 29.21 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| AWS textract | 75.77 | 77.70 | **70.47** | 69.13 | 46.39 | 43.32 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ - - -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. - -.. autofunction:: doctr.models.zoo.ocr_predictor - - -Model export ------------- -Utility functions to make the most of document analysis models. - -.. currentmodule:: doctr.models.export - -Model compression -^^^^^^^^^^^^^^^^^ - -.. autofunction:: convert_to_tflite - -.. autofunction:: convert_to_fp16 - -.. autofunction:: quantize_model - -Using SavedModel -^^^^^^^^^^^^^^^^ - -Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -`SavedModel `_ format as follows: - - - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> _ = model(input_t, training=False) - >>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/') - -And loaded just as easily: - - - >>> import tensorflow as tf - >>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/') diff --git a/v0.8.1/_sources/transforms.rst.txt b/v0.8.1/_sources/transforms.rst.txt deleted file mode 100644 index 0230fe75f5..0000000000 --- a/v0.8.1/_sources/transforms.rst.txt +++ /dev/null @@ -1,32 +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: - -.. autoclass:: Resize -.. autoclass:: Normalize -.. autoclass:: LambdaTransformation -.. autoclass:: ToGray -.. autoclass:: ColorInversion -.. autoclass:: RandomBrightness -.. autoclass:: RandomContrast -.. autoclass:: RandomSaturation -.. autoclass:: RandomHue -.. autoclass:: RandomGamma -.. autoclass:: RandomJpegQuality - - -Composing transformations ---------------------------------------------- -It is common to require several transformations to be performed consecutively. - -.. autoclass:: Compose -.. autoclass:: OneOf -.. autoclass:: RandomApply diff --git a/v0.8.1/_sources/utils.rst.txt b/v0.8.1/_sources/utils.rst.txt deleted file mode 100644 index 69c1abe0eb..0000000000 --- a/v0.8.1/_sources/utils.rst.txt +++ /dev/null @@ -1,36 +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 - - -.. _metrics: - -Task evaluation ---------------- -Implementations of task-specific metrics to easily assess your model performances. - -.. currentmodule:: doctr.utils.metrics - -.. autoclass:: TextMatch - - .. automethod:: summary - -.. autoclass:: LocalizationConfusion - - .. automethod:: summary - -.. autoclass:: OCRMetric - - .. automethod:: summary diff --git a/v0.8.1/_static/basic.css b/v0.8.1/_static/basic.css index f316efcb47..7ebbd6d07b 100644 --- a/v0.8.1/_static/basic.css +++ b/v0.8.1/_static/basic.css @@ -1,12 +1,5 @@ /* - * basic.css - * ~~~~~~~~~ - * * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ /* -- main layout ----------------------------------------------------------- */ @@ -115,15 +108,11 @@ img { /* -- search page ----------------------------------------------------------- */ ul.search { - margin: 10px 0 0 20px; - padding: 0; + margin-top: 10px; } ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; + padding: 5px 0; } ul.search li a { diff --git a/v0.8.1/_static/doctools.js b/v0.8.1/_static/doctools.js index 4d67807d17..0398ebb9f0 100644 --- a/v0.8.1/_static/doctools.js +++ b/v0.8.1/_static/doctools.js @@ -1,12 +1,5 @@ /* - * doctools.js - * ~~~~~~~~~~~ - * * Base JavaScript utilities for all Sphinx HTML documentation. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; diff --git a/v0.8.1/_static/language_data.js b/v0.8.1/_static/language_data.js index 367b8ed81b..c7fe6c6faf 100644 --- a/v0.8.1/_static/language_data.js +++ b/v0.8.1/_static/language_data.js @@ -1,13 +1,6 @@ /* - * language_data.js - * ~~~~~~~~~~~~~~~~ - * * This script contains the language-specific data used by searchtools.js, * namely the list of stopwords, stemmer, scorer and splitter. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; diff --git a/v0.8.1/_static/searchtools.js b/v0.8.1/_static/searchtools.js index b08d58c9b9..2c774d17af 100644 --- a/v0.8.1/_static/searchtools.js +++ b/v0.8.1/_static/searchtools.js @@ -1,12 +1,5 @@ /* - * searchtools.js - * ~~~~~~~~~~~~~~~~ - * * Sphinx JavaScript utilities for the full-text search. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; @@ -20,7 +13,7 @@ if (typeof Scorer === "undefined") { // and returns the new score. /* score: result => { - const [docname, title, anchor, descr, score, filename] = result + const [docname, title, anchor, descr, score, filename, kind] = result return score }, */ @@ -47,6 +40,14 @@ if (typeof Scorer === "undefined") { }; } +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + const _removeChildren = (element) => { while (element && element.lastChild) element.removeChild(element.lastChild); }; @@ -64,9 +65,13 @@ const _displayItem = (item, searchTerms, highlightTerms) => { const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; const contentRoot = document.documentElement.dataset.content_root; - const [docName, title, anchor, descr, score, _filename] = item; + const [docName, title, anchor, descr, score, _filename, kind] = item; let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); let requestUrl; let linkUrl; if (docBuilder === "dirhtml") { @@ -115,8 +120,10 @@ const _finishSearch = (resultCount) => { "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." ); else - Search.status.innerText = _( - "Search finished, found ${resultCount} page(s) matching the search query." + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, ).replace('${resultCount}', resultCount); }; const _displayNextItem = ( @@ -138,7 +145,7 @@ const _displayNextItem = ( else _finishSearch(resultCount); }; // Helper function used by query() to order search results. -// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. // Order the results by score (in opposite order of appearance, since the // `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. const _orderResultsByScoreThenName = (a, b) => { @@ -248,6 +255,7 @@ const Search = { searchSummary.classList.add("search-summary"); searchSummary.innerText = ""; const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); searchList.classList.add("search"); const out = document.getElementById("search-results"); @@ -318,7 +326,7 @@ const Search = { const indexEntries = Search._index.indexentries; // Collect multiple result groups to be sorted separately and then ordered. - // Each is an array of [docname, title, anchor, descr, score, filename]. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. const normalResults = []; const nonMainIndexResults = []; @@ -337,6 +345,7 @@ const Search = { null, score + boost, filenames[file], + SearchResultKind.title, ]); } } @@ -354,6 +363,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.index, ]; if (isMain) { normalResults.push(result); @@ -475,6 +485,7 @@ const Search = { descr, score, filenames[match[0]], + SearchResultKind.object, ]); }; Object.keys(objects).forEach((prefix) => @@ -585,6 +596,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.text, ]); } return results; diff --git a/v0.8.1/changelog.html b/v0.8.1/changelog.html index 3f802a9ee4..f805dd7cba 100644 --- a/v0.8.1/changelog.html +++ b/v0.8.1/changelog.html @@ -14,7 +14,7 @@ - + Changelog - docTR documentation @@ -425,7 +425,7 @@

    v0.1.0 (2021-03-05) - + diff --git a/v0.8.1/community/resources.html b/v0.8.1/community/resources.html index 2564037893..9a1988258c 100644 --- a/v0.8.1/community/resources.html +++ b/v0.8.1/community/resources.html @@ -14,7 +14,7 @@ - + Community resources - docTR documentation @@ -389,7 +389,7 @@

    Community resources - + diff --git a/v0.8.1/contributing/code_of_conduct.html b/v0.8.1/contributing/code_of_conduct.html index 51e26624aa..c8e56f2887 100644 --- a/v0.8.1/contributing/code_of_conduct.html +++ b/v0.8.1/contributing/code_of_conduct.html @@ -14,7 +14,7 @@ - + Contributor Covenant Code of Conduct - docTR documentation @@ -498,7 +498,7 @@

    Attribution - + diff --git a/v0.8.1/contributing/contributing.html b/v0.8.1/contributing/contributing.html index 967bbb0d4d..0ed3b44c28 100644 --- a/v0.8.1/contributing/contributing.html +++ b/v0.8.1/contributing/contributing.html @@ -14,7 +14,7 @@ - + Contributing to docTR - docTR documentation @@ -475,7 +475,7 @@

    Let’s connect - + diff --git a/v0.8.1/datasets.html b/v0.8.1/datasets.html deleted file mode 100644 index 193e576c57..0000000000 --- a/v0.8.1/datasets.html +++ /dev/null @@ -1,578 +0,0 @@ - - - - - - - - - - - - - doctr.datasets - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.datasets

    -

    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.

    -
    -

    Available Datasets

    -

    The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL.

    -
    -
    -class doctr.datasets.datasets.VisionDataset(url: str, file_name: str | None = None, file_hash: str | None = None, extract_archive: bool = False, download: bool = False, overwrite: bool = False)[source]
    -
    - -

    Here are all datasets that are available through DocTR:

    -
    -
    -class doctr.datasets.FUNSD(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    FUNSD dataset from “FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents”.

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD
    ->>> train_set = FUNSD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.SROIE(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    SROIE dataset from “ICDAR2019 Competition on Scanned Receipt OCR and Information Extraction”.

    -
    -
    Example::
    >>> from doctr.datasets import SROIE
    ->>> train_set = SROIE(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.CORD(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    CORD dataset from “CORD: A Consolidated Receipt Dataset forPost-OCR Parsing”.

    -
    -
    Example::
    >>> from doctr.datasets import CORD
    ->>> train_set = CORD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.OCRDataset(img_folder: str, label_file: str, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    Implements an OCR dataset

    -
    -
    Parameters:
    -
      -
    • img_folder – local path to image folder (all jpg at the root)

    • -
    • label_file – local path to the label file

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -

    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.

    -
    -
    -class doctr.datasets.loader.DataLoader(dataset, shuffle: bool = True, batch_size: int = 1, drop_last: bool = False, workers: int | None = None)[source]
    -

    Implements a dataset wrapper for fast data loading

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD, 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)
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • dataset – the dataset

    • -
    • shuffle – whether the samples should be shuffled before passing it to the iterator

    • -
    • batch_size – number of elements in each batch

    • -
    • drop_last – if True, drops the last batch if it isn’t full

    • -
    • workers – number of workers to use for data loading

    • -
    -
    -
    -
    - -
    -
    -

    Supported Vocabs

    -

    Since textual content has to be encoded properly for models to interpret them efficiently, DocTR supports multiple sets -of vocabs.

    -
    - - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    DocTR Vocabs

    Name

    size

    characters

    digits

    10

    0123456789

    ascii_letters

    52

    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

    punctuation

    32

    !”#$%&'()*+,-./:;<=>?@[\]^_`{|}~

    currency

    5

    £€¥¢฿

    latin

    96

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°

    french

    154

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿

    -
    -
    -
    -doctr.datasets.encode_sequences(sequences: List[str], vocab: str, target_size: int | None = None, eos: int = -1, sos: int | None = None, pad: int | None = None, **kwargs: Any) ndarray[source]
    -

    Encode character sequences using a given vocab as mapping

    -
    -
    Parameters:
    -
      -
    • sequences – the list of character sequences of size N

    • -
    • vocab – the ordered vocab to use for encoding

    • -
    • target_size – maximum length of the encoded data

    • -
    • eos – encoding of End Of String

    • -
    • sos – optional encoding of Start Of String

    • -
    • pad – optional encoding for padding. In case of padding, all sequences are followed by 1 EOS then PAD

    • -
    -
    -
    Returns:
    -

    the padded encoded data as a tensor

    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.1/documents.html b/v0.8.1/documents.html deleted file mode 100644 index 98cbb2c5ef..0000000000 --- a/v0.8.1/documents.html +++ /dev/null @@ -1,772 +0,0 @@ - - - - - - - - - - - - - doctr.documents - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.documents

    -

    The documents module enables users to easily access content from documents and export analysis -results to structured formats.

    -
    -

    Document structure

    -

    Structural organization of the documents.

    -
    -

    Word

    -

    A Word is an uninterrupted sequence of characters.

    -
    -
    -class doctr.documents.Word(value: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float])[source]
    -

    Implements a word element

    -
    -
    Parameters:
    -
      -
    • value – the text string of the word

    • -
    • confidence – the confidence associated with the text prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to

    • -
    • size (the page's)

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Line(words: List[Word], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float] | None = None)[source]
    -

    Implements a line element as a collection of words

    -
    -
    Parameters:
    -
      -
    • words – list of word elements

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all words in it.

    • -
    -
    -
    -
    - -
    -
    -

    Artefact

    -

    An Artefact is a non-textual element (e.g. QR code, picture, chart, signature, logo, etc.).

    -
    -
    -class doctr.documents.Artefact(artefact_type: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]])[source]
    -

    Implements a non-textual element

    -
    -
    Parameters:
    -
      -
    • artefact_type – the type of artefact

    • -
    • confidence – the confidence of the type prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size.

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Block(lines: List[Line] = [], artefacts: List[Artefact] = [], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float] | None = None)[source]
    -

    Implements a block element as a collection of lines and artefacts

    -
    -
    Parameters:
    -
      -
    • lines – list of line elements

    • -
    • artefacts – list of artefacts

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all lines and artefacts in it.

    • -
    -
    -
    -
    - -
    -
    -

    Page

    -

    A Page is a collection of Blocks that were on the same physical page.

    -
    -
    -class doctr.documents.Page(blocks: List[Block], page_idx: int, dimensions: Tuple[int, int], orientation: Dict[str, Any] | None = None, language: Dict[str, Any] | None = None)[source]
    -

    Implements a page element as a collection of blocks

    -
    -
    Parameters:
    -
      -
    • blocks – list of block elements

    • -
    • page_idx – the index of the page in the input raw document

    • -
    • dimensions – the page size in pixels in format (width, height)

    • -
    • orientation – a dictionary with the value of the rotation angle in degress and confidence of the prediction

    • -
    • language – a dictionary with the language value and confidence of the prediction

    • -
    -
    -
    -
    -
    -show(page: ndarray, interactive: bool = True, **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -
      -
    • page – image encoded as a numpy array in uint8

    • -
    • interactive – whether the display should be interactive

    • -
    -
    -
    -
    - -
    - -
    -
    -

    Document

    -

    A Document is a collection of Pages.

    -
    -
    -class doctr.documents.Document(pages: List[Page])[source]
    -

    Implements a document element as a collection of pages

    -
    -
    Parameters:
    -

    pages – list of page elements

    -
    -
    -
    -
    -show(pages: List[ndarray], **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -

    pages – list of images encoded as numpy arrays in uint8

    -
    -
    -
    - -
    - -
    -
    -
    -

    File reading

    -

    High-performance file reading and conversion to processable structured data.

    -
    -
    -doctr.documents.read_pdf(file: str | Path | bytes, **kwargs: Any) Document[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_pdf
    ->>> doc = read_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_img(file: str | Path | bytes, output_size: Tuple[int, int] | None = None, rgb_output: bool = True) ndarray[source]
    -

    Read an image file into numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_img
    ->>> page = read_img("path/to/your/doc.jpg")
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • file – the path to the image file

    • -
    • output_size – the expected output size of each page in format H x W

    • -
    • rgb_output – whether the output ndarray channel order should be RGB instead of BGR.

    • -
    -
    -
    Returns:
    -

    the page decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_html(url: str, **kwargs: Any) bytes[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_html
    ->>> doc = read_html("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – URL of the target web page

    -
    -
    Returns:
    -

    decoded PDF file as a bytes stream

    -
    -
    -
    - -
    -
    -class doctr.documents.DocumentFile[source]
    -

    Read a document from multiple extensions

    -
    -
    -classmethod from_pdf(file: str | Path | bytes, **kwargs) PDF[source]
    -

    Read a PDF file

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file or a binary stream

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_url(url: str, **kwargs) PDF[source]
    -

    Interpret a web page as a PDF document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_url("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – the URL of the target web page

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_images(files: Sequence[str | Path | bytes] | str | Path | bytes, **kwargs) List[ndarray][source]
    -

    Read an image file (or a collection of image files) and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"])
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    files – the path to the image file or a binary stream, or a collection of those

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    - -
    -
    -class doctr.documents.PDF(doc: Document)[source]
    -

    PDF document template

    -
    -
    Parameters:
    -

    doc – input PDF document

    -
    -
    -
    -
    -as_images(**kwargs) List[ndarray][source]
    -

    Convert all document pages to images

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of convert_page_to_numpy

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -get_words(**kwargs) List[List[Tuple[Tuple[float, float, float, float], str]]][source]
    -

    Get the annotations for all words in the document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of fitz.Page.getTextWords

    -
    -
    Returns:
    -

    the list of pages annotations, represented as a list of tuple (bounding box, value)

    -
    -
    -
    - -
    -
    -get_artefacts() List[List[Tuple[float, float, float, float]]][source]
    -

    Get the artefacts for the entire document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts()
    -
    -
    -
    -
    -
    -
    Returns:
    -

    the list of pages artefacts, represented as a list of bounding boxes

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.1/genindex.html b/v0.8.1/genindex.html index ac2652d193..985d8bd94d 100644 --- a/v0.8.1/genindex.html +++ b/v0.8.1/genindex.html @@ -13,7 +13,7 @@ - Index - docTR documentation + Index - docTR documentation @@ -746,7 +746,7 @@

    W

    - + diff --git a/v0.8.1/getting_started/installing.html b/v0.8.1/getting_started/installing.html index a3422df41a..523060e132 100644 --- a/v0.8.1/getting_started/installing.html +++ b/v0.8.1/getting_started/installing.html @@ -14,7 +14,7 @@ - + Installation - docTR documentation @@ -431,7 +431,7 @@

    Via Git - + diff --git a/v0.8.1/index.html b/v0.8.1/index.html index cde19babe1..ef6d129db6 100644 --- a/v0.8.1/index.html +++ b/v0.8.1/index.html @@ -14,7 +14,7 @@ - + docTR documentation @@ -437,7 +437,7 @@

    Supported datasets - + diff --git a/v0.8.1/installing.html b/v0.8.1/installing.html deleted file mode 100644 index b61c60134b..0000000000 --- a/v0.8.1/installing.html +++ /dev/null @@ -1,395 +0,0 @@ - - - - - - - - - - - - - Installation - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    Installation

    -

    This library requires Python 3.6 or higher.

    -
    -

    Prerequisites

    -

    Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so:

    - -

    If you are running another OS than Linux, you will need a few extra dependencies.

    -

    For MacOS users, you can install them as follows:

    -
    brew install cairo pango gdk-pixbuf libffi
    -
    -
    -

    For Windows users, those dependencies are included in GTK. You can find the latest installer over here.

    -
    -
    -

    Via Python Package

    -

    Install the last stable release of the package using pip:

    -
    pip install python-doctr
    -
    -
    -
    -
    -

    Via Git

    -

    Install the library in developper mode:

    -
    git clone https://github.com/mindee/doctr.git
    -pip install -e doctr/.
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.1/models.html b/v0.8.1/models.html deleted file mode 100644 index b5cd44c9fa..0000000000 --- a/v0.8.1/models.html +++ /dev/null @@ -1,1002 +0,0 @@ - - - - - - - - - - - - - doctr.models - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.models

    -

    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 TensorFlow model.

    • -
    • Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable.

    • -
    -
    -

    Text Detection

    -

    Localizing text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Input shape

    # params

    Recall

    Precision

    Recall

    Precision

    FPS

    db_resnet50

    (1024, 1024, 3)

    25.2 M

    82.14

    87.64

    92.49

    89.66

    2.1

    -
    -

    All text detection models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for detection

    -

    In DocTR, the pre-processing scheme for detection is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) with potential deformation.

    2. -
    3. batch images together

    4. -
    5. normalize the batch using the training data statistics

    6. -
    -
    -
    -

    Detection models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.detection.db_resnet50(pretrained: bool = False, **kwargs: Any) DBNet[source]
    -

    DBNet as described in “Real-time Scene Text Detection with Differentiable Binarization”, using a ResNet-50 backbone.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -doctr.models.detection.linknet16(pretrained: bool = False, **kwargs: Any) LinkNet[source]
    -

    LinkNet as described in “LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import linknet16
    ->>> model = linknet16(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -

    Detection predictors

    -

    Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information.

    -
    -
    -doctr.models.detection.detection_predictor(arch: str = 'db_resnet50', pretrained: bool = False, **kwargs: Any) DetectionPredictor[source]
    -

    Text detection architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import detection_predictor
    ->>> model = detection_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_resnet50’)

    • -
    • pretrained – If True, returns a model pre-trained on our text detection dataset

    • -
    -
    -
    Returns:
    -

    Detection predictor

    -
    -
    -
    - -
    -
    -
    -

    Text Recognition

    -

    Identifying strings in images

    -
    - - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Text recognition model zoo

    Architecture

    Input shape

    # params

    FUNSD

    CORD

    FPS

    crnn_vgg16_bn

    (32, 128, 3)

    15.8M

    86.02

    91.3

    12.8

    sar_vgg16_bn

    (32, 128, 3)

    21.5M

    86.2

    91.7

    3.3

    sar_resnet31

    (32, 128, 3)

    53.1M

    86.3

    92.1

    2.7

    -
    -

    All text recognition models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All these recognition models are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for recognition

    -

    In DocTR, the pre-processing scheme for recognition is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) without deformation.

    2. -
    3. pad the image to the target size (with zeros by default)

    4. -
    5. batch images together

    6. -
    7. normalize the batch using the training data statistics

    8. -
    -
    -
    -

    Recognition models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.recognition.crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) CRNN[source]
    -

    CRNN with a VGG-16 backbone as described in “An End-to-End Trainable Neural Network for Image-based -Sequence Recognition and Its Application to Scene Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import crnn_vgg16_bn
    ->>> model = crnn_vgg16_bn(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a VGG16 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_vgg16_bn
    ->>> model = sar_vgg16_bn(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_resnet31(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a resnet-31 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -

    Example

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_resnet31
    ->>> model = sar_resnet31(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.master(pretrained: bool = False, **kwargs: Any) MASTER[source]
    -

    MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_. -Example:

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import master
    ->>> model = master(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 48, 160, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -

    Recognition predictors

    -

    Combining the right components around a given architecture for easier usage.

    -
    -
    -doctr.models.recognition.recognition_predictor(arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) RecognitionPredictor[source]
    -

    Text recognition architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import recognition_predictor
    ->>> model = recognition_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(32, 128, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘crnn_vgg16_bn’, ‘crnn_resnet31’, ‘sar_vgg16_bn’, ‘sar_resnet31’)

    • -
    • pretrained – If True, returns a model pre-trained on our text recognition dataset

    • -
    -
    -
    Returns:
    -

    Recognition predictor

    -
    -
    -
    - -
    -
    -
    -

    End-to-End OCR

    -

    Predictors that localize and identify text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Recall

    Precision

    FPS

    Recall

    Precision

    FPS

    db_resnet50 + crnn_vgg16_bn

    70.08

    74.77

    0.85

    82.19

    79.67

    1.6

    db_resnet50 + sar_vgg16_bn

    N/A

    N/A

    0.49

    N/A

    N/A

    1.0

    db_resnet50 + sar_resnet31

    N/A

    N/A

    0.27

    N/A

    N/A

    0.83

    Gvision text detection

    59.50

    62.50

    75.30

    70.00

    Gvision doc. text detection

    64.00

    53.30

    68.90

    61.10

    AWS textract

    78.10

    83.00

    87.50

    66.00

    -
    -

    All OCR models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All recognition models of predictors are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -

    Results on private ocr datasets

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Receipts

    Invoices

    IDs

    Architecture

    Recall

    Precision

    Recall

    Precision

    Recall

    Precision

    db_resnet50 + crnn_vgg16_bn (ours)

    78.90

    81.01

    65.68

    69.86

    49.48

    50.46

    Gvision doc. text detection

    68.91

    59.89

    63.20

    52.85

    43.70

    29.21

    AWS textract

    75.77

    77.70

    70.47

    69.13

    46.39

    43.32

    -
    -
    -

    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.

    -
    -
    -doctr.models.zoo.ocr_predictor(det_arch: str = 'db_resnet50', reco_arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) OCRPredictor[source]
    -

    End-to-end OCR architecture using one model for localization, and another for text recognition.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import ocr_predictor
    ->>> model = ocr_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_sar_vgg’, ‘db_sar_resnet’, ‘db_crnn_vgg’, ‘db_crnn_resnet’)

    • -
    • pretrained – If True, returns a model pre-trained on our OCR dataset

    • -
    -
    -
    Returns:
    -

    OCR predictor

    -
    -
    -
    - -
    -
    -
    -

    Model export

    -

    Utility functions to make the most of document analysis models.

    -
    -

    Model compression

    -
    -
    -doctr.models.export.convert_to_tflite(tf_model: Model) bytes[source]
    -

    Converts a model to TFLite format

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_tflite, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_tflite(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.convert_to_fp16(tf_model: Model) bytes[source]
    -

    Converts a model to half precision

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_fp16, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_fp16(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the serialized FP16 model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) bytes[source]
    -

    Quantize a Tensorflow model

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import quantize_model, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = quantize_model(model, (224, 224, 3))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • tf_model – a keras model

    • -
    • input_shape – shape of the expected input tensor (excluding batch dimension) with channel last order

    • -
    -
    -
    Returns:
    -

    the serialized quantized model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -

    Using SavedModel

    -

    Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -SavedModel format as follows:

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> _ = model(input_t, training=False)
    ->>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/')
    -
    -
    -

    And loaded just as easily:

    -
    >>> import tensorflow as tf
    ->>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/')
    -
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.1/modules/contrib.html b/v0.8.1/modules/contrib.html index e99f6b3f74..7fb86b8b38 100644 --- a/v0.8.1/modules/contrib.html +++ b/v0.8.1/modules/contrib.html @@ -14,7 +14,7 @@ - + doctr.contrib - docTR documentation @@ -380,7 +380,7 @@

    Supported contribution modules - + diff --git a/v0.8.1/modules/datasets.html b/v0.8.1/modules/datasets.html index 618fc1971e..409c9ea616 100644 --- a/v0.8.1/modules/datasets.html +++ b/v0.8.1/modules/datasets.html @@ -14,7 +14,7 @@ - + doctr.datasets - docTR documentation @@ -1049,7 +1049,7 @@

    Returns: - + diff --git a/v0.8.1/modules/io.html b/v0.8.1/modules/io.html index 6ac8de4585..76c06b4207 100644 --- a/v0.8.1/modules/io.html +++ b/v0.8.1/modules/io.html @@ -14,7 +14,7 @@ - + doctr.io - docTR documentation @@ -752,7 +752,7 @@

    Returns: - + diff --git a/v0.8.1/modules/models.html b/v0.8.1/modules/models.html index 8529b1e8f6..5678645a16 100644 --- a/v0.8.1/modules/models.html +++ b/v0.8.1/modules/models.html @@ -14,7 +14,7 @@ - + doctr.models - docTR documentation @@ -1540,7 +1540,7 @@

    Args: - + diff --git a/v0.8.1/modules/transforms.html b/v0.8.1/modules/transforms.html index e4eb8ee1fa..cf547592b1 100644 --- a/v0.8.1/modules/transforms.html +++ b/v0.8.1/modules/transforms.html @@ -14,7 +14,7 @@ - + doctr.transforms - docTR documentation @@ -804,7 +804,7 @@

    Args:< - + diff --git a/v0.8.1/modules/utils.html b/v0.8.1/modules/utils.html index 06c83105f5..adf1487e5b 100644 --- a/v0.8.1/modules/utils.html +++ b/v0.8.1/modules/utils.html @@ -14,7 +14,7 @@ - + doctr.utils - docTR documentation @@ -737,7 +737,7 @@

    Args:< - + diff --git a/v0.8.1/notebooks.html b/v0.8.1/notebooks.html index 7af9bc55b4..5b76c79ab2 100644 --- a/v0.8.1/notebooks.html +++ b/v0.8.1/notebooks.html @@ -14,7 +14,7 @@ - + docTR Notebooks - docTR documentation @@ -379,7 +379,7 @@

    docTR Notebooks - + diff --git a/v0.8.1/py-modindex.html b/v0.8.1/py-modindex.html deleted file mode 100644 index c1569be607..0000000000 --- a/v0.8.1/py-modindex.html +++ /dev/null @@ -1,330 +0,0 @@ - - - - - - - - - - - Python Module Index - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    - -
    -

    Python Module Index

    - -
    - - - - - - - - - - - -
     
    d
    - doctr -
    - -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.8.1/search.html b/v0.8.1/search.html index 2fe88c7d93..1dd2b30845 100644 --- a/v0.8.1/search.html +++ b/v0.8.1/search.html @@ -14,7 +14,7 @@ - + Search - docTR documentation @@ -334,7 +334,7 @@ - + diff --git a/v0.8.1/searchindex.js b/v0.8.1/searchindex.js index 4eb7904e61..c1e9808759 100644 --- a/v0.8.1/searchindex.js +++ b/v0.8.1/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles": {"1. Correction": [[1, "correction"]], "2. Warning": [[1, "warning"]], "3. Temporary Ban": [[1, "temporary-ban"]], "4. Permanent Ban": [[1, "permanent-ban"]], "AWS Lambda": [[12, null]], "Advanced options": [[16, "advanced-options"]], "Args:": [[5, "args"], [5, "id4"], [5, "id7"], [5, "id10"], [5, "id13"], [5, "id16"], [5, "id19"], [5, "id22"], [5, "id25"], [5, "id29"], [5, "id32"], [5, "id37"], [5, "id40"], [5, "id46"], [5, "id49"], [5, "id50"], [5, "id51"], [5, "id54"], [5, "id57"], [5, "id60"], [5, "id61"], [6, "args"], [6, "id2"], [6, "id3"], [6, "id4"], [6, "id5"], [6, "id6"], [6, "id7"], [6, "id10"], [6, "id12"], [6, "id14"], [6, "id16"], [6, "id20"], [6, "id24"], [6, "id28"], [7, "args"], [7, "id3"], [7, "id8"], [7, "id13"], [7, "id17"], [7, "id21"], [7, "id26"], [7, "id31"], [7, "id36"], [7, "id41"], [7, "id45"], [7, "id49"], [7, "id54"], [7, "id58"], [7, "id63"], [7, "id68"], [7, "id72"], [7, "id76"], [7, "id81"], [7, "id86"], [7, "id90"], [7, "id95"], [7, "id100"], [7, "id105"], [7, "id110"], [7, "id114"], [7, "id118"], [7, "id123"], [7, "id128"], [7, "id133"], [7, "id137"], [7, "id141"], [7, "id146"], [7, "id150"], [7, "id154"], [7, "id158"], [7, "id160"], [7, "id162"], [7, "id164"], [8, "args"], [8, "id1"], [8, "id2"], [8, "id3"], [8, "id4"], [8, "id5"], [8, "id6"], [8, "id7"], [8, "id8"], [8, "id9"], [8, "id10"], [8, "id11"], [8, "id12"], [8, "id13"], [8, "id14"], [8, "id15"], [8, "id16"], [8, "id17"], [8, "id18"], [9, "args"], [9, "id3"], [9, "id5"], [9, "id6"], [9, "id7"], [9, "id8"], [9, "id9"], [9, "id10"], [9, "id11"]], "Artefact": [[6, "artefact"]], "Attribution": [[1, "attribution"]], "Available Datasets": [[14, "available-datasets"]], "Available architectures": [[16, "available-architectures"], [16, "id1"], [16, "id2"]], "Block": [[6, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[14, null]], "Choosing the right model": [[16, null]], "Classification": [[13, "classification"]], "Code quality": [[2, "code-quality"]], "Code style verification": [[2, "code-style-verification"]], "Codebase structure": [[2, "codebase-structure"]], "Commits": [[2, "commits"]], "Composing transformations": [[8, "composing-transformations"]], "Continuous Integration": [[2, "continuous-integration"]], "Contributing to docTR": [[2, null]], "Contributor Covenant Code of Conduct": [[1, null]], "Custom dataset loader": [[5, "custom-dataset-loader"]], "Data Loading": [[14, "data-loading"]], "Dataloader": [[5, "dataloader"]], "Detection": [[13, "detection"], [14, "detection"]], "Detection predictors": [[16, "detection-predictors"]], "Developer mode installation": [[2, "developer-mode-installation"]], "Developing docTR": [[2, "developing-doctr"]], "Document": [[6, "document"]], "Document structure": [[6, "document-structure"]], "End-to-End OCR": [[16, "end-to-end-ocr"]], "Enforcement": [[1, "enforcement"]], "Enforcement Guidelines": [[1, "enforcement-guidelines"]], "Enforcement Responsibilities": [[1, "enforcement-responsibilities"]], "Export to ONNX": [[15, "export-to-onnx"]], "Feature requests & bug report": [[2, "feature-requests-bug-report"]], "Feedback": [[2, "feedback"]], "File reading": [[6, "file-reading"]], "Half-precision": [[15, "half-precision"]], "Installation": [[3, null]], "Let\u2019s connect": [[2, "let-s-connect"]], "Line": [[6, "line"]], "Loading from Huggingface Hub": [[13, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[11, "loading-your-custom-trained-model"]], "Main Features": [[4, "main-features"]], "Model optimization": [[15, "model-optimization"]], "Model zoo": [[4, "model-zoo"]], "Modifying the documentation": [[2, "modifying-the-documentation"]], "Naming conventions": [[13, "naming-conventions"]], "Object Detection": [[14, "object-detection"]], "Our Pledge": [[1, "our-pledge"]], "Our Standards": [[1, "our-standards"]], "Page": [[6, "page"]], "Preparing your model for inference": [[15, null]], "Prerequisites": [[3, "prerequisites"]], "Pretrained community models": [[13, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[13, "pushing-to-the-huggingface-hub"]], "Questions": [[2, "questions"]], "Recognition": [[13, "recognition"], [14, "recognition"]], "Recognition predictors": [[16, "recognition-predictors"]], "Returns:": [[5, "returns"], [6, "returns"], [6, "id11"], [6, "id13"], [6, "id15"], [6, "id19"], [6, "id23"], [6, "id27"], [6, "id31"], [7, "returns"], [7, "id6"], [7, "id11"], [7, "id16"], [7, "id20"], [7, "id24"], [7, "id29"], [7, "id34"], [7, "id39"], [7, "id44"], [7, "id48"], [7, "id52"], [7, "id57"], [7, "id61"], [7, "id66"], [7, "id71"], [7, "id75"], [7, "id79"], [7, "id84"], [7, "id89"], [7, "id93"], [7, "id98"], [7, "id103"], [7, "id108"], [7, "id113"], [7, "id117"], [7, "id121"], [7, "id126"], [7, "id131"], [7, "id136"], [7, "id140"], [7, "id144"], [7, "id149"], [7, "id153"], [7, "id157"], [7, "id159"], [7, "id161"], [7, "id163"], [9, "returns"], [9, "id4"]], "Scope": [[1, "scope"]], "Share your model with the community": [[13, null]], "Supported Vocabs": [[5, "supported-vocabs"]], "Supported datasets": [[4, "supported-datasets"]], "Supported transformations": [[8, "supported-transformations"]], "Synthetic dataset generator": [[5, "synthetic-dataset-generator"], [14, "synthetic-dataset-generator"]], "Task evaluation": [[9, "task-evaluation"]], "Text Detection": [[16, "text-detection"]], "Text Recognition": [[16, "text-recognition"]], "Text detection models": [[4, "text-detection-models"]], "Text recognition models": [[4, "text-recognition-models"]], "Train your own model": [[11, null]], "Two-stage approaches": [[16, "two-stage-approaches"]], "Unit tests": [[2, "unit-tests"]], "Use your own datasets": [[14, "use-your-own-datasets"]], "Using your ONNX exported model in docTR": [[15, "using-your-onnx-exported-model-in-doctr"]], "Via Conda (Only for Linux)": [[3, "via-conda-only-for-linux"]], "Via Git": [[3, "via-git"]], "Via Python Package": [[3, "via-python-package"]], "Visualization": [[9, "visualization"]], "What should I do with the output?": [[16, "what-should-i-do-with-the-output"]], "Word": [[6, "word"]], "docTR Notebooks": [[10, null]], "docTR Vocabs": [[5, "id62"]], "docTR: Document Text Recognition": [[4, null]], "doctr.datasets": [[5, null], [5, "datasets"]], "doctr.io": [[6, null]], "doctr.models": [[7, null]], "doctr.models.classification": [[7, "doctr-models-classification"]], "doctr.models.detection": [[7, "doctr-models-detection"]], "doctr.models.factory": [[7, "doctr-models-factory"]], "doctr.models.recognition": [[7, "doctr-models-recognition"]], "doctr.models.zoo": [[7, "doctr-models-zoo"]], "doctr.transforms": [[8, null]], "doctr.utils": [[9, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]], "v0.7.0 (2023-09-09)": [[0, "v0-7-0-2023-09-09"]], "v0.8.0 (2024-02-28)": [[0, "v0-8-0-2024-02-28"]]}, "docnames": ["changelog", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[6, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[6, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[8, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[5, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[8, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[8, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[5, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[7, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[5, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[7, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[7, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[6, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[7, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[5, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[5, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[6, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[6, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[5, "doctr.datasets.encode_sequences", false]], "fast_base() (in module doctr.models.detection)": [[7, "doctr.models.detection.fast_base", false]], "fast_small() (in module doctr.models.detection)": [[7, "doctr.models.detection.fast_small", false]], "fast_tiny() (in module doctr.models.detection)": [[7, "doctr.models.detection.fast_tiny", false]], "from_hub() (in module doctr.models.factory)": [[7, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[6, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[6, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[6, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[5, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[8, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[8, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[5, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[5, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[5, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[5, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[5, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[7, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[8, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[6, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[7, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet34() (in module doctr.models.detection)": [[7, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[7, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[7, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[7, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[5, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_orientation() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_small_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[8, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[7, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[5, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[8, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[6, "doctr.io.Page", false]], "parseq() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[7, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[8, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[8, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[8, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[8, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[8, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[8, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[8, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[8, "doctr.transforms.RandomJpegQuality", false]], "randomrotate (class in doctr.transforms)": [[8, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[8, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[8, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[6, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[6, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[6, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[6, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[5, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[8, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[6, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[6, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[5, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[9, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[9, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[9, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[9, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[5, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[5, "doctr.datasets.SVT", false]], "synthesize_page() (in module doctr.utils.visualization)": [[9, "doctr.utils.visualization.synthesize_page", false]], "synthtext (class in doctr.datasets)": [[5, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.TextMatch", false]], "textnet_base() (in module doctr.models.classification)": [[7, "doctr.models.classification.textnet_base", false]], "textnet_small() (in module doctr.models.classification)": [[7, "doctr.models.classification.textnet_small", false]], "textnet_tiny() (in module doctr.models.classification)": [[7, "doctr.models.classification.textnet_tiny", false]], "togray (class in doctr.transforms)": [[8, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[9, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[9, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[9, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[9, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[7, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[9, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[7, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[7, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.vitstr_small", false]], "wildreceipt (class in doctr.datasets)": [[5, "doctr.datasets.WILDRECEIPT", false]], "word (class in doctr.io)": [[6, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[5, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[5, 0, 1, "", "CORD"], [5, 0, 1, "", "CharacterGenerator"], [5, 0, 1, "", "DetectionDataset"], [5, 0, 1, "", "DocArtefacts"], [5, 0, 1, "", "FUNSD"], [5, 0, 1, "", "IC03"], [5, 0, 1, "", "IC13"], [5, 0, 1, "", "IIIT5K"], [5, 0, 1, "", "IIITHWS"], [5, 0, 1, "", "IMGUR5K"], [5, 0, 1, "", "MJSynth"], [5, 0, 1, "", "OCRDataset"], [5, 0, 1, "", "RecognitionDataset"], [5, 0, 1, "", "SROIE"], [5, 0, 1, "", "SVHN"], [5, 0, 1, "", "SVT"], [5, 0, 1, "", "SynthText"], [5, 0, 1, "", "WILDRECEIPT"], [5, 0, 1, "", "WordGenerator"], [5, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[5, 0, 1, "", "DataLoader"]], "doctr.io": [[6, 0, 1, "", "Artefact"], [6, 0, 1, "", "Block"], [6, 0, 1, "", "Document"], [6, 0, 1, "", "DocumentFile"], [6, 0, 1, "", "Line"], [6, 0, 1, "", "Page"], [6, 0, 1, "", "Word"], [6, 1, 1, "", "decode_img_as_tensor"], [6, 1, 1, "", "read_html"], [6, 1, 1, "", "read_img_as_numpy"], [6, 1, 1, "", "read_img_as_tensor"], [6, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[6, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[6, 2, 1, "", "from_images"], [6, 2, 1, "", "from_pdf"], [6, 2, 1, "", "from_url"]], "doctr.io.Page": [[6, 2, 1, "", "show"]], "doctr.models": [[7, 1, 1, "", "kie_predictor"], [7, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[7, 1, 1, "", "crop_orientation_predictor"], [7, 1, 1, "", "magc_resnet31"], [7, 1, 1, "", "mobilenet_v3_large"], [7, 1, 1, "", "mobilenet_v3_large_r"], [7, 1, 1, "", "mobilenet_v3_small"], [7, 1, 1, "", "mobilenet_v3_small_orientation"], [7, 1, 1, "", "mobilenet_v3_small_r"], [7, 1, 1, "", "resnet18"], [7, 1, 1, "", "resnet31"], [7, 1, 1, "", "resnet34"], [7, 1, 1, "", "resnet50"], [7, 1, 1, "", "textnet_base"], [7, 1, 1, "", "textnet_small"], [7, 1, 1, "", "textnet_tiny"], [7, 1, 1, "", "vgg16_bn_r"], [7, 1, 1, "", "vit_b"], [7, 1, 1, "", "vit_s"]], "doctr.models.detection": [[7, 1, 1, "", "db_mobilenet_v3_large"], [7, 1, 1, "", "db_resnet50"], [7, 1, 1, "", "detection_predictor"], [7, 1, 1, "", "fast_base"], [7, 1, 1, "", "fast_small"], [7, 1, 1, "", "fast_tiny"], [7, 1, 1, "", "linknet_resnet18"], [7, 1, 1, "", "linknet_resnet34"], [7, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[7, 1, 1, "", "from_hub"], [7, 1, 1, "", "login_to_hub"], [7, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[7, 1, 1, "", "crnn_mobilenet_v3_large"], [7, 1, 1, "", "crnn_mobilenet_v3_small"], [7, 1, 1, "", "crnn_vgg16_bn"], [7, 1, 1, "", "master"], [7, 1, 1, "", "parseq"], [7, 1, 1, "", "recognition_predictor"], [7, 1, 1, "", "sar_resnet31"], [7, 1, 1, "", "vitstr_base"], [7, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[8, 0, 1, "", "ChannelShuffle"], [8, 0, 1, "", "ColorInversion"], [8, 0, 1, "", "Compose"], [8, 0, 1, "", "GaussianBlur"], [8, 0, 1, "", "GaussianNoise"], [8, 0, 1, "", "LambdaTransformation"], [8, 0, 1, "", "Normalize"], [8, 0, 1, "", "OneOf"], [8, 0, 1, "", "RandomApply"], [8, 0, 1, "", "RandomBrightness"], [8, 0, 1, "", "RandomContrast"], [8, 0, 1, "", "RandomCrop"], [8, 0, 1, "", "RandomGamma"], [8, 0, 1, "", "RandomHorizontalFlip"], [8, 0, 1, "", "RandomHue"], [8, 0, 1, "", "RandomJpegQuality"], [8, 0, 1, "", "RandomRotate"], [8, 0, 1, "", "RandomSaturation"], [8, 0, 1, "", "RandomShadow"], [8, 0, 1, "", "Resize"], [8, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[9, 0, 1, "", "DetectionMetric"], [9, 0, 1, "", "LocalizationConfusion"], [9, 0, 1, "", "OCRMetric"], [9, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.visualization": [[9, 1, 1, "", "synthesize_page"], [9, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [1, 6, 7, 9, 13], "0": [1, 3, 5, 8, 9, 11, 14, 16], "00": 16, "01": 16, "0123456789": 5, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 5, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 5, "02": [], "02562": 7, "03": 16, "035": 16, "0361328125": 16, "04": [], "05": 16, "06": 16, "06640625": 16, "07": 16, "08": [8, 16], "09": 16, "0966796875": 16, "1": [3, 5, 6, 7, 8, 9, 11, 14, 16], "10": [5, 9, 16], "100": [5, 8, 9, 14, 16], "1000": 16, "101": 5, "1024": [7, 9, 11, 16], "104": 5, "106": 5, "108": 5, "1095": 14, "11": 16, "110": 9, "1107": 14, "114": 5, "115": [], "1156": 14, "116": 5, "118": 5, "11800h": 16, "11th": 16, "12": [3, 16], "120": 5, "123": 5, "126": 5, "1268": 14, "128": [7, 11, 15, 16], "13": [9, 16], "130": 5, "13068": 14, "131": 5, "1337891": 14, "1357421875": 16, "1396484375": 16, "14": 16, "1420": 16, "14470v1": 5, "149": 14, "15": 16, "150": [9, 16], "154": [], "1552": 16, "16": [7, 15, 16], "160": [], "1630859375": 16, "1684": 16, "16x16": 7, "17": 16, "1778": 16, "1782": 16, "18": 7, "185546875": 16, "19": [], "1900": 16, "1910": 7, "19342": 14, "19370": 14, "195": 5, "19598": 14, "199": 16, "1999": 16, "1m": [], "2": [3, 4, 5, 6, 8, 16], "20": 16, "200": 9, "2000": 14, "2003": [4, 5], "2012": 5, "2013": [4, 5], "2015": 5, "2019": 4, "2021": [], "2023": [], "207901": 14, "21": 16, "2103": 5, "2186": 14, "21888": 14, "22": 16, "224": [7, 8], "225": 8, "22672": 14, "229": [8, 14], "23": 16, "233": 14, "234": 5, "236": [], "24": 16, "246": 14, "249": 14, "25": 16, "2504": 16, "255": [6, 7, 8, 9, 16], "256": 7, "257": 14, "26": 16, "26032": 14, "264": 11, "27": 16, "2700": 14, "2710": 16, "2749": 11, "28": 16, "287": 11, "29": 16, "296": 11, "299": 11, "2d": 16, "3": [3, 4, 6, 7, 8, 9, 15, 16], "30": 16, "300": 14, "3000": 14, "301": 11, "30595": 16, "30ghz": 16, "31": 7, "32": [5, 7, 8, 11, 14, 15, 16], "3232421875": 16, "33": [8, 16], "33402": 14, "33608": 14, "34": [7, 16], "340": 16, "3456": 16, "35": [], "3515625": 16, "36": [], "360": 14, "37": [5, 16], "38": 16, "39": 16, "4": [7, 8, 9, 16], "40": 16, "406": 8, "41": 16, "42": 16, "43": 16, "44": 16, "45": 16, "456": 8, "46": 16, "47": 16, "472": 14, "48": [5, 16], "485": 8, "49": 16, "49377": 14, "5": [5, 8, 9, 16], "50": [7, 14, 16], "51": 16, "51171875": 16, "512": 7, "52": [5, 16], "529": 16, "53": 16, "533": [], "54": 16, "540": 16, "5478515625": 16, "55": 16, "56": 16, "57": 16, "58": 16, "580": 16, "5810546875": 16, "583": 16, "59": 16, "595": [], "597": 16, "5k": [4, 5], "5m": 16, "6": [8, 16], "60": 8, "600": [7, 9, 16], "61": 16, "611": [], "62": 16, "625": [], "626": 14, "629": [], "63": 16, "630": [], "64": [7, 8, 16], "640": [], "641": 16, "647": 14, "65": 16, "66": 16, "660": [], "664": [], "666": [], "67": 16, "672": [], "68": 16, "689": [], "69": 16, "693": 11, "694": 11, "695": 11, "6m": 16, "7": 16, "70": [9, 16], "700": [], "701": [], "702": [], "707470": 14, "71": 16, "7100000": 14, "713": [], "7141797": 14, "7149": 14, "72": 16, "72dpi": 6, "73": 16, "73257": 14, "733": [], "74": 16, "745": [], "75": [8, 16], "753": [], "7581382": 14, "76": 16, "77": 16, "772": 11, "772875": 14, "78": 16, "780": [], "781": [], "783": [], "785": 11, "789": [], "79": 16, "793533": 14, "796": 14, "798": 11, "7m": 16, "8": [3, 7, 8, 16], "80": 16, "800": [7, 9, 14, 16], "81": 16, "817": [], "82": 16, "8275l": [], "83": 16, "830": [], "84": 16, "849": 14, "85": 16, "8564453125": 16, "857": 16, "85875": 14, "86": 16, "860": [], "8603515625": 16, "862": [], "863": [], "87": 16, "8707": 14, "875": [], "88": 16, "89": 16, "8m": [], "9": 16, "90": 16, "90k": 5, "90kdict32px": 5, "91": 16, "913": [], "914085328578949": 16, "917": [], "92": 16, "921": [], "93": 16, "94": [5, 16], "95": [9, 16], "9578408598899841": 16, "96": 16, "97": [], "98": 16, "99": 16, "9949972033500671": 16, "A": [1, 2, 4, 5, 6, 7, 10, 15], "And": [], "As": 2, "Be": 16, "Being": 1, "By": 12, "For": [1, 2, 3, 11, 16], "If": [2, 3, 6, 7, 11, 16], "In": [2, 5, 14], "It": [8, 13, 15], "Its": [4, 7], "No": [1, 16], "Of": 5, "Or": [], "The": [1, 2, 5, 6, 9, 12, 16], "Then": 7, "To": [2, 3, 12, 13, 16], "_": [1, 5, 7], "__call__": 16, "_build": 2, "_i": 9, "ab": 5, "abc": [], "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 5, "abdef": [5, 14], "abl": [14, 16], "about": [1, 14, 16], "abov": 16, "abstract": [], "abstractdataset": 5, "abus": 1, "accent": [], "accept": 1, "access": [4, 6, 14, 16], "account": [1, 13], "accur": 16, "accuraci": 9, "achiev": 15, "act": 1, "action": 1, "activ": 4, "ad": [2, 7, 8], "adapt": 1, "add": [8, 9, 13, 16], "add_hook": 16, "add_label": 9, "addit": [2, 3, 6], "addition": [2, 16], "address": [1, 6], "adjust": 8, "advanc": 1, "advantag": 15, "advis": 2, "aesthet": [4, 5], "affect": 1, "after": [13, 16], "ag": 1, "again": 7, "aggreg": [9, 14], "aggress": 1, "align": [1, 6], "all": [1, 2, 5, 6, 8, 9, 14, 16], "allow": 1, "along": 16, "alreadi": 2, "also": [1, 7, 13, 14, 16], "alwai": 14, "an": [1, 2, 4, 5, 6, 7, 9, 15, 16], "analysi": 6, "ancient_greek": 5, "andrej": [], "angl": [6, 8], "ani": [1, 5, 6, 7, 8, 9, 16], "annot": 5, "anot": 14, "anoth": [3, 7, 11, 14], "answer": 1, "anyascii": [], "anyon": 4, "anyth": [], "api": [2, 4], "apolog": 1, "apologi": 1, "app": 2, "appear": 1, "appli": [1, 5, 8], "applic": [4, 7], "appoint": 1, "appreci": 13, "appropri": [1, 2, 16], "ar": [1, 2, 3, 5, 6, 8, 9, 10, 14, 16], "arab": 5, "arabic_diacrit": 5, "arabic_lett": 5, "arabic_punctu": 5, "arbitrarili": [4, 7], "arch": [7, 13], "architectur": [4, 7, 13], "archiv": [], "area": 16, "arg": [], "argument": [5, 6, 7, 9, 16], "around": 1, "arrai": [6, 8, 9], "art": 4, "artefact": [9, 10, 16], "artefact_typ": 6, "articl": [], "artifici": [4, 5], "arxiv": [5, 7], "as_imag": [], "asarrai": 9, "ascii_lett": 5, "aspect": [4, 7, 8, 16], "assess": 9, "assign": 9, "associ": 6, "assum": 7, "assume_straight_pag": [7, 16], "astyp": [7, 9, 16], "attack": 1, "attend": [4, 7], "attent": [1, 7], "autoclass": [], "autom": 4, "automat": 16, "autoregress": [4, 7], "avail": [1, 4, 8], "averag": [8, 16], "avoid": [1, 3], "aw": [4, 16], "awar": 16, "azur": 16, "b": [7, 9, 16], "b_j": 9, "back": 2, "backbon": 7, "backend": 16, "background": 14, "bangla": [], "bar": [], "bar_cod": 14, "baranovskij": [], "base": [4, 7], "baselin": [4, 7, 16], "batch": [5, 7, 8, 14, 16], "batch_siz": [5, 11, 14, 15], "bblanchon": 3, "bbox": 16, "becaus": 12, "been": [2, 9, 14, 16], "befor": [5, 7, 8, 16], "begin": 9, "behavior": [1, 16], "being": [9, 16], "belong": 16, "benchmark": 16, "best": 1, "beta": [], "better": [10, 16], "between": [8, 9, 16], "bgr": 6, "bilinear": 8, "bin_thresh": 16, "binar": [4, 7, 16], "binari": [6, 15, 16], "bit": 15, "blank": 9, "block": [9, 16], "block_1_1": 16, "blue": 9, "blur": 8, "bmvc": 5, "bn": 13, "bodi": [1, 16], "bool": [5, 6, 7, 8, 9], "boolean": [7, 16], "both": [4, 5, 8, 14, 16], "bottom": [7, 16], "bound": [5, 6, 7, 8, 9, 16], "box": [5, 6, 7, 8, 9, 14, 16], "box_thresh": 16, "brew": 3, "bright": 8, "broadcast": 9, "browser": [2, 4], "build": [2, 3], "built": 2, "byte": [6, 16], "c": [3, 6, 9], "c5": [], "c_j": 9, "cach": [2, 5, 12], "cache_sampl": 5, "cairo": 3, "call": [], "callabl": [5, 8], "can": [2, 3, 11, 12, 13, 14, 16], "capabl": [2, 10, 16], "case": [5, 9], "cf": 16, "cfg": 16, "challeng": 5, "challenge2_test_task12_imag": 5, "challenge2_test_task1_gt": 5, "challenge2_training_task12_imag": 5, "challenge2_training_task1_gt": 5, "chang": 12, "changelog": [], "channel": [1, 2, 6, 8], "channel_prior": 3, "channelshuffl": 8, "charact": [4, 5, 6, 9, 14, 16], "charactergener": [5, 14], "characterist": 1, "charg": 16, "charset": 16, "chart": 6, "check": [2, 13, 16], "checkpoint": 7, "chip": 3, "christian": [], "ci": 2, "clarifi": 1, "clariti": 1, "class": [1, 5, 6, 8, 9, 16], "class_nam": 11, "classif": 14, "classif_mobilenet_v3_smal": 7, "classmethod": 6, "clear": 2, "clone": 3, "close": 2, "co": 13, "code": [4, 6], "codecov": 2, "colab": 10, "collate_fn": 5, "collect": 6, "color": [8, 9], "colorinvers": 8, "column": 6, "com": [1, 3, 6, 7, 13], "combin": 16, "come": 15, "command": 2, "comment": 1, "commit": 1, "common": [1, 8, 9, 15], "commun": 1, "compar": 4, "comparison": [9, 16], "competit": 5, "compil": [10, 16], "complaint": 1, "complementari": 9, "complet": 2, "compon": 16, "compos": [5, 16], "comprehens": 16, "comput": [5, 9, 15, 16], "conf_threshold": [], "confid": [6, 9, 16], "config": [3, 7], "configur": 7, "confus": 9, "consecut": [8, 16], "consequ": 1, "consid": [1, 2, 5, 6, 9, 16], "consist": 16, "consolid": [4, 5], "constant": 8, "construct": 1, "consum": 9, "contact": 1, "contain": [5, 14], "content": [5, 6, 9, 16], "context": 7, "contib": [], "continu": 1, "contrast": 8, "contrast_factor": 8, "contrib": [], "contribut": 1, "contributor": 2, "conv_sequ": [], "convers": 6, "convert": [6, 8], "convert_page_to_numpi": [], "convert_to_fp16": [], "convert_to_tflit": [], "convolut": 7, "cool": [], "coordin": [6, 16], "cord": [4, 5, 14, 16], "core": [9, 16], "corner": 16, "correct": 8, "correspond": [3, 6, 16], "could": 1, "counterpart": 9, "cover": 2, "coverag": 2, "cpu": [4, 11], "creat": 13, "crnn": [4, 7, 13], "crnn_mobilenet_v3_larg": [7, 13, 16], "crnn_mobilenet_v3_smal": [7, 15, 16], "crnn_resnet31": [], "crnn_vgg16_bn": [7, 11, 13, 16], "crop": [7, 8, 14, 16], "crop_orient": [], "crop_orientation_predictor": 7, "crop_param": [], "croporientationpredictor": 7, "cuda": 15, "currenc": 5, "current": [2, 16], "custom": [13, 16], "custom_crop_orientation_model": [], "custom_page_orientation_model": [], "customhook": 16, "cvit": 4, "czczup": 7, "czech": 5, "d": [5, 14], "daili": [], "danish": 5, "data": [4, 5, 6, 8, 9, 11, 13], "dataload": 14, "dataset": [7, 11, 16], "dataset_info": 5, "date": [11, 16], "db": 13, "db_crnn_resnet": [], "db_crnn_vgg": [], "db_mobilenet_v3_larg": [7, 13, 16], "db_resnet34": 16, "db_resnet50": [7, 11, 13, 16], "db_resnet50_rot": [], "db_sar_resnet": [], "db_sar_vgg": [], "dbnet": [4, 7], "deal": [], "decis": 1, "decod": 6, "decode_img_as_tensor": 6, "dedic": [], "deem": 1, "deep": [7, 16], "def": 16, "default": [3, 6, 9, 11, 12, 16], "defer": 14, "defin": [9, 15], "deform": [], "degre": 8, "degress": 6, "delet": 2, "delimit": 16, "delta": 8, "demo": [2, 4], "demonstr": 1, "depend": [2, 3, 4], "deploi": 2, "deploy": 4, "derogatori": 1, "describ": [7, 9], "descript": 10, "design": 8, "desir": 6, "det_arch": [7, 11, 13, 15], "det_b": [], "det_model": [11, 13], "det_param": 11, "det_predictor": [11, 16], "detail": [11, 16], "detect": [5, 9, 10, 11], "detect_languag": 7, "detect_orient": 7, "detection_predictor": [7, 16], "detection_task": [], "detectiondataset": [5, 14], "detectionmetr": 9, "detectionpredictor": [7, 11], "detector": [4, 7], "deterior": 7, "determin": 1, "dev": [2, 12], "develop": 3, "developp": [], "deviat": 8, "devic": 15, "dict": [6, 9, 16], "dictionari": [6, 9], "differ": 1, "differenti": [4, 7], "digit": [4, 5, 14], "dimens": [6, 9, 16], "dimension": 8, "direct": 5, "directli": [13, 16], "directori": [2, 12], "disabl": [1, 12, 16], "disable_crop_orient": [], "disable_page_orient": [], "disclaim": 16, "discuss": 2, "disk": [], "disparag": 1, "displai": [6, 9], "display_artefact": 9, "distanc": [], "distribut": 8, "div": 16, "divers": 1, "divid": 6, "do": [2, 3, 7], "doc": [2, 6, 15, 16], "docartefact": [5, 14], "docstr": 2, "doctr": [3, 11, 12, 13, 14, 16], "doctr_cache_dir": 12, "doctr_multiprocessing_dis": 12, "document": [5, 7, 9, 10, 14, 16], "documentbuild": 16, "documentfil": [6, 13], "doesn": [], "don": [11, 16], "done": 8, "download": [5, 14], "downsiz": 7, "draw": [8, 9], "draw_proba": 9, "drop": 5, "drop_last": 5, "dtype": [6, 7, 8, 9, 15], "dual": [4, 5], "dummi": 13, "dummy_img": 16, "dummy_input": 15, "dure": 1, "dutch": 5, "dynam": 5, "dynamic_seq_length": 5, "e": [1, 2, 3, 6, 7], "each": [4, 5, 6, 7, 8, 9, 14, 16], "eas": 2, "easi": [4, 9, 13], "easier": [], "easili": [6, 9, 11, 13, 14, 16], "econom": 1, "edit": 1, "educ": 1, "effect": [], "effici": [2, 4, 5, 7], "either": [9, 16], "element": [5, 6, 7, 9, 16], "els": 2, "email": 1, "empathi": 1, "en": 16, "enabl": [5, 6], "enclos": 6, "encod": [4, 5, 6, 7, 16], "encode_sequ": 5, "encount": 2, "encrypt": 6, "end": [4, 5, 7, 9], "english": [5, 14], "enough": [2, 16], "ensur": 2, "entir": [], "entri": 5, "environ": [1, 12], "eo": 5, "equiv": 16, "error": [], "estim": 7, "etc": 6, "ethnic": 1, "evalu": [14, 16], "event": 1, "everyon": 1, "everyth": [2, 16], "exact": [9, 16], "exactmatch": [], "exampl": [1, 2, 4, 5, 7, 13, 16], "exchang": 15, "exclud": [], "execut": 16, "exist": 13, "expand": 8, "expect": [6, 8, 9], "experi": 1, "explan": [1, 16], "explicit": 1, "exploit": [4, 7], "export": [6, 7, 9, 10, 16], "export_as_straight_box": [7, 16], "export_as_xml": 16, "export_model_to_onnx": 15, "express": [1, 8], "extens": 6, "extern": [1, 14], "extra": 3, "extract": [4, 5], "extract_arch": [], "extractor": 7, "f_": 9, "f_a": 9, "factor": 8, "fair": 1, "fairli": 1, "fals": [5, 6, 7, 8, 9, 11, 16], "famili": 9, "faq": 1, "fascan": 13, "fast": [4, 5, 7], "fast_bas": [7, 16], "fast_smal": [7, 16], "fast_tini": [7, 16], "faster": [4, 7, 15], "fasterrcnn_mobilenet_v3_large_fpn": 7, "favorit": 16, "featur": [3, 7, 9, 10], "feed": [], "feedback": 1, "feel": [2, 13], "felix92": 13, "few": [3, 15, 16], "figsiz": 9, "figur": 9, "file": [2, 5], "file_hash": [], "file_nam": [], "final": 7, "find": [2, 3, 14], "fine": [], "finnish": 5, "first": [2, 5], "firsthand": 5, "fit": [7, 16], "fitz": [], "flag": 16, "flexibl": [], "flip": 8, "float": [6, 8, 9, 15], "float32": [6, 7, 8, 15], "fn": 8, "focu": 13, "focus": [1, 5], "folder": 5, "follow": [1, 2, 3, 5, 8, 9, 11, 12, 13, 16], "font": [5, 9], "font_famili": [5, 9], "font_siz": 9, "foral": 9, "forc": 2, "forg": 3, "form": [4, 5, 16], "format": [6, 9, 11, 14, 15, 16], "forpost": [4, 5], "forum": 2, "found": [], "fp": [], "fp16": 15, "frac": 9, "frame": [], "framework": [3, 13, 14, 16], "free": [1, 2, 13], "french": [5, 11, 13, 16], "friendli": 4, "from": [1, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16], "from_hub": [7, 13], "from_imag": [6, 13], "from_pdf": 6, "from_url": 6, "full": [5, 9, 16], "fulli": [], "function": [5, 8, 9], "funsd": [4, 5, 14, 16], "further": 14, "futur": 5, "g": [6, 7], "g_": 9, "g_x": 9, "gallagh": [], "gamma": 8, "gaussian": 8, "gaussianblur": 8, "gaussiannois": 8, "gdk": 3, "gen": 16, "gender": 1, "gener": [2, 4, 7], "generic_cyrillic_lett": [], "geometri": [4, 6, 16], "geq": 9, "german": [5, 11, 13], "get": 16, "get_artefact": [], "get_word": [], "gettextword": [], "git": 13, "github": [2, 3, 7, 13], "give": 1, "given": [5, 6, 8, 9, 16], "global": 7, "go": 16, "good": 15, "googl": 2, "googlevis": 4, "gpu": [4, 15], "gracefulli": 1, "graph": [4, 5, 6], "grayscal": 8, "ground": 9, "groung": 9, "group": [4, 16], "gt": 9, "gt_box": 9, "gt_label": 9, "gtk": 3, "guid": 2, "guidanc": 14, "gvision": 16, "h": [6, 7, 8], "h_": 9, "ha": [2, 5, 9, 14], "half": [], "handl": [14, 16], "handwrit": 5, "handwritten": 14, "harass": 1, "hardwar": [], "harm": 1, "hat": 9, "have": [1, 2, 9, 11, 13, 14, 16], "head": [7, 16], "healthi": 1, "hebrew": 5, "height": 6, "hello": [9, 16], "help": 15, "here": [3, 8, 10, 14, 16], "hf": 7, "hf_hub_download": 7, "high": 6, "higher": [3, 5, 16], "hindi": [], "hindi_digit": 5, "hocr": 16, "homebrew": 3, "hook": 16, "horizont": [6, 8], "hous": 5, "how": [2, 11, 13, 14], "howev": 14, "hsv": 8, "html": [1, 2, 6, 16], "http": [1, 3, 5, 6, 7, 13, 16], "hub": 7, "hue": 8, "huggingfac": 7, "hw": 5, "i": [1, 2, 5, 6, 7, 8, 9, 12, 13, 14, 15], "i7": 16, "ibrahimov": [], "ic03": [4, 5, 14], "ic13": [4, 5, 14], "icdar": [4, 5], "icdar2019": 5, "id": 16, "ident": 1, "identifi": 4, "ignor": [], "ignore_acc": [], "ignore_cas": [], "iiit": [4, 5], "iiit5k": [5, 14], "iiithw": [4, 5, 14], "imag": [4, 5, 6, 7, 8, 9, 13, 14, 16], "imagenet": 7, "imageri": 1, "images_90k_norm": 5, "img": [5, 8, 14], "img_cont": 6, "img_fold": [5, 14], "img_path": 6, "img_transform": 5, "imgur5k": [4, 5, 14], "imgur5k_annot": 5, "imlist": 5, "impact": 1, "implement": [5, 6, 7, 8, 9, 16], "import": [5, 6, 7, 8, 9, 11, 13, 14, 15, 16], "improv": 7, "inappropri": 1, "incid": 1, "includ": [1, 3, 5, 14, 15], "inclus": 1, "increas": 8, "independ": [], "index": [2, 6], "indic": 9, "individu": 1, "infer": [4, 7, 8], "inform": [1, 2, 4, 5, 14], "inherit": [], "input": [2, 6, 7, 8, 15, 16], "input_crop": 7, "input_pag": [7, 9, 16], "input_shap": 15, "input_t": [], "input_tensor": 7, "inspir": [1, 8], "instal": 13, "instanc": [1, 16], "instanti": [7, 16], "instead": [5, 6, 7], "insult": 1, "int": [5, 6, 8, 9], "int64": [8, 9], "integ": 9, "integr": [4, 13, 14], "intel": 16, "interact": [1, 6, 9], "interfac": 13, "interoper": 15, "interpol": 8, "interpret": [5, 6], "intersect": 9, "invert": 8, "investig": 1, "invis": 1, "invoic": [], "involv": [1, 16], "io": 13, "iou": 9, "iou_thresh": 9, "iou_threshold": [], "irregular": [4, 7, 14], "isn": 5, "issu": [1, 2, 13], "italian": 5, "iter": [5, 8, 14, 16], "its": [6, 7, 8, 9, 14, 16], "itself": [7, 13], "j": 9, "jame": [], "job": 2, "join": 2, "jpeg": 8, "jpegqual": 8, "jpg": [5, 6, 13], "json": [5, 14, 16], "json_output": 16, "jump": 2, "just": 1, "kei": [4, 5], "kera": [7, 15], "kernel": [4, 7, 8], "kernel_s": [], "kernel_shap": 8, "keywoard": 7, "keyword": [5, 6, 7, 9], "kie": [7, 11], "kie_predictor": [7, 11], "kiepredictor": 7, "kind": 1, "know": 2, "kwarg": [5, 6, 7, 9], "l": 9, "l_j": 9, "label": [5, 8, 9, 14], "label_fil": [5, 14], "label_fold": 5, "label_path": [5, 14], "labels_path": [5, 14], "ladder": 1, "lambda": 8, "lambdatransform": 8, "lang": 16, "languag": [1, 4, 5, 6, 7, 13, 16], "larg": [7, 13], "largest": 9, "last": [3, 5], "latenc": 7, "later": 2, "latest": [3, 16], "latin": 5, "layer": 15, "layout": 16, "lead": 1, "leader": 1, "learn": [1, 4, 7, 15, 16], "least": 3, "left": [9, 16], "legacy_french": 5, "length": [5, 16], "less": [15, 16], "let": [], "letter": [], "level": [1, 5, 9, 16], "levenshtein": [], "leverag": 10, "lf": 13, "libffi": 3, "librari": [2, 3, 10, 11], "light": 4, "lightweight": [], "like": 1, "limits_": 9, "line": [4, 7, 9, 16], "line_1_1": 16, "link": 11, "linknet": [4, 7], "linknet16": [], "linknet_resnet18": [7, 11, 16], "linknet_resnet18_rot": [], "linknet_resnet34": [7, 15, 16], "linknet_resnet50": [7, 16], "linux": [], "list": [5, 6, 8, 9, 13], "ll": 9, "load": [4, 5, 7], "load_state_dict": 11, "load_weight": 11, "loader": [], "loc_pr": 16, "local": [2, 4, 5, 7, 9, 14, 16], "localis": 5, "localizationconfus": 9, "locat": [2, 6, 16], "login": 7, "login_to_hub": [7, 13], "logo": [6, 14], "love": 13, "lower": [8, 9, 16], "m": [2, 9, 16], "m1": 3, "macbook": 3, "machin": 15, "maco": 3, "made": 4, "magc_resnet31": 7, "mai": [1, 2], "mail": 1, "main": 10, "maintain": 4, "mainten": 2, "make": [1, 2, 9, 12, 13, 15, 16], "mani": [14, 16], "manipul": 16, "map": [5, 7], "map_loc": 11, "mask_shap": 9, "master": [4, 7, 16], "match": [9, 16], "mathcal": 9, "matplotlib": [6, 9], "max": [5, 8, 9], "max_angl": 8, "max_area": 8, "max_char": [5, 14], "max_delta": 8, "max_dist": [], "max_gain": 8, "max_gamma": 8, "max_qual": 8, "max_ratio": 8, "maximum": [5, 8], "maxval": [7, 8], "mbox": 9, "mean": [8, 9, 11], "meaniou": 9, "meant": [6, 15], "measur": 16, "media": 1, "median": 7, "meet": 11, "member": 1, "memori": [9, 12, 15], "mention": 16, "merg": 5, "messag": 2, "meta": 16, "metadata": 15, "metal": 3, "method": [6, 8, 16], "metric": [9, 16], "middl": 16, "might": [15, 16], "min": 8, "min_area": 8, "min_char": [5, 14], "min_gain": 8, "min_gamma": 8, "min_qual": 8, "min_ratio": 8, "min_val": 8, "minde": [1, 3, 4, 7], "minim": [2, 4], "minimalist": [4, 7], "minimum": [3, 5, 8, 9, 16], "minval": 8, "miss": 3, "mistak": 1, "mix": [], "mixed_float16": 15, "mixed_precis": 15, "mjsynth": [4, 5, 14], "mnt": 5, "mobilenet": [7, 13], "mobilenet_v3_larg": 7, "mobilenet_v3_large_r": 7, "mobilenet_v3_smal": 7, "mobilenet_v3_small_crop_orient": [], "mobilenet_v3_small_orient": 7, "mobilenet_v3_small_page_orient": [], "mobilenet_v3_small_r": 7, "mobilenetv3": 7, "modal": [4, 5], "mode": 3, "model": [5, 9, 12, 14], "model_nam": [7, 13, 15], "model_path": 15, "moder": 1, "modif": 2, "modifi": [7, 12, 16], "modul": [6, 7, 8, 9, 16], "moment": [], "more": [2, 9, 14, 16], "moscardi": [], "most": 16, "mozilla": 1, "multi": [4, 7], "multilingu": [5, 13], "multipl": [5, 6, 8, 16], "multipli": 8, "multiprocess": 12, "my": 7, "my_awesome_model": 13, "my_hook": 16, "n": [5, 9], "na": [], "name": [5, 7, 15, 16], "nation": 1, "natur": [1, 4, 5], "nb": [], "ndarrai": [5, 6, 8, 9], "necessari": [3, 11, 12], "need": [2, 3, 5, 9, 11, 12, 13, 16], "neg": 8, "nest": 16, "nestedobject": [], "netraj": [], "network": [4, 5, 7, 15], "neural": [4, 5, 7, 15], "new": [2, 9], "newer": [], "next": [5, 14], "nois": 8, "noisi": [4, 5], "non": [4, 5, 6, 7, 8, 9], "none": [5, 6, 7, 8, 9, 16], "normal": [7, 8], "norwegian": 5, "note": [0, 2, 5, 7, 13, 15], "now": 2, "np": [7, 8, 9, 16], "num_output_channel": 8, "num_sampl": [5, 14], "num_work": 5, "number": [5, 8, 9, 16], "numpi": [6, 7, 9, 16], "o": 3, "obb": [], "obj_detect": 13, "object": [5, 9, 10, 16], "objectness_scor": [], "oblig": 1, "obtain": 16, "occupi": 15, "ocr": [4, 5, 7, 9, 13, 14], "ocr_carea": 16, "ocr_db_crnn": 9, "ocr_lin": 16, "ocr_pag": 16, "ocr_par": 16, "ocr_predictor": [7, 11, 13, 15, 16], "ocrdataset": [5, 14], "ocrmetr": 9, "ocrpredictor": [7, 11], "ocrx_word": 16, "offens": 1, "offici": [1, 7], "offlin": 1, "offset": 8, "onc": 16, "one": [2, 5, 7, 8, 11, 13, 16], "oneof": 8, "ones": [5, 8, 9], "onli": [2, 7, 8, 9, 13, 14, 15, 16], "onlin": 1, "onnx": [], "onnxruntim": [], "onnxtr": [], "opac": 8, "opacity_rang": 8, "open": [1, 2, 13, 15], "opinion": 1, "optic": [4, 16], "optim": 4, "option": [5, 7, 11], "order": [2, 5, 6, 8], "org": [1, 5, 7, 16], "organ": 6, "orient": [1, 6, 7, 16], "orientationpredictor": [], "other": [1, 2], "otherwis": [1, 6, 9], "our": [2, 7, 16], "out": [2, 7, 8, 9, 16], "outpout": 16, "output": [6, 8, 15], "output_s": [6, 8], "outsid": 12, "over": [3, 5, 9, 16], "overal": [1, 7], "overlai": 6, "overview": [], "overwrit": [], "overwritten": 13, "own": 4, "p": [8, 9, 16], "packag": [2, 4, 9, 12, 14], "pad": [5, 7, 8, 16], "page": [3, 5, 7, 9, 16], "page1": 6, "page2": 6, "page_1": 16, "page_idx": [6, 16], "page_orientation_predictor": [], "page_param": [], "pair": 9, "pango": 3, "paper": 7, "par_1_1": 16, "paragraph": 16, "paragraph_break": 16, "parallel": [], "param": [8, 16], "paramet": [4, 6, 7, 15], "pars": [4, 5], "parseq": [4, 7, 13, 16], "part": [5, 8, 16], "parti": 3, "partial": 16, "particip": 1, "pass": [5, 6, 7, 16], "password": 6, "patch": [7, 9], "path": [5, 6, 14], "path_to_checkpoint": 11, "path_to_custom_model": [], "path_to_pt": 11, "patil": [], "pattern": 1, "pdf": [6, 7, 10], "pdfpage": 6, "peopl": 1, "per": [8, 16], "perform": [4, 6, 7, 8, 9, 12, 15, 16], "period": 1, "permiss": 1, "permut": [4, 7], "persian_lett": 5, "person": [1, 14], "phase": 16, "photo": 14, "physic": [1, 6], "pick": 8, "pictur": 6, "pip": [2, 3], "pipelin": 16, "pixbuf": 3, "pixel": [6, 8, 16], "platinum": [], "pleas": 2, "plot": 9, "plt": 9, "plug": 13, "plugin": 3, "png": 6, "point": 15, "polici": 12, "polish": 5, "polit": 1, "polygon": [5, 9, 16], "pool": 7, "portugues": 5, "posit": [1, 9], "possibl": [2, 9, 13, 16], "post": [1, 16], "postprocessor": 16, "potenti": 7, "power": 4, "ppageno": 16, "pre": [2, 7], "precis": [9, 16], "pred": 9, "pred_box": 9, "pred_label": 9, "predefin": 14, "predict": [6, 7, 9, 16], "predictor": [4, 6, 7, 11, 13, 15], "prefer": 14, "preinstal": [], "preprocessor": [11, 16], "prerequisit": 13, "present": 10, "preserv": [7, 8, 16], "preserve_aspect_ratio": [6, 7, 8, 11, 16], "pretrain": [4, 7, 9, 11, 15, 16], "pretrained_backbon": [7, 11], "print": 16, "prior": 5, "privaci": 1, "privat": 1, "probabl": 8, "problem": 2, "procedur": 8, "process": [2, 4, 6, 11, 16], "processor": 16, "produc": [10, 16], "product": 15, "profession": 1, "project": [2, 14], "promptli": 1, "proper": 2, "properli": 5, "properti": [], "provid": [1, 2, 4, 13, 14, 16], "public": [1, 4], "publicli": 16, "publish": 1, "pull": 13, "punctuat": 5, "pure": 5, "purpos": 2, "push_to_hf_hub": [7, 13], "py": 13, "pypdfium2": [3, 6], "pyplot": [6, 9], "python": 2, "python3": 13, "pytorch": [3, 4, 7, 8, 11, 13, 15, 16], "q": 2, "qr": 6, "qr_code": 14, "qualiti": 8, "quantiz": [], "quantize_model": [], "question": 1, "quickli": 4, "quicktour": 10, "r": 16, "race": 1, "ramdisk": 5, "rand": [7, 8, 9, 15, 16], "random": [7, 8, 9, 16], "randomappli": 8, "randombright": 8, "randomcontrast": 8, "randomcrop": 8, "randomgamma": 8, "randomhorizontalflip": 8, "randomhu": 8, "randomjpegqu": 8, "randomli": 8, "randomres": [], "randomrot": 8, "randomsatur": 8, "randomshadow": 8, "rang": 8, "rassi": 13, "ratio": [7, 8, 16], "raw": [6, 9], "re": 15, "read": [4, 5, 7], "read_html": 6, "read_img": [], "read_img_as_numpi": 6, "read_img_as_tensor": 6, "read_pdf": 6, "readi": 15, "real": [4, 7, 8], "realli": [], "reason": [1, 4, 5], "rebuild": 2, "rebuilt": 2, "recal": [9, 16], "receipt": [4, 5, 16], "reco_arch": [7, 11, 13, 15], "reco_b": [], "reco_model": [11, 13], "reco_param": 11, "reco_predictor": 11, "recogn": 16, "recognit": [5, 9, 11], "recognition_predictor": [7, 16], "recognition_task": [5, 14], "recognitiondataset": [5, 14], "recognitionpredictor": [7, 11], "rectangular": 7, "recurr": [], "red": 9, "reduc": [3, 8], "refer": [2, 3, 11, 13, 14, 16], "regardless": 1, "region": 16, "regroup": 9, "regular": 14, "reject": 1, "rel": [6, 8, 9, 16], "relat": 6, "releas": [0, 3], "relev": [], "religion": 1, "relu": [], "remov": 1, "render": [6, 16], "repo": 7, "repo_id": [7, 13], "report": 1, "repositori": [5, 7, 13], "repres": [1, 9, 15, 16], "represent": [4, 7], "request": [1, 13], "requir": [3, 8], "research": 4, "residu": 7, "resiz": [8, 16], "resnet": 7, "resnet18": [7, 13], "resnet31": 7, "resnet34": 7, "resnet50": [7, 13], "resolv": 6, "resolve_block": 16, "resolve_lin": 16, "resourc": 14, "respect": 1, "respons": 9, "rest": [2, 8, 9], "restrict": 12, "result": [2, 5, 6, 10, 13, 16], "return": 16, "reusabl": 16, "review": 1, "rgb": [6, 8], "rgb_mode": 6, "rgb_output": 6, "right": [1, 7, 9], "roboflow": [], "robust": [4, 5], "root": 5, "rotat": [5, 6, 7, 8, 9, 14, 16], "rotated_bbox": [], "run": [2, 3, 7], "same": [2, 6, 9, 14, 16], "sampl": [5, 14, 16], "sample_transform": 5, "sanjin": [], "sar": [4, 7], "sar_resnet31": [7, 16], "sar_vgg16_bn": [], "satur": 8, "save": [7, 14], "saved_model": [], "scale": [6, 7, 8, 9], "scale_rang": [], "scan": [4, 5], "scene": [4, 5, 7], "scheme": [], "score": 9, "scratch": [], "script": [2, 14], "seamless": 4, "seamlessli": [4, 16], "search": 7, "searchabl": 10, "sec": 16, "second": 16, "section": [11, 13, 15, 16], "secur": [1, 12], "see": [1, 2], "seemlessli": [], "seen": 16, "segment": [4, 7, 16], "self": 16, "semant": [4, 7], "send": 16, "sens": 9, "sensit": 14, "separ": 16, "sequenc": [4, 5, 6, 7, 9, 16], "sequenti": [8, 16], "seri": 1, "serial": [], "serialized_model": [], "seriou": 1, "set": [1, 3, 5, 7, 9, 12, 16], "set_global_polici": 15, "sever": [6, 8, 16], "sex": 1, "sexual": 1, "sha256": [], "shade": 8, "shape": [4, 6, 7, 8, 9, 16], "share": [12, 14], "shift": 8, "shm": 12, "should": [2, 5, 6, 8, 9], "show": [4, 6, 7, 9, 11, 13], "showcas": 2, "shuffl": [5, 8], "side": 9, "signatur": 6, "signific": 14, "simpl": [4, 7], "simpler": 7, "sinc": [5, 14], "singl": [1, 2, 4, 5], "single_img_doc": [], "size": [1, 5, 6, 8, 9, 16], "skew": 16, "slack": 2, "slightli": 7, "small": [2, 7], "smallest": 6, "snapshot_download": 7, "snippet": 16, "so": [2, 3, 5, 7, 13, 14], "social": 1, "socio": 1, "some": [3, 10, 13, 14], "someth": 2, "somewher": 2, "soon": 15, "sort": 1, "sourc": [5, 6, 7, 8, 9, 13], "space": [1, 16], "span": 16, "spanish": 5, "spatial": [4, 5, 6, 9], "special": [], "specif": [2, 3, 9, 11, 14, 16], "specifi": [1, 5, 6], "speed": [4, 7], "sphinx": 2, "sroie": [4, 5, 14], "stabl": 3, "stackoverflow": 2, "stage": 4, "standalon": [], "standard": 8, "start": 5, "state": [4, 9], "static": 9, "statist": [], "statu": 1, "std": [8, 11], "step": 12, "still": 16, "str": [5, 6, 7, 8, 9], "straight": [5, 7, 14, 16], "straighten": [], "straighten_pag": 7, "straigten_pag": [], "stream": 6, "street": [4, 5], "strict": 3, "strictli": 9, "string": [5, 6, 9, 16], "strive": 3, "strong": [4, 7], "structur": [15, 16], "subset": [5, 16], "suggest": [2, 13], "sum": 9, "summari": 9, "support": [15, 16], "sustain": 1, "svhn": [4, 5, 14], "svt": [5, 14], "swedish": 5, "symbol": [], "symmetr": [7, 8, 16], "symmetric_pad": [7, 8, 16], "synthes": 9, "synthesize_pag": 9, "synthet": 4, "synthtext": [4, 5, 14], "system": 16, "t": [2, 5, 11, 16], "tabl": 13, "take": [1, 5, 16], "target": [5, 6, 8, 9, 14], "target_s": 5, "task": [4, 5, 7, 13, 14, 16], "task2": 5, "team": 3, "techminde": 3, "templat": [2, 4], "tensor": [5, 6, 8, 16], "tensorflow": [3, 4, 6, 7, 8, 11, 13, 15, 16], "tensorspec": 15, "term": 1, "test": [5, 14], "test_set": 5, "text": [5, 6, 7, 9, 14], "text_output": 16, "textmatch": 9, "textnet": 7, "textnet_bas": 7, "textnet_smal": 7, "textnet_tini": 7, "textract": [4, 16], "textstylebrush": [4, 5], "textual": [4, 5, 6, 7, 16], "tf": [3, 6, 7, 8, 13, 15], "tf_model": [], "tflite": [], "than": [2, 3, 9, 13], "thank": 2, "thei": [1, 9], "them": [3, 5, 16], "thi": [1, 2, 3, 5, 9, 11, 12, 13, 14, 15, 16], "thing": [15, 16], "third": 3, "those": [1, 3, 6, 16], "threaten": 1, "threshold": 16, "through": [1, 8, 14], "tilman": 13, "time": [1, 4, 7, 9, 14], "tini": 7, "titl": [6, 16], "tm": 16, "tmp": 12, "togeth": [2, 6], "tograi": 8, "tool": 14, "top": [9, 16], "topic": 2, "torch": [3, 8, 11, 13, 15], "torchvis": 8, "total": 11, "toward": [1, 3], "train": [2, 5, 7, 8, 13, 14, 15, 16], "train_it": [5, 14], "train_load": [5, 14], "train_pytorch": 13, "train_set": [5, 14], "train_tensorflow": 13, "trainabl": [4, 7], "tranform": 8, "transcrib": 16, "transfer": [4, 5], "transfo": 8, "transform": [4, 5, 7], "translat": 1, "troll": 1, "true": [5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16], "truth": 9, "tune": 15, "tupl": [5, 6, 8, 9], "turn": [], "two": [6, 12], "txt": 5, "type": [6, 9, 13, 15, 16], "typic": 16, "u": [1, 2], "ucsd": 5, "udac": 2, "uint8": [6, 7, 9, 16], "ukrainian": [], "unaccept": 1, "underli": [14, 16], "underneath": 6, "understand": [4, 5, 16], "unidecod": 9, "uniform": [7, 8], "uniformli": 8, "uninterrupt": [6, 16], "union": 9, "unit": [], "unittest": 2, "unlock": 6, "unoffici": 7, "unprofession": 1, "unsolicit": 1, "unsupervis": 4, "unwelcom": 1, "up": [7, 16], "updat": 9, "upgrad": 2, "upper": [5, 8], "uppercas": 14, "url": 6, "us": [1, 2, 3, 5, 7, 9, 11, 12, 13, 16], "usabl": 16, "usag": [12, 15], "use_broadcast": 9, "use_polygon": [5, 9, 14], "useabl": 16, "user": [3, 4, 6, 10], "utf": 16, "util": 15, "v0": [], "v1": 13, "v3": [7, 13, 16], "valid": 14, "valu": [2, 6, 8, 16], "valuabl": 4, "variabl": 12, "varieti": 5, "veri": 7, "verifi": [], "verma": [], "version": [1, 2, 3, 15, 16], "vgg": 7, "vgg16": 13, "vgg16_bn_r": 7, "via": 1, "video": [], "vietnames": 5, "view": [4, 5], "viewpoint": 1, "violat": 1, "visibl": 1, "vision": [4, 5, 7], "visiondataset": 5, "visiontransform": 7, "visual": 4, "visualize_pag": 9, "vit_": 7, "vit_b": 7, "vitstr": [4, 7, 15], "vitstr_bas": [7, 16], "vitstr_smal": [7, 11, 15, 16], "viz": [], "vocab": [11, 13, 14, 16], "vocabulari": [5, 11, 13], "w": [6, 7, 8, 9], "w3": 16, "wa": 1, "wai": [1, 4, 14], "want": [2, 15, 16], "warm": [], "warmup": 16, "wasn": 2, "we": [1, 2, 3, 4, 6, 8, 13, 14, 15, 16], "weasyprint": 6, "web": [2, 6], "websit": 5, "weight": 11, "welcom": 1, "well": [1, 15], "were": [1, 6, 16], "what": 1, "when": [1, 2, 7], "whenev": 2, "where": [2, 6, 8, 9], "whether": [2, 5, 6, 8, 9, 14, 16], "which": [1, 7, 12, 14, 16], "whichev": 3, "while": [8, 16], "why": 1, "width": 6, "wiki": 1, "wildreceipt": [4, 5, 14], "window": [3, 7, 9], "wish": 2, "within": 1, "without": [1, 5, 7], "wonder": 2, "word": [4, 5, 7, 9, 16], "word_1_1": 16, "word_1_2": 16, "word_1_3": 16, "wordgener": [5, 14], "words_onli": 9, "work": [12, 16], "worker": 5, "workflow": 2, "worklow": 2, "world": [9, 16], "worth": 7, "wrap": 16, "wrapper": [5, 8], "write": 12, "written": [1, 6], "www": [1, 6, 16], "x": [6, 8, 9], "x12larg": [], "x_ascend": 16, "x_descend": 16, "x_i": 9, "x_size": 16, "x_wconf": 16, "xeon": [], "xhtml": 16, "xmax": 6, "xmin": 6, "xml": 16, "xml_bytes_str": 16, "xml_element": 16, "xml_output": 16, "xmln": 16, "y": 9, "y_i": 9, "y_j": 9, "yet": [], "ymax": 6, "ymin": 6, "yolov8": [], "you": [2, 3, 5, 6, 7, 11, 12, 13, 14, 15, 16], "your": [2, 4, 6, 9, 16], "yoursit": 6, "yugesh": [], "zero": [8, 9], "zoo": [], "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 5, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 5, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": 5, "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 5, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 5, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": [], "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 5, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 5, "\u00e4\u00f6\u00e4\u00f6": 5, "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 5, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": 5, "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": 5, "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": 5, "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 5, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": [], "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": [], "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": 5, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 5, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 5, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 5, "\u067e\u0686\u06a2\u06a4\u06af": 5, "\u0905": [], "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": [], "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": [], "\u0950": [], "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": [], "\u09bd": [], "\u09ce": [], "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": []}, "titles": ["Changelog", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 2, "0": 0, "01": 0, "02": 0, "03": 0, "04": [], "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 1], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 1], "2021": 0, "2022": 0, "2023": 0, "2024": 0, "21": [], "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 1], "31": 0, "4": [0, 1], "5": 0, "6": 0, "7": 0, "8": 0, "9": [], "advanc": 16, "approach": 16, "architectur": 16, "arg": [5, 6, 7, 8, 9], "artefact": 6, "artefactdetect": [], "attribut": 1, "avail": [14, 16], "aw": 12, "ban": 1, "block": 6, "bug": 2, "build": [], "changelog": 0, "choos": [14, 16], "classif": [7, 13], "code": [1, 2], "codebas": 2, "commit": 2, "commun": 13, "compos": 8, "compress": [], "conda": 3, "conduct": 1, "connect": 2, "content": [], "continu": 2, "contrib": [], "contribut": 2, "contributor": 1, "convent": 13, "correct": 1, "coven": 1, "custom": [5, 11], "data": 14, "dataload": 5, "dataset": [4, 5, 14], "detect": [4, 7, 13, 14, 16], "develop": 2, "do": 16, "doctr": [2, 4, 5, 6, 7, 8, 9, 10, 15], "document": [2, 4, 6], "end": 16, "enforc": 1, "evalu": 9, "export": 15, "factori": 7, "featur": [2, 4], "feedback": 2, "file": 6, "from": 13, "gener": [5, 14], "get": [], "git": 3, "guidelin": 1, "half": 15, "hub": 13, "huggingfac": 13, "i": 16, "implement": [], "infer": 15, "instal": [2, 3], "integr": 2, "io": 6, "lambda": 12, "let": 2, "line": 6, "linux": 3, "load": [11, 13, 14], "loader": 5, "main": 4, "mode": 2, "model": [4, 7, 11, 13, 15, 16], "modifi": 2, "modul": [], "name": 13, "note": [], "notebook": 10, "object": 14, "ocr": 16, "onli": 3, "onnx": 15, "optim": 15, "option": 16, "orient": [], "our": 1, "output": 16, "own": [11, 14], "packag": 3, "page": 6, "perman": 1, "pipelin": [], "pledg": 1, "post": [], "pre": [], "precis": 15, "predictor": 16, "prepar": 15, "prerequisit": 3, "pretrain": 13, "process": [], "push": 13, "python": 3, "qualiti": 2, "question": 2, "read": 6, "readi": 14, "recognit": [4, 7, 13, 14, 16], "refer": [], "report": 2, "request": 2, "resourc": [], "respons": 1, "return": [5, 6, 7, 9], "right": 16, "savedmodel": [], "scope": 1, "share": 13, "should": 16, "stage": 16, "standard": 1, "start": [], "structur": [2, 6], "style": 2, "support": [4, 5, 8], "synthet": [5, 14], "task": 9, "temporari": 1, "test": 2, "text": [4, 16], "train": 11, "transform": 8, "two": 16, "unit": 2, "us": [14, 15], "util": 9, "v0": 0, "verif": 2, "via": 3, "visual": 9, "vocab": 5, "warn": 1, "what": 16, "word": 6, "your": [11, 13, 14, 15], "zoo": [4, 7]}}) \ No newline at end of file +Search.setIndex({"alltitles": {"1. Correction": [[1, "correction"]], "2. Warning": [[1, "warning"]], "3. Temporary Ban": [[1, "temporary-ban"]], "4. Permanent Ban": [[1, "permanent-ban"]], "AWS Lambda": [[12, null]], "Advanced options": [[16, "advanced-options"]], "Args:": [[5, "args"], [5, "id4"], [5, "id7"], [5, "id10"], [5, "id13"], [5, "id16"], [5, "id19"], [5, "id22"], [5, "id25"], [5, "id29"], [5, "id32"], [5, "id37"], [5, "id40"], [5, "id46"], [5, "id49"], [5, "id50"], [5, "id51"], [5, "id54"], [5, "id57"], [5, "id60"], [5, "id61"], [6, "args"], [6, "id2"], [6, "id3"], [6, "id4"], [6, "id5"], [6, "id6"], [6, "id7"], [6, "id10"], [6, "id12"], [6, "id14"], [6, "id16"], [6, "id20"], [6, "id24"], [6, "id28"], [7, "args"], [7, "id3"], [7, "id8"], [7, "id13"], [7, "id17"], [7, "id21"], [7, "id26"], [7, "id31"], [7, "id36"], [7, "id41"], [7, "id45"], [7, "id49"], [7, "id54"], [7, "id58"], [7, "id63"], [7, "id68"], [7, "id72"], [7, "id76"], [7, "id81"], [7, "id86"], [7, "id90"], [7, "id95"], [7, "id100"], [7, "id105"], [7, "id110"], [7, "id114"], [7, "id118"], [7, "id123"], [7, "id128"], [7, "id133"], [7, "id137"], [7, "id141"], [7, "id146"], [7, "id150"], [7, "id154"], [7, "id158"], [7, "id160"], [7, "id162"], [7, "id164"], [8, "args"], [8, "id1"], [8, "id2"], [8, "id3"], [8, "id4"], [8, "id5"], [8, "id6"], [8, "id7"], [8, "id8"], [8, "id9"], [8, "id10"], [8, "id11"], [8, "id12"], [8, "id13"], [8, "id14"], [8, "id15"], [8, "id16"], [8, "id17"], [8, "id18"], [9, "args"], [9, "id3"], [9, "id5"], [9, "id6"], [9, "id7"], [9, "id8"], [9, "id9"], [9, "id10"], [9, "id11"]], "Artefact": [[6, "artefact"]], "Attribution": [[1, "attribution"]], "Available Datasets": [[14, "available-datasets"]], "Available architectures": [[16, "available-architectures"], [16, "id1"], [16, "id2"]], "Block": [[6, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[14, null]], "Choosing the right model": [[16, null]], "Classification": [[13, "classification"]], "Code quality": [[2, "code-quality"]], "Code style verification": [[2, "code-style-verification"]], "Codebase structure": [[2, "codebase-structure"]], "Commits": [[2, "commits"]], "Composing transformations": [[8, "composing-transformations"]], "Continuous Integration": [[2, "continuous-integration"]], "Contributing to docTR": [[2, null]], "Contributor Covenant Code of Conduct": [[1, null]], "Custom dataset loader": [[5, "custom-dataset-loader"]], "Data Loading": [[14, "data-loading"]], "Dataloader": [[5, "dataloader"]], "Detection": [[13, "detection"], [14, "detection"]], "Detection predictors": [[16, "detection-predictors"]], "Developer mode installation": [[2, "developer-mode-installation"]], "Developing docTR": [[2, "developing-doctr"]], "Document": [[6, "document"]], "Document structure": [[6, "document-structure"]], "End-to-End OCR": [[16, "end-to-end-ocr"]], "Enforcement": [[1, "enforcement"]], "Enforcement Guidelines": [[1, "enforcement-guidelines"]], "Enforcement Responsibilities": [[1, "enforcement-responsibilities"]], "Export to ONNX": [[15, "export-to-onnx"]], "Feature requests & bug report": [[2, "feature-requests-bug-report"]], "Feedback": [[2, "feedback"]], "File reading": [[6, "file-reading"]], "Half-precision": [[15, "half-precision"]], "Installation": [[3, null]], "Let\u2019s connect": [[2, "let-s-connect"]], "Line": [[6, "line"]], "Loading from Huggingface Hub": [[13, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[11, "loading-your-custom-trained-model"]], "Main Features": [[4, "main-features"]], "Model optimization": [[15, "model-optimization"]], "Model zoo": [[4, "model-zoo"]], "Modifying the documentation": [[2, "modifying-the-documentation"]], "Naming conventions": [[13, "naming-conventions"]], "Object Detection": [[14, "object-detection"]], "Our Pledge": [[1, "our-pledge"]], "Our Standards": [[1, "our-standards"]], "Page": [[6, "page"]], "Preparing your model for inference": [[15, null]], "Prerequisites": [[3, "prerequisites"]], "Pretrained community models": [[13, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[13, "pushing-to-the-huggingface-hub"]], "Questions": [[2, "questions"]], "Recognition": [[13, "recognition"], [14, "recognition"]], "Recognition predictors": [[16, "recognition-predictors"]], "Returns:": [[5, "returns"], [6, "returns"], [6, "id11"], [6, "id13"], [6, "id15"], [6, "id19"], [6, "id23"], [6, "id27"], [6, "id31"], [7, "returns"], [7, "id6"], [7, "id11"], [7, "id16"], [7, "id20"], [7, "id24"], [7, "id29"], [7, "id34"], [7, "id39"], [7, "id44"], [7, "id48"], [7, "id52"], [7, "id57"], [7, "id61"], [7, "id66"], [7, "id71"], [7, "id75"], [7, "id79"], [7, "id84"], [7, "id89"], [7, "id93"], [7, "id98"], [7, "id103"], [7, "id108"], [7, "id113"], [7, "id117"], [7, "id121"], [7, "id126"], [7, "id131"], [7, "id136"], [7, "id140"], [7, "id144"], [7, "id149"], [7, "id153"], [7, "id157"], [7, "id159"], [7, "id161"], [7, "id163"], [9, "returns"], [9, "id4"]], "Scope": [[1, "scope"]], "Share your model with the community": [[13, null]], "Supported Vocabs": [[5, "supported-vocabs"]], "Supported datasets": [[4, "supported-datasets"]], "Supported transformations": [[8, "supported-transformations"]], "Synthetic dataset generator": [[5, "synthetic-dataset-generator"], [14, "synthetic-dataset-generator"]], "Task evaluation": [[9, "task-evaluation"]], "Text Detection": [[16, "text-detection"]], "Text Recognition": [[16, "text-recognition"]], "Text detection models": [[4, "text-detection-models"]], "Text recognition models": [[4, "text-recognition-models"]], "Train your own model": [[11, null]], "Two-stage approaches": [[16, "two-stage-approaches"]], "Unit tests": [[2, "unit-tests"]], "Use your own datasets": [[14, "use-your-own-datasets"]], "Using your ONNX exported model in docTR": [[15, "using-your-onnx-exported-model-in-doctr"]], "Via Conda (Only for Linux)": [[3, "via-conda-only-for-linux"]], "Via Git": [[3, "via-git"]], "Via Python Package": [[3, "via-python-package"]], "Visualization": [[9, "visualization"]], "What should I do with the output?": [[16, "what-should-i-do-with-the-output"]], "Word": [[6, "word"]], "docTR Notebooks": [[10, null]], "docTR Vocabs": [[5, "id62"]], "docTR: Document Text Recognition": [[4, null]], "doctr.datasets": [[5, null], [5, "datasets"]], "doctr.io": [[6, null]], "doctr.models": [[7, null]], "doctr.models.classification": [[7, "doctr-models-classification"]], "doctr.models.detection": [[7, "doctr-models-detection"]], "doctr.models.factory": [[7, "doctr-models-factory"]], "doctr.models.recognition": [[7, "doctr-models-recognition"]], "doctr.models.zoo": [[7, "doctr-models-zoo"]], "doctr.transforms": [[8, null]], "doctr.utils": [[9, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]], "v0.7.0 (2023-09-09)": [[0, "v0-7-0-2023-09-09"]], "v0.8.0 (2024-02-28)": [[0, "v0-8-0-2024-02-28"]]}, "docnames": ["changelog", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[6, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[6, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[8, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[5, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[8, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[8, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[5, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[7, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[5, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[7, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[7, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[6, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[7, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[5, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[5, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[6, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[6, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[5, "doctr.datasets.encode_sequences", false]], "fast_base() (in module doctr.models.detection)": [[7, "doctr.models.detection.fast_base", false]], "fast_small() (in module doctr.models.detection)": [[7, "doctr.models.detection.fast_small", false]], "fast_tiny() (in module doctr.models.detection)": [[7, "doctr.models.detection.fast_tiny", false]], "from_hub() (in module doctr.models.factory)": [[7, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[6, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[6, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[6, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[5, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[8, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[8, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[5, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[5, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[5, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[5, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[5, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[7, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[8, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[6, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[7, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet34() (in module doctr.models.detection)": [[7, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[7, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[7, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[7, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[5, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_orientation() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_small_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[7, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[8, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[7, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[5, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[8, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[6, "doctr.io.Page", false]], "parseq() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[7, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[8, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[8, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[8, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[8, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[8, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[8, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[8, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[8, "doctr.transforms.RandomJpegQuality", false]], "randomrotate (class in doctr.transforms)": [[8, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[8, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[8, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[6, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[6, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[6, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[6, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[5, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[8, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[7, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[6, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[6, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[5, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[9, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[9, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[9, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[9, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[5, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[5, "doctr.datasets.SVT", false]], "synthesize_page() (in module doctr.utils.visualization)": [[9, "doctr.utils.visualization.synthesize_page", false]], "synthtext (class in doctr.datasets)": [[5, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[9, "doctr.utils.metrics.TextMatch", false]], "textnet_base() (in module doctr.models.classification)": [[7, "doctr.models.classification.textnet_base", false]], "textnet_small() (in module doctr.models.classification)": [[7, "doctr.models.classification.textnet_small", false]], "textnet_tiny() (in module doctr.models.classification)": [[7, "doctr.models.classification.textnet_tiny", false]], "togray (class in doctr.transforms)": [[8, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[9, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[9, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[9, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[9, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[7, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[9, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[7, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[7, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[7, "doctr.models.recognition.vitstr_small", false]], "wildreceipt (class in doctr.datasets)": [[5, "doctr.datasets.WILDRECEIPT", false]], "word (class in doctr.io)": [[6, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[5, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[5, 0, 1, "", "CORD"], [5, 0, 1, "", "CharacterGenerator"], [5, 0, 1, "", "DetectionDataset"], [5, 0, 1, "", "DocArtefacts"], [5, 0, 1, "", "FUNSD"], [5, 0, 1, "", "IC03"], [5, 0, 1, "", "IC13"], [5, 0, 1, "", "IIIT5K"], [5, 0, 1, "", "IIITHWS"], [5, 0, 1, "", "IMGUR5K"], [5, 0, 1, "", "MJSynth"], [5, 0, 1, "", "OCRDataset"], [5, 0, 1, "", "RecognitionDataset"], [5, 0, 1, "", "SROIE"], [5, 0, 1, "", "SVHN"], [5, 0, 1, "", "SVT"], [5, 0, 1, "", "SynthText"], [5, 0, 1, "", "WILDRECEIPT"], [5, 0, 1, "", "WordGenerator"], [5, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[5, 0, 1, "", "DataLoader"]], "doctr.io": [[6, 0, 1, "", "Artefact"], [6, 0, 1, "", "Block"], [6, 0, 1, "", "Document"], [6, 0, 1, "", "DocumentFile"], [6, 0, 1, "", "Line"], [6, 0, 1, "", "Page"], [6, 0, 1, "", "Word"], [6, 1, 1, "", "decode_img_as_tensor"], [6, 1, 1, "", "read_html"], [6, 1, 1, "", "read_img_as_numpy"], [6, 1, 1, "", "read_img_as_tensor"], [6, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[6, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[6, 2, 1, "", "from_images"], [6, 2, 1, "", "from_pdf"], [6, 2, 1, "", "from_url"]], "doctr.io.Page": [[6, 2, 1, "", "show"]], "doctr.models": [[7, 1, 1, "", "kie_predictor"], [7, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[7, 1, 1, "", "crop_orientation_predictor"], [7, 1, 1, "", "magc_resnet31"], [7, 1, 1, "", "mobilenet_v3_large"], [7, 1, 1, "", "mobilenet_v3_large_r"], [7, 1, 1, "", "mobilenet_v3_small"], [7, 1, 1, "", "mobilenet_v3_small_orientation"], [7, 1, 1, "", "mobilenet_v3_small_r"], [7, 1, 1, "", "resnet18"], [7, 1, 1, "", "resnet31"], [7, 1, 1, "", "resnet34"], [7, 1, 1, "", "resnet50"], [7, 1, 1, "", "textnet_base"], [7, 1, 1, "", "textnet_small"], [7, 1, 1, "", "textnet_tiny"], [7, 1, 1, "", "vgg16_bn_r"], [7, 1, 1, "", "vit_b"], [7, 1, 1, "", "vit_s"]], "doctr.models.detection": [[7, 1, 1, "", "db_mobilenet_v3_large"], [7, 1, 1, "", "db_resnet50"], [7, 1, 1, "", "detection_predictor"], [7, 1, 1, "", "fast_base"], [7, 1, 1, "", "fast_small"], [7, 1, 1, "", "fast_tiny"], [7, 1, 1, "", "linknet_resnet18"], [7, 1, 1, "", "linknet_resnet34"], [7, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[7, 1, 1, "", "from_hub"], [7, 1, 1, "", "login_to_hub"], [7, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[7, 1, 1, "", "crnn_mobilenet_v3_large"], [7, 1, 1, "", "crnn_mobilenet_v3_small"], [7, 1, 1, "", "crnn_vgg16_bn"], [7, 1, 1, "", "master"], [7, 1, 1, "", "parseq"], [7, 1, 1, "", "recognition_predictor"], [7, 1, 1, "", "sar_resnet31"], [7, 1, 1, "", "vitstr_base"], [7, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[8, 0, 1, "", "ChannelShuffle"], [8, 0, 1, "", "ColorInversion"], [8, 0, 1, "", "Compose"], [8, 0, 1, "", "GaussianBlur"], [8, 0, 1, "", "GaussianNoise"], [8, 0, 1, "", "LambdaTransformation"], [8, 0, 1, "", "Normalize"], [8, 0, 1, "", "OneOf"], [8, 0, 1, "", "RandomApply"], [8, 0, 1, "", "RandomBrightness"], [8, 0, 1, "", "RandomContrast"], [8, 0, 1, "", "RandomCrop"], [8, 0, 1, "", "RandomGamma"], [8, 0, 1, "", "RandomHorizontalFlip"], [8, 0, 1, "", "RandomHue"], [8, 0, 1, "", "RandomJpegQuality"], [8, 0, 1, "", "RandomRotate"], [8, 0, 1, "", "RandomSaturation"], [8, 0, 1, "", "RandomShadow"], [8, 0, 1, "", "Resize"], [8, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[9, 0, 1, "", "DetectionMetric"], [9, 0, 1, "", "LocalizationConfusion"], [9, 0, 1, "", "OCRMetric"], [9, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[9, 2, 1, "", "summary"], [9, 2, 1, "", "update"]], "doctr.utils.visualization": [[9, 1, 1, "", "synthesize_page"], [9, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [1, 6, 7, 9, 13], "0": [1, 3, 5, 8, 9, 11, 14, 16], "00": 16, "01": 16, "0123456789": 5, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 5, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 5, "02": [], "02562": 7, "03": 16, "035": 16, "0361328125": 16, "04": [], "05": 16, "06": 16, "06640625": 16, "07": 16, "08": [8, 16], "09": 16, "0966796875": 16, "1": [3, 5, 6, 7, 8, 9, 11, 14, 16], "10": [5, 9, 16], "100": [5, 8, 9, 14, 16], "1000": 16, "101": 5, "1024": [7, 9, 11, 16], "104": 5, "106": 5, "108": 5, "1095": 14, "11": 16, "110": 9, "1107": 14, "114": 5, "115": [], "1156": 14, "116": 5, "118": 5, "11800h": 16, "11th": 16, "12": [3, 16], "120": 5, "123": 5, "126": 5, "1268": 14, "128": [7, 11, 15, 16], "13": [9, 16], "130": 5, "13068": 14, "131": 5, "1337891": 14, "1357421875": 16, "1396484375": 16, "14": 16, "1420": 16, "14470v1": 5, "149": 14, "15": 16, "150": [9, 16], "1552": 16, "16": [7, 15, 16], "1630859375": 16, "1684": 16, "16x16": 7, "17": 16, "1778": 16, "1782": 16, "18": 7, "185546875": 16, "1900": 16, "1910": 7, "19342": 14, "19370": 14, "195": 5, "19598": 14, "199": 16, "1999": 16, "2": [3, 4, 5, 6, 8, 16], "20": 16, "200": 9, "2000": 14, "2003": [4, 5], "2012": 5, "2013": [4, 5], "2015": 5, "2019": 4, "2023": [], "207901": 14, "21": 16, "2103": 5, "2186": 14, "21888": 14, "22": 16, "224": [7, 8], "225": 8, "22672": 14, "229": [8, 14], "23": 16, "233": 14, "234": 5, "236": [], "24": 16, "246": 14, "249": 14, "25": 16, "2504": 16, "255": [6, 7, 8, 9, 16], "256": 7, "257": 14, "26": 16, "26032": 14, "264": 11, "27": 16, "2700": 14, "2710": 16, "2749": 11, "28": 16, "287": 11, "29": 16, "296": 11, "299": 11, "2d": 16, "3": [3, 4, 6, 7, 8, 9, 15, 16], "30": 16, "300": 14, "3000": 14, "301": 11, "30595": 16, "30ghz": 16, "31": 7, "32": [5, 7, 8, 11, 14, 15, 16], "3232421875": 16, "33": [8, 16], "33402": 14, "33608": 14, "34": [7, 16], "340": 16, "3456": 16, "35": [], "3515625": 16, "36": [], "360": 14, "37": [5, 16], "38": 16, "39": 16, "4": [7, 8, 9, 16], "40": 16, "406": 8, "41": 16, "42": 16, "43": 16, "44": 16, "45": 16, "456": 8, "46": 16, "47": 16, "472": 14, "48": [5, 16], "485": 8, "49": 16, "49377": 14, "5": [5, 8, 9, 16], "50": [7, 14, 16], "51": 16, "51171875": 16, "512": 7, "52": [5, 16], "529": 16, "53": 16, "54": 16, "540": 16, "5478515625": 16, "55": 16, "56": 16, "57": 16, "58": 16, "580": 16, "5810546875": 16, "583": 16, "59": 16, "597": 16, "5k": [4, 5], "5m": 16, "6": [8, 16], "60": 8, "600": [7, 9, 16], "61": 16, "62": 16, "626": 14, "63": 16, "64": [7, 8, 16], "641": 16, "647": 14, "65": 16, "66": 16, "67": 16, "68": 16, "69": 16, "693": 11, "694": 11, "695": 11, "6m": 16, "7": 16, "70": [9, 16], "707470": 14, "71": 16, "7100000": 14, "7141797": 14, "7149": 14, "72": 16, "72dpi": 6, "73": 16, "73257": 14, "74": 16, "75": [8, 16], "7581382": 14, "76": 16, "77": 16, "772": 11, "772875": 14, "78": 16, "785": 11, "79": 16, "793533": 14, "796": 14, "798": 11, "7m": 16, "8": [3, 7, 8, 16], "80": 16, "800": [7, 9, 14, 16], "81": 16, "82": 16, "83": 16, "84": 16, "849": 14, "85": 16, "8564453125": 16, "857": 16, "85875": 14, "86": 16, "8603515625": 16, "87": 16, "8707": 14, "88": 16, "89": 16, "9": 16, "90": 16, "90k": 5, "90kdict32px": 5, "91": 16, "914085328578949": 16, "92": 16, "93": 16, "94": [5, 16], "95": [9, 16], "9578408598899841": 16, "96": 16, "97": [], "98": 16, "99": 16, "9949972033500671": 16, "A": [1, 2, 4, 5, 6, 7, 10, 15], "As": 2, "Be": 16, "Being": 1, "By": 12, "For": [1, 2, 3, 11, 16], "If": [2, 3, 6, 7, 11, 16], "In": [2, 5, 14], "It": [8, 13, 15], "Its": [4, 7], "No": [1, 16], "Of": 5, "Or": [], "The": [1, 2, 5, 6, 9, 12, 16], "Then": 7, "To": [2, 3, 12, 13, 16], "_": [1, 5, 7], "__call__": 16, "_build": 2, "_i": 9, "ab": 5, "abc": [], "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 5, "abdef": [5, 14], "abl": [14, 16], "about": [1, 14, 16], "abov": 16, "abstract": [], "abstractdataset": 5, "abus": 1, "accept": 1, "access": [4, 6, 14, 16], "account": [1, 13], "accur": 16, "accuraci": 9, "achiev": 15, "act": 1, "action": 1, "activ": 4, "ad": [2, 7, 8], "adapt": 1, "add": [8, 9, 13, 16], "add_hook": 16, "add_label": 9, "addit": [2, 3, 6], "addition": [2, 16], "address": [1, 6], "adjust": 8, "advanc": 1, "advantag": 15, "advis": 2, "aesthet": [4, 5], "affect": 1, "after": [13, 16], "ag": 1, "again": 7, "aggreg": [9, 14], "aggress": 1, "align": [1, 6], "all": [1, 2, 5, 6, 8, 9, 14, 16], "allow": 1, "along": 16, "alreadi": 2, "also": [1, 7, 13, 14, 16], "alwai": 14, "an": [1, 2, 4, 5, 6, 7, 9, 15, 16], "analysi": 6, "ancient_greek": 5, "andrej": [], "angl": [6, 8], "ani": [1, 5, 6, 7, 8, 9, 16], "annot": 5, "anot": 14, "anoth": [3, 7, 11, 14], "answer": 1, "anyascii": [], "anyon": 4, "anyth": [], "api": [2, 4], "apolog": 1, "apologi": 1, "app": 2, "appear": 1, "appli": [1, 5, 8], "applic": [4, 7], "appoint": 1, "appreci": 13, "appropri": [1, 2, 16], "ar": [1, 2, 3, 5, 6, 8, 9, 10, 14, 16], "arab": 5, "arabic_diacrit": 5, "arabic_lett": 5, "arabic_punctu": 5, "arbitrarili": [4, 7], "arch": [7, 13], "architectur": [4, 7, 13], "area": 16, "arg": [], "argument": [5, 6, 7, 9, 16], "around": 1, "arrai": [6, 8, 9], "art": 4, "artefact": [9, 10, 16], "artefact_typ": 6, "articl": [], "artifici": [4, 5], "arxiv": [5, 7], "asarrai": 9, "ascii_lett": 5, "aspect": [4, 7, 8, 16], "assess": 9, "assign": 9, "associ": 6, "assum": 7, "assume_straight_pag": [7, 16], "astyp": [7, 9, 16], "attack": 1, "attend": [4, 7], "attent": [1, 7], "autom": 4, "automat": 16, "autoregress": [4, 7], "avail": [1, 4, 8], "averag": [8, 16], "avoid": [1, 3], "aw": [4, 16], "awar": 16, "azur": 16, "b": [7, 9, 16], "b_j": 9, "back": 2, "backbon": 7, "backend": 16, "background": 14, "bangla": [], "bar": [], "bar_cod": 14, "baranovskij": [], "base": [4, 7], "baselin": [4, 7, 16], "batch": [5, 7, 8, 14, 16], "batch_siz": [5, 11, 14, 15], "bblanchon": 3, "bbox": 16, "becaus": 12, "been": [2, 9, 14, 16], "befor": [5, 7, 8, 16], "begin": 9, "behavior": [1, 16], "being": [9, 16], "belong": 16, "benchmark": 16, "best": 1, "better": [10, 16], "between": [8, 9, 16], "bgr": 6, "bilinear": 8, "bin_thresh": 16, "binar": [4, 7, 16], "binari": [6, 15, 16], "bit": 15, "blank": 9, "block": [9, 16], "block_1_1": 16, "blue": 9, "blur": 8, "bmvc": 5, "bn": 13, "bodi": [1, 16], "bool": [5, 6, 7, 8, 9], "boolean": [7, 16], "both": [4, 5, 8, 14, 16], "bottom": [7, 16], "bound": [5, 6, 7, 8, 9, 16], "box": [5, 6, 7, 8, 9, 14, 16], "box_thresh": 16, "brew": 3, "bright": 8, "broadcast": 9, "browser": [2, 4], "build": [2, 3], "built": 2, "byte": [6, 16], "c": [3, 6, 9], "c_j": 9, "cach": [2, 5, 12], "cache_sampl": 5, "cairo": 3, "call": [], "callabl": [5, 8], "can": [2, 3, 11, 12, 13, 14, 16], "capabl": [2, 10, 16], "case": [5, 9], "cf": 16, "cfg": 16, "challeng": 5, "challenge2_test_task12_imag": 5, "challenge2_test_task1_gt": 5, "challenge2_training_task12_imag": 5, "challenge2_training_task1_gt": 5, "chang": 12, "channel": [1, 2, 6, 8], "channel_prior": 3, "channelshuffl": 8, "charact": [4, 5, 6, 9, 14, 16], "charactergener": [5, 14], "characterist": 1, "charg": 16, "charset": 16, "chart": 6, "check": [2, 13, 16], "checkpoint": 7, "chip": 3, "christian": [], "ci": 2, "clarifi": 1, "clariti": 1, "class": [1, 5, 6, 8, 9, 16], "class_nam": 11, "classif": 14, "classif_mobilenet_v3_smal": 7, "classmethod": 6, "clear": 2, "clone": 3, "close": 2, "co": 13, "code": [4, 6], "codecov": 2, "colab": 10, "collate_fn": 5, "collect": 6, "color": [8, 9], "colorinvers": 8, "column": 6, "com": [1, 3, 6, 7, 13], "combin": 16, "come": 15, "command": 2, "comment": 1, "commit": 1, "common": [1, 8, 9, 15], "commun": 1, "compar": 4, "comparison": [9, 16], "competit": 5, "compil": [10, 16], "complaint": 1, "complementari": 9, "complet": 2, "compon": 16, "compos": [5, 16], "comprehens": 16, "comput": [5, 9, 15, 16], "conf_threshold": [], "confid": [6, 9, 16], "config": [3, 7], "configur": 7, "confus": 9, "consecut": [8, 16], "consequ": 1, "consid": [1, 2, 5, 6, 9, 16], "consist": 16, "consolid": [4, 5], "constant": 8, "construct": 1, "consum": 9, "contact": 1, "contain": [5, 14], "content": [5, 6, 9, 16], "context": 7, "contib": [], "continu": 1, "contrast": 8, "contrast_factor": 8, "contrib": [], "contribut": 1, "contributor": 2, "convers": 6, "convert": [6, 8], "convolut": 7, "cool": [], "coordin": [6, 16], "cord": [4, 5, 14, 16], "core": [9, 16], "corner": 16, "correct": 8, "correspond": [3, 6, 16], "could": 1, "counterpart": 9, "cover": 2, "coverag": 2, "cpu": [4, 11], "creat": 13, "crnn": [4, 7, 13], "crnn_mobilenet_v3_larg": [7, 13, 16], "crnn_mobilenet_v3_smal": [7, 15, 16], "crnn_vgg16_bn": [7, 11, 13, 16], "crop": [7, 8, 14, 16], "crop_orient": [], "crop_orientation_predictor": 7, "crop_param": [], "croporientationpredictor": 7, "cuda": 15, "currenc": 5, "current": [2, 16], "custom": [13, 16], "custom_crop_orientation_model": [], "custom_page_orientation_model": [], "customhook": 16, "cvit": 4, "czczup": 7, "czech": 5, "d": [5, 14], "danish": 5, "data": [4, 5, 6, 8, 9, 11, 13], "dataload": 14, "dataset": [7, 11, 16], "dataset_info": 5, "date": [11, 16], "db": 13, "db_mobilenet_v3_larg": [7, 13, 16], "db_resnet34": 16, "db_resnet50": [7, 11, 13, 16], "db_resnet50_rot": [], "dbnet": [4, 7], "deal": [], "decis": 1, "decod": 6, "decode_img_as_tensor": 6, "dedic": [], "deem": 1, "deep": [7, 16], "def": 16, "default": [3, 6, 9, 11, 12, 16], "defer": 14, "defin": [9, 15], "degre": 8, "degress": 6, "delet": 2, "delimit": 16, "delta": 8, "demo": [2, 4], "demonstr": 1, "depend": [2, 3, 4], "deploi": 2, "deploy": 4, "derogatori": 1, "describ": [7, 9], "descript": 10, "design": 8, "desir": 6, "det_arch": [7, 11, 13, 15], "det_b": [], "det_model": [11, 13], "det_param": 11, "det_predictor": [11, 16], "detail": [11, 16], "detect": [5, 9, 10, 11], "detect_languag": 7, "detect_orient": 7, "detection_predictor": [7, 16], "detection_task": [], "detectiondataset": [5, 14], "detectionmetr": 9, "detectionpredictor": [7, 11], "detector": [4, 7], "deterior": 7, "determin": 1, "dev": [2, 12], "develop": 3, "deviat": 8, "devic": 15, "dict": [6, 9, 16], "dictionari": [6, 9], "differ": 1, "differenti": [4, 7], "digit": [4, 5, 14], "dimens": [6, 9, 16], "dimension": 8, "direct": 5, "directli": [13, 16], "directori": [2, 12], "disabl": [1, 12, 16], "disable_crop_orient": [], "disable_page_orient": [], "disclaim": 16, "discuss": 2, "disparag": 1, "displai": [6, 9], "display_artefact": 9, "distribut": 8, "div": 16, "divers": 1, "divid": 6, "do": [2, 3, 7], "doc": [2, 6, 15, 16], "docartefact": [5, 14], "docstr": 2, "doctr": [3, 11, 12, 13, 14, 16], "doctr_cache_dir": 12, "doctr_multiprocessing_dis": 12, "document": [5, 7, 9, 10, 14, 16], "documentbuild": 16, "documentfil": [6, 13], "doesn": [], "don": [11, 16], "done": 8, "download": [5, 14], "downsiz": 7, "draw": [8, 9], "draw_proba": 9, "drop": 5, "drop_last": 5, "dtype": [6, 7, 8, 9, 15], "dual": [4, 5], "dummi": 13, "dummy_img": 16, "dummy_input": 15, "dure": 1, "dutch": 5, "dynam": 5, "dynamic_seq_length": 5, "e": [1, 2, 3, 6, 7], "each": [4, 5, 6, 7, 8, 9, 14, 16], "eas": 2, "easi": [4, 9, 13], "easili": [6, 9, 11, 13, 14, 16], "econom": 1, "edit": 1, "educ": 1, "effect": [], "effici": [2, 4, 5, 7], "either": [9, 16], "element": [5, 6, 7, 9, 16], "els": 2, "email": 1, "empathi": 1, "en": 16, "enabl": [5, 6], "enclos": 6, "encod": [4, 5, 6, 7, 16], "encode_sequ": 5, "encount": 2, "encrypt": 6, "end": [4, 5, 7, 9], "english": [5, 14], "enough": [2, 16], "ensur": 2, "entri": 5, "environ": [1, 12], "eo": 5, "equiv": 16, "estim": 7, "etc": 6, "ethnic": 1, "evalu": [14, 16], "event": 1, "everyon": 1, "everyth": [2, 16], "exact": [9, 16], "exampl": [1, 2, 4, 5, 7, 13, 16], "exchang": 15, "execut": 16, "exist": 13, "expand": 8, "expect": [6, 8, 9], "experi": 1, "explan": [1, 16], "explicit": 1, "exploit": [4, 7], "export": [6, 7, 9, 10, 16], "export_as_straight_box": [7, 16], "export_as_xml": 16, "export_model_to_onnx": 15, "express": [1, 8], "extens": 6, "extern": [1, 14], "extra": 3, "extract": [4, 5], "extractor": 7, "f_": 9, "f_a": 9, "factor": 8, "fair": 1, "fairli": 1, "fals": [5, 6, 7, 8, 9, 11, 16], "famili": 9, "faq": 1, "fascan": 13, "fast": [4, 5, 7], "fast_bas": [7, 16], "fast_smal": [7, 16], "fast_tini": [7, 16], "faster": [4, 7, 15], "fasterrcnn_mobilenet_v3_large_fpn": 7, "favorit": 16, "featur": [3, 7, 9, 10], "feedback": 1, "feel": [2, 13], "felix92": 13, "few": [3, 15, 16], "figsiz": 9, "figur": 9, "file": [2, 5], "final": 7, "find": [2, 3, 14], "fine": [], "finnish": 5, "first": [2, 5], "firsthand": 5, "fit": [7, 16], "flag": 16, "flip": 8, "float": [6, 8, 9, 15], "float32": [6, 7, 8, 15], "fn": 8, "focu": 13, "focus": [1, 5], "folder": 5, "follow": [1, 2, 3, 5, 8, 9, 11, 12, 13, 16], "font": [5, 9], "font_famili": [5, 9], "font_siz": 9, "foral": 9, "forc": 2, "forg": 3, "form": [4, 5, 16], "format": [6, 9, 11, 14, 15, 16], "forpost": [4, 5], "forum": 2, "found": [], "fp16": 15, "frac": 9, "framework": [3, 13, 14, 16], "free": [1, 2, 13], "french": [5, 11, 13, 16], "friendli": 4, "from": [1, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16], "from_hub": [7, 13], "from_imag": [6, 13], "from_pdf": 6, "from_url": 6, "full": [5, 9, 16], "function": [5, 8, 9], "funsd": [4, 5, 14, 16], "further": 14, "futur": 5, "g": [6, 7], "g_": 9, "g_x": 9, "gallagh": [], "gamma": 8, "gaussian": 8, "gaussianblur": 8, "gaussiannois": 8, "gdk": 3, "gen": 16, "gender": 1, "gener": [2, 4, 7], "generic_cyrillic_lett": [], "geometri": [4, 6, 16], "geq": 9, "german": [5, 11, 13], "get": 16, "git": 13, "github": [2, 3, 7, 13], "give": 1, "given": [5, 6, 8, 9, 16], "global": 7, "go": 16, "good": 15, "googl": 2, "googlevis": 4, "gpu": [4, 15], "gracefulli": 1, "graph": [4, 5, 6], "grayscal": 8, "ground": 9, "groung": 9, "group": [4, 16], "gt": 9, "gt_box": 9, "gt_label": 9, "gtk": 3, "guid": 2, "guidanc": 14, "gvision": 16, "h": [6, 7, 8], "h_": 9, "ha": [2, 5, 9, 14], "handl": [14, 16], "handwrit": 5, "handwritten": 14, "harass": 1, "hardwar": [], "harm": 1, "hat": 9, "have": [1, 2, 9, 11, 13, 14, 16], "head": [7, 16], "healthi": 1, "hebrew": 5, "height": 6, "hello": [9, 16], "help": 15, "here": [3, 8, 10, 14, 16], "hf": 7, "hf_hub_download": 7, "high": 6, "higher": [3, 5, 16], "hindi": [], "hindi_digit": 5, "hocr": 16, "homebrew": 3, "hook": 16, "horizont": [6, 8], "hous": 5, "how": [2, 11, 13, 14], "howev": 14, "hsv": 8, "html": [1, 2, 6, 16], "http": [1, 3, 5, 6, 7, 13, 16], "hub": 7, "hue": 8, "huggingfac": 7, "hw": 5, "i": [1, 2, 5, 6, 7, 8, 9, 12, 13, 14, 15], "i7": 16, "ibrahimov": [], "ic03": [4, 5, 14], "ic13": [4, 5, 14], "icdar": [4, 5], "icdar2019": 5, "id": 16, "ident": 1, "identifi": 4, "iiit": [4, 5], "iiit5k": [5, 14], "iiithw": [4, 5, 14], "imag": [4, 5, 6, 7, 8, 9, 13, 14, 16], "imagenet": 7, "imageri": 1, "images_90k_norm": 5, "img": [5, 8, 14], "img_cont": 6, "img_fold": [5, 14], "img_path": 6, "img_transform": 5, "imgur5k": [4, 5, 14], "imgur5k_annot": 5, "imlist": 5, "impact": 1, "implement": [5, 6, 7, 8, 9, 16], "import": [5, 6, 7, 8, 9, 11, 13, 14, 15, 16], "improv": 7, "inappropri": 1, "incid": 1, "includ": [1, 3, 5, 14, 15], "inclus": 1, "increas": 8, "independ": [], "index": [2, 6], "indic": 9, "individu": 1, "infer": [4, 7, 8], "inform": [1, 2, 4, 5, 14], "input": [2, 6, 7, 8, 15, 16], "input_crop": 7, "input_pag": [7, 9, 16], "input_shap": 15, "input_tensor": 7, "inspir": [1, 8], "instal": 13, "instanc": [1, 16], "instanti": [7, 16], "instead": [5, 6, 7], "insult": 1, "int": [5, 6, 8, 9], "int64": [8, 9], "integ": 9, "integr": [4, 13, 14], "intel": 16, "interact": [1, 6, 9], "interfac": 13, "interoper": 15, "interpol": 8, "interpret": [5, 6], "intersect": 9, "invert": 8, "investig": 1, "invis": 1, "involv": [1, 16], "io": 13, "iou": 9, "iou_thresh": 9, "iou_threshold": [], "irregular": [4, 7, 14], "isn": 5, "issu": [1, 2, 13], "italian": 5, "iter": [5, 8, 14, 16], "its": [6, 7, 8, 9, 14, 16], "itself": [7, 13], "j": 9, "jame": [], "job": 2, "join": 2, "jpeg": 8, "jpegqual": 8, "jpg": [5, 6, 13], "json": [5, 14, 16], "json_output": 16, "jump": 2, "just": 1, "kei": [4, 5], "kera": [7, 15], "kernel": [4, 7, 8], "kernel_shap": 8, "keywoard": 7, "keyword": [5, 6, 7, 9], "kie": [7, 11], "kie_predictor": [7, 11], "kiepredictor": 7, "kind": 1, "know": 2, "kwarg": [5, 6, 7, 9], "l": 9, "l_j": 9, "label": [5, 8, 9, 14], "label_fil": [5, 14], "label_fold": 5, "label_path": [5, 14], "labels_path": [5, 14], "ladder": 1, "lambda": 8, "lambdatransform": 8, "lang": 16, "languag": [1, 4, 5, 6, 7, 13, 16], "larg": [7, 13], "largest": 9, "last": [3, 5], "latenc": 7, "later": 2, "latest": [3, 16], "latin": 5, "layer": 15, "layout": 16, "lead": 1, "leader": 1, "learn": [1, 4, 7, 15, 16], "least": 3, "left": [9, 16], "legacy_french": 5, "length": [5, 16], "less": [15, 16], "level": [1, 5, 9, 16], "leverag": 10, "lf": 13, "libffi": 3, "librari": [2, 3, 10, 11], "light": 4, "lightweight": [], "like": 1, "limits_": 9, "line": [4, 7, 9, 16], "line_1_1": 16, "link": 11, "linknet": [4, 7], "linknet_resnet18": [7, 11, 16], "linknet_resnet18_rot": [], "linknet_resnet34": [7, 15, 16], "linknet_resnet50": [7, 16], "linux": [], "list": [5, 6, 8, 9, 13], "ll": 9, "load": [4, 5, 7], "load_state_dict": 11, "load_weight": 11, "loc_pr": 16, "local": [2, 4, 5, 7, 9, 14, 16], "localis": 5, "localizationconfus": 9, "locat": [2, 6, 16], "login": 7, "login_to_hub": [7, 13], "logo": [6, 14], "love": 13, "lower": [8, 9, 16], "m": [2, 9, 16], "m1": 3, "macbook": 3, "machin": 15, "maco": 3, "made": 4, "magc_resnet31": 7, "mai": [1, 2], "mail": 1, "main": 10, "maintain": 4, "mainten": 2, "make": [1, 2, 9, 12, 13, 15, 16], "mani": [14, 16], "manipul": 16, "map": [5, 7], "map_loc": 11, "mask_shap": 9, "master": [4, 7, 16], "match": [9, 16], "mathcal": 9, "matplotlib": [6, 9], "max": [5, 8, 9], "max_angl": 8, "max_area": 8, "max_char": [5, 14], "max_delta": 8, "max_gain": 8, "max_gamma": 8, "max_qual": 8, "max_ratio": 8, "maximum": [5, 8], "maxval": [7, 8], "mbox": 9, "mean": [8, 9, 11], "meaniou": 9, "meant": [6, 15], "measur": 16, "media": 1, "median": 7, "meet": 11, "member": 1, "memori": [9, 12, 15], "mention": 16, "merg": 5, "messag": 2, "meta": 16, "metadata": 15, "metal": 3, "method": [6, 8, 16], "metric": [9, 16], "middl": 16, "might": [15, 16], "min": 8, "min_area": 8, "min_char": [5, 14], "min_gain": 8, "min_gamma": 8, "min_qual": 8, "min_ratio": 8, "min_val": 8, "minde": [1, 3, 4, 7], "minim": [2, 4], "minimalist": [4, 7], "minimum": [3, 5, 8, 9, 16], "minval": 8, "miss": 3, "mistak": 1, "mixed_float16": 15, "mixed_precis": 15, "mjsynth": [4, 5, 14], "mnt": 5, "mobilenet": [7, 13], "mobilenet_v3_larg": 7, "mobilenet_v3_large_r": 7, "mobilenet_v3_smal": 7, "mobilenet_v3_small_crop_orient": [], "mobilenet_v3_small_orient": 7, "mobilenet_v3_small_page_orient": [], "mobilenet_v3_small_r": 7, "mobilenetv3": 7, "modal": [4, 5], "mode": 3, "model": [5, 9, 12, 14], "model_nam": [7, 13, 15], "model_path": 15, "moder": 1, "modif": 2, "modifi": [7, 12, 16], "modul": [6, 7, 8, 9, 16], "moment": [], "more": [2, 9, 14, 16], "moscardi": [], "most": 16, "mozilla": 1, "multi": [4, 7], "multilingu": [5, 13], "multipl": [5, 6, 8, 16], "multipli": 8, "multiprocess": 12, "my": 7, "my_awesome_model": 13, "my_hook": 16, "n": [5, 9], "name": [5, 7, 15, 16], "nation": 1, "natur": [1, 4, 5], "nb": [], "ndarrai": [5, 6, 8, 9], "necessari": [3, 11, 12], "need": [2, 3, 5, 9, 11, 12, 13, 16], "neg": 8, "nest": 16, "netraj": [], "network": [4, 5, 7, 15], "neural": [4, 5, 7, 15], "new": [2, 9], "next": [5, 14], "nois": 8, "noisi": [4, 5], "non": [4, 5, 6, 7, 8, 9], "none": [5, 6, 7, 8, 9, 16], "normal": [7, 8], "norwegian": 5, "note": [0, 2, 5, 7, 13, 15], "now": 2, "np": [7, 8, 9, 16], "num_output_channel": 8, "num_sampl": [5, 14], "num_work": 5, "number": [5, 8, 9, 16], "numpi": [6, 7, 9, 16], "o": 3, "obb": [], "obj_detect": 13, "object": [5, 9, 10, 16], "objectness_scor": [], "oblig": 1, "obtain": 16, "occupi": 15, "ocr": [4, 5, 7, 9, 13, 14], "ocr_carea": 16, "ocr_db_crnn": 9, "ocr_lin": 16, "ocr_pag": 16, "ocr_par": 16, "ocr_predictor": [7, 11, 13, 15, 16], "ocrdataset": [5, 14], "ocrmetr": 9, "ocrpredictor": [7, 11], "ocrx_word": 16, "offens": 1, "offici": [1, 7], "offlin": 1, "offset": 8, "onc": 16, "one": [2, 5, 7, 8, 11, 13, 16], "oneof": 8, "ones": [5, 8, 9], "onli": [2, 7, 8, 9, 13, 14, 15, 16], "onlin": 1, "onnx": [], "onnxruntim": [], "onnxtr": [], "opac": 8, "opacity_rang": 8, "open": [1, 2, 13, 15], "opinion": 1, "optic": [4, 16], "optim": 4, "option": [5, 7, 11], "order": [2, 5, 6, 8], "org": [1, 5, 7, 16], "organ": 6, "orient": [1, 6, 7, 16], "orientationpredictor": [], "other": [1, 2], "otherwis": [1, 6, 9], "our": [2, 7, 16], "out": [2, 7, 8, 9, 16], "outpout": 16, "output": [6, 8, 15], "output_s": [6, 8], "outsid": 12, "over": [3, 5, 9, 16], "overal": [1, 7], "overlai": 6, "overview": [], "overwrit": [], "overwritten": 13, "own": 4, "p": [8, 9, 16], "packag": [2, 4, 9, 12, 14], "pad": [5, 7, 8, 16], "page": [3, 5, 7, 9, 16], "page1": 6, "page2": 6, "page_1": 16, "page_idx": [6, 16], "page_orientation_predictor": [], "page_param": [], "pair": 9, "pango": 3, "paper": 7, "par_1_1": 16, "paragraph": 16, "paragraph_break": 16, "parallel": [], "param": [8, 16], "paramet": [4, 6, 7, 15], "pars": [4, 5], "parseq": [4, 7, 13, 16], "part": [5, 8, 16], "parti": 3, "partial": 16, "particip": 1, "pass": [5, 6, 7, 16], "password": 6, "patch": [7, 9], "path": [5, 6, 14], "path_to_checkpoint": 11, "path_to_custom_model": [], "path_to_pt": 11, "patil": [], "pattern": 1, "pdf": [6, 7, 10], "pdfpage": 6, "peopl": 1, "per": [8, 16], "perform": [4, 6, 7, 8, 9, 12, 15, 16], "period": 1, "permiss": 1, "permut": [4, 7], "persian_lett": 5, "person": [1, 14], "phase": 16, "photo": 14, "physic": [1, 6], "pick": 8, "pictur": 6, "pip": [2, 3], "pipelin": 16, "pixbuf": 3, "pixel": [6, 8, 16], "pleas": 2, "plot": 9, "plt": 9, "plug": 13, "plugin": 3, "png": 6, "point": 15, "polici": 12, "polish": 5, "polit": 1, "polygon": [5, 9, 16], "pool": 7, "portugues": 5, "posit": [1, 9], "possibl": [2, 9, 13, 16], "post": [1, 16], "postprocessor": 16, "potenti": 7, "power": 4, "ppageno": 16, "pre": [2, 7], "precis": [9, 16], "pred": 9, "pred_box": 9, "pred_label": 9, "predefin": 14, "predict": [6, 7, 9, 16], "predictor": [4, 6, 7, 11, 13, 15], "prefer": 14, "preinstal": [], "preprocessor": [11, 16], "prerequisit": 13, "present": 10, "preserv": [7, 8, 16], "preserve_aspect_ratio": [6, 7, 8, 11, 16], "pretrain": [4, 7, 9, 11, 15, 16], "pretrained_backbon": [7, 11], "print": 16, "prior": 5, "privaci": 1, "privat": 1, "probabl": 8, "problem": 2, "procedur": 8, "process": [2, 4, 6, 11, 16], "processor": 16, "produc": [10, 16], "product": 15, "profession": 1, "project": [2, 14], "promptli": 1, "proper": 2, "properli": 5, "provid": [1, 2, 4, 13, 14, 16], "public": [1, 4], "publicli": 16, "publish": 1, "pull": 13, "punctuat": 5, "pure": 5, "purpos": 2, "push_to_hf_hub": [7, 13], "py": 13, "pypdfium2": [3, 6], "pyplot": [6, 9], "python": 2, "python3": 13, "pytorch": [3, 4, 7, 8, 11, 13, 15, 16], "q": 2, "qr": 6, "qr_code": 14, "qualiti": 8, "question": 1, "quickli": 4, "quicktour": 10, "r": 16, "race": 1, "ramdisk": 5, "rand": [7, 8, 9, 15, 16], "random": [7, 8, 9, 16], "randomappli": 8, "randombright": 8, "randomcontrast": 8, "randomcrop": 8, "randomgamma": 8, "randomhorizontalflip": 8, "randomhu": 8, "randomjpegqu": 8, "randomli": 8, "randomres": [], "randomrot": 8, "randomsatur": 8, "randomshadow": 8, "rang": 8, "rassi": 13, "ratio": [7, 8, 16], "raw": [6, 9], "re": 15, "read": [4, 5, 7], "read_html": 6, "read_img": [], "read_img_as_numpi": 6, "read_img_as_tensor": 6, "read_pdf": 6, "readi": 15, "real": [4, 7, 8], "realli": [], "reason": [1, 4, 5], "rebuild": 2, "rebuilt": 2, "recal": [9, 16], "receipt": [4, 5, 16], "reco_arch": [7, 11, 13, 15], "reco_b": [], "reco_model": [11, 13], "reco_param": 11, "reco_predictor": 11, "recogn": 16, "recognit": [5, 9, 11], "recognition_predictor": [7, 16], "recognition_task": [5, 14], "recognitiondataset": [5, 14], "recognitionpredictor": [7, 11], "rectangular": 7, "red": 9, "reduc": [3, 8], "refer": [2, 3, 11, 13, 14, 16], "regardless": 1, "region": 16, "regroup": 9, "regular": 14, "reject": 1, "rel": [6, 8, 9, 16], "relat": 6, "releas": [0, 3], "relev": [], "religion": 1, "remov": 1, "render": [6, 16], "repo": 7, "repo_id": [7, 13], "report": 1, "repositori": [5, 7, 13], "repres": [1, 9, 15, 16], "represent": [4, 7], "request": [1, 13], "requir": [3, 8], "research": 4, "residu": 7, "resiz": [8, 16], "resnet": 7, "resnet18": [7, 13], "resnet31": 7, "resnet34": 7, "resnet50": [7, 13], "resolv": 6, "resolve_block": 16, "resolve_lin": 16, "resourc": 14, "respect": 1, "respons": 9, "rest": [2, 8, 9], "restrict": 12, "result": [2, 5, 6, 10, 13, 16], "return": 16, "reusabl": 16, "review": 1, "rgb": [6, 8], "rgb_mode": 6, "rgb_output": 6, "right": [1, 7, 9], "roboflow": [], "robust": [4, 5], "root": 5, "rotat": [5, 6, 7, 8, 9, 14, 16], "run": [2, 3, 7], "same": [2, 6, 9, 14, 16], "sampl": [5, 14, 16], "sample_transform": 5, "sanjin": [], "sar": [4, 7], "sar_resnet31": [7, 16], "satur": 8, "save": [7, 14], "scale": [6, 7, 8, 9], "scale_rang": [], "scan": [4, 5], "scene": [4, 5, 7], "score": 9, "script": [2, 14], "seamless": 4, "seamlessli": [4, 16], "search": 7, "searchabl": 10, "sec": 16, "second": 16, "section": [11, 13, 15, 16], "secur": [1, 12], "see": [1, 2], "seen": 16, "segment": [4, 7, 16], "self": 16, "semant": [4, 7], "send": 16, "sens": 9, "sensit": 14, "separ": 16, "sequenc": [4, 5, 6, 7, 9, 16], "sequenti": [8, 16], "seri": 1, "seriou": 1, "set": [1, 3, 5, 7, 9, 12, 16], "set_global_polici": 15, "sever": [6, 8, 16], "sex": 1, "sexual": 1, "shade": 8, "shape": [4, 6, 7, 8, 9, 16], "share": [12, 14], "shift": 8, "shm": 12, "should": [2, 5, 6, 8, 9], "show": [4, 6, 7, 9, 11, 13], "showcas": 2, "shuffl": [5, 8], "side": 9, "signatur": 6, "signific": 14, "simpl": [4, 7], "simpler": 7, "sinc": [5, 14], "singl": [1, 2, 4, 5], "single_img_doc": [], "size": [1, 5, 6, 8, 9, 16], "skew": 16, "slack": 2, "slightli": 7, "small": [2, 7], "smallest": 6, "snapshot_download": 7, "snippet": 16, "so": [2, 3, 5, 7, 13, 14], "social": 1, "socio": 1, "some": [3, 10, 13, 14], "someth": 2, "somewher": 2, "soon": 15, "sort": 1, "sourc": [5, 6, 7, 8, 9, 13], "space": [1, 16], "span": 16, "spanish": 5, "spatial": [4, 5, 6, 9], "specif": [2, 3, 9, 11, 14, 16], "specifi": [1, 5, 6], "speed": [4, 7], "sphinx": 2, "sroie": [4, 5, 14], "stabl": 3, "stackoverflow": 2, "stage": 4, "standalon": [], "standard": 8, "start": 5, "state": [4, 9], "static": 9, "statist": [], "statu": 1, "std": [8, 11], "step": 12, "still": 16, "str": [5, 6, 7, 8, 9], "straight": [5, 7, 14, 16], "straighten": [], "straighten_pag": 7, "straigten_pag": [], "stream": 6, "street": [4, 5], "strict": 3, "strictli": 9, "string": [5, 6, 9, 16], "strive": 3, "strong": [4, 7], "structur": [15, 16], "subset": [5, 16], "suggest": [2, 13], "sum": 9, "summari": 9, "support": [15, 16], "sustain": 1, "svhn": [4, 5, 14], "svt": [5, 14], "swedish": 5, "symmetr": [7, 8, 16], "symmetric_pad": [7, 8, 16], "synthes": 9, "synthesize_pag": 9, "synthet": 4, "synthtext": [4, 5, 14], "system": 16, "t": [2, 5, 11, 16], "tabl": 13, "take": [1, 5, 16], "target": [5, 6, 8, 9, 14], "target_s": 5, "task": [4, 5, 7, 13, 14, 16], "task2": 5, "team": 3, "techminde": 3, "templat": [2, 4], "tensor": [5, 6, 8, 16], "tensorflow": [3, 4, 6, 7, 8, 11, 13, 15, 16], "tensorspec": 15, "term": 1, "test": [5, 14], "test_set": 5, "text": [5, 6, 7, 9, 14], "text_output": 16, "textmatch": 9, "textnet": 7, "textnet_bas": 7, "textnet_smal": 7, "textnet_tini": 7, "textract": [4, 16], "textstylebrush": [4, 5], "textual": [4, 5, 6, 7, 16], "tf": [3, 6, 7, 8, 13, 15], "than": [2, 3, 9, 13], "thank": 2, "thei": [1, 9], "them": [3, 5, 16], "thi": [1, 2, 3, 5, 9, 11, 12, 13, 14, 15, 16], "thing": [15, 16], "third": 3, "those": [1, 3, 6, 16], "threaten": 1, "threshold": 16, "through": [1, 8, 14], "tilman": 13, "time": [1, 4, 7, 9, 14], "tini": 7, "titl": [6, 16], "tm": 16, "tmp": 12, "togeth": [2, 6], "tograi": 8, "tool": 14, "top": [9, 16], "topic": 2, "torch": [3, 8, 11, 13, 15], "torchvis": 8, "total": 11, "toward": [1, 3], "train": [2, 5, 7, 8, 13, 14, 15, 16], "train_it": [5, 14], "train_load": [5, 14], "train_pytorch": 13, "train_set": [5, 14], "train_tensorflow": 13, "trainabl": [4, 7], "tranform": 8, "transcrib": 16, "transfer": [4, 5], "transfo": 8, "transform": [4, 5, 7], "translat": 1, "troll": 1, "true": [5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16], "truth": 9, "tune": 15, "tupl": [5, 6, 8, 9], "two": [6, 12], "txt": 5, "type": [6, 9, 13, 15, 16], "typic": 16, "u": [1, 2], "ucsd": 5, "udac": 2, "uint8": [6, 7, 9, 16], "ukrainian": [], "unaccept": 1, "underli": [14, 16], "underneath": 6, "understand": [4, 5, 16], "unidecod": 9, "uniform": [7, 8], "uniformli": 8, "uninterrupt": [6, 16], "union": 9, "unit": [], "unittest": 2, "unlock": 6, "unoffici": 7, "unprofession": 1, "unsolicit": 1, "unsupervis": 4, "unwelcom": 1, "up": [7, 16], "updat": 9, "upgrad": 2, "upper": [5, 8], "uppercas": 14, "url": 6, "us": [1, 2, 3, 5, 7, 9, 11, 12, 13, 16], "usabl": 16, "usag": [12, 15], "use_broadcast": 9, "use_polygon": [5, 9, 14], "useabl": 16, "user": [3, 4, 6, 10], "utf": 16, "util": 15, "v1": 13, "v3": [7, 13, 16], "valid": 14, "valu": [2, 6, 8, 16], "valuabl": 4, "variabl": 12, "varieti": 5, "veri": 7, "verma": [], "version": [1, 2, 3, 15, 16], "vgg": 7, "vgg16": 13, "vgg16_bn_r": 7, "via": 1, "video": [], "vietnames": 5, "view": [4, 5], "viewpoint": 1, "violat": 1, "visibl": 1, "vision": [4, 5, 7], "visiondataset": 5, "visiontransform": 7, "visual": 4, "visualize_pag": 9, "vit_": 7, "vit_b": 7, "vitstr": [4, 7, 15], "vitstr_bas": [7, 16], "vitstr_smal": [7, 11, 15, 16], "viz": [], "vocab": [11, 13, 14, 16], "vocabulari": [5, 11, 13], "w": [6, 7, 8, 9], "w3": 16, "wa": 1, "wai": [1, 4, 14], "want": [2, 15, 16], "warmup": 16, "wasn": 2, "we": [1, 2, 3, 4, 6, 8, 13, 14, 15, 16], "weasyprint": 6, "web": [2, 6], "websit": 5, "weight": 11, "welcom": 1, "well": [1, 15], "were": [1, 6, 16], "what": 1, "when": [1, 2, 7], "whenev": 2, "where": [2, 6, 8, 9], "whether": [2, 5, 6, 8, 9, 14, 16], "which": [1, 7, 12, 14, 16], "whichev": 3, "while": [8, 16], "why": 1, "width": 6, "wiki": 1, "wildreceipt": [4, 5, 14], "window": [3, 7, 9], "wish": 2, "within": 1, "without": [1, 5, 7], "wonder": 2, "word": [4, 5, 7, 9, 16], "word_1_1": 16, "word_1_2": 16, "word_1_3": 16, "wordgener": [5, 14], "words_onli": 9, "work": [12, 16], "worker": 5, "workflow": 2, "worklow": 2, "world": [9, 16], "worth": 7, "wrap": 16, "wrapper": [5, 8], "write": 12, "written": [1, 6], "www": [1, 6, 16], "x": [6, 8, 9], "x_ascend": 16, "x_descend": 16, "x_i": 9, "x_size": 16, "x_wconf": 16, "xhtml": 16, "xmax": 6, "xmin": 6, "xml": 16, "xml_bytes_str": 16, "xml_element": 16, "xml_output": 16, "xmln": 16, "y": 9, "y_i": 9, "y_j": 9, "yet": [], "ymax": 6, "ymin": 6, "yolov8": [], "you": [2, 3, 5, 6, 7, 11, 12, 13, 14, 15, 16], "your": [2, 4, 6, 9, 16], "yoursit": 6, "yugesh": [], "zero": [8, 9], "zoo": [], "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 5, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 5, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": 5, "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 5, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 5, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": [], "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 5, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 5, "\u00e4\u00f6\u00e4\u00f6": 5, "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 5, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": 5, "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": 5, "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": 5, "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 5, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": [], "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": [], "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": 5, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 5, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 5, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 5, "\u067e\u0686\u06a2\u06a4\u06af": 5, "\u0905": [], "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": [], "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": [], "\u0950": [], "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": [], "\u09bd": [], "\u09ce": [], "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": []}, "titles": ["Changelog", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 2, "0": 0, "01": 0, "02": 0, "03": 0, "04": [], "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 1], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 1], "2021": 0, "2022": 0, "2023": 0, "2024": 0, "21": [], "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 1], "31": 0, "4": [0, 1], "5": 0, "6": 0, "7": 0, "8": 0, "9": [], "advanc": 16, "approach": 16, "architectur": 16, "arg": [5, 6, 7, 8, 9], "artefact": 6, "artefactdetect": [], "attribut": 1, "avail": [14, 16], "aw": 12, "ban": 1, "block": 6, "bug": 2, "changelog": 0, "choos": [14, 16], "classif": [7, 13], "code": [1, 2], "codebas": 2, "commit": 2, "commun": 13, "compos": 8, "conda": 3, "conduct": 1, "connect": 2, "continu": 2, "contrib": [], "contribut": 2, "contributor": 1, "convent": 13, "correct": 1, "coven": 1, "custom": [5, 11], "data": 14, "dataload": 5, "dataset": [4, 5, 14], "detect": [4, 7, 13, 14, 16], "develop": 2, "do": 16, "doctr": [2, 4, 5, 6, 7, 8, 9, 10, 15], "document": [2, 4, 6], "end": 16, "enforc": 1, "evalu": 9, "export": 15, "factori": 7, "featur": [2, 4], "feedback": 2, "file": 6, "from": 13, "gener": [5, 14], "git": 3, "guidelin": 1, "half": 15, "hub": 13, "huggingfac": 13, "i": 16, "infer": 15, "instal": [2, 3], "integr": 2, "io": 6, "lambda": 12, "let": 2, "line": 6, "linux": 3, "load": [11, 13, 14], "loader": 5, "main": 4, "mode": 2, "model": [4, 7, 11, 13, 15, 16], "modifi": 2, "modul": [], "name": 13, "notebook": 10, "object": 14, "ocr": 16, "onli": 3, "onnx": 15, "optim": 15, "option": 16, "orient": [], "our": 1, "output": 16, "own": [11, 14], "packag": 3, "page": 6, "perman": 1, "pipelin": [], "pledg": 1, "precis": 15, "predictor": 16, "prepar": 15, "prerequisit": 3, "pretrain": 13, "push": 13, "python": 3, "qualiti": 2, "question": 2, "read": 6, "readi": 14, "recognit": [4, 7, 13, 14, 16], "report": 2, "request": 2, "resourc": [], "respons": 1, "return": [5, 6, 7, 9], "right": 16, "scope": 1, "share": 13, "should": 16, "stage": 16, "standard": 1, "structur": [2, 6], "style": 2, "support": [4, 5, 8], "synthet": [5, 14], "task": 9, "temporari": 1, "test": 2, "text": [4, 16], "train": 11, "transform": 8, "two": 16, "unit": 2, "us": [14, 15], "util": 9, "v0": 0, "verif": 2, "via": 3, "visual": 9, "vocab": 5, "warn": 1, "what": 16, "word": 6, "your": [11, 13, 14, 15], "zoo": [4, 7]}}) \ No newline at end of file diff --git a/v0.8.1/transforms.html b/v0.8.1/transforms.html deleted file mode 100644 index 85e94d8a76..0000000000 --- a/v0.8.1/transforms.html +++ /dev/null @@ -1,684 +0,0 @@ - - - - - - - - - - - - - doctr.transforms - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    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:

    -
    -
    -class doctr.transforms.Resize(output_size: Tuple[int, int], method: str = 'bilinear', preserve_aspect_ratio: bool = False, symmetric_pad: bool = False)[source]
    -

    Resizes a tensor to a target size

    -
    -
    Example::
    >>> from doctr.transforms import Resize
    ->>> import tensorflow as tf
    ->>> transfo = Resize((32, 32))
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • output_size – expected output size

    • -
    • method – interpolation method

    • -
    • preserve_aspect_ratio – if True, preserve aspect ratio and pad the rest with zeros

    • -
    • symmetric_pad – if True while preserving aspect ratio, the padding will be done symmetrically

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.Normalize(mean: Tuple[float, float, float], std: Tuple[float, float, float])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • mean – average value per channel

    • -
    • std – standard deviation per channel

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.LambdaTransformation(fn: Callable[[Tensor], Tensor])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import LambdaTransformation
    ->>> import tensorflow as tf
    ->>> transfo = LambdaTransformation(lambda x: x/ 255.)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    fn – the function to be applied to the input tensor

    -
    -
    -
    - -
    -
    -class doctr.transforms.ToGray[source]
    -

    Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ToGray()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    - -
    -
    -class doctr.transforms.ColorInversion(min_val: float = 0.5)[source]
    -

    Applies the following tranformation to a tensor (image or batch of images): -convert to grayscale, colorize (shift 0-values randomly), and then invert colors

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ColorInversion(min_val=0.6)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    min_val – range [min_val, 1] to colorize RGB pixels

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomBrightness(max_delta: float = 0.3)[source]
    -

    Randomly adjust brightness of a tensor (batch of images or image) by adding a delta -to all pixels

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Brightness()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    • -
    • p – probability to apply transformation

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomContrast(delta: float = 0.3)[source]
    -

    Randomly adjust contrast of a tensor (batch of images or image) by adjusting -each pixel: (img - mean) * contrast_factor + mean.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Contrast()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomSaturation(delta: float = 0.5)[source]
    -

    Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and -increasing saturation by a factor.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Saturation()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomHue(max_delta: float = 0.3)[source]
    -

    Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Hue()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomGamma(min_gamma: float = 0.5, max_gamma: float = 1.5, min_gain: float = 0.8, max_gain: float = 1.2)[source]
    -

    randomly performs gamma correction for a tensor (batch of images or image)

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Gamma()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_gamma – non-negative real number, lower bound for gamma param

    • -
    • max_gamma – non-negative real number, upper bound for gamma

    • -
    • min_gain – lower bound for constant multiplier

    • -
    • max_gain – upper bound for constant multiplier

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomJpegQuality(min_quality: int = 60, max_quality: int = 100)[source]
    -

    Randomly adjust jpeg quality of a 3 dimensional RGB image

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = JpegQuality()
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_quality – int between [0, 100]

    • -
    • max_quality – int between [0, 100]

    • -
    -
    -
    -
    - -
    -
    -

    Composing transformations

    -

    It is common to require several transformations to be performed consecutively.

    -
    -
    -class doctr.transforms.Compose(transforms: List[Callable[[Any], Any]])[source]
    -

    Implements a wrapper that will apply transformations sequentially

    -
    -
    Example::
    >>> from doctr.transforms import Compose, Resize
    ->>> import tensorflow as tf
    ->>> transfos = Compose([Resize((32, 32))])
    ->>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformation modules

    -
    -
    -
    - -
    -
    -class doctr.transforms.OneOf(transforms: List[Callable[[Any], Any]])[source]
    -

    Randomly apply one of the input transformations

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = OneOf([JpegQuality(), Gamma()])
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformations, one only will be picked

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomApply(transform: Callable[[Any], Any], p: float = 0.5)[source]
    -

    Apply with a probability p the input transformation

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = RandomApply(Gamma(), p=.5)
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • transform – transformation to apply

    • -
    • p – probability to apply

    • -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.8.1/using_doctr/custom_models_training.html b/v0.8.1/using_doctr/custom_models_training.html index e7bfa08b73..238645ae9e 100644 --- a/v0.8.1/using_doctr/custom_models_training.html +++ b/v0.8.1/using_doctr/custom_models_training.html @@ -14,7 +14,7 @@ - + Train your own model - docTR documentation @@ -545,7 +545,7 @@

    Loading your custom trained model - + diff --git a/v0.8.1/using_doctr/running_on_aws.html b/v0.8.1/using_doctr/running_on_aws.html index 012b68298e..a6a1e4e5ba 100644 --- a/v0.8.1/using_doctr/running_on_aws.html +++ b/v0.8.1/using_doctr/running_on_aws.html @@ -14,7 +14,7 @@ - + AWS Lambda - docTR documentation @@ -356,7 +356,7 @@

    AWS Lambda - + diff --git a/v0.8.1/using_doctr/sharing_models.html b/v0.8.1/using_doctr/sharing_models.html index 2df93350f0..24408f1601 100644 --- a/v0.8.1/using_doctr/sharing_models.html +++ b/v0.8.1/using_doctr/sharing_models.html @@ -14,7 +14,7 @@ - + Share your model with the community - docTR documentation @@ -538,7 +538,7 @@

    Recognition - + diff --git a/v0.8.1/using_doctr/using_contrib_modules.html b/v0.8.1/using_doctr/using_contrib_modules.html index 50598dae5d..0c5fffdf9f 100644 --- a/v0.8.1/using_doctr/using_contrib_modules.html +++ b/v0.8.1/using_doctr/using_contrib_modules.html @@ -14,7 +14,7 @@ - + Integrate contributions into your pipeline - docTR documentation @@ -415,7 +415,7 @@

    ArtefactDetection - + diff --git a/v0.8.1/using_doctr/using_datasets.html b/v0.8.1/using_doctr/using_datasets.html index 640244db19..dfeb445a8f 100644 --- a/v0.8.1/using_doctr/using_datasets.html +++ b/v0.8.1/using_doctr/using_datasets.html @@ -14,7 +14,7 @@ - + Choose a ready to use dataset - docTR documentation @@ -623,7 +623,7 @@

    Data Loading - + diff --git a/v0.8.1/using_doctr/using_model_export.html b/v0.8.1/using_doctr/using_model_export.html index 46cccf92cd..ff37d657a6 100644 --- a/v0.8.1/using_doctr/using_model_export.html +++ b/v0.8.1/using_doctr/using_model_export.html @@ -14,7 +14,7 @@ - + Preparing your model for inference - docTR documentation @@ -436,7 +436,7 @@

    Using your ONNX exported model in docTR - + diff --git a/v0.8.1/using_doctr/using_models.html b/v0.8.1/using_doctr/using_models.html index aa71c2af71..d2e0000fa9 100644 --- a/v0.8.1/using_doctr/using_models.html +++ b/v0.8.1/using_doctr/using_models.html @@ -14,7 +14,7 @@ - + Choosing the right model - docTR documentation @@ -1198,7 +1198,7 @@

    Advanced options - + diff --git a/v0.8.1/utils.html b/v0.8.1/utils.html deleted file mode 100644 index e2f223f06a..0000000000 --- a/v0.8.1/utils.html +++ /dev/null @@ -1,574 +0,0 @@ - - - - - - - - - - - - - doctr.utils - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.utils

    -

    This module regroups non-core features that are complementary to the rest of the package.

    -
    -

    Visualization

    -

    Easy-to-use functions to make sense of your model’s predictions.

    -
    -
    -doctr.utils.visualization.visualize_page(page: Dict[str, Any], image: ndarray, words_only: bool = True, display_artefacts: bool = True, scale: float = 10, interactive: bool = True, add_labels: bool = True, **kwargs: Any) Figure[source]
    -

    Visualize a full page with predicted blocks, lines and words

    -
    -
    Example::
    >>> import numpy as np
    ->>> import matplotlib.pyplot as plt
    ->>> from doctr.utils.visualization import visualize_page
    ->>> from doctr.models import ocr_db_crnn
    ->>> model = ocr_db_crnn(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([[input_page]])
    ->>> visualize_page(out[0].pages[0].export(), input_page)
    ->>> plt.show()
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • page – the exported Page of a Document

    • -
    • image – np array of the page, needs to have the same shape than page[‘dimensions’]

    • -
    • words_only – whether only words should be displayed

    • -
    • display_artefacts – whether artefacts should be displayed

    • -
    • scale – figsize of the largest windows side

    • -
    • interactive – whether the plot should be interactive

    • -
    • add_labels – for static plot, adds text labels on top of bounding box

    • -
    -
    -
    -
    - -
    -
    -

    Task evaluation

    -

    Implementations of task-specific metrics to easily assess your model performances.

    -
    -
    -class doctr.utils.metrics.TextMatch[source]
    -

    Implements text match metric (word-level accuracy) for recognition task.

    -

    The raw aggregated metric is computed as follows:

    -
    -
    -\[\forall X, Y \in \mathcal{W}^N, -TextMatch(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N f_{Y_i}(X_i)\]
    -
    -

    with the indicator function \(f_{a}\) defined as:

    -
    -
    -\[\begin{split}\forall a, x \in \mathcal{W}, -f_a(x) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } x = a \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{W}\) is the set of all possible character sequences, -\(N\) is a strictly positive integer.

    -
    -
    Example::
    >>> from doctr.utils import TextMatch
    ->>> metric = TextMatch()
    ->>> metric.update(['Hello', 'world'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    -summary() Dict[str, float][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a dictionary with the exact match score for the raw data, its lower-case counterpart, its unidecode -counterpart and its lower-case unidecode counterpart

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.LocalizationConfusion(iou_thresh: float = 0.5, rotated_bbox: bool = False, mask_shape: Tuple[int, int] = (1024, 1024))[source]
    -

    Implements common confusion metrics and mean IoU for localization evaluation.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall Y \in \mathcal{B}^N, \forall X \in \mathcal{B}^M, \\ -Recall(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -Precision(X, Y) = \frac{1}{M} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -meanIoU(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(X_i, Y_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(g_{X}\) defined as:

    -
    -
    -\[\begin{split}\forall y \in \mathcal{B}, -g_X(y) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } y\mbox{ has been assigned to any }(X_i)_i\mbox{ with an }IoU \geq 0.5 \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import LocalizationConfusion
    ->>> metric = LocalizationConfusion(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]))
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[float | None, float | None, float | None][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall, precision and meanIoU scores

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.OCRMetric(iou_thresh: float = 0.5, rotated_bbox: bool = False, mask_shape: Tuple[int, int] = (1024, 1024))[source]
    -

    Implements end-to-end OCR metric.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall (B, L) \in \mathcal{B}^N \times \mathcal{L}^N, -\forall (\hat{B}, \hat{L}) \in \mathcal{B}^M \times \mathcal{L}^M, \\ -Recall(B, \hat{B}, L, \hat{L}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -Precision(B, \hat{B}, L, \hat{L}) = \frac{1}{M} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(h_{B, L}\) defined as:

    -
    -
    -\[\begin{split}\forall (b, l) \in \mathcal{B} \times \mathcal{L}, -h_{B,L}(b, l) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ - & IoU \geq 0.5 \mbox{ and that for this assignment, } l = L_j\\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(\mathcal{L}\) is the set of possible character sequences, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import OCRMetric
    ->>> metric = OCRMetric(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]),
    -['hello'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[Dict[str, float | None], Dict[str, float | None], float | None][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall & precision for each string comparison flexibility and the mean IoU

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.9.0/_modules/doctr/datasets/cord.html b/v0.9.0/_modules/doctr/datasets/cord.html index 354f0062c2..85f1a47a08 100644 --- a/v0.9.0/_modules/doctr/datasets/cord.html +++ b/v0.9.0/_modules/doctr/datasets/cord.html @@ -13,7 +13,7 @@ - + doctr.datasets.cord - docTR documentation @@ -447,7 +447,7 @@

    Source code for doctr.datasets.cord

         
       
    - + diff --git a/v0.9.0/_modules/doctr/datasets/core.html b/v0.9.0/_modules/doctr/datasets/core.html deleted file mode 100644 index b3dcc29ff9..0000000000 --- a/v0.9.0/_modules/doctr/datasets/core.html +++ /dev/null @@ -1,417 +0,0 @@ - - - - - - - - - - - - doctr.datasets.core - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.core

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from pathlib import Path
    -from zipfile import ZipFile
    -from typing import List, Any, Optional, Tuple
    -import tensorflow as tf
    -
    -from doctr.models.utils import download_from_url
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset:
    -
    -    data: List[Any] = []
    -
    -    def __len__(self):
    -        return len(self.data)
    -
    -    def __getitem__(
    -        self,
    -        index: int
    -    ) -> Tuple[tf.Tensor, Any]:
    -
    -        img_name, target = self.data[index]
    -        # Read image
    -        img = tf.io.read_file(os.path.join(self.root, img_name))
    -        img = tf.image.decode_jpeg(img, channels=3)
    -        if self.sample_transforms is not None:
    -            img = self.sample_transforms(img)
    -
    -        return img, target
    -
    -    def extra_repr(self) -> str:
    -        return ""
    -
    -    def __repr__(self) -> str:
    -        return f"{self.__class__.__name__}({self.extra_repr()})"
    -
    -    @staticmethod
    -    def collate_fn(samples: List[Tuple[tf.Tensor, Any]]) -> Tuple[tf.Tensor, List[Any]]:
    -
    -        images, targets = zip(*samples)
    -        images = tf.stack(images, axis=0)
    -
    -        return images, list(targets)
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset): - """Implements an abstract dataset - - Args: - url: URL of the dataset - file_name: name of the file once downloaded - file_hash: expected SHA256 of the file - extract_archive: whether the downloaded file is an archive to be extracted - download: whether the dataset should be downloaded if not present on disk - overwrite: whether the archive should be re-extracted - """ - - def __init__( - self, - url: str, - file_name: Optional[str] = None, - file_hash: Optional[str] = None, - extract_archive: bool = False, - download: bool = False, - overwrite: bool = False, - ) -> None: - - dataset_cache = os.path.join(os.path.expanduser('~'), '.cache', 'doctr', 'datasets') - - file_name = file_name if isinstance(file_name, str) else os.path.basename(url) - # Download the file if not present - archive_path = os.path.join(dataset_cache, file_name) - - if not os.path.exists(archive_path) and not download: - raise ValueError("the dataset needs to be downloaded first with download=True") - - archive_path = download_from_url(url, file_name, file_hash, cache_subdir='datasets') - - # Extract the archive - if extract_archive: - archive_path = Path(archive_path) - dataset_path = archive_path.parent.joinpath(archive_path.stem) - if not dataset_path.is_dir() or overwrite: - with ZipFile(archive_path, 'r') as f: - f.extractall(path=dataset_path) - - # List images - self._root = dataset_path if extract_archive else archive_path - self.data: List[Any] = []
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.9.0/_modules/doctr/datasets/datasets/tensorflow.html b/v0.9.0/_modules/doctr/datasets/datasets/tensorflow.html deleted file mode 100644 index a236abd9fe..0000000000 --- a/v0.9.0/_modules/doctr/datasets/datasets/tensorflow.html +++ /dev/null @@ -1,356 +0,0 @@ - - - - - - - - - - - - doctr.datasets.datasets.tensorflow - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.datasets.datasets.tensorflow

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import os
    -from typing import List, Any, Tuple
    -import tensorflow as tf
    -
    -from .base import _AbstractDataset, _VisionDataset
    -
    -
    -__all__ = ['AbstractDataset', 'VisionDataset']
    -
    -
    -class AbstractDataset(_AbstractDataset):
    -
    -    def _read_sample(self, index: int) -> Tuple[tf.Tensor, Any]:
    -        img_name, target = self.data[index]
    -        # Read image
    -        img = tf.io.read_file(os.path.join(self.root, img_name))
    -        img = tf.image.decode_jpeg(img, channels=3)
    -
    -        return img, target
    -
    -    @staticmethod
    -    def collate_fn(samples: List[Tuple[tf.Tensor, Any]]) -> Tuple[tf.Tensor, List[Any]]:
    -
    -        images, targets = zip(*samples)
    -        images = tf.stack(images, axis=0)
    -
    -        return images, list(targets)
    -
    -
    -
    -[docs] -class VisionDataset(AbstractDataset, _VisionDataset): - pass
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.9.0/_modules/doctr/datasets/detection.html b/v0.9.0/_modules/doctr/datasets/detection.html index faf9256c89..706b89a562 100644 --- a/v0.9.0/_modules/doctr/datasets/detection.html +++ b/v0.9.0/_modules/doctr/datasets/detection.html @@ -13,7 +13,7 @@ - + doctr.datasets.detection - docTR documentation @@ -424,7 +424,7 @@

    Source code for doctr.datasets.detection

         
       
    - + diff --git a/v0.9.0/_modules/doctr/datasets/doc_artefacts.html b/v0.9.0/_modules/doctr/datasets/doc_artefacts.html index 886999868b..dc8e8f9c29 100644 --- a/v0.9.0/_modules/doctr/datasets/doc_artefacts.html +++ b/v0.9.0/_modules/doctr/datasets/doc_artefacts.html @@ -13,7 +13,7 @@ - + doctr.datasets.doc_artefacts - docTR documentation @@ -408,7 +408,7 @@

    Source code for doctr.datasets.doc_artefacts

       
    - + diff --git a/v0.9.0/_modules/doctr/datasets/funsd.html b/v0.9.0/_modules/doctr/datasets/funsd.html index 60f7e51592..6f7ab121f0 100644 --- a/v0.9.0/_modules/doctr/datasets/funsd.html +++ b/v0.9.0/_modules/doctr/datasets/funsd.html @@ -13,7 +13,7 @@ - + doctr.datasets.funsd - docTR documentation @@ -438,7 +438,7 @@

    Source code for doctr.datasets.funsd

         
       
    - + diff --git a/v0.9.0/_modules/doctr/datasets/generator/tensorflow.html b/v0.9.0/_modules/doctr/datasets/generator/tensorflow.html index fecf8b2d82..814dc0822d 100644 --- a/v0.9.0/_modules/doctr/datasets/generator/tensorflow.html +++ b/v0.9.0/_modules/doctr/datasets/generator/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.datasets.generator.tensorflow - docTR documentation @@ -389,7 +389,7 @@

    Source code for doctr.datasets.generator.tensorflow

    - + diff --git a/v0.9.0/_modules/doctr/datasets/ic03.html b/v0.9.0/_modules/doctr/datasets/ic03.html index 83f7bcddf0..cf8999d751 100644 --- a/v0.9.0/_modules/doctr/datasets/ic03.html +++ b/v0.9.0/_modules/doctr/datasets/ic03.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic03 - docTR documentation @@ -452,7 +452,7 @@

    Source code for doctr.datasets.ic03

         
       
    - + diff --git a/v0.9.0/_modules/doctr/datasets/ic13.html b/v0.9.0/_modules/doctr/datasets/ic13.html index 1d92d10349..7650af381c 100644 --- a/v0.9.0/_modules/doctr/datasets/ic13.html +++ b/v0.9.0/_modules/doctr/datasets/ic13.html @@ -13,7 +13,7 @@ - + doctr.datasets.ic13 - docTR documentation @@ -425,7 +425,7 @@

    Source code for doctr.datasets.ic13

         
       
    - + diff --git a/v0.9.0/_modules/doctr/datasets/iiit5k.html b/v0.9.0/_modules/doctr/datasets/iiit5k.html index 14ab1db716..b4a54e7e22 100644 --- a/v0.9.0/_modules/doctr/datasets/iiit5k.html +++ b/v0.9.0/_modules/doctr/datasets/iiit5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiit5k - docTR documentation @@ -429,7 +429,7 @@

    Source code for doctr.datasets.iiit5k

         
       
    - + diff --git a/v0.9.0/_modules/doctr/datasets/iiithws.html b/v0.9.0/_modules/doctr/datasets/iiithws.html index e7c0d4e8dd..052a85cd56 100644 --- a/v0.9.0/_modules/doctr/datasets/iiithws.html +++ b/v0.9.0/_modules/doctr/datasets/iiithws.html @@ -13,7 +13,7 @@ - + doctr.datasets.iiithws - docTR documentation @@ -401,7 +401,7 @@

    Source code for doctr.datasets.iiithws

         
       
    - + diff --git a/v0.9.0/_modules/doctr/datasets/imgur5k.html b/v0.9.0/_modules/doctr/datasets/imgur5k.html index 202d29445f..24314d9dd1 100644 --- a/v0.9.0/_modules/doctr/datasets/imgur5k.html +++ b/v0.9.0/_modules/doctr/datasets/imgur5k.html @@ -13,7 +13,7 @@ - + doctr.datasets.imgur5k - docTR documentation @@ -475,7 +475,7 @@

    Source code for doctr.datasets.imgur5k

         
       
    - + diff --git a/v0.9.0/_modules/doctr/datasets/loader.html b/v0.9.0/_modules/doctr/datasets/loader.html index ddcafcea67..f78b43db97 100644 --- a/v0.9.0/_modules/doctr/datasets/loader.html +++ b/v0.9.0/_modules/doctr/datasets/loader.html @@ -13,7 +13,7 @@ - + doctr.datasets.loader - docTR documentation @@ -425,7 +425,7 @@

    Source code for doctr.datasets.loader

         
       
    - + diff --git a/v0.9.0/_modules/doctr/datasets/mjsynth.html b/v0.9.0/_modules/doctr/datasets/mjsynth.html index d7a7e66e35..c95f99e6d5 100644 --- a/v0.9.0/_modules/doctr/datasets/mjsynth.html +++ b/v0.9.0/_modules/doctr/datasets/mjsynth.html @@ -13,7 +13,7 @@ - + doctr.datasets.mjsynth - docTR documentation @@ -432,7 +432,7 @@

    Source code for doctr.datasets.mjsynth

         
       
    - + diff --git a/v0.9.0/_modules/doctr/datasets/ocr.html b/v0.9.0/_modules/doctr/datasets/ocr.html index c6e09faee3..a1a249b259 100644 --- a/v0.9.0/_modules/doctr/datasets/ocr.html +++ b/v0.9.0/_modules/doctr/datasets/ocr.html @@ -13,7 +13,7 @@ - + doctr.datasets.ocr - docTR documentation @@ -397,7 +397,7 @@

    Source code for doctr.datasets.ocr

         
       
    - + diff --git a/v0.9.0/_modules/doctr/datasets/recognition.html b/v0.9.0/_modules/doctr/datasets/recognition.html index 1e14da06a9..95612cdadb 100644 --- a/v0.9.0/_modules/doctr/datasets/recognition.html +++ b/v0.9.0/_modules/doctr/datasets/recognition.html @@ -13,7 +13,7 @@ - + doctr.datasets.recognition - docTR documentation @@ -382,7 +382,7 @@

    Source code for doctr.datasets.recognition

         
       
    - + diff --git a/v0.9.0/_modules/doctr/datasets/sroie.html b/v0.9.0/_modules/doctr/datasets/sroie.html index f3ac7b9547..32b4b17983 100644 --- a/v0.9.0/_modules/doctr/datasets/sroie.html +++ b/v0.9.0/_modules/doctr/datasets/sroie.html @@ -13,7 +13,7 @@ - + doctr.datasets.sroie - docTR documentation @@ -429,7 +429,7 @@

    Source code for doctr.datasets.sroie

         
       
    - + diff --git a/v0.9.0/_modules/doctr/datasets/svhn.html b/v0.9.0/_modules/doctr/datasets/svhn.html index f10a8cfd8e..5633dcfd6c 100644 --- a/v0.9.0/_modules/doctr/datasets/svhn.html +++ b/v0.9.0/_modules/doctr/datasets/svhn.html @@ -13,7 +13,7 @@ - + doctr.datasets.svhn - docTR documentation @@ -457,7 +457,7 @@

    Source code for doctr.datasets.svhn

         
       
    - + diff --git a/v0.9.0/_modules/doctr/datasets/svt.html b/v0.9.0/_modules/doctr/datasets/svt.html index 0d64efedf4..0ed4482c50 100644 --- a/v0.9.0/_modules/doctr/datasets/svt.html +++ b/v0.9.0/_modules/doctr/datasets/svt.html @@ -13,7 +13,7 @@ - + doctr.datasets.svt - docTR documentation @@ -443,7 +443,7 @@

    Source code for doctr.datasets.svt

         
       
    - + diff --git a/v0.9.0/_modules/doctr/datasets/synthtext.html b/v0.9.0/_modules/doctr/datasets/synthtext.html index 333de06da8..edd5c63c80 100644 --- a/v0.9.0/_modules/doctr/datasets/synthtext.html +++ b/v0.9.0/_modules/doctr/datasets/synthtext.html @@ -13,7 +13,7 @@ - + doctr.datasets.synthtext - docTR documentation @@ -454,7 +454,7 @@

    Source code for doctr.datasets.synthtext

         
       
    - + diff --git a/v0.9.0/_modules/doctr/datasets/utils.html b/v0.9.0/_modules/doctr/datasets/utils.html index 18a602f09b..f4a2e6a244 100644 --- a/v0.9.0/_modules/doctr/datasets/utils.html +++ b/v0.9.0/_modules/doctr/datasets/utils.html @@ -13,7 +13,7 @@ - + doctr.datasets.utils - docTR documentation @@ -545,7 +545,7 @@

    Source code for doctr.datasets.utils

         
       
    - + diff --git a/v0.9.0/_modules/doctr/datasets/wildreceipt.html b/v0.9.0/_modules/doctr/datasets/wildreceipt.html index 2b386ae694..6b5a52a10e 100644 --- a/v0.9.0/_modules/doctr/datasets/wildreceipt.html +++ b/v0.9.0/_modules/doctr/datasets/wildreceipt.html @@ -13,7 +13,7 @@ - + doctr.datasets.wildreceipt - docTR documentation @@ -437,7 +437,7 @@

    Source code for doctr.datasets.wildreceipt

         
       
    - + diff --git a/v0.9.0/_modules/doctr/documents/elements.html b/v0.9.0/_modules/doctr/documents/elements.html deleted file mode 100644 index 10c1e142d2..0000000000 --- a/v0.9.0/_modules/doctr/documents/elements.html +++ /dev/null @@ -1,577 +0,0 @@ - - - - - - - - - - - - doctr.documents.elements - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.elements

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import matplotlib.pyplot as plt
    -from typing import Tuple, Dict, List, Any, Optional, Union
    -
    -from doctr.utils.geometry import resolve_enclosing_bbox, resolve_enclosing_rbbox
    -from doctr.utils.visualization import visualize_page
    -from doctr.utils.common_types import BoundingBox, RotatedBbox
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['Element', 'Word', 'Artefact', 'Line', 'Block', 'Page', 'Document']
    -
    -
    -class Element(NestedObject):
    -    """Implements an abstract document element with exporting and text rendering capabilities"""
    -
    -    _exported_keys: List[str] = []
    -
    -    def __init__(self, **kwargs: Any) -> None:
    -        self._children_names: List[str] = []
    -        for k, v in kwargs.items():
    -            setattr(self, k, v)
    -            self._children_names.append(k)
    -
    -    def export(self) -> Dict[str, Any]:
    -        """Exports the object into a nested dict format"""
    -
    -        export_dict = {k: getattr(self, k) for k in self._exported_keys}
    -        for children_name in self._children_names:
    -            export_dict[children_name] = [c.export() for c in getattr(self, children_name)]
    -
    -        return export_dict
    -
    -    def render(self) -> str:
    -        raise NotImplementedError
    -
    -
    -
    -[docs] -class Word(Element): - """Implements a word element - - Args: - value: the text string of the word - confidence: the confidence associated with the text prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size - """ - - _exported_keys: List[str] = ["value", "confidence", "geometry"] - - def __init__(self, value: str, confidence: float, geometry: Union[BoundingBox, RotatedBbox]) -> None: - super().__init__() - self.value = value - self.confidence = confidence - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return self.value - - def extra_repr(self) -> str: - return f"value='{self.value}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Artefact(Element): - """Implements a non-textual element - - Args: - artefact_type: the type of artefact - confidence: the confidence of the type prediction - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. - """ - - _exported_keys: List[str] = ["geometry", "type", "confidence"] - - def __init__(self, artefact_type: str, confidence: float, geometry: BoundingBox) -> None: - super().__init__() - self.geometry = geometry - self.type = artefact_type - self.confidence = confidence - - def render(self) -> str: - """Renders the full text of the element""" - return f"[{self.type.upper()}]" - - def extra_repr(self) -> str: - return f"type='{self.type}', confidence={self.confidence:.2}"
    - - - -
    -[docs] -class Line(Element): - """Implements a line element as a collection of words - - Args: - words: list of word elements - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all words in it. - """ - - _exported_keys: List[str] = ["geometry"] - words: List[Word] = [] - - def __init__( - self, - words: List[Word], - geometry: Optional[Union[BoundingBox, RotatedBbox]] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - # Check whether this is a rotated or straight box - box_resolution_fn = resolve_enclosing_rbbox if len(words[0].geometry) == 5 else resolve_enclosing_bbox - geometry = box_resolution_fn([w.geometry for w in words]) # type: ignore[operator, misc] - - super().__init__(words=words) - self.geometry = geometry - - def render(self) -> str: - """Renders the full text of the element""" - return " ".join(w.render() for w in self.words)
    - - - -
    -[docs] -class Block(Element): - """Implements a block element as a collection of lines and artefacts - - Args: - lines: list of line elements - artefacts: list of artefacts - geometry: bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to - the page's size. If not specified, it will be resolved by default to the smallest bounding box enclosing - all lines and artefacts in it. - """ - - _exported_keys: List[str] = ["geometry"] - lines: List[Line] = [] - artefacts: List[Artefact] = [] - - def __init__( - self, - lines: List[Line] = [], - artefacts: List[Artefact] = [], - geometry: Optional[Union[BoundingBox, RotatedBbox]] = None, - ) -> None: - # Resolve the geometry using the smallest enclosing bounding box - if geometry is None: - line_boxes = [word.geometry for line in lines for word in line.words] - artefact_boxes = [artefact.geometry for artefact in artefacts] - box_resolution_fn = resolve_enclosing_rbbox if len(lines[0].geometry) == 5 else resolve_enclosing_bbox - geometry = box_resolution_fn(line_boxes + artefact_boxes) # type: ignore[operator, arg-type] - - super().__init__(lines=lines, artefacts=artefacts) - self.geometry = geometry - - def render(self, line_break: str = '\n') -> str: - """Renders the full text of the element""" - return line_break.join(line.render() for line in self.lines)
    - - - -
    -[docs] -class Page(Element): - """Implements a page element as a collection of blocks - - Args: - blocks: list of block elements - page_idx: the index of the page in the input raw document - dimensions: the page size in pixels in format (width, height) - orientation: a dictionary with the value of the rotation angle in degress and confidence of the prediction - language: a dictionary with the language value and confidence of the prediction - """ - - _exported_keys: List[str] = ["page_idx", "dimensions", "orientation", "language"] - blocks: List[Block] = [] - - def __init__( - self, - blocks: List[Block], - page_idx: int, - dimensions: Tuple[int, int], - orientation: Optional[Dict[str, Any]] = None, - language: Optional[Dict[str, Any]] = None, - ) -> None: - super().__init__(blocks=blocks) - self.page_idx = page_idx - self.dimensions = dimensions - self.orientation = orientation if isinstance(orientation, dict) else dict(value=None, confidence=None) - self.language = language if isinstance(language, dict) else dict(value=None, confidence=None) - - def render(self, block_break: str = '\n\n') -> str: - """Renders the full text of the element""" - return block_break.join(b.render() for b in self.blocks) - - def extra_repr(self) -> str: - return f"dimensions={self.dimensions}" - -
    -[docs] - def show( - self, page: np.ndarray, interactive: bool = True, **kwargs - ) -> None: - """Overlay the result on a given image - - Args: - page: image encoded as a numpy array in uint8 - interactive: whether the display should be interactive - """ - visualize_page(self.export(), page, interactive=interactive) - plt.show(**kwargs)
    -
    - - - -
    -[docs] -class Document(Element): - """Implements a document element as a collection of pages - - Args: - pages: list of page elements - """ - - pages: List[Page] = [] - - def __init__( - self, - pages: List[Page], - ) -> None: - super().__init__(pages=pages) - - def render(self, page_break: str = '\n\n\n\n') -> str: - """Renders the full text of the element""" - return page_break.join(p.render() for p in self.pages) - -
    -[docs] - def show(self, pages: List[np.ndarray], **kwargs) -> None: - """Overlay the result on a given image - - Args: - pages: list of images encoded as numpy arrays in uint8 - """ - for img, result in zip(pages, self.pages): - result.show(img, **kwargs)
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.9.0/_modules/doctr/documents/reader.html b/v0.9.0/_modules/doctr/documents/reader.html deleted file mode 100644 index cdcd814b6c..0000000000 --- a/v0.9.0/_modules/doctr/documents/reader.html +++ /dev/null @@ -1,612 +0,0 @@ - - - - - - - - - - - - doctr.documents.reader - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.documents.reader

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import numpy as np
    -import cv2
    -from pathlib import Path
    -import fitz
    -from weasyprint import HTML
    -from typing import List, Tuple, Optional, Any, Union, Sequence, Dict
    -
    -__all__ = ['read_pdf', 'read_img', 'read_html', 'DocumentFile', 'PDF']
    -
    -
    -AbstractPath = Union[str, Path]
    -AbstractFile = Union[AbstractPath, bytes]
    -Bbox = Tuple[float, float, float, float]
    -
    -
    -
    -[docs] -def read_img( - file: AbstractFile, - output_size: Optional[Tuple[int, int]] = None, - rgb_output: bool = True, -) -> np.ndarray: - """Read an image file into numpy format - - Example:: - >>> from doctr.documents import read_img - >>> page = read_img("path/to/your/doc.jpg") - - Args: - file: the path to the image file - output_size: the expected output size of each page in format H x W - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - Returns: - the page decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)): - if not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - img = cv2.imread(str(file), cv2.IMREAD_COLOR) - elif isinstance(file, bytes): - file = np.frombuffer(file, np.uint8) - img = cv2.imdecode(file, cv2.IMREAD_COLOR) - else: - raise TypeError("unsupported object type for argument 'file'") - - # Validity check - if img is None: - raise ValueError("unable to read file.") - # Resizing - if isinstance(output_size, tuple): - img = cv2.resize(img, output_size[::-1], interpolation=cv2.INTER_LINEAR) - # Switch the channel order - if rgb_output: - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - return img
    - - - -
    -[docs] -def read_pdf(file: AbstractFile, **kwargs: Any) -> fitz.Document: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_pdf - >>> doc = read_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - - if isinstance(file, (str, Path)) and not Path(file).is_file(): - raise FileNotFoundError(f"unable to access {file}") - - fitz_args: Dict[str, AbstractFile] = {} - - if isinstance(file, (str, Path)): - fitz_args['filename'] = file - elif isinstance(file, bytes): - fitz_args['stream'] = file - else: - raise TypeError("unsupported object type for argument 'file'") - - # Read pages with fitz and convert them to numpy ndarrays - return fitz.open(**fitz_args, filetype="pdf", **kwargs)
    - - - -def convert_page_to_numpy( - page: fitz.fitz.Page, - output_size: Optional[Tuple[int, int]] = None, - bgr_output: bool = False, - default_scales: Tuple[float, float] = (2, 2), -) -> np.ndarray: - """Convert a fitz page to a numpy-formatted image - - Args: - page: the page of a file read with PyMuPDF - output_size: the expected output size of each page in format H x W. Default goes to 840 x 595 for A4 pdf, - if you want to increase the resolution while preserving the original A4 aspect ratio can pass (1024, 726) - rgb_output: whether the output ndarray channel order should be RGB instead of BGR. - default_scales: spatial scaling to be applied when output_size is not specified where (1, 1) - corresponds to 72 dpi rendering. - - Returns: - the rendered image in numpy format - """ - - # If no output size is specified, keep the origin one - if output_size is not None: - scales = (output_size[1] / page.MediaBox[2], output_size[0] / page.MediaBox[3]) - else: - # Default 72 DPI (scales of (1, 1)) is unnecessarily low - scales = default_scales - - transform_matrix = fitz.Matrix(*scales) - - # Generate the pixel map using the transformation matrix - pixmap = page.getPixmap(matrix=transform_matrix) - # Decode it into a numpy - img = np.frombuffer(pixmap.samples, dtype=np.uint8).reshape(pixmap.height, pixmap.width, 3) - - # Switch the channel order - if bgr_output: - img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) - - return img - - -
    -[docs] -def read_html(url: str, **kwargs: Any) -> bytes: - """Read a PDF file and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import read_html - >>> doc = read_html("https://www.yoursite.com") - - Args: - url: URL of the target web page - Returns: - decoded PDF file as a bytes stream - """ - - return HTML(url, **kwargs).write_pdf()
    - - - -
    -[docs] -class PDF: - """PDF document template - - Args: - doc: input PDF document - """ - def __init__(self, doc: fitz.Document) -> None: - self.doc = doc - -
    -[docs] - def as_images(self, **kwargs) -> List[np.ndarray]: - """Convert all document pages to images - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images() - - Args: - kwargs: keyword arguments of `convert_page_to_numpy` - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - return [convert_page_to_numpy(page, **kwargs) for page in self.doc]
    - - - def get_page_words(self, idx, **kwargs) -> List[Tuple[Bbox, str]]: - """Get the annotations for all words of a given page""" - - # xmin, ymin, xmax, ymax, value, block_idx, line_idx, word_idx - return [(info[:4], info[4]) for info in self.doc[idx].getTextWords(**kwargs)] - -
    -[docs] - def get_words(self, **kwargs) -> List[List[Tuple[Bbox, str]]]: - """Get the annotations for all words in the document - - Example:: - >>> from doctr.documents import DocumentFile - >>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words() - - Args: - kwargs: keyword arguments of `fitz.Page.getTextWords` - Returns: - the list of pages annotations, represented as a list of tuple (bounding box, value) - """ - return [self.get_page_words(idx, **kwargs) for idx in range(len(self.doc))]
    - - - def get_page_artefacts(self, idx) -> List[Tuple[float, float, float, float]]: - return [tuple(self.doc[idx].getImageBbox(artefact)) # type: ignore[misc] - for artefact in self.doc[idx].get_images(full=True)] - -
    -[docs] - def get_artefacts(self) -> List[List[Tuple[float, float, float, float]]]: - """Get the artefacts for the entire document - - Example:: - >>> from doctr.documents import DocumentFile - >>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts() - - Returns: - the list of pages artefacts, represented as a list of bounding boxes - """ - - return [self.get_page_artefacts(idx) for idx in range(len(self.doc))]
    -
    - - - -
    -[docs] -class DocumentFile: - """Read a document from multiple extensions""" - -
    -[docs] - @classmethod - def from_pdf(cls, file: AbstractFile, **kwargs) -> PDF: - """Read a PDF file - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf") - - Args: - file: the path to the PDF file or a binary stream - Returns: - a PDF document - """ - - doc = read_pdf(file, **kwargs) - - return PDF(doc)
    - - -
    -[docs] - @classmethod - def from_url(cls, url: str, **kwargs) -> PDF: - """Interpret a web page as a PDF document - - Example:: - >>> from doctr.documents import DocumentFile - >>> doc = DocumentFile.from_url("https://www.yoursite.com") - - Args: - url: the URL of the target web page - Returns: - a PDF document - """ - pdf_stream = read_html(url) - return cls.from_pdf(pdf_stream, **kwargs)
    - - -
    -[docs] - @classmethod - def from_images(cls, files: Union[Sequence[AbstractFile], AbstractFile], **kwargs) -> List[np.ndarray]: - """Read an image file (or a collection of image files) and convert it into an image in numpy format - - Example:: - >>> from doctr.documents import DocumentFile - >>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"]) - - Args: - files: the path to the image file or a binary stream, or a collection of those - Returns: - the list of pages decoded as numpy ndarray of shape H x W x 3 - """ - if isinstance(files, (str, Path, bytes)): - files = [files] - - return [read_img(file, **kwargs) for file in files]
    -
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.9.0/_modules/doctr/io/elements.html b/v0.9.0/_modules/doctr/io/elements.html index 9a33fc8fba..a2f537c913 100644 --- a/v0.9.0/_modules/doctr/io/elements.html +++ b/v0.9.0/_modules/doctr/io/elements.html @@ -13,7 +13,7 @@ - + doctr.io.elements - docTR documentation @@ -996,7 +996,7 @@

    Source code for doctr.io.elements

         
       
    - + diff --git a/v0.9.0/_modules/doctr/io/html.html b/v0.9.0/_modules/doctr/io/html.html index 4524736555..9262f244e3 100644 --- a/v0.9.0/_modules/doctr/io/html.html +++ b/v0.9.0/_modules/doctr/io/html.html @@ -13,7 +13,7 @@ - + doctr.io.html - docTR documentation @@ -356,7 +356,7 @@

    Source code for doctr.io.html

         
       
    - + diff --git a/v0.9.0/_modules/doctr/io/image/base.html b/v0.9.0/_modules/doctr/io/image/base.html index 1b42de0506..54663fa868 100644 --- a/v0.9.0/_modules/doctr/io/image/base.html +++ b/v0.9.0/_modules/doctr/io/image/base.html @@ -13,7 +13,7 @@ - + doctr.io.image.base - docTR documentation @@ -382,7 +382,7 @@

    Source code for doctr.io.image.base

         
       
    - + diff --git a/v0.9.0/_modules/doctr/io/image/tensorflow.html b/v0.9.0/_modules/doctr/io/image/tensorflow.html index e428325472..d1b1c41b7f 100644 --- a/v0.9.0/_modules/doctr/io/image/tensorflow.html +++ b/v0.9.0/_modules/doctr/io/image/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.io.image.tensorflow - docTR documentation @@ -441,7 +441,7 @@

    Source code for doctr.io.image.tensorflow

         
       
    - + diff --git a/v0.9.0/_modules/doctr/io/pdf.html b/v0.9.0/_modules/doctr/io/pdf.html index cb64f8eb89..1c88d93ff4 100644 --- a/v0.9.0/_modules/doctr/io/pdf.html +++ b/v0.9.0/_modules/doctr/io/pdf.html @@ -13,7 +13,7 @@ - + doctr.io.pdf - docTR documentation @@ -373,7 +373,7 @@

    Source code for doctr.io.pdf

         
       
    - + diff --git a/v0.9.0/_modules/doctr/io/reader.html b/v0.9.0/_modules/doctr/io/reader.html index 0a80b2867f..957d19e452 100644 --- a/v0.9.0/_modules/doctr/io/reader.html +++ b/v0.9.0/_modules/doctr/io/reader.html @@ -13,7 +13,7 @@ - + doctr.io.reader - docTR documentation @@ -422,7 +422,7 @@

    Source code for doctr.io.reader

         
       
    - + diff --git a/v0.9.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html b/v0.9.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html index 1b97d83911..4dd332b464 100644 --- a/v0.9.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html +++ b/v0.9.0/_modules/doctr/models/classification/magc_resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.magc_resnet.tensorflow - docTR documentation @@ -518,7 +518,7 @@

    Source code for doctr.models.classification.magc_resnet.tensorflow

    - + diff --git a/v0.9.0/_modules/doctr/models/classification/mobilenet/tensorflow.html b/v0.9.0/_modules/doctr/models/classification/mobilenet/tensorflow.html index 6ee5272878..4504bdd58f 100644 --- a/v0.9.0/_modules/doctr/models/classification/mobilenet/tensorflow.html +++ b/v0.9.0/_modules/doctr/models/classification/mobilenet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.mobilenet.tensorflow - docTR documentation @@ -783,7 +783,7 @@

    Source code for doctr.models.classification.mobilenet.tensorflow

    - + diff --git a/v0.9.0/_modules/doctr/models/classification/resnet/tensorflow.html b/v0.9.0/_modules/doctr/models/classification/resnet/tensorflow.html index 67c7ede371..77a5747d8b 100644 --- a/v0.9.0/_modules/doctr/models/classification/resnet/tensorflow.html +++ b/v0.9.0/_modules/doctr/models/classification/resnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.resnet.tensorflow - docTR documentation @@ -730,7 +730,7 @@

    Source code for doctr.models.classification.resnet.tensorflow

    - + diff --git a/v0.9.0/_modules/doctr/models/classification/textnet/tensorflow.html b/v0.9.0/_modules/doctr/models/classification/textnet/tensorflow.html index ef5264039a..c17b2f02e2 100644 --- a/v0.9.0/_modules/doctr/models/classification/textnet/tensorflow.html +++ b/v0.9.0/_modules/doctr/models/classification/textnet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.textnet.tensorflow - docTR documentation @@ -601,7 +601,7 @@

    Source code for doctr.models.classification.textnet.tensorflow

    - + diff --git a/v0.9.0/_modules/doctr/models/classification/vgg/tensorflow.html b/v0.9.0/_modules/doctr/models/classification/vgg/tensorflow.html index 57e34af78f..8dc381674b 100644 --- a/v0.9.0/_modules/doctr/models/classification/vgg/tensorflow.html +++ b/v0.9.0/_modules/doctr/models/classification/vgg/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vgg.tensorflow - docTR documentation @@ -439,7 +439,7 @@

    Source code for doctr.models.classification.vgg.tensorflow

    - + diff --git a/v0.9.0/_modules/doctr/models/classification/vit/tensorflow.html b/v0.9.0/_modules/doctr/models/classification/vit/tensorflow.html index 717a6d1649..84d68b5388 100644 --- a/v0.9.0/_modules/doctr/models/classification/vit/tensorflow.html +++ b/v0.9.0/_modules/doctr/models/classification/vit/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.classification.vit.tensorflow - docTR documentation @@ -521,7 +521,7 @@

    Source code for doctr.models.classification.vit.tensorflow

    - + diff --git a/v0.9.0/_modules/doctr/models/classification/zoo.html b/v0.9.0/_modules/doctr/models/classification/zoo.html index a1b54f64bb..36a1f0fb84 100644 --- a/v0.9.0/_modules/doctr/models/classification/zoo.html +++ b/v0.9.0/_modules/doctr/models/classification/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.classification.zoo - docTR documentation @@ -429,7 +429,7 @@

    Source code for doctr.models.classification.zoo

    <
    - + diff --git a/v0.9.0/_modules/doctr/models/detection/differentiable_binarization.html b/v0.9.0/_modules/doctr/models/detection/differentiable_binarization.html deleted file mode 100644 index 38e9b36ec2..0000000000 --- a/v0.9.0/_modules/doctr/models/detection/differentiable_binarization.html +++ /dev/null @@ -1,879 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.differentiable_binarization - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.differentiable_binarization

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -import cv2
    -from copy import deepcopy
    -import numpy as np
    -from shapely.geometry import Polygon
    -import pyclipper
    -import tensorflow as tf
    -from tensorflow import keras
    -from tensorflow.keras import layers
    -from typing import Union, List, Tuple, Optional, Any, Dict
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..utils import IntermediateLayerGetter, load_pretrained_params, conv_sequence
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['DBPostProcessor', 'DBNet', 'db_resnet50']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'db_resnet50': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'backbone': 'ResNet50',
    -        'fpn_layers': ["conv2_block3_out", "conv3_block4_out", "conv4_block6_out", "conv5_block3_out"],
    -        'fpn_channels': 128,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'DBPostProcessor',
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.2.0/db_resnet50-adcafc63.zip',
    -    },
    -}
    -
    -
    -class DBPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for DBNet adapted from the implementation of `xuannianz
    -    <https://github.com/xuannianz/DifferentiableBinarization>`_.
    -
    -    Args:
    -        unclip ratio: ratio used to unshrink polygons
    -        min_size_box: minimal length (pix) to keep a box
    -        max_candidates: maximum boxes to consider in a single page
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        unclip_ratio: Union[float, int] = 1.5,
    -        max_candidates: int = 1000,
    -        box_thresh: float = 0.1,
    -        bin_thresh: float = 0.3,
    -    ) -> None:
    -
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -        self.unclip_ratio = unclip_ratio
    -        self.max_candidates = max_candidates
    -
    -    def polygon_to_box(
    -        self,
    -        points: np.ndarray,
    -    ) -> Optional[Tuple[int, int, int, int]]:
    -        """Expand a polygon (points) by a factor unclip_ratio, and returns a 4-points box
    -
    -        Args:
    -            points: The first parameter.
    -
    -        Returns:
    -            a box in absolute coordinates (x, y, w, h)
    -        """
    -        poly = Polygon(points)
    -        distance = poly.area * self.unclip_ratio / poly.length  # compute distance to expand polygon
    -        offset = pyclipper.PyclipperOffset()
    -        offset.AddPath(points, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        _points = offset.Execute(distance)
    -        # Take biggest stack of points
    -        idx = 0
    -        if len(_points) > 1:
    -            max_size = 0
    -            for _idx, p in enumerate(_points):
    -                if len(p) > max_size:
    -                    idx = _idx
    -                    max_size = len(p)
    -            # We ensure that _points can be correctly casted to a ndarray
    -            _points = [_points[idx]]
    -        expanded_points = np.asarray(_points)  # expand polygon
    -        if len(expanded_points) < 1:
    -            return None
    -        x, y, w, h = cv2.boundingRect(expanded_points)  # compute a 4-points box from expanded polygon
    -        return x, y, w, h
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map
    -
    -        Args:
    -            pred: Pred map from differentiable binarization output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        # get contours from connected components on the bitmap
    -        contours, _ = cv2.findContours(bitmap.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    -        for contour in contours[:self.max_candidates]:
    -            # Check whether smallest enclosing bounding box is not too small
    -            if np.any(contour[:, 0].max(axis=0) - contour[:, 0].min(axis=0) < min_size_box):
    -                continue
    -            x, y, w, h = cv2.boundingRect(contour)
    -            points = np.array([[x, y], [x, y + h], [x + w, y + h], [x + w, y]])
    -            # Compute objectness
    -            score = self.box_score(pred, points)
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            _box = self.polygon_to_box(points)
    -
    -            if _box is None or _box[2] < min_size_box or _box[3] < min_size_box:  # remove to small boxes
    -                continue
    -            x, y, w, h = _box
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -class FeaturePyramidNetwork(layers.Layer, NestedObject):
    -    """Feature Pyramid Network as described in `"Feature Pyramid Networks for Object Detection"
    -    <https://arxiv.org/pdf/1612.03144.pdf>`_.
    -
    -    Args:
    -        channels: number of channel to output
    -    """
    -
    -    def __init__(
    -        self,
    -        channels: int,
    -    ) -> None:
    -        super().__init__()
    -        self.channels = channels
    -        self.upsample = layers.UpSampling2D(size=(2, 2), interpolation='nearest')
    -        self.inner_blocks = [layers.Conv2D(channels, 1, strides=1, kernel_initializer='he_normal') for _ in range(4)]
    -        self.layer_blocks = [self.build_upsampling(channels, dilation_factor=2 ** idx) for idx in range(4)]
    -
    -    @staticmethod
    -    def build_upsampling(
    -        channels: int,
    -        dilation_factor: int = 1,
    -    ) -> layers.Layer:
    -        """Module which performs a 3x3 convolution followed by up-sampling
    -
    -        Args:
    -            channels: number of output channels
    -            dilation_factor (int): dilation factor to scale the convolution output before concatenation
    -
    -        Returns:
    -            a keras.layers.Layer object, wrapping these operations in a sequential module
    -
    -        """
    -
    -        _layers = conv_sequence(channels, 'relu', True, kernel_size=3)
    -
    -        if dilation_factor > 1:
    -            _layers.append(layers.UpSampling2D(size=(dilation_factor, dilation_factor), interpolation='nearest'))
    -
    -        module = keras.Sequential(_layers)
    -
    -        return module
    -
    -    def extra_repr(self) -> str:
    -        return f"channels={self.channels}"
    -
    -    def call(
    -        self,
    -        x: List[tf.Tensor],
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # Channel mapping
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.inner_blocks, x)]
    -        # Upsample & sum
    -        for idx in range(len(results) - 1, -1):
    -            results[idx] += self.upsample(results[idx + 1])
    -        # Conv & upsample
    -        results = [block(fmap, **kwargs) for block, fmap in zip(self.layer_blocks, results)]
    -
    -        return layers.concatenate(results)
    -
    -
    -class DBNet(DetectionModel, NestedObject):
    -    """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization"
    -    <https://arxiv.org/pdf/1911.08947.pdf>`_.
    -
    -    Args:
    -        feature extractor: the backbone serving as feature extractor
    -        fpn_channels: number of channels each extracted feature maps is mapped to
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'fpn', 'probability_head', 'threshold_head', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: IntermediateLayerGetter,
    -        fpn_channels: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(cfg=cfg)
    -
    -        self.shrink_ratio = 0.4
    -        self.thresh_min = 0.3
    -        self.thresh_max = 0.7
    -        self.min_size_box = 3
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.fpn = FeaturePyramidNetwork(channels=fpn_channels)
    -        # Initialize kernels
    -        _inputs = [layers.Input(shape=in_shape[1:]) for in_shape in self.feat_extractor.output_shape]
    -        output_shape = tuple(self.fpn(_inputs).shape)
    -
    -        self.probability_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -        self.threshold_head = keras.Sequential(
    -            [
    -                *conv_sequence(64, 'relu', True, kernel_size=3, input_shape=output_shape[1:]),
    -                layers.Conv2DTranspose(64, 2, strides=2, use_bias=False, kernel_initializer='he_normal'),
    -                layers.BatchNormalization(),
    -                layers.Activation('relu'),
    -                layers.Conv2DTranspose(1, 2, strides=2, kernel_initializer='he_normal'),
    -            ]
    -        )
    -
    -        self.postprocessor = DBPostProcessor()
    -
    -    @staticmethod
    -    def compute_distance(
    -        xs: np.array,
    -        ys: np.array,
    -        a: np.array,
    -        b: np.array,
    -        eps: float = 1e-7,
    -    ) -> float:
    -        """Compute the distance for each point of the map (xs, ys) to the (a, b) segment
    -
    -        Args:
    -            xs : map of x coordinates (height, width)
    -            ys : map of y coordinates (height, width)
    -            a: first point defining the [ab] segment
    -            b: second point defining the [ab] segment
    -
    -        Returns:
    -            The computed distance
    -
    -        """
    -        square_dist_1 = np.square(xs - a[0]) + np.square(ys - a[1])
    -        square_dist_2 = np.square(xs - b[0]) + np.square(ys - b[1])
    -        square_dist = np.square(a[0] - b[0]) + np.square(a[1] - b[1])
    -        cosin = (square_dist - square_dist_1 - square_dist_2) / (2 * np.sqrt(square_dist_1 * square_dist_2) + eps)
    -        square_sin = 1 - np.square(cosin)
    -        square_sin = np.nan_to_num(square_sin)
    -        result = np.sqrt(square_dist_1 * square_dist_2 * square_sin / square_dist)
    -        result[cosin < 0] = np.sqrt(np.fmin(square_dist_1, square_dist_2))[cosin < 0]
    -        return result
    -
    -    def draw_thresh_map(
    -        self,
    -        polygon: np.array,
    -        canvas: np.array,
    -        mask: np.array,
    -    ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
    -        """Draw a polygon treshold map on a canvas, as described in the DB paper
    -
    -        Args:
    -            polygon : array of coord., to draw the boundary of the polygon
    -            canvas : threshold map to fill with polygons
    -            mask : mask for training on threshold polygons
    -        """
    -        if polygon.ndim != 2 or polygon.shape[1] != 2:
    -            raise AttributeError("polygon should be a 2 dimensional array of coords")
    -
    -        # Augment polygon by shrink_ratio
    -        polygon_shape = Polygon(polygon)
    -        distance = polygon_shape.area * (1 - np.power(self.shrink_ratio, 2)) / polygon_shape.length
    -        subject = [tuple(coor) for coor in polygon]  # Get coord as list of tuples
    -        padding = pyclipper.PyclipperOffset()
    -        padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -        padded_polygon = np.array(padding.Execute(distance)[0])
    -
    -        # Fill the mask with 1 on the new padded polygon
    -        cv2.fillPoly(mask, [padded_polygon.astype(np.int32)], 1.0)
    -
    -        # Get min/max to recover polygon after distance computation
    -        xmin = padded_polygon[:, 0].min()
    -        xmax = padded_polygon[:, 0].max()
    -        ymin = padded_polygon[:, 1].min()
    -        ymax = padded_polygon[:, 1].max()
    -        width = xmax - xmin + 1
    -        height = ymax - ymin + 1
    -        # Get absolute polygon for distance computation
    -        polygon[:, 0] = polygon[:, 0] - xmin
    -        polygon[:, 1] = polygon[:, 1] - ymin
    -        # Get absolute padded polygon
    -        xs = np.broadcast_to(np.linspace(0, width - 1, num=width).reshape(1, width), (height, width))
    -        ys = np.broadcast_to(np.linspace(0, height - 1, num=height).reshape(height, 1), (height, width))
    -
    -        # Compute distance map to fill the padded polygon
    -        distance_map = np.zeros((polygon.shape[0], height, width), dtype=np.float32)
    -        for i in range(polygon.shape[0]):
    -            j = (i + 1) % polygon.shape[0]
    -            absolute_distance = self.compute_distance(xs, ys, polygon[i], polygon[j])
    -            distance_map[i] = np.clip(absolute_distance / distance, 0, 1)
    -        distance_map = np.min(distance_map, axis=0)
    -
    -        # Clip the padded polygon inside the canvas
    -        xmin_valid = min(max(0, xmin), canvas.shape[1] - 1)
    -        xmax_valid = min(max(0, xmax), canvas.shape[1] - 1)
    -        ymin_valid = min(max(0, ymin), canvas.shape[0] - 1)
    -        ymax_valid = min(max(0, ymax), canvas.shape[0] - 1)
    -
    -        # Fill the canvas with the distances computed inside the valid padded polygon
    -        canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1] = np.fmax(
    -            1 - distance_map[
    -                ymin_valid - ymin:ymax_valid - ymin + 1,
    -                xmin_valid - xmin:xmax_valid - xmin + 1
    -            ],
    -            canvas[ymin_valid:ymax_valid + 1, xmin_valid:xmax_valid + 1]
    -        )
    -
    -        return polygon, canvas, mask
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor, tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.uint8)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -        thresh_target = np.zeros(output_shape, dtype=np.uint8)
    -        thresh_mask = np.ones(output_shape, dtype=np.uint8)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            polys = np.stack([
    -                abs_boxes[:, [0, 1]],
    -                abs_boxes[:, [0, 3]],
    -                abs_boxes[:, [2, 3]],
    -                abs_boxes[:, [2, 1]],
    -            ], axis=1)
    -
    -            for box, box_size, poly, is_ambiguous in zip(abs_boxes, boxes_size, polys, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -
    -                # Negative shrink for gt, as described in paper
    -                polygon = Polygon(poly)
    -                distance = polygon.area * (1 - np.power(self.shrink_ratio, 2)) / polygon.length
    -                subject = [tuple(coor) for coor in poly]
    -                padding = pyclipper.PyclipperOffset()
    -                padding.AddPath(subject, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
    -                shrinked = padding.Execute(-distance)
    -
    -                # Draw polygon on gt if it is valid
    -                if len(shrinked) == 0:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                shrinked = np.array(shrinked[0]).reshape(-1, 2)
    -                if shrinked.shape[0] <= 2 or not Polygon(shrinked).is_valid:
    -                    seg_mask[box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                cv2.fillPoly(seg_target[idx], [shrinked.astype(np.int32)], 1)
    -
    -                # Draw on both thresh map and thresh mask
    -                poly, thresh_target[idx], thresh_mask[idx] = self.draw_thresh_map(poly, thresh_target[idx],
    -                                                                                  thresh_mask[idx])
    -
    -        thresh_target = thresh_target.astype(np.float32) * (self.thresh_max - self.thresh_min) + self.thresh_min
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -        thresh_target = tf.convert_to_tensor(thresh_target, dtype=tf.float32)
    -        thresh_mask = tf.convert_to_tensor(thresh_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask, thresh_target, thresh_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        thresh_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts, masks, thresh_gts, thresh_masks from a list of boxes
    -        and a list of masks for each image. From there it computes the loss with the model output
    -
    -        Args:
    -            out_map: output feature map of the model of shape (N, H, W, C)
    -            thresh_map: threshold map of shape (N, H, W, C)
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -
    -        prob_map = tf.math.sigmoid(tf.squeeze(out_map, axis=[-1]))
    -        thresh_map = tf.math.sigmoid(tf.squeeze(thresh_map, axis=[-1]))
    -
    -        seg_target, seg_mask, thresh_target, thresh_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute balanced BCE loss for proba_map
    -        bce_scale = 5.
    -        bce_loss = tf.keras.losses.binary_crossentropy(seg_target[..., None], out_map, from_logits=True)[seg_mask]
    -
    -        neg_target = 1 - seg_target[seg_mask]
    -        positive_count = tf.math.reduce_sum(seg_target[seg_mask])
    -        negative_count = tf.math.reduce_min([tf.math.reduce_sum(neg_target), 3. * positive_count])
    -        negative_loss = bce_loss * neg_target
    -        negative_loss, _ = tf.nn.top_k(negative_loss, tf.cast(negative_count, tf.int32))
    -        sum_losses = tf.math.reduce_sum(bce_loss * seg_target[seg_mask]) + tf.math.reduce_sum(negative_loss)
    -        balanced_bce_loss = sum_losses / (positive_count + negative_count + 1e-6)
    -
    -        # Compute dice loss for approxbin_map
    -        bin_map = 1 / (1 + tf.exp(-50. * (prob_map[seg_mask] - thresh_map[seg_mask])))
    -
    -        bce_min = tf.math.reduce_min(bce_loss)
    -        weights = (bce_loss - bce_min) / (tf.math.reduce_max(bce_loss) - bce_min) + 1.
    -        inter = tf.math.reduce_sum(bin_map * seg_target[seg_mask] * weights)
    -        union = tf.math.reduce_sum(bin_map) + tf.math.reduce_sum(seg_target[seg_mask]) + 1e-8
    -        dice_loss = 1 - 2.0 * inter / union
    -
    -        # Compute l1 loss for thresh_map
    -        l1_scale = 10.
    -        if tf.reduce_any(thresh_mask):
    -            l1_loss = tf.math.reduce_mean(tf.math.abs(thresh_map[thresh_mask] - thresh_target[thresh_mask]))
    -        else:
    -            l1_loss = tf.constant(0.)
    -
    -        return l1_scale * l1_loss + bce_scale * balanced_bce_loss + dice_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        feat_maps = self.feat_extractor(x, **kwargs)
    -        feat_concat = self.fpn(feat_maps, **kwargs)
    -        logits = self.probability_head(feat_concat, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            thresh_map = self.threshold_head(feat_concat, **kwargs)
    -            loss = self.compute_loss(logits, thresh_map, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _db_resnet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> DBNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['fpn_channels'] = kwargs.get('fpn_channels', _cfg['fpn_channels'])
    -
    -    # Feature extractor
    -    resnet = tf.keras.applications.__dict__[_cfg['backbone']](
    -        include_top=False,
    -        weights=None,
    -        input_shape=_cfg['input_shape'],
    -        pooling=None,
    -    )
    -
    -    feat_extractor = IntermediateLayerGetter(
    -        resnet,
    -        _cfg['fpn_layers'],
    -    )
    -
    -    kwargs['fpn_channels'] = _cfg['fpn_channels']
    -
    -    # Build the model
    -    model = DBNet(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def db_resnet50(pretrained: bool = False, **kwargs: Any) -> DBNet: - """DBNet as described in `"Real-time Scene Text Detection with Differentiable Binarization" - <https://arxiv.org/pdf/1911.08947.pdf>`_, using a ResNet-50 backbone. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _db_resnet('db_resnet50', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.9.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html b/v0.9.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html index b3523b2fb5..5cf3b58dbb 100644 --- a/v0.9.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html +++ b/v0.9.0/_modules/doctr/models/detection/differentiable_binarization/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.differentiable_binarization.tensorflow - docTR documentation @@ -731,7 +731,7 @@

    Source code for doctr.models.detection.differentiable_binarization.tensorflo

    - + diff --git a/v0.9.0/_modules/doctr/models/detection/fast/tensorflow.html b/v0.9.0/_modules/doctr/models/detection/fast/tensorflow.html index 81a4fa8f54..3e2da22214 100644 --- a/v0.9.0/_modules/doctr/models/detection/fast/tensorflow.html +++ b/v0.9.0/_modules/doctr/models/detection/fast/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.fast.tensorflow - docTR documentation @@ -762,7 +762,7 @@

    Source code for doctr.models.detection.fast.tensorflow

    - + diff --git a/v0.9.0/_modules/doctr/models/detection/linknet.html b/v0.9.0/_modules/doctr/models/detection/linknet.html deleted file mode 100644 index 129cfdce8b..0000000000 --- a/v0.9.0/_modules/doctr/models/detection/linknet.html +++ /dev/null @@ -1,644 +0,0 @@ - - - - - - - - - - - - doctr.models.detection.linknet - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.detection.linknet

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -# Credits: post-processing adapted from https://github.com/xuannianz/DifferentiableBinarization
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -import numpy as np
    -import cv2
    -from tensorflow.keras import layers, Sequential
    -from typing import Dict, Any, Tuple, Optional, List
    -
    -from .core import DetectionModel, DetectionPostProcessor
    -from ..backbones import ResnetStage
    -from ..utils import conv_sequence, load_pretrained_params
    -from ...utils.repr import NestedObject
    -
    -__all__ = ['LinkNet', 'linknet', 'LinkNetPostProcessor']
    -
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'linknet': {
    -        'mean': (0.798, 0.785, 0.772),
    -        'std': (0.264, 0.2749, 0.287),
    -        'out_chan': 1,
    -        'input_shape': (1024, 1024, 3),
    -        'post_processor': 'LinkNetPostProcessor',
    -        'url': None,
    -    },
    -}
    -
    -
    -class LinkNetPostProcessor(DetectionPostProcessor):
    -    """Implements a post processor for LinkNet model.
    -
    -    Args:
    -        min_size_box: minimal length (pix) to keep a box
    -        box_thresh: minimal objectness score to consider a box
    -        bin_thresh: threshold used to binzarized p_map at inference time
    -
    -    """
    -    def __init__(
    -        self,
    -        min_size_box: int = 3,
    -        bin_thresh: float = 0.15,
    -        box_thresh: float = 0.1,
    -    ) -> None:
    -        super().__init__(
    -            box_thresh,
    -            bin_thresh
    -        )
    -
    -    def bitmap_to_boxes(
    -        self,
    -        pred: np.ndarray,
    -        bitmap: np.ndarray,
    -    ) -> np.ndarray:
    -        """Compute boxes from a bitmap/pred_map: find connected components then filter boxes
    -
    -        Args:
    -            pred: Pred map from differentiable linknet output
    -            bitmap: Bitmap map computed from pred (binarized)
    -
    -        Returns:
    -            np tensor boxes for the bitmap, each box is a 5-element list
    -                containing x, y, w, h, score for the box
    -        """
    -        label_num, labelimage = cv2.connectedComponents(bitmap.astype(np.uint8), connectivity=4)
    -        height, width = bitmap.shape[:2]
    -        min_size_box = 1 + int(height / 512)
    -        boxes = []
    -        for label in range(1, label_num + 1):
    -            points = np.array(np.where(labelimage == label)[::-1]).T
    -            if points.shape[0] < 4:  # remove polygons with 3 points or less
    -                continue
    -            score = self.box_score(pred, points.reshape(-1, 2))
    -            if self.box_thresh > score:   # remove polygons with a weak objectness
    -                continue
    -            x, y, w, h = cv2.boundingRect(points)
    -            if min(w, h) < min_size_box:  # filter too small boxes
    -                continue
    -            # compute relative polygon to get rid of img shape
    -            xmin, ymin, xmax, ymax = x / width, y / height, (x + w) / width, (y + h) / height
    -            boxes.append([xmin, ymin, xmax, ymax, score])
    -        return np.clip(np.asarray(boxes), 0, 1) if len(boxes) > 0 else np.zeros((0, 5), dtype=np.float32)
    -
    -
    -def decoder_block(in_chan: int, out_chan: int) -> Sequential:
    -    """Creates a LinkNet decoder block"""
    -
    -    return Sequential([
    -        *conv_sequence(in_chan // 4, 'relu', True, kernel_size=1),
    -        layers.Conv2DTranspose(
    -            filters=in_chan // 4,
    -            kernel_size=3,
    -            strides=2,
    -            padding="same",
    -            use_bias=False,
    -            kernel_initializer='he_normal'
    -        ),
    -        layers.BatchNormalization(),
    -        layers.Activation('relu'),
    -        *conv_sequence(out_chan, 'relu', True, kernel_size=1),
    -    ])
    -
    -
    -class LinkNetFPN(layers.Layer, NestedObject):
    -    """LinkNet Encoder-Decoder module
    -
    -    """
    -
    -    def __init__(
    -        self,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.encoder_1 = ResnetStage(num_blocks=2, output_channels=64, downsample=True)
    -        self.encoder_2 = ResnetStage(num_blocks=2, output_channels=128, downsample=True)
    -        self.encoder_3 = ResnetStage(num_blocks=2, output_channels=256, downsample=True)
    -        self.encoder_4 = ResnetStage(num_blocks=2, output_channels=512, downsample=True)
    -        self.decoder_1 = decoder_block(in_chan=64, out_chan=64)
    -        self.decoder_2 = decoder_block(in_chan=128, out_chan=64)
    -        self.decoder_3 = decoder_block(in_chan=256, out_chan=128)
    -        self.decoder_4 = decoder_block(in_chan=512, out_chan=256)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor
    -    ) -> tf.Tensor:
    -        x_1 = self.encoder_1(x)
    -        x_2 = self.encoder_2(x_1)
    -        x_3 = self.encoder_3(x_2)
    -        x_4 = self.encoder_4(x_3)
    -        y_4 = self.decoder_4(x_4)
    -        y_3 = self.decoder_3(y_4 + x_3)
    -        y_2 = self.decoder_2(y_3 + x_2)
    -        y_1 = self.decoder_1(y_2 + x_1)
    -        return y_1
    -
    -
    -class LinkNet(DetectionModel, NestedObject):
    -    """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation"
    -    <https://arxiv.org/pdf/1707.03718.pdf>`_.
    -
    -    Args:
    -        out_chan: number of channels for the output
    -    """
    -
    -    _children_names: List[str] = ['stem', 'fpn', 'classifier', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        out_chan: int = 1,
    -        input_shape: Tuple[int, int, int] = (512, 512, 3),
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(cfg=cfg)
    -
    -        self.stem = Sequential([
    -            *conv_sequence(64, 'relu', True, strides=2, kernel_size=7, input_shape=input_shape),
    -            layers.MaxPool2D(pool_size=(3, 3), strides=2, padding='same'),
    -        ])
    -
    -        self.fpn = LinkNetFPN()
    -
    -        self.classifier = Sequential([
    -            layers.Conv2DTranspose(
    -                filters=32,
    -                kernel_size=3,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -            layers.BatchNormalization(),
    -            layers.Activation('relu'),
    -            *conv_sequence(32, 'relu', True, strides=1, kernel_size=3),
    -            layers.Conv2DTranspose(
    -                filters=out_chan,
    -                kernel_size=2,
    -                strides=2,
    -                padding="same",
    -                use_bias=False,
    -                kernel_initializer='he_normal'
    -            ),
    -        ])
    -
    -        self.min_size_box = 3
    -
    -        self.postprocessor = LinkNetPostProcessor()
    -
    -    def compute_target(
    -        self,
    -        target: List[Dict[str, Any]],
    -        output_shape: Tuple[int, int, int],
    -    ) -> Tuple[tf.Tensor, tf.Tensor]:
    -
    -        seg_target = np.zeros(output_shape, dtype=np.bool)
    -        seg_mask = np.ones(output_shape, dtype=np.bool)
    -
    -        for idx, _target in enumerate(target):
    -            # Draw each polygon on gt
    -            if _target['boxes'].shape[0] == 0:
    -                # Empty image, full masked
    -                seg_mask[idx] = False
    -
    -            # Absolute bounding boxes
    -            abs_boxes = _target['boxes'].copy()
    -            abs_boxes[:, [0, 2]] *= output_shape[-1]
    -            abs_boxes[:, [1, 3]] *= output_shape[-2]
    -            abs_boxes = abs_boxes.round().astype(np.int32)
    -
    -            boxes_size = np.minimum(abs_boxes[:, 2] - abs_boxes[:, 0], abs_boxes[:, 3] - abs_boxes[:, 1])
    -
    -            for box, box_size, is_ambiguous in zip(abs_boxes, boxes_size, _target['flags']):
    -                # Mask ambiguous boxes
    -                if is_ambiguous:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Mask boxes that are too small
    -                if box_size < self.min_size_box:
    -                    seg_mask[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = False
    -                    continue
    -                # Fill polygon with 1
    -                seg_target[idx, box[1]: box[3] + 1, box[0]: box[2] + 1] = True
    -
    -        seg_target = tf.convert_to_tensor(seg_target, dtype=tf.float32)
    -        seg_mask = tf.convert_to_tensor(seg_mask, dtype=tf.bool)
    -
    -        return seg_target, seg_mask
    -
    -    def compute_loss(
    -        self,
    -        out_map: tf.Tensor,
    -        target: List[Dict[str, Any]]
    -    ) -> tf.Tensor:
    -        """Compute a batch of gts and masks from a list of boxes and a list of masks for each image
    -        Then, it computes the loss function with proba_map, gts and masks
    -
    -        Args:
    -            out_map: output feature map of the model of shape N x H x W x 1
    -            target: list of dictionary where each dict has a `boxes` and a `flags` entry
    -
    -        Returns:
    -            A loss tensor
    -        """
    -        seg_target, seg_mask = self.compute_target(target, out_map.shape[:3])
    -
    -        # Compute BCE loss
    -        return tf.math.reduce_mean(tf.keras.losses.binary_crossentropy(
    -            seg_target[seg_mask],
    -            tf.squeeze(out_map, axis=[-1])[seg_mask],
    -            from_logits=True
    -        ))
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[Dict[str, Any]]] = None,
    -        return_model_output: bool = False,
    -        return_boxes: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        logits = self.stem(x)
    -        logits = self.fpn(logits)
    -        logits = self.classifier(logits)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output or target is None or return_boxes:
    -            prob_map = tf.math.sigmoid(logits)
    -        if return_model_output:
    -            out["out_map"] = prob_map
    -
    -        if target is None or return_boxes:
    -            # Post-process boxes
    -            out["boxes"] = self.postprocessor(prob_map)
    -
    -        if target is not None:
    -            loss = self.compute_loss(logits, target)
    -            out['loss'] = loss
    -
    -        return out
    -
    -
    -def _linknet(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> LinkNet:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['out_chan'] = kwargs.get('out_chan', _cfg['out_chan'])
    -
    -    kwargs['out_chan'] = _cfg['out_chan']
    -    kwargs['input_shape'] = _cfg['input_shape']
    -    # Build the model
    -    model = LinkNet(cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def linknet(pretrained: bool = False, **kwargs: Any) -> LinkNet: - """LinkNet as described in `"LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation" - <https://arxiv.org/pdf/1707.03718.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import linknet - >>> model = linknet(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text detection dataset - - Returns: - text detection architecture - """ - - return _linknet('linknet', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.9.0/_modules/doctr/models/detection/linknet/tensorflow.html b/v0.9.0/_modules/doctr/models/detection/linknet/tensorflow.html index c5fd053513..d374bb6d1e 100644 --- a/v0.9.0/_modules/doctr/models/detection/linknet/tensorflow.html +++ b/v0.9.0/_modules/doctr/models/detection/linknet/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.detection.linknet.tensorflow - docTR documentation @@ -698,7 +698,7 @@

    Source code for doctr.models.detection.linknet.tensorflow

    - + diff --git a/v0.9.0/_modules/doctr/models/detection/zoo.html b/v0.9.0/_modules/doctr/models/detection/zoo.html index 26a8767a16..345175018e 100644 --- a/v0.9.0/_modules/doctr/models/detection/zoo.html +++ b/v0.9.0/_modules/doctr/models/detection/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.detection.zoo - docTR documentation @@ -431,7 +431,7 @@

    Source code for doctr.models.detection.zoo

         
       
    - + diff --git a/v0.9.0/_modules/doctr/models/export.html b/v0.9.0/_modules/doctr/models/export.html deleted file mode 100644 index f25a81aa21..0000000000 --- a/v0.9.0/_modules/doctr/models/export.html +++ /dev/null @@ -1,411 +0,0 @@ - - - - - - - - - - - - doctr.models.export - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.export

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import logging
    -import numpy as np
    -import tensorflow as tf
    -from tensorflow.keras import Model
    -from typing import Tuple
    -
    -logging.getLogger("tensorflow").setLevel(logging.DEBUG)
    -
    -
    -__all__ = ['convert_to_tflite', 'convert_to_fp16', 'quantize_model']
    -
    -
    -
    -[docs] -def convert_to_tflite(tf_model: Model) -> bytes: - """Converts a model to TFLite format - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_tflite, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_tflite(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - return converter.convert()
    - - - -
    -[docs] -def convert_to_fp16(tf_model: Model) -> bytes: - """Converts a model to half precision - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import convert_to_fp16, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = convert_to_fp16(model) - - Args: - tf_model: a keras model - - Returns: - bytes: the serialized FP16 model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - converter.target_spec.supported_types = [tf.float16] - return converter.convert()
    - - - -
    -[docs] -def quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) -> bytes: - """Quantize a Tensorflow model - - Example:: - >>> from tensorflow.keras import Sequential - >>> from doctr.models import quantize_model, conv_sequence - >>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3))) - >>> serialized_model = quantize_model(model, (224, 224, 3)) - - Args: - tf_model: a keras model - input_shape: shape of the expected input tensor (excluding batch dimension) with channel last order - - Returns: - bytes: the serialized quantized model - """ - converter = tf.lite.TFLiteConverter.from_keras_model(tf_model) - - converter.optimizations = [tf.lite.Optimize.DEFAULT] - - # Float fallback for operators that do not have an integer implementation - def representative_dataset(): - for _ in range(100): - data = np.random.rand(1, *input_shape) - yield [data.astype(np.float32)] - - converter.representative_dataset = representative_dataset - converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] - converter.inference_input_type = tf.int8 - converter.inference_output_type = tf.int8 - - return converter.convert()
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.9.0/_modules/doctr/models/factory/hub.html b/v0.9.0/_modules/doctr/models/factory/hub.html index 6161edb5f2..3e02d45cd1 100644 --- a/v0.9.0/_modules/doctr/models/factory/hub.html +++ b/v0.9.0/_modules/doctr/models/factory/hub.html @@ -13,7 +13,7 @@ - + doctr.models.factory.hub - docTR documentation @@ -565,7 +565,7 @@

    Source code for doctr.models.factory.hub

         
       
    - + diff --git a/v0.9.0/_modules/doctr/models/recognition/crnn.html b/v0.9.0/_modules/doctr/models/recognition/crnn.html deleted file mode 100644 index daa2393439..0000000000 --- a/v0.9.0/_modules/doctr/models/recognition/crnn.html +++ /dev/null @@ -1,565 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.crnn - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.crnn

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import layers
    -from tensorflow.keras.models import Sequential
    -from typing import Tuple, Dict, Any, Optional, List
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel, RecognitionPostProcessor
    -
    -__all__ = ['CRNN', 'crnn_vgg16_bn', 'crnn_resnet31', 'CTCPostProcessor']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'crnn_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/crnn_vgg16_bn-748c855f.zip',
    -    },
    -    'crnn_resnet31': {
    -        'mean': (0.694, 0.695, 0.693),
    -        'std': (0.299, 0.296, 0.301),
    -        'backbone': 'resnet31', 'rnn_units': 128,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'CTCPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.1/crnn_resnet31-69ab71db.zip',
    -    },
    -}
    -
    -
    -class CTCPostProcessor(RecognitionPostProcessor):
    -    """
    -    Postprocess raw prediction of the model (logits) to a list of words using CTC decoding
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor
    -    ) -> List[Tuple[str, float]]:
    -        """
    -        Performs decoding of raw output with CTC and decoding of CTC predictions
    -        with label_to_idx mapping dictionnary
    -
    -        Args:
    -            logits: raw output of the model, shape BATCH_SIZE X SEQ_LEN X NUM_CLASSES + 1
    -
    -        Returns:
    -            A list of decoded words of length BATCH_SIZE
    -
    -        """
    -        # Decode CTC
    -        _decoded, _log_prob = tf.nn.ctc_beam_search_decoder(
    -            tf.transpose(logits, perm=[1, 0, 2]),
    -            tf.fill(logits.shape[0], logits.shape[1]),
    -            beam_width=1, top_paths=1,
    -        )
    -        out_idxs = tf.sparse.to_dense(_decoded[0], default_value=len(self.vocab))
    -        probs = tf.math.exp(tf.squeeze(_log_prob, axis=1))
    -
    -        # Map it to characters
    -        _decoded_strings_pred = tf.strings.reduce_join(
    -            inputs=tf.nn.embedding_lookup(self._embedding, out_idxs),
    -            axis=-1
    -        )
    -        _decoded_strings_pred = tf.strings.split(_decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(_decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -class CRNN(RecognitionModel):
    -    """Implements a CRNN architecture as described in `"An End-to-End Trainable Neural Network for Image-based
    -    Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of units in the LSTM layers
    -        cfg: configuration dictionary
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor: tf.keras.Model,
    -        vocab: str,
    -        rnn_units: int = 128,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -        super().__init__(vocab=vocab, cfg=cfg)
    -        self.feat_extractor = feature_extractor
    -
    -        # Initialize kernels
    -        h, w, c = self.feat_extractor.output_shape[1:]
    -        self.max_length = w
    -
    -        self.decoder = Sequential(
    -            [
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Bidirectional(layers.LSTM(units=rnn_units, return_sequences=True)),
    -                layers.Dense(units=len(vocab) + 1)
    -            ]
    -        )
    -        self.decoder.build(input_shape=(None, w, h * c))
    -
    -        self.postprocessor = CTCPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        target: List[str],
    -    ) -> tf.Tensor:
    -        """Compute CTC loss for the model.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        gt, seq_len = self.compute_target(target)
    -        batch_len = model_output.shape[0]
    -        input_length = model_output.shape[1] * tf.ones(shape=(batch_len))
    -        ctc_loss = tf.nn.ctc_loss(
    -            gt, model_output, seq_len, input_length, logits_time_major=False, blank_index=len(self.vocab)
    -        )
    -        return ctc_loss
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        # B x H x W x C --> B x W x H x C
    -        transposed_feat = tf.transpose(features, perm=[0, 2, 1, 3])
    -        w, h, c = transposed_feat.get_shape().as_list()[1:]
    -        # B x W x H x C --> B x W x H * C
    -        features_seq = tf.reshape(transposed_feat, shape=(-1, w, h * c))
    -        logits = self.decoder(features_seq, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = logits
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(logits)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(logits, target)
    -
    -        return out
    -
    -
    -def _crnn(arch: str, pretrained: bool, input_shape: Optional[Tuple[int, int, int]] = None, **kwargs: Any) -> CRNN:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[_cfg['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -
    -    # Build the model
    -    model = CRNN(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, _cfg['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a VGG-16 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_vgg16_bn - >>> model = crnn_vgg16_bn(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_vgg16_bn', pretrained, **kwargs)
    - - - -def crnn_resnet31(pretrained: bool = False, **kwargs: Any) -> CRNN: - """CRNN with a resnet31 backbone as described in `"An End-to-End Trainable Neural Network for Image-based - Sequence Recognition and Its Application to Scene Text Recognition" <https://arxiv.org/pdf/1507.05717.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import crnn_resnet31 - >>> model = crnn_resnet31(pretrained=True) - >>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _crnn('crnn_resnet31', pretrained, **kwargs) -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.9.0/_modules/doctr/models/recognition/crnn/tensorflow.html b/v0.9.0/_modules/doctr/models/recognition/crnn/tensorflow.html index a00647e1b2..a8a19605ba 100644 --- a/v0.9.0/_modules/doctr/models/recognition/crnn/tensorflow.html +++ b/v0.9.0/_modules/doctr/models/recognition/crnn/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.crnn.tensorflow - docTR documentation @@ -650,7 +650,7 @@

    Source code for doctr.models.recognition.crnn.tensorflow

    - + diff --git a/v0.9.0/_modules/doctr/models/recognition/master/tensorflow.html b/v0.9.0/_modules/doctr/models/recognition/master/tensorflow.html index 446786da5f..fa02c4de73 100644 --- a/v0.9.0/_modules/doctr/models/recognition/master/tensorflow.html +++ b/v0.9.0/_modules/doctr/models/recognition/master/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.master.tensorflow - docTR documentation @@ -644,7 +644,7 @@

    Source code for doctr.models.recognition.master.tensorflow

    - + diff --git a/v0.9.0/_modules/doctr/models/recognition/parseq/tensorflow.html b/v0.9.0/_modules/doctr/models/recognition/parseq/tensorflow.html index 3e2d71751a..3c719b14fe 100644 --- a/v0.9.0/_modules/doctr/models/recognition/parseq/tensorflow.html +++ b/v0.9.0/_modules/doctr/models/recognition/parseq/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.parseq.tensorflow - docTR documentation @@ -840,7 +840,7 @@

    Source code for doctr.models.recognition.parseq.tensorflow

    - + diff --git a/v0.9.0/_modules/doctr/models/recognition/sar.html b/v0.9.0/_modules/doctr/models/recognition/sar.html deleted file mode 100644 index 2482e9f156..0000000000 --- a/v0.9.0/_modules/doctr/models/recognition/sar.html +++ /dev/null @@ -1,712 +0,0 @@ - - - - - - - - - - - - doctr.models.recognition.sar - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.models.recognition.sar

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -from copy import deepcopy
    -import tensorflow as tf
    -from tensorflow.keras import Sequential, layers
    -from typing import Tuple, Dict, List, Any, Optional
    -
    -from .. import backbones
    -from ..utils import load_pretrained_params
    -from .core import RecognitionModel
    -from .core import RecognitionPostProcessor
    -from doctr.utils.repr import NestedObject
    -
    -__all__ = ['SAR', 'SARPostProcessor', 'sar_vgg16_bn', 'sar_resnet31']
    -
    -default_cfgs: Dict[str, Dict[str, Any]] = {
    -    'sar_vgg16_bn': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'vgg16_bn', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1-models/sar_vgg16bn-0d7e2c26.zip',
    -    },
    -    'sar_resnet31': {
    -        'mean': (.5, .5, .5),
    -        'std': (1., 1., 1.),
    -        'backbone': 'resnet31', 'rnn_units': 512, 'max_length': 30, 'num_decoders': 2,
    -        'input_shape': (32, 128, 3),
    -        'post_processor': 'SARPostProcessor',
    -        'vocab': ('3K}7eé;5àÎYho]QwV6qU~W"XnbBvcADfËmy.9ÔpÛ*{CôïE%M4#ÈR:g@T$x?0î£|za1ù8,OG€P-'
    -                  'kçHëÀÂ2É/ûIJ\'j(LNÙFut[)èZs+&°Sd=Ï!<â_Ç>rêi`l'),
    -        'url': 'https://github.com/mindee/doctr/releases/download/v0.1.0/sar_resnet31-ea202587.zip',
    -    },
    -}
    -
    -
    -class AttentionModule(layers.Layer, NestedObject):
    -    """Implements attention module of the SAR model
    -
    -    Args:
    -        attention_units: number of hidden attention units
    -
    -    """
    -    def __init__(
    -        self,
    -        attention_units: int
    -    ) -> None:
    -
    -        super().__init__()
    -        self.hidden_state_projector = layers.Conv2D(
    -            attention_units, 1, strides=1, use_bias=False, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.features_projector = layers.Conv2D(
    -            attention_units, 3, strides=1, use_bias=True, padding='same', kernel_initializer='he_normal',
    -        )
    -        self.attention_projector = layers.Conv2D(
    -            1, 1, strides=1, use_bias=False, padding="same", kernel_initializer='he_normal',
    -        )
    -        self.flatten = layers.Flatten()
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        hidden_state: tf.Tensor,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        [H, W] = features.get_shape().as_list()[1:3]
    -        # shape (N, 1, 1, rnn_units) -> (N, 1, 1, attention_units)
    -        hidden_state_projection = self.hidden_state_projector(hidden_state, **kwargs)
    -        # shape (N, H, W, vgg_units) -> (N, H, W, attention_units)
    -        features_projection = self.features_projector(features, **kwargs)
    -        projection = tf.math.tanh(hidden_state_projection + features_projection)
    -        # shape (N, H, W, attention_units) -> (N, H, W, 1)
    -        attention = self.attention_projector(projection, **kwargs)
    -        # shape (N, H, W, 1) -> (N, H * W)
    -        attention = self.flatten(attention)
    -        attention = tf.nn.softmax(attention)
    -        # shape (N, H * W) -> (N, H, W, 1)
    -        attention_map = tf.reshape(attention, [-1, H, W, 1])
    -        glimpse = tf.math.multiply(features, attention_map)
    -        # shape (N, H * W) -> (N, 1)
    -        glimpse = tf.reduce_sum(glimpse, axis=[1, 2])
    -        return glimpse
    -
    -
    -class SARDecoder(layers.Layer, NestedObject):
    -    """Implements decoder module of the SAR model
    -
    -    Args:
    -        rnn_units: number of hidden units in recurrent cells
    -        max_length: maximum length of a sequence
    -        vocab_size: number of classes in the model alphabet
    -        embedding_units: number of hidden embedding units
    -        attention_units: number of hidden attention units
    -        num_decoder_layers: number of LSTM layers to stack
    -
    -    """
    -    def __init__(
    -        self,
    -        rnn_units: int,
    -        max_length: int,
    -        vocab_size: int,
    -        embedding_units: int,
    -        attention_units: int,
    -        num_decoder_layers: int = 2,
    -        input_shape: Optional[List[Tuple[Optional[int]]]] = None,
    -    ) -> None:
    -
    -        super().__init__()
    -        self.vocab_size = vocab_size
    -        self.lstm_decoder = layers.StackedRNNCells(
    -            [layers.LSTMCell(rnn_units, dtype=tf.float32, implementation=1) for _ in range(num_decoder_layers)]
    -        )
    -        self.embed = layers.Dense(embedding_units, use_bias=False, input_shape=(None, self.vocab_size + 1))
    -        self.attention_module = AttentionModule(attention_units)
    -        self.output_dense = layers.Dense(vocab_size + 1, use_bias=True, input_shape=(None, 2 * rnn_units))
    -        self.max_length = max_length
    -
    -        # Initialize kernels
    -        if input_shape is not None:
    -            self.attention_module.call(layers.Input(input_shape[0][1:]), layers.Input((1, 1, rnn_units)))
    -
    -    def call(
    -        self,
    -        features: tf.Tensor,
    -        holistic: tf.Tensor,
    -        gt: Optional[tf.Tensor] = None,
    -        **kwargs: Any,
    -    ) -> tf.Tensor:
    -
    -        # initialize states (each of shape (N, rnn_units))
    -        states = self.lstm_decoder.get_initial_state(
    -            inputs=None, batch_size=features.shape[0], dtype=tf.float32
    -        )
    -        # run first step of lstm
    -        # holistic: shape (N, rnn_units)
    -        _, states = self.lstm_decoder(holistic, states, **kwargs)
    -        # Initialize with the index of virtual START symbol (placed after <eos>)
    -        symbol = tf.fill(features.shape[0], self.vocab_size + 1)
    -        logits_list = []
    -        if kwargs.get('training') and gt is None:
    -            raise ValueError('Need to provide labels during training for teacher forcing')
    -        for t in range(self.max_length + 1):  # keep 1 step for <eos>
    -            # one-hot symbol with depth vocab_size + 1
    -            # embeded_symbol: shape (N, embedding_units)
    -            embeded_symbol = self.embed(tf.one_hot(symbol, depth=self.vocab_size + 1), **kwargs)
    -            logits, states = self.lstm_decoder(embeded_symbol, states, **kwargs)
    -            glimpse = self.attention_module(
    -                features, tf.expand_dims(tf.expand_dims(logits, axis=1), axis=1), **kwargs,
    -            )
    -            # logits: shape (N, rnn_units), glimpse: shape (N, 1)
    -            logits = tf.concat([logits, glimpse], axis=-1)
    -            # shape (N, rnn_units + 1) -> (N, vocab_size + 1)
    -            logits = self.output_dense(logits, **kwargs)
    -            # update symbol with predicted logits for t+1 step
    -            if kwargs.get('training'):
    -                symbol = gt[:, t]
    -            else:
    -                symbol = tf.argmax(logits, axis=-1)
    -            logits_list.append(logits)
    -        outputs = tf.stack(logits_list, axis=1)  # shape (N, max_length + 1, vocab_size + 1)
    -
    -        return outputs
    -
    -
    -class SAR(RecognitionModel):
    -    """Implements a SAR architecture as described in `"Show, Attend and Read:A Simple and Strong Baseline for
    -    Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_.
    -
    -    Args:
    -        feature_extractor: the backbone serving as feature extractor
    -        vocab: vocabulary used for encoding
    -        rnn_units: number of hidden units in both encoder and decoder LSTM
    -        embedding_units: number of embedding units
    -        attention_units: number of hidden units in attention module
    -        max_length: maximum word length handled by the model
    -        num_decoders: number of LSTM to stack in decoder layer
    -
    -    """
    -
    -    _children_names: List[str] = ['feat_extractor', 'encoder', 'decoder', 'postprocessor']
    -
    -    def __init__(
    -        self,
    -        feature_extractor,
    -        vocab: str,
    -        rnn_units: int = 512,
    -        embedding_units: int = 512,
    -        attention_units: int = 512,
    -        max_length: int = 30,
    -        num_decoders: int = 2,
    -        cfg: Optional[Dict[str, Any]] = None,
    -    ) -> None:
    -
    -        super().__init__(vocab=vocab, cfg=cfg)
    -
    -        self.max_length = max_length + 1  # Add 1 timestep for EOS after the longest word
    -
    -        self.feat_extractor = feature_extractor
    -
    -        self.encoder = Sequential(
    -            [
    -                layers.LSTM(units=rnn_units, return_sequences=True),
    -                layers.LSTM(units=rnn_units, return_sequences=False)
    -            ]
    -        )
    -        # Initialize the kernels (watch out for reduce_max)
    -        self.encoder.build(input_shape=(None,) + self.feat_extractor.output_shape[2:])
    -
    -        self.decoder = SARDecoder(
    -            rnn_units, max_length, len(vocab), embedding_units, attention_units, num_decoders,
    -            input_shape=[self.feat_extractor.output_shape, self.encoder.output_shape]
    -        )
    -
    -        self.postprocessor = SARPostProcessor(vocab=vocab)
    -
    -    def compute_loss(
    -        self,
    -        model_output: tf.Tensor,
    -        gt: tf.Tensor,
    -        seq_len: tf.Tensor,
    -    ) -> tf.Tensor:
    -        """Compute categorical cross-entropy loss for the model.
    -        Sequences are masked after the EOS character.
    -
    -        Args:
    -            gt: the encoded tensor with gt labels
    -            model_output: predicted logits of the model
    -            seq_len: lengths of each gt word inside the batch
    -
    -        Returns:
    -            The loss of the model on the batch
    -        """
    -        # Input length : number of timesteps
    -        input_len = tf.shape(model_output)[1]
    -        # Add one for additional <eos> token
    -        seq_len = seq_len + 1
    -        # One-hot gt labels
    -        oh_gt = tf.one_hot(gt, depth=model_output.shape[2])
    -        # Compute loss
    -        cce = tf.nn.softmax_cross_entropy_with_logits(oh_gt, model_output)
    -        # Compute mask
    -        mask_values = tf.zeros_like(cce)
    -        mask_2d = tf.sequence_mask(seq_len, input_len)
    -        masked_loss = tf.where(mask_2d, cce, mask_values)
    -        ce_loss = tf.math.divide(tf.reduce_sum(masked_loss, axis=1), tf.cast(seq_len, tf.float32))
    -        return tf.expand_dims(ce_loss, axis=1)
    -
    -    def call(
    -        self,
    -        x: tf.Tensor,
    -        target: Optional[List[str]] = None,
    -        return_model_output: bool = False,
    -        return_preds: bool = False,
    -        **kwargs: Any,
    -    ) -> Dict[str, Any]:
    -
    -        features = self.feat_extractor(x, **kwargs)
    -        pooled_features = tf.reduce_max(features, axis=1)  # vertical max pooling
    -        encoded = self.encoder(pooled_features, **kwargs)
    -        if target is not None:
    -            gt, seq_len = self.compute_target(target)
    -        decoded_features = self.decoder(features, encoded, gt=None if target is None else gt, **kwargs)
    -
    -        out: Dict[str, tf.Tensor] = {}
    -        if return_model_output:
    -            out["out_map"] = decoded_features
    -
    -        if target is None or return_preds:
    -            # Post-process boxes
    -            out["preds"] = self.postprocessor(decoded_features)
    -
    -        if target is not None:
    -            out['loss'] = self.compute_loss(decoded_features, gt, seq_len)
    -
    -        return out
    -
    -
    -class SARPostProcessor(RecognitionPostProcessor):
    -    """Post processor for SAR architectures
    -
    -    Args:
    -        vocab: string containing the ordered sequence of supported characters
    -        ignore_case: if True, ignore case of letters
    -        ignore_accents: if True, ignore accents of letters
    -    """
    -
    -    def __call__(
    -        self,
    -        logits: tf.Tensor,
    -    ) -> List[Tuple[str, float]]:
    -        # compute pred with argmax for attention models
    -        out_idxs = tf.math.argmax(logits, axis=2)
    -        # N x L
    -        probs = tf.gather(tf.nn.softmax(logits, axis=-1), out_idxs, axis=-1, batch_dims=2)
    -        # Take the minimum confidence of the sequence
    -        probs = tf.math.reduce_min(probs, axis=1)
    -
    -        # decode raw output of the model with tf_label_to_idx
    -        out_idxs = tf.cast(out_idxs, dtype='int32')
    -        decoded_strings_pred = tf.strings.reduce_join(inputs=tf.nn.embedding_lookup(self._embedding, out_idxs), axis=-1)
    -        decoded_strings_pred = tf.strings.split(decoded_strings_pred, "<eos>")
    -        decoded_strings_pred = tf.sparse.to_dense(decoded_strings_pred.to_sparse(), default_value='not valid')[:, 0]
    -        word_values = [word.decode() for word in decoded_strings_pred.numpy().tolist()]
    -
    -        return list(zip(word_values, probs.numpy().tolist()))
    -
    -
    -def _sar(arch: str, pretrained: bool, input_shape: Tuple[int, int, int] = None, **kwargs: Any) -> SAR:
    -
    -    # Patch the config
    -    _cfg = deepcopy(default_cfgs[arch])
    -    _cfg['input_shape'] = input_shape or _cfg['input_shape']
    -    _cfg['vocab'] = kwargs.get('vocab', _cfg['vocab'])
    -    _cfg['rnn_units'] = kwargs.get('rnn_units', _cfg['rnn_units'])
    -    _cfg['embedding_units'] = kwargs.get('embedding_units', _cfg['rnn_units'])
    -    _cfg['attention_units'] = kwargs.get('attention_units', _cfg['rnn_units'])
    -    _cfg['max_length'] = kwargs.get('max_length', _cfg['max_length'])
    -    _cfg['num_decoders'] = kwargs.get('num_decoders', _cfg['num_decoders'])
    -
    -    # Feature extractor
    -    feat_extractor = backbones.__dict__[default_cfgs[arch]['backbone']](
    -        input_shape=_cfg['input_shape'],
    -        include_top=False,
    -    )
    -
    -    kwargs['vocab'] = _cfg['vocab']
    -    kwargs['rnn_units'] = _cfg['rnn_units']
    -    kwargs['embedding_units'] = _cfg['embedding_units']
    -    kwargs['attention_units'] = _cfg['attention_units']
    -    kwargs['max_length'] = _cfg['max_length']
    -    kwargs['num_decoders'] = _cfg['num_decoders']
    -
    -    # Build the model
    -    model = SAR(feat_extractor, cfg=_cfg, **kwargs)
    -    # Load pretrained parameters
    -    if pretrained:
    -        load_pretrained_params(model, default_cfgs[arch]['url'])
    -
    -    return model
    -
    -
    -
    -[docs] -def sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a VGG16 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example:: - >>> import tensorflow as tf - >>> from doctr.models import sar_vgg16_bn - >>> model = sar_vgg16_bn(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_vgg16_bn', pretrained, **kwargs)
    - - - -
    -[docs] -def sar_resnet31(pretrained: bool = False, **kwargs: Any) -> SAR: - """SAR with a resnet-31 feature extractor as described in `"Show, Attend and Read:A Simple and Strong - Baseline for Irregular Text Recognition" <https://arxiv.org/pdf/1811.00751.pdf>`_. - - Example: - >>> import tensorflow as tf - >>> from doctr.models import sar_resnet31 - >>> model = sar_resnet31(pretrained=False) - >>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32) - >>> out = model(input_tensor) - - Args: - pretrained (bool): If True, returns a model pre-trained on our text recognition dataset - - Returns: - text recognition architecture - """ - - return _sar('sar_resnet31', pretrained, **kwargs)
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.9.0/_modules/doctr/models/recognition/sar/tensorflow.html b/v0.9.0/_modules/doctr/models/recognition/sar/tensorflow.html index 60d2b9b4bd..a432db0986 100644 --- a/v0.9.0/_modules/doctr/models/recognition/sar/tensorflow.html +++ b/v0.9.0/_modules/doctr/models/recognition/sar/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.sar.tensorflow - docTR documentation @@ -749,7 +749,7 @@

    Source code for doctr.models.recognition.sar.tensorflow

    - + diff --git a/v0.9.0/_modules/doctr/models/recognition/vitstr/tensorflow.html b/v0.9.0/_modules/doctr/models/recognition/vitstr/tensorflow.html index 1a97114efa..7131ac4a5b 100644 --- a/v0.9.0/_modules/doctr/models/recognition/vitstr/tensorflow.html +++ b/v0.9.0/_modules/doctr/models/recognition/vitstr/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.vitstr.tensorflow - docTR documentation @@ -610,7 +610,7 @@

    Source code for doctr.models.recognition.vitstr.tensorflow

    - + diff --git a/v0.9.0/_modules/doctr/models/recognition/zoo.html b/v0.9.0/_modules/doctr/models/recognition/zoo.html index a48a001041..cb3c812d02 100644 --- a/v0.9.0/_modules/doctr/models/recognition/zoo.html +++ b/v0.9.0/_modules/doctr/models/recognition/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.recognition.zoo - docTR documentation @@ -403,7 +403,7 @@

    Source code for doctr.models.recognition.zoo

       
    - + diff --git a/v0.9.0/_modules/doctr/models/zoo.html b/v0.9.0/_modules/doctr/models/zoo.html index d8485a1e3e..324b382fcf 100644 --- a/v0.9.0/_modules/doctr/models/zoo.html +++ b/v0.9.0/_modules/doctr/models/zoo.html @@ -13,7 +13,7 @@ - + doctr.models.zoo - docTR documentation @@ -572,7 +572,7 @@

    Source code for doctr.models.zoo

         
       
    - + diff --git a/v0.9.0/_modules/doctr/transforms/modules.html b/v0.9.0/_modules/doctr/transforms/modules.html deleted file mode 100644 index ba8269e7ef..0000000000 --- a/v0.9.0/_modules/doctr/transforms/modules.html +++ /dev/null @@ -1,734 +0,0 @@ - - - - - - - - - - - - doctr.transforms.modules - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    -

    Source code for doctr.transforms.modules

    -# Copyright (C) 2021, Mindee.
    -
    -# This program is licensed under the Apache License version 2.
    -# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
    -
    -import random
    -import tensorflow as tf
    -from typing import List, Any, Tuple, Callable
    -
    -from doctr.utils.repr import NestedObject
    -from . import functional as F
    -
    -
    -__all__ = ['Compose', 'Resize', 'Normalize', 'LambdaTransformation', 'ToGray', 'ColorInversion',
    -           'RandomBrightness', 'RandomContrast', 'RandomSaturation', 'RandomHue', 'RandomGamma', 'RandomJpegQuality',
    -           'OneOf', 'RandomApply']
    -
    -
    -
    -[docs] -class Compose(NestedObject): - """Implements a wrapper that will apply transformations sequentially - - Example:: - >>> from doctr.transforms import Compose, Resize - >>> import tensorflow as tf - >>> transfos = Compose([Resize((32, 32))]) - >>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformation modules - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, x: Any) -> Any: - for t in self.transforms: - x = t(x) - - return x
    - - - -
    -[docs] -class Resize(NestedObject): - """Resizes a tensor to a target size - - Example:: - >>> from doctr.transforms import Resize - >>> import tensorflow as tf - >>> transfo = Resize((32, 32)) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - output_size: expected output size - method: interpolation method - preserve_aspect_ratio: if `True`, preserve aspect ratio and pad the rest with zeros - symmetric_pad: if `True` while preserving aspect ratio, the padding will be done symmetrically - """ - def __init__( - self, - output_size: Tuple[int, int], - method: str = 'bilinear', - preserve_aspect_ratio: bool = False, - symmetric_pad: bool = False, - ) -> None: - self.output_size = output_size - self.method = method - self.preserve_aspect_ratio = preserve_aspect_ratio - self.symmetric_pad = symmetric_pad - - def extra_repr(self) -> str: - _repr = f"output_size={self.output_size}, method='{self.method}'" - if self.preserve_aspect_ratio: - _repr += f", preserve_aspect_ratio={self.preserve_aspect_ratio}, symmetric_pad={self.symmetric_pad}" - return _repr - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img = tf.image.resize(img, self.output_size, self.method, self.preserve_aspect_ratio) - if self.preserve_aspect_ratio: - # pad width - if not self.symmetric_pad: - offset = (0, 0) - elif self.output_size[0] == img.shape[0]: - offset = (0, int((self.output_size[1] - img.shape[1]) / 2)) - else: - offset = (int((self.output_size[0] - img.shape[0]) / 2), 0) - img = tf.image.pad_to_bounding_box(img, *offset, *self.output_size) - return img
    - - - -
    -[docs] -class Normalize(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - mean: average value per channel - std: standard deviation per channel - """ - def __init__(self, mean: Tuple[float, float, float], std: Tuple[float, float, float]) -> None: - self.mean = tf.constant(mean, dtype=tf.float32) - self.std = tf.constant(std, dtype=tf.float32) - - def extra_repr(self) -> str: - return f"mean={self.mean.numpy().tolist()}, std={self.std.numpy().tolist()}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - img -= self.mean - img /= self.std - return img
    - - - -
    -[docs] -class LambdaTransformation(NestedObject): - """Normalize a tensor to a Gaussian distribution for each channel - - Example:: - >>> from doctr.transforms import LambdaTransformation - >>> import tensorflow as tf - >>> transfo = LambdaTransformation(lambda x: x/ 255.) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - fn: the function to be applied to the input tensor - """ - def __init__(self, fn: Callable[[tf.Tensor], tf.Tensor]) -> None: - self.fn = fn - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return self.fn(img)
    - - - -
    -[docs] -class ToGray(NestedObject): - """Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ToGray() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - """ - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.rgb_to_grayscale(img)
    - - - -
    -[docs] -class ColorInversion(NestedObject): - """Applies the following tranformation to a tensor (image or batch of images): - convert to grayscale, colorize (shift 0-values randomly), and then invert colors - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = ColorInversion(min_val=0.6) - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_val: range [min_val, 1] to colorize RGB pixels - """ - def __init__(self, min_val: float = 0.6) -> None: - self.min_val = min_val - - def extra_repr(self) -> str: - return f"min_val={self.min_val}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return F.invert_colors(img, self.min_val)
    - - - -
    -[docs] -class RandomBrightness(NestedObject): - """Randomly adjust brightness of a tensor (batch of images or image) by adding a delta - to all pixels - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Brightness() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - p: probability to apply transformation - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_brightness(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomContrast(NestedObject): - """Randomly adjust contrast of a tensor (batch of images or image) by adjusting - each pixel: (img - mean) * contrast_factor + mean. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Contrast() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1) - """ - def __init__(self, delta: float = .3) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_contrast(img, lower=1 - self.delta, upper=1 / (1 - self.delta))
    - - - -
    -[docs] -class RandomSaturation(NestedObject): - """Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and - increasing saturation by a factor. - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Saturation() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - delta: multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1) - """ - def __init__(self, delta: float = .5) -> None: - self.delta = delta - - def extra_repr(self) -> str: - return f"delta={self.delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_saturation(img, lower=1 - self.delta, upper=1 + self.delta)
    - - - -
    -[docs] -class RandomHue(NestedObject): - """Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Hue() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - max_delta: offset to add to each pixel is randomly picked in [-max_delta, max_delta] - """ - def __init__(self, max_delta: float = 0.3) -> None: - self.max_delta = max_delta - - def extra_repr(self) -> str: - return f"max_delta={self.max_delta}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_hue(img, max_delta=self.max_delta)
    - - - -
    -[docs] -class RandomGamma(NestedObject): - """randomly performs gamma correction for a tensor (batch of images or image) - - Example: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = Gamma() - >>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1)) - - Args: - min_gamma: non-negative real number, lower bound for gamma param - max_gamma: non-negative real number, upper bound for gamma - min_gain: lower bound for constant multiplier - max_gain: upper bound for constant multiplier - """ - def __init__( - self, - min_gamma: float = 0.5, - max_gamma: float = 1.5, - min_gain: float = 0.8, - max_gain: float = 1.2, - ) -> None: - self.min_gamma = min_gamma - self.max_gamma = max_gamma - self.min_gain = min_gain - self.max_gain = max_gain - - def extra_repr(self) -> str: - return f"""gamma_range=({self.min_gamma}, {self.max_gamma}), - gain_range=({self.min_gain}, {self.max_gain})""" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - gamma = random.uniform(self.min_gamma, self.max_gamma) - gain = random.uniform(self.min_gain, self.max_gain) - return tf.image.adjust_gamma(img, gamma=gamma, gain=gain)
    - - - -
    -[docs] -class RandomJpegQuality(NestedObject): - """Randomly adjust jpeg quality of a 3 dimensional RGB image - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = JpegQuality() - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - min_quality: int between [0, 100] - max_quality: int between [0, 100] - """ - def __init__(self, min_quality: int = 60, max_quality: int = 100) -> None: - self.min_quality = min_quality - self.max_quality = max_quality - - def extra_repr(self) -> str: - return f"min_quality={self.min_quality}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - return tf.image.random_jpeg_quality( - img, min_jpeg_quality=self.min_quality, max_jpeg_quality=self.max_quality - )
    - - - -
    -[docs] -class OneOf(NestedObject): - """Randomly apply one of the input transformations - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = OneOf([JpegQuality(), Gamma()]) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transforms: list of transformations, one only will be picked - """ - - _children_names: List[str] = ['transforms'] - - def __init__(self, transforms: List[NestedObject]) -> None: - self.transforms = transforms - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - # Pick transformation - transfo = self.transforms[int(random.random() * len(self.transforms))] - # Apply - return transfo(img)
    - - - -
    -[docs] -class RandomApply(NestedObject): - """Apply with a probability p the input transformation - - Example:: - >>> from doctr.transforms import Normalize - >>> import tensorflow as tf - >>> transfo = RandomApply(Gamma(), p=.5) - >>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1)) - - Args: - transform: transformation to apply - p: probability to apply - """ - def __init__(self, transform: NestedObject, p: float = .5) -> None: - self.transform = transform - self.p = p - - def extra_repr(self) -> str: - return f"transform={self.transform}, p={self.p}" - - def __call__(self, img: tf.Tensor) -> tf.Tensor: - if random.random() < self.p: - return self.transform(img) - return img
    - -
    -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.9.0/_modules/doctr/transforms/modules/base.html b/v0.9.0/_modules/doctr/transforms/modules/base.html index c49bc667c1..caf55a50e3 100644 --- a/v0.9.0/_modules/doctr/transforms/modules/base.html +++ b/v0.9.0/_modules/doctr/transforms/modules/base.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.base - docTR documentation @@ -639,7 +639,7 @@

    Source code for doctr.transforms.modules.base

    - + diff --git a/v0.9.0/_modules/doctr/transforms/modules/tensorflow.html b/v0.9.0/_modules/doctr/transforms/modules/tensorflow.html index 69301ece9b..752fb56149 100644 --- a/v0.9.0/_modules/doctr/transforms/modules/tensorflow.html +++ b/v0.9.0/_modules/doctr/transforms/modules/tensorflow.html @@ -13,7 +13,7 @@ - + doctr.transforms.modules.tensorflow - docTR documentation @@ -949,7 +949,7 @@

    Source code for doctr.transforms.modules.tensorflow

    - + diff --git a/v0.9.0/_modules/doctr/utils/metrics.html b/v0.9.0/_modules/doctr/utils/metrics.html index 2fa0f40a60..678580fbb3 100644 --- a/v0.9.0/_modules/doctr/utils/metrics.html +++ b/v0.9.0/_modules/doctr/utils/metrics.html @@ -13,7 +13,7 @@ - + doctr.utils.metrics - docTR documentation @@ -932,7 +932,7 @@

    Source code for doctr.utils.metrics

         
       
    - + diff --git a/v0.9.0/_modules/doctr/utils/visualization.html b/v0.9.0/_modules/doctr/utils/visualization.html index 4f109e3da7..9e56c2f210 100644 --- a/v0.9.0/_modules/doctr/utils/visualization.html +++ b/v0.9.0/_modules/doctr/utils/visualization.html @@ -13,7 +13,7 @@ - + doctr.utils.visualization - docTR documentation @@ -716,7 +716,7 @@

    Source code for doctr.utils.visualization

         
       
    - + diff --git a/v0.9.0/_modules/index.html b/v0.9.0/_modules/index.html index de317b11c8..50a1a152f8 100644 --- a/v0.9.0/_modules/index.html +++ b/v0.9.0/_modules/index.html @@ -13,7 +13,7 @@ - + Overview: module code - docTR documentation @@ -374,7 +374,7 @@

    All modules for which code is available

    - + diff --git a/v0.9.0/_sources/datasets.rst.txt b/v0.9.0/_sources/datasets.rst.txt deleted file mode 100644 index 354122f1e5..0000000000 --- a/v0.9.0/_sources/datasets.rst.txt +++ /dev/null @@ -1,68 +0,0 @@ -doctr.datasets -============== - -.. currentmodule:: doctr.datasets - -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. - - -.. _datasets: - -Available Datasets ------------------- -The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL. - -.. autoclass:: doctr.datasets.datasets.VisionDataset - - -Here are all datasets that are available through DocTR: - -.. autoclass:: FUNSD -.. autoclass:: SROIE -.. autoclass:: CORD -.. autoclass:: OCRDataset - - -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. - -.. autoclass:: doctr.datasets.loader.DataLoader - - -.. _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 - * - digits - - 10 - - 0123456789 - * - ascii_letters - - 52 - - abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ - * - punctuation - - 32 - - !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ - * - currency - - 5 - - £€¥¢฿ - * - latin - - 96 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~° - * - french - - 154 - - 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿ - -.. autofunction:: encode_sequences diff --git a/v0.9.0/_sources/documents.rst.txt b/v0.9.0/_sources/documents.rst.txt deleted file mode 100644 index 655730073e..0000000000 --- a/v0.9.0/_sources/documents.rst.txt +++ /dev/null @@ -1,87 +0,0 @@ -doctr.documents -=============== - - -.. currentmodule:: doctr.documents - -The documents module enables users to easily access content from documents and export analysis -results to structured formats. - - -Document structure ------------------- - -Structural organization of the documents. - -Word -^^^^ -A Word is an uninterrupted sequence of characters. - -.. autoclass:: Word - -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 - -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 - - -Document -^^^^^^^^ - -A Document is a collection of Pages. - -.. autoclass:: Document - - .. automethod:: show - - -File reading ------------- - -High-performance file reading and conversion to processable structured data. - -.. autofunction:: read_pdf - -.. autofunction:: read_img - -.. autofunction:: read_html - - -.. autoclass:: DocumentFile - - .. automethod:: from_pdf - - .. automethod:: from_url - - .. automethod:: from_images - -.. autoclass:: PDF - - .. automethod:: as_images - - .. automethod:: get_words - - .. automethod:: get_artefacts diff --git a/v0.9.0/_sources/installing.rst.txt b/v0.9.0/_sources/installing.rst.txt deleted file mode 100644 index 5c8779dc1c..0000000000 --- a/v0.9.0/_sources/installing.rst.txt +++ /dev/null @@ -1,46 +0,0 @@ - -************ -Installation -************ - -This library requires Python 3.6 or higher. - - -Prerequisites -============= - -Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so: - -* TensorFlow: `installation page `_. -* PyTorch: `installation page `_. - -If you are running another OS than Linux, you will need a few extra dependencies. - -For MacOS users, you can install them as follows: - -.. code:: shell - - brew install cairo pango gdk-pixbuf libffi - -For Windows users, those dependencies are included in GTK. You can find the latest installer over `here `_. - - -Via Python Package -================== - -Install the last stable release of the package using pip: - -.. code:: bash - - pip install python-doctr - - -Via Git -======= - -Install the library in developper mode: - -.. code:: bash - - git clone https://github.com/mindee/doctr.git - pip install -e doctr/. diff --git a/v0.9.0/_sources/models.rst.txt b/v0.9.0/_sources/models.rst.txt deleted file mode 100644 index 9830c6c153..0000000000 --- a/v0.9.0/_sources/models.rst.txt +++ /dev/null @@ -1,215 +0,0 @@ -doctr.models -============ - -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. - -.. currentmodule:: doctr.models - -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 TensorFlow model. -* Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable. - - -Text Detection --------------- -Localizing text elements in images - -+---------------------------------------------------+----------------------------+----------------------------+---------+ -| | FUNSD | CORD | | -+==================+=================+==============+============+===============+============+===============+=========+ -| **Architecture** | **Input shape** | **# params** | **Recall** | **Precision** | **Recall** | **Precision** | **FPS** | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ -| db_resnet50 | (1024, 1024, 3) | 25.2 M | 82.14 | 87.64 | 92.49 | 89.66 | 2.1 | -+------------------+-----------------+--------------+------------+---------------+------------+---------------+---------+ - -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 combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for detection -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for detection is the following: - -1. resize each input image to the target size (bilinear interpolation by default) with potential deformation. -2. batch images together -3. normalize the batch using the training data statistics - - -Detection models -^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - -.. autofunction:: doctr.models.detection.db_resnet50 -.. autofunction:: doctr.models.detection.linknet16 - -Detection predictors -^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information. - -.. autofunction:: doctr.models.detection.detection_predictor - - -Text Recognition ----------------- -Identifying strings in images - -.. list-table:: Text recognition model zoo - :widths: 20 20 15 10 10 10 - :header-rows: 1 - - * - Architecture - - Input shape - - # params - - FUNSD - - CORD - - FPS - * - crnn_vgg16_bn - - (32, 128, 3) - - 15.8M - - 86.02 - - 91.3 - - 12.8 - * - sar_vgg16_bn - - (32, 128, 3) - - 21.5M - - 86.2 - - 91.7 - - 3.3 - * - sar_resnet31 - - (32, 128, 3) - - 53.1M - - **86.3** - - **92.1** - - 2.7 - -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 metrics being used are available in :ref:`metrics`. - -All these recognition models are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Pre-processing for recognition -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -In DocTR, the pre-processing scheme for recognition is the following: - -1. resize each input image to the target size (bilinear interpolation by default) without deformation. -2. pad the image to the target size (with zeros by default) -3. batch images together -4. normalize the batch using the training data statistics - -Recognition models -^^^^^^^^^^^^^^^^^^ -Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models: - - -.. autofunction:: doctr.models.recognition.crnn_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_vgg16_bn -.. autofunction:: doctr.models.recognition.sar_resnet31 -.. autofunction:: doctr.models.recognition.master - - -Recognition predictors -^^^^^^^^^^^^^^^^^^^^^^ -Combining the right components around a given architecture for easier usage. - -.. autofunction:: doctr.models.recognition.recognition_predictor - - -End-to-End OCR --------------- -Predictors that localize and identify text elements in images - -+-----------------------------+--------------------------------------+--------------------------------------+ -| | FUNSD | CORD | -+=============================+============+===============+=========+============+===============+=========+ -| **Architecture** | **Recall** | **Precision** | **FPS** | **Recall** | **Precision** | **FPS** | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + crnn_vgg16_bn | 70.08 | 74.77 | 0.85 | 82.19 | **79.67** | 1.6 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_vgg16_bn | N/A | N/A | 0.49 | N/A | N/A | 1.0 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| db_resnet50 + sar_resnet31 | N/A | N/A | 0.27 | N/A | N/A | 0.83 | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision text detection | 59.50 | 62.50 | | 75.30 | 70.00 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| Gvision doc. text detection | 64.00 | 53.30 | | 68.90 | 61.10 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ -| AWS textract | **78.10** | **83.00** | | **87.50** | 66.00 | | -+-----------------------------+------------+---------------+---------+------------+---------------+---------+ - -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`. - -All recognition models of predictors are trained with our french vocab (cf. :ref:`vocabs`). - -*Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities* - -FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments. - -Results on private ocr datasets - -+------------------------------------+----------------------------+----------------------------+----------------------------+ -| | Receipts | Invoices | IDs | -+====================================+============+===============+============+===============+============+===============+ -| **Architecture** | **Recall** | **Precision** | **Recall** | **Precision** | **Recall** | **Precision** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| db_resnet50 + crnn_vgg16_bn (ours) | **78.90** | **81.01** | 65.68 | **69.86** | **49.48** | **50.46** | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| Gvision doc. text detection | 68.91 | 59.89 | 63.20 | 52.85 | 43.70 | 29.21 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ -| AWS textract | 75.77 | 77.70 | **70.47** | 69.13 | 46.39 | 43.32 | -+------------------------------------+------------+---------------+------------+---------------+------------+---------------+ - - -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. - -.. autofunction:: doctr.models.zoo.ocr_predictor - - -Model export ------------- -Utility functions to make the most of document analysis models. - -.. currentmodule:: doctr.models.export - -Model compression -^^^^^^^^^^^^^^^^^ - -.. autofunction:: convert_to_tflite - -.. autofunction:: convert_to_fp16 - -.. autofunction:: quantize_model - -Using SavedModel -^^^^^^^^^^^^^^^^ - -Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -`SavedModel `_ format as follows: - - - >>> import tensorflow as tf - >>> from doctr.models import db_resnet50 - >>> model = db_resnet50(pretrained=True) - >>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32) - >>> _ = model(input_t, training=False) - >>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/') - -And loaded just as easily: - - - >>> import tensorflow as tf - >>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/') diff --git a/v0.9.0/_sources/transforms.rst.txt b/v0.9.0/_sources/transforms.rst.txt deleted file mode 100644 index 0230fe75f5..0000000000 --- a/v0.9.0/_sources/transforms.rst.txt +++ /dev/null @@ -1,32 +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: - -.. autoclass:: Resize -.. autoclass:: Normalize -.. autoclass:: LambdaTransformation -.. autoclass:: ToGray -.. autoclass:: ColorInversion -.. autoclass:: RandomBrightness -.. autoclass:: RandomContrast -.. autoclass:: RandomSaturation -.. autoclass:: RandomHue -.. autoclass:: RandomGamma -.. autoclass:: RandomJpegQuality - - -Composing transformations ---------------------------------------------- -It is common to require several transformations to be performed consecutively. - -.. autoclass:: Compose -.. autoclass:: OneOf -.. autoclass:: RandomApply diff --git a/v0.9.0/_sources/utils.rst.txt b/v0.9.0/_sources/utils.rst.txt deleted file mode 100644 index 69c1abe0eb..0000000000 --- a/v0.9.0/_sources/utils.rst.txt +++ /dev/null @@ -1,36 +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 - - -.. _metrics: - -Task evaluation ---------------- -Implementations of task-specific metrics to easily assess your model performances. - -.. currentmodule:: doctr.utils.metrics - -.. autoclass:: TextMatch - - .. automethod:: summary - -.. autoclass:: LocalizationConfusion - - .. automethod:: summary - -.. autoclass:: OCRMetric - - .. automethod:: summary diff --git a/v0.9.0/_static/basic.css b/v0.9.0/_static/basic.css index f316efcb47..7ebbd6d07b 100644 --- a/v0.9.0/_static/basic.css +++ b/v0.9.0/_static/basic.css @@ -1,12 +1,5 @@ /* - * basic.css - * ~~~~~~~~~ - * * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ /* -- main layout ----------------------------------------------------------- */ @@ -115,15 +108,11 @@ img { /* -- search page ----------------------------------------------------------- */ ul.search { - margin: 10px 0 0 20px; - padding: 0; + margin-top: 10px; } ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; + padding: 5px 0; } ul.search li a { diff --git a/v0.9.0/_static/doctools.js b/v0.9.0/_static/doctools.js index 4d67807d17..0398ebb9f0 100644 --- a/v0.9.0/_static/doctools.js +++ b/v0.9.0/_static/doctools.js @@ -1,12 +1,5 @@ /* - * doctools.js - * ~~~~~~~~~~~ - * * Base JavaScript utilities for all Sphinx HTML documentation. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; diff --git a/v0.9.0/_static/language_data.js b/v0.9.0/_static/language_data.js index 367b8ed81b..c7fe6c6faf 100644 --- a/v0.9.0/_static/language_data.js +++ b/v0.9.0/_static/language_data.js @@ -1,13 +1,6 @@ /* - * language_data.js - * ~~~~~~~~~~~~~~~~ - * * This script contains the language-specific data used by searchtools.js, * namely the list of stopwords, stemmer, scorer and splitter. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"]; diff --git a/v0.9.0/_static/searchtools.js b/v0.9.0/_static/searchtools.js index b08d58c9b9..2c774d17af 100644 --- a/v0.9.0/_static/searchtools.js +++ b/v0.9.0/_static/searchtools.js @@ -1,12 +1,5 @@ /* - * searchtools.js - * ~~~~~~~~~~~~~~~~ - * * Sphinx JavaScript utilities for the full-text search. - * - * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; @@ -20,7 +13,7 @@ if (typeof Scorer === "undefined") { // and returns the new score. /* score: result => { - const [docname, title, anchor, descr, score, filename] = result + const [docname, title, anchor, descr, score, filename, kind] = result return score }, */ @@ -47,6 +40,14 @@ if (typeof Scorer === "undefined") { }; } +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + const _removeChildren = (element) => { while (element && element.lastChild) element.removeChild(element.lastChild); }; @@ -64,9 +65,13 @@ const _displayItem = (item, searchTerms, highlightTerms) => { const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; const contentRoot = document.documentElement.dataset.content_root; - const [docName, title, anchor, descr, score, _filename] = item; + const [docName, title, anchor, descr, score, _filename, kind] = item; let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); let requestUrl; let linkUrl; if (docBuilder === "dirhtml") { @@ -115,8 +120,10 @@ const _finishSearch = (resultCount) => { "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." ); else - Search.status.innerText = _( - "Search finished, found ${resultCount} page(s) matching the search query." + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, ).replace('${resultCount}', resultCount); }; const _displayNextItem = ( @@ -138,7 +145,7 @@ const _displayNextItem = ( else _finishSearch(resultCount); }; // Helper function used by query() to order search results. -// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. // Order the results by score (in opposite order of appearance, since the // `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. const _orderResultsByScoreThenName = (a, b) => { @@ -248,6 +255,7 @@ const Search = { searchSummary.classList.add("search-summary"); searchSummary.innerText = ""; const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); searchList.classList.add("search"); const out = document.getElementById("search-results"); @@ -318,7 +326,7 @@ const Search = { const indexEntries = Search._index.indexentries; // Collect multiple result groups to be sorted separately and then ordered. - // Each is an array of [docname, title, anchor, descr, score, filename]. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. const normalResults = []; const nonMainIndexResults = []; @@ -337,6 +345,7 @@ const Search = { null, score + boost, filenames[file], + SearchResultKind.title, ]); } } @@ -354,6 +363,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.index, ]; if (isMain) { normalResults.push(result); @@ -475,6 +485,7 @@ const Search = { descr, score, filenames[match[0]], + SearchResultKind.object, ]); }; Object.keys(objects).forEach((prefix) => @@ -585,6 +596,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.text, ]); } return results; diff --git a/v0.9.0/changelog.html b/v0.9.0/changelog.html index 48c6f3ae85..b3d57fe04a 100644 --- a/v0.9.0/changelog.html +++ b/v0.9.0/changelog.html @@ -14,7 +14,7 @@ - + Changelog - docTR documentation @@ -432,7 +432,7 @@

    v0.1.0 (2021-03-05) - + diff --git a/v0.9.0/community/resources.html b/v0.9.0/community/resources.html index 2564037893..9a1988258c 100644 --- a/v0.9.0/community/resources.html +++ b/v0.9.0/community/resources.html @@ -14,7 +14,7 @@ - + Community resources - docTR documentation @@ -389,7 +389,7 @@

    Community resources - + diff --git a/v0.9.0/contributing/code_of_conduct.html b/v0.9.0/contributing/code_of_conduct.html index 007a7a2e13..6c5bee3652 100644 --- a/v0.9.0/contributing/code_of_conduct.html +++ b/v0.9.0/contributing/code_of_conduct.html @@ -14,7 +14,7 @@ - + Contributor Covenant Code of Conduct - docTR documentation @@ -500,7 +500,7 @@

    Attribution - + diff --git a/v0.9.0/contributing/contributing.html b/v0.9.0/contributing/contributing.html index 5e4ebc62fc..496f96f0f9 100644 --- a/v0.9.0/contributing/contributing.html +++ b/v0.9.0/contributing/contributing.html @@ -14,7 +14,7 @@ - + Contributing to docTR - docTR documentation @@ -477,7 +477,7 @@

    Let’s connect - + diff --git a/v0.9.0/datasets.html b/v0.9.0/datasets.html deleted file mode 100644 index 193e576c57..0000000000 --- a/v0.9.0/datasets.html +++ /dev/null @@ -1,578 +0,0 @@ - - - - - - - - - - - - - doctr.datasets - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.datasets

    -

    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.

    -
    -

    Available Datasets

    -

    The datasets from DocTR inherit from an abstract class that handles verified downloading from a given URL.

    -
    -
    -class doctr.datasets.datasets.VisionDataset(url: str, file_name: str | None = None, file_hash: str | None = None, extract_archive: bool = False, download: bool = False, overwrite: bool = False)[source]
    -
    - -

    Here are all datasets that are available through DocTR:

    -
    -
    -class doctr.datasets.FUNSD(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    FUNSD dataset from “FUNSD: A Dataset for Form Understanding in Noisy Scanned Documents”.

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD
    ->>> train_set = FUNSD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.SROIE(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    SROIE dataset from “ICDAR2019 Competition on Scanned Receipt OCR and Information Extraction”.

    -
    -
    Example::
    >>> from doctr.datasets import SROIE
    ->>> train_set = SROIE(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.CORD(train: bool = True, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    CORD dataset from “CORD: A Consolidated Receipt Dataset forPost-OCR Parsing”.

    -
    -
    Example::
    >>> from doctr.datasets import CORD
    ->>> train_set = CORD(train=True, download=True)
    ->>> img, target = train_set[0]
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • train – whether the subset should be the training one

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -class doctr.datasets.OCRDataset(img_folder: str, label_file: str, sample_transforms: Callable[[Any], Any] | None = None, rotated_bbox: bool = False, **kwargs: Any)[source]
    -

    Implements an OCR dataset

    -
    -
    Parameters:
    -
      -
    • img_folder – local path to image folder (all jpg at the root)

    • -
    • label_file – local path to the label file

    • -
    • sample_transforms – composable transformations that will be applied to each image

    • -
    • rotated_bbox – whether polygons should be considered as rotated bounding box (instead of straight ones)

    • -
    • **kwargs – keyword arguments from VisionDataset.

    • -
    -
    -
    -
    - -
    -
    -

    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.

    -
    -
    -class doctr.datasets.loader.DataLoader(dataset, shuffle: bool = True, batch_size: int = 1, drop_last: bool = False, workers: int | None = None)[source]
    -

    Implements a dataset wrapper for fast data loading

    -
    -
    Example::
    >>> from doctr.datasets import FUNSD, 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)
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • dataset – the dataset

    • -
    • shuffle – whether the samples should be shuffled before passing it to the iterator

    • -
    • batch_size – number of elements in each batch

    • -
    • drop_last – if True, drops the last batch if it isn’t full

    • -
    • workers – number of workers to use for data loading

    • -
    -
    -
    -
    - -
    -
    -

    Supported Vocabs

    -

    Since textual content has to be encoded properly for models to interpret them efficiently, DocTR supports multiple sets -of vocabs.

    -
    - - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    DocTR Vocabs

    Name

    size

    characters

    digits

    10

    0123456789

    ascii_letters

    52

    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

    punctuation

    32

    !”#$%&'()*+,-./:;<=>?@[\]^_`{|}~

    currency

    5

    £€¥¢฿

    latin

    96

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°

    french

    154

    0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!”#$%&'()*+,-./:;<=>?@[\]^_`{|}~°àâéèêëîïôùûçÀÂÉÈËÎÏÔÙÛÇ£€¥¢฿

    -
    -
    -
    -doctr.datasets.encode_sequences(sequences: List[str], vocab: str, target_size: int | None = None, eos: int = -1, sos: int | None = None, pad: int | None = None, **kwargs: Any) ndarray[source]
    -

    Encode character sequences using a given vocab as mapping

    -
    -
    Parameters:
    -
      -
    • sequences – the list of character sequences of size N

    • -
    • vocab – the ordered vocab to use for encoding

    • -
    • target_size – maximum length of the encoded data

    • -
    • eos – encoding of End Of String

    • -
    • sos – optional encoding of Start Of String

    • -
    • pad – optional encoding for padding. In case of padding, all sequences are followed by 1 EOS then PAD

    • -
    -
    -
    Returns:
    -

    the padded encoded data as a tensor

    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.9.0/documents.html b/v0.9.0/documents.html deleted file mode 100644 index 98cbb2c5ef..0000000000 --- a/v0.9.0/documents.html +++ /dev/null @@ -1,772 +0,0 @@ - - - - - - - - - - - - - doctr.documents - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.documents

    -

    The documents module enables users to easily access content from documents and export analysis -results to structured formats.

    -
    -

    Document structure

    -

    Structural organization of the documents.

    -
    -

    Word

    -

    A Word is an uninterrupted sequence of characters.

    -
    -
    -class doctr.documents.Word(value: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float])[source]
    -

    Implements a word element

    -
    -
    Parameters:
    -
      -
    • value – the text string of the word

    • -
    • confidence – the confidence associated with the text prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to

    • -
    • size (the page's)

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Line(words: List[Word], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float] | None = None)[source]
    -

    Implements a line element as a collection of words

    -
    -
    Parameters:
    -
      -
    • words – list of word elements

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all words in it.

    • -
    -
    -
    -
    - -
    -
    -

    Artefact

    -

    An Artefact is a non-textual element (e.g. QR code, picture, chart, signature, logo, etc.).

    -
    -
    -class doctr.documents.Artefact(artefact_type: str, confidence: float, geometry: Tuple[Tuple[float, float], Tuple[float, float]])[source]
    -

    Implements a non-textual element

    -
    -
    Parameters:
    -
      -
    • artefact_type – the type of artefact

    • -
    • confidence – the confidence of the type prediction

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size.

    • -
    -
    -
    -
    - -
    -
    -

    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).

    -
    -
    -class doctr.documents.Block(lines: List[Line] = [], artefacts: List[Artefact] = [], geometry: Tuple[Tuple[float, float], Tuple[float, float]] | Tuple[float, float, float, float, float] | None = None)[source]
    -

    Implements a block element as a collection of lines and artefacts

    -
    -
    Parameters:
    -
      -
    • lines – list of line elements

    • -
    • artefacts – list of artefacts

    • -
    • geometry – bounding box of the word in format ((xmin, ymin), (xmax, ymax)) where coordinates are relative to -the page’s size. If not specified, it will be resolved by default to the smallest bounding box enclosing -all lines and artefacts in it.

    • -
    -
    -
    -
    - -
    -
    -

    Page

    -

    A Page is a collection of Blocks that were on the same physical page.

    -
    -
    -class doctr.documents.Page(blocks: List[Block], page_idx: int, dimensions: Tuple[int, int], orientation: Dict[str, Any] | None = None, language: Dict[str, Any] | None = None)[source]
    -

    Implements a page element as a collection of blocks

    -
    -
    Parameters:
    -
      -
    • blocks – list of block elements

    • -
    • page_idx – the index of the page in the input raw document

    • -
    • dimensions – the page size in pixels in format (width, height)

    • -
    • orientation – a dictionary with the value of the rotation angle in degress and confidence of the prediction

    • -
    • language – a dictionary with the language value and confidence of the prediction

    • -
    -
    -
    -
    -
    -show(page: ndarray, interactive: bool = True, **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -
      -
    • page – image encoded as a numpy array in uint8

    • -
    • interactive – whether the display should be interactive

    • -
    -
    -
    -
    - -
    - -
    -
    -

    Document

    -

    A Document is a collection of Pages.

    -
    -
    -class doctr.documents.Document(pages: List[Page])[source]
    -

    Implements a document element as a collection of pages

    -
    -
    Parameters:
    -

    pages – list of page elements

    -
    -
    -
    -
    -show(pages: List[ndarray], **kwargs) None[source]
    -

    Overlay the result on a given image

    -
    -
    Parameters:
    -

    pages – list of images encoded as numpy arrays in uint8

    -
    -
    -
    - -
    - -
    -
    -
    -

    File reading

    -

    High-performance file reading and conversion to processable structured data.

    -
    -
    -doctr.documents.read_pdf(file: str | Path | bytes, **kwargs: Any) Document[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_pdf
    ->>> doc = read_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_img(file: str | Path | bytes, output_size: Tuple[int, int] | None = None, rgb_output: bool = True) ndarray[source]
    -

    Read an image file into numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_img
    ->>> page = read_img("path/to/your/doc.jpg")
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • file – the path to the image file

    • -
    • output_size – the expected output size of each page in format H x W

    • -
    • rgb_output – whether the output ndarray channel order should be RGB instead of BGR.

    • -
    -
    -
    Returns:
    -

    the page decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -doctr.documents.read_html(url: str, **kwargs: Any) bytes[source]
    -

    Read a PDF file and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import read_html
    ->>> doc = read_html("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – URL of the target web page

    -
    -
    Returns:
    -

    decoded PDF file as a bytes stream

    -
    -
    -
    - -
    -
    -class doctr.documents.DocumentFile[source]
    -

    Read a document from multiple extensions

    -
    -
    -classmethod from_pdf(file: str | Path | bytes, **kwargs) PDF[source]
    -

    Read a PDF file

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_pdf("path/to/your/doc.pdf")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    file – the path to the PDF file or a binary stream

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_url(url: str, **kwargs) PDF[source]
    -

    Interpret a web page as a PDF document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> doc = DocumentFile.from_url("https://www.yoursite.com")
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    url – the URL of the target web page

    -
    -
    Returns:
    -

    a PDF document

    -
    -
    -
    - -
    -
    -classmethod from_images(files: Sequence[str | Path | bytes] | str | Path | bytes, **kwargs) List[ndarray][source]
    -

    Read an image file (or a collection of image files) and convert it into an image in numpy format

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_images(["path/to/your/page1.png", "path/to/your/page2.png"])
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    files – the path to the image file or a binary stream, or a collection of those

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    - -
    -
    -class doctr.documents.PDF(doc: Document)[source]
    -

    PDF document template

    -
    -
    Parameters:
    -

    doc – input PDF document

    -
    -
    -
    -
    -as_images(**kwargs) List[ndarray][source]
    -

    Convert all document pages to images

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> pages = DocumentFile.from_pdf("path/to/your/doc.pdf").as_images()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of convert_page_to_numpy

    -
    -
    Returns:
    -

    the list of pages decoded as numpy ndarray of shape H x W x 3

    -
    -
    -
    - -
    -
    -get_words(**kwargs) List[List[Tuple[Tuple[float, float, float, float], str]]][source]
    -

    Get the annotations for all words in the document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> words = DocumentFile.from_pdf("path/to/your/doc.pdf").get_words()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    kwargs – keyword arguments of fitz.Page.getTextWords

    -
    -
    Returns:
    -

    the list of pages annotations, represented as a list of tuple (bounding box, value)

    -
    -
    -
    - -
    -
    -get_artefacts() List[List[Tuple[float, float, float, float]]][source]
    -

    Get the artefacts for the entire document

    -
    -
    Example::
    >>> from doctr.documents import DocumentFile
    ->>> artefacts = DocumentFile.from_pdf("path/to/your/doc.pdf").get_artefacts()
    -
    -
    -
    -
    -
    -
    Returns:
    -

    the list of pages artefacts, represented as a list of bounding boxes

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.9.0/genindex.html b/v0.9.0/genindex.html index dfd304f0ff..34b60498eb 100644 --- a/v0.9.0/genindex.html +++ b/v0.9.0/genindex.html @@ -13,7 +13,7 @@ - Index - docTR documentation + Index - docTR documentation @@ -752,7 +752,7 @@

    W

    - + diff --git a/v0.9.0/getting_started/installing.html b/v0.9.0/getting_started/installing.html index 0ff133aeec..7645f38a83 100644 --- a/v0.9.0/getting_started/installing.html +++ b/v0.9.0/getting_started/installing.html @@ -14,7 +14,7 @@ - + Installation - docTR documentation @@ -431,7 +431,7 @@

    Via Git - + diff --git a/v0.9.0/index.html b/v0.9.0/index.html index e60d5efd26..79b35e0b90 100644 --- a/v0.9.0/index.html +++ b/v0.9.0/index.html @@ -14,7 +14,7 @@ - + docTR documentation @@ -439,7 +439,7 @@

    Supported datasets - + diff --git a/v0.9.0/installing.html b/v0.9.0/installing.html deleted file mode 100644 index b61c60134b..0000000000 --- a/v0.9.0/installing.html +++ /dev/null @@ -1,395 +0,0 @@ - - - - - - - - - - - - - Installation - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    Installation

    -

    This library requires Python 3.6 or higher.

    -
    -

    Prerequisites

    -

    Whichever OS you are running, you will need to install at least TensorFlow or PyTorch. You can refer to their corresponding installation pages to do so:

    - -

    If you are running another OS than Linux, you will need a few extra dependencies.

    -

    For MacOS users, you can install them as follows:

    -
    brew install cairo pango gdk-pixbuf libffi
    -
    -
    -

    For Windows users, those dependencies are included in GTK. You can find the latest installer over here.

    -
    -
    -

    Via Python Package

    -

    Install the last stable release of the package using pip:

    -
    pip install python-doctr
    -
    -
    -
    -
    -

    Via Git

    -

    Install the library in developper mode:

    -
    git clone https://github.com/mindee/doctr.git
    -pip install -e doctr/.
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.9.0/models.html b/v0.9.0/models.html deleted file mode 100644 index b5cd44c9fa..0000000000 --- a/v0.9.0/models.html +++ /dev/null @@ -1,1002 +0,0 @@ - - - - - - - - - - - - - doctr.models - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.models

    -

    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 TensorFlow model.

    • -
    • Model: a deep learning model, implemented with TensorFlow backend along with its specific post-processor to make outputs structured and reusable.

    • -
    -
    -

    Text Detection

    -

    Localizing text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Input shape

    # params

    Recall

    Precision

    Recall

    Precision

    FPS

    db_resnet50

    (1024, 1024, 3)

    25.2 M

    82.14

    87.64

    92.49

    89.66

    2.1

    -
    -

    All text detection models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 1024, 1024, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 1024, 1024, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for detection

    -

    In DocTR, the pre-processing scheme for detection is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) with potential deformation.

    2. -
    3. batch images together

    4. -
    5. normalize the batch using the training data statistics

    6. -
    -
    -
    -

    Detection models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.detection.db_resnet50(pretrained: bool = False, **kwargs: Any) DBNet[source]
    -

    DBNet as described in “Real-time Scene Text Detection with Differentiable Binarization”, using a ResNet-50 backbone.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -doctr.models.detection.linknet16(pretrained: bool = False, **kwargs: Any) LinkNet[source]
    -

    LinkNet as described in “LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import linknet16
    ->>> model = linknet16(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text detection dataset

    -
    -
    Returns:
    -

    text detection architecture

    -
    -
    -
    - -
    -
    -

    Detection predictors

    -

    Combining the right components around a given architecture for easier usage, predictors lets you pass numpy images as inputs and return structured information.

    -
    -
    -doctr.models.detection.detection_predictor(arch: str = 'db_resnet50', pretrained: bool = False, **kwargs: Any) DetectionPredictor[source]
    -

    Text detection architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import detection_predictor
    ->>> model = detection_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_resnet50’)

    • -
    • pretrained – If True, returns a model pre-trained on our text detection dataset

    • -
    -
    -
    Returns:
    -

    Detection predictor

    -
    -
    -
    - -
    -
    -
    -

    Text Recognition

    -

    Identifying strings in images

    -
    - - -------- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Text recognition model zoo

    Architecture

    Input shape

    # params

    FUNSD

    CORD

    FPS

    crnn_vgg16_bn

    (32, 128, 3)

    15.8M

    86.02

    91.3

    12.8

    sar_vgg16_bn

    (32, 128, 3)

    21.5M

    86.2

    91.7

    3.3

    sar_resnet31

    (32, 128, 3)

    53.1M

    86.3

    92.1

    2.7

    -
    -

    All text recognition models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All these recognition models are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 30595 word-level crops which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the model, we feed the model with 100 random tensors of shape [1, 32, 128, 3] as a warm-up. Then, we measure the average speed of the model on 1000 batches of 1 frame (random tensors of shape [1, 32, 128, 3]). -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -
    -

    Pre-processing for recognition

    -

    In DocTR, the pre-processing scheme for recognition is the following:

    -
      -
    1. resize each input image to the target size (bilinear interpolation by default) without deformation.

    2. -
    3. pad the image to the target size (with zeros by default)

    4. -
    5. batch images together

    6. -
    7. normalize the batch using the training data statistics

    8. -
    -
    -
    -

    Recognition models

    -

    Models expect a TensorFlow tensor as input and produces one in return. DocTR includes implementations and pretrained versions of the following models:

    -
    -
    -doctr.models.recognition.crnn_vgg16_bn(pretrained: bool = False, **kwargs: Any) CRNN[source]
    -

    CRNN with a VGG-16 backbone as described in “An End-to-End Trainable Neural Network for Image-based -Sequence Recognition and Its Application to Scene Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import crnn_vgg16_bn
    ->>> model = crnn_vgg16_bn(pretrained=True)
    ->>> input_tensor = tf.random.uniform(shape=[1, 32, 128, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_vgg16_bn(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a VGG16 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -
    -
    Example::
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_vgg16_bn
    ->>> model = sar_vgg16_bn(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.sar_resnet31(pretrained: bool = False, **kwargs: Any) SAR[source]
    -

    SAR with a resnet-31 feature extractor as described in “Show, Attend and Read:A Simple and Strong -Baseline for Irregular Text Recognition”.

    -

    Example

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import sar_resnet31
    ->>> model = sar_resnet31(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 64, 256, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -doctr.models.recognition.master(pretrained: bool = False, **kwargs: Any) MASTER[source]
    -

    MASTER as described in paper: <https://arxiv.org/pdf/1910.02562.pdf>`_. -Example:

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import master
    ->>> model = master(pretrained=False)
    ->>> input_tensor = tf.random.uniform(shape=[1, 48, 160, 3], maxval=1, dtype=tf.float32)
    ->>> out = model(input_tensor)
    -
    -
    -
    -
    Parameters:
    -

    pretrained (bool) – If True, returns a model pre-trained on our text recognition dataset

    -
    -
    Returns:
    -

    text recognition architecture

    -
    -
    -
    - -
    -
    -

    Recognition predictors

    -

    Combining the right components around a given architecture for easier usage.

    -
    -
    -doctr.models.recognition.recognition_predictor(arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) RecognitionPredictor[source]
    -

    Text recognition architecture.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import recognition_predictor
    ->>> model = recognition_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(32, 128, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘crnn_vgg16_bn’, ‘crnn_resnet31’, ‘sar_vgg16_bn’, ‘sar_resnet31’)

    • -
    • pretrained – If True, returns a model pre-trained on our text recognition dataset

    • -
    -
    -
    Returns:
    -

    Recognition predictor

    -
    -
    -
    - -
    -
    -
    -

    End-to-End OCR

    -

    Predictors that localize and identify text elements in images

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    FUNSD

    CORD

    Architecture

    Recall

    Precision

    FPS

    Recall

    Precision

    FPS

    db_resnet50 + crnn_vgg16_bn

    70.08

    74.77

    0.85

    82.19

    79.67

    1.6

    db_resnet50 + sar_vgg16_bn

    N/A

    N/A

    0.49

    N/A

    N/A

    1.0

    db_resnet50 + sar_resnet31

    N/A

    N/A

    0.27

    N/A

    N/A

    0.83

    Gvision text detection

    59.50

    62.50

    75.30

    70.00

    Gvision doc. text detection

    64.00

    53.30

    68.90

    61.10

    AWS textract

    78.10

    83.00

    87.50

    66.00

    -
    -

    All OCR models above have been evaluated using both the training and evaluation sets of FUNSD and CORD (cf. Available Datasets). -Explanations about the metrics being used are available in Task evaluation.

    -

    All recognition models of predictors are trained with our french vocab (cf. Supported Vocabs).

    -

    Disclaimer: both FUNSD subsets combine have 199 pages which might not be representative enough of the model capabilities

    -

    FPS (Frames per second) is computed this way: we instantiate the predictor, we warm-up the model and then we measure the average speed of the end-to-end predictor on the datasets, with a batch size of 1. -We used a c5.x12large from AWS instances (CPU Xeon Platinum 8275L) to perform experiments.

    -

    Results on private ocr datasets

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    Receipts

    Invoices

    IDs

    Architecture

    Recall

    Precision

    Recall

    Precision

    Recall

    Precision

    db_resnet50 + crnn_vgg16_bn (ours)

    78.90

    81.01

    65.68

    69.86

    49.48

    50.46

    Gvision doc. text detection

    68.91

    59.89

    63.20

    52.85

    43.70

    29.21

    AWS textract

    75.77

    77.70

    70.47

    69.13

    46.39

    43.32

    -
    -
    -

    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.

    -
    -
    -doctr.models.zoo.ocr_predictor(det_arch: str = 'db_resnet50', reco_arch: str = 'crnn_vgg16_bn', pretrained: bool = False, **kwargs: Any) OCRPredictor[source]
    -

    End-to-end OCR architecture using one model for localization, and another for text recognition.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.models import ocr_predictor
    ->>> model = ocr_predictor(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([input_page])
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • arch – name of the architecture to use (‘db_sar_vgg’, ‘db_sar_resnet’, ‘db_crnn_vgg’, ‘db_crnn_resnet’)

    • -
    • pretrained – If True, returns a model pre-trained on our OCR dataset

    • -
    -
    -
    Returns:
    -

    OCR predictor

    -
    -
    -
    - -
    -
    -
    -

    Model export

    -

    Utility functions to make the most of document analysis models.

    -
    -

    Model compression

    -
    -
    -doctr.models.export.convert_to_tflite(tf_model: Model) bytes[source]
    -

    Converts a model to TFLite format

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_tflite, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_tflite(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.convert_to_fp16(tf_model: Model) bytes[source]
    -

    Converts a model to half precision

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import convert_to_fp16, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = convert_to_fp16(model)
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    tf_model – a keras model

    -
    -
    Returns:
    -

    the serialized FP16 model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -doctr.models.export.quantize_model(tf_model: Model, input_shape: Tuple[int, int, int]) bytes[source]
    -

    Quantize a Tensorflow model

    -
    -
    Example::
    >>> from tensorflow.keras import Sequential
    ->>> from doctr.models import quantize_model, conv_sequence
    ->>> model = Sequential(conv_sequence(32, 'relu', True, kernel_size=3, input_shape=(224, 224, 3)))
    ->>> serialized_model = quantize_model(model, (224, 224, 3))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • tf_model – a keras model

    • -
    • input_shape – shape of the expected input tensor (excluding batch dimension) with channel last order

    • -
    -
    -
    Returns:
    -

    the serialized quantized model

    -
    -
    Return type:
    -

    bytes

    -
    -
    -
    - -
    -
    -

    Using SavedModel

    -

    Additionally, models in DocTR inherit TensorFlow 2 model properties and can be exported to -SavedModel format as follows:

    -
    >>> import tensorflow as tf
    ->>> from doctr.models import db_resnet50
    ->>> model = db_resnet50(pretrained=True)
    ->>> input_t = tf.random.uniform(shape=[1, 1024, 1024, 3], maxval=1, dtype=tf.float32)
    ->>> _ = model(input_t, training=False)
    ->>> tf.saved_model.save(model, 'path/to/your/folder/db_resnet50/')
    -
    -
    -

    And loaded just as easily:

    -
    >>> import tensorflow as tf
    ->>> model = tf.saved_model.load('path/to/your/folder/db_resnet50/')
    -
    -
    -
    -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.9.0/modules/contrib.html b/v0.9.0/modules/contrib.html index 08a6d7be12..8c5490014b 100644 --- a/v0.9.0/modules/contrib.html +++ b/v0.9.0/modules/contrib.html @@ -14,7 +14,7 @@ - + doctr.contrib - docTR documentation @@ -376,7 +376,7 @@

    Supported contribution modules - + diff --git a/v0.9.0/modules/datasets.html b/v0.9.0/modules/datasets.html index 4bef8a382c..fd825b1a49 100644 --- a/v0.9.0/modules/datasets.html +++ b/v0.9.0/modules/datasets.html @@ -14,7 +14,7 @@ - + doctr.datasets - docTR documentation @@ -1058,7 +1058,7 @@

    Returns: - + diff --git a/v0.9.0/modules/io.html b/v0.9.0/modules/io.html index b809300a22..ae9d1b8aff 100644 --- a/v0.9.0/modules/io.html +++ b/v0.9.0/modules/io.html @@ -14,7 +14,7 @@ - + doctr.io - docTR documentation @@ -756,7 +756,7 @@

    Returns: - + diff --git a/v0.9.0/modules/models.html b/v0.9.0/modules/models.html index bb6930cf22..c1aaa3ad8a 100644 --- a/v0.9.0/modules/models.html +++ b/v0.9.0/modules/models.html @@ -14,7 +14,7 @@ - + doctr.models - docTR documentation @@ -1598,7 +1598,7 @@

    Args: - + diff --git a/v0.9.0/modules/transforms.html b/v0.9.0/modules/transforms.html index 9be1b73323..ada5ec8d1e 100644 --- a/v0.9.0/modules/transforms.html +++ b/v0.9.0/modules/transforms.html @@ -14,7 +14,7 @@ - + doctr.transforms - docTR documentation @@ -831,7 +831,7 @@

    Args:< - + diff --git a/v0.9.0/modules/utils.html b/v0.9.0/modules/utils.html index 65f59737ce..8d375d93c5 100644 --- a/v0.9.0/modules/utils.html +++ b/v0.9.0/modules/utils.html @@ -14,7 +14,7 @@ - + doctr.utils - docTR documentation @@ -711,7 +711,7 @@

    Args: - + diff --git a/v0.9.0/notebooks.html b/v0.9.0/notebooks.html index 9c65c97b9d..dae441b209 100644 --- a/v0.9.0/notebooks.html +++ b/v0.9.0/notebooks.html @@ -14,7 +14,7 @@ - + docTR Notebooks - docTR documentation @@ -381,7 +381,7 @@

    docTR Notebooks - + diff --git a/v0.9.0/py-modindex.html b/v0.9.0/py-modindex.html deleted file mode 100644 index c1569be607..0000000000 --- a/v0.9.0/py-modindex.html +++ /dev/null @@ -1,330 +0,0 @@ - - - - - - - - - - - Python Module Index - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    -
    - -
    - -
    -
    - -
    -

    Python Module Index

    - -
    - - - - - - - - - - - -
     
    d
    - doctr -
    - -
    -
    -
    - - -
    -
    - - Made with Sphinx and @pradyunsg's - - Furo - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - - - - - - - - \ No newline at end of file diff --git a/v0.9.0/search.html b/v0.9.0/search.html index c6b16a4f1a..8f039172f3 100644 --- a/v0.9.0/search.html +++ b/v0.9.0/search.html @@ -14,7 +14,7 @@ - + Search - docTR documentation @@ -336,7 +336,7 @@ - + diff --git a/v0.9.0/searchindex.js b/v0.9.0/searchindex.js index 94c6d7bf4e..9731d4328a 100644 --- a/v0.9.0/searchindex.js +++ b/v0.9.0/searchindex.js @@ -1 +1 @@ -Search.setIndex({"alltitles": {"1. Correction": [[1, "correction"]], "2. Warning": [[1, "warning"]], "3. Temporary Ban": [[1, "temporary-ban"]], "4. Permanent Ban": [[1, "permanent-ban"]], "AWS Lambda": [[13, null]], "Advanced options": [[18, "advanced-options"]], "Args:": [[6, "args"], [6, "id4"], [6, "id7"], [6, "id10"], [6, "id13"], [6, "id16"], [6, "id19"], [6, "id22"], [6, "id25"], [6, "id29"], [6, "id32"], [6, "id37"], [6, "id40"], [6, "id46"], [6, "id49"], [6, "id50"], [6, "id51"], [6, "id54"], [6, "id57"], [6, "id60"], [6, "id61"], [7, "args"], [7, "id2"], [7, "id3"], [7, "id4"], [7, "id5"], [7, "id6"], [7, "id7"], [7, "id10"], [7, "id12"], [7, "id14"], [7, "id16"], [7, "id20"], [7, "id24"], [7, "id28"], [8, "args"], [8, "id3"], [8, "id8"], [8, "id13"], [8, "id17"], [8, "id21"], [8, "id26"], [8, "id31"], [8, "id36"], [8, "id41"], [8, "id46"], [8, "id50"], [8, "id54"], [8, "id59"], [8, "id63"], [8, "id68"], [8, "id73"], [8, "id77"], [8, "id81"], [8, "id85"], [8, "id90"], [8, "id95"], [8, "id99"], [8, "id104"], [8, "id109"], [8, "id114"], [8, "id119"], [8, "id123"], [8, "id127"], [8, "id132"], [8, "id137"], [8, "id142"], [8, "id146"], [8, "id150"], [8, "id155"], [8, "id159"], [8, "id163"], [8, "id167"], [8, "id169"], [8, "id171"], [8, "id173"], [9, "args"], [9, "id1"], [9, "id2"], [9, "id3"], [9, "id4"], [9, "id5"], [9, "id6"], [9, "id7"], [9, "id8"], [9, "id9"], [9, "id10"], [9, "id11"], [9, "id12"], [9, "id13"], [9, "id14"], [9, "id15"], [9, "id16"], [9, "id17"], [9, "id18"], [9, "id19"], [10, "args"], [10, "id3"], [10, "id4"], [10, "id5"], [10, "id6"], [10, "id7"], [10, "id8"], [10, "id9"]], "Artefact": [[7, "artefact"]], "ArtefactDetection": [[15, "artefactdetection"]], "Attribution": [[1, "attribution"]], "Available Datasets": [[16, "available-datasets"]], "Available architectures": [[18, "available-architectures"], [18, "id1"], [18, "id2"]], "Available contribution modules": [[15, "available-contribution-modules"]], "Block": [[7, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[16, null]], "Choosing the right model": [[18, null]], "Classification": [[14, "classification"]], "Code quality": [[2, "code-quality"]], "Code style verification": [[2, "code-style-verification"]], "Codebase structure": [[2, "codebase-structure"]], "Commits": [[2, "commits"]], "Composing transformations": [[9, "composing-transformations"]], "Continuous Integration": [[2, "continuous-integration"]], "Contributing to docTR": [[2, null]], "Contributor Covenant Code of Conduct": [[1, null]], "Custom dataset loader": [[6, "custom-dataset-loader"]], "Data Loading": [[16, "data-loading"]], "Dataloader": [[6, "dataloader"]], "Detection": [[14, "detection"], [16, "detection"]], "Detection predictors": [[18, "detection-predictors"]], "Developer mode installation": [[2, "developer-mode-installation"]], "Developing docTR": [[2, "developing-doctr"]], "Document": [[7, "document"]], "Document structure": [[7, "document-structure"]], "End-to-End OCR": [[18, "end-to-end-ocr"]], "Enforcement": [[1, "enforcement"]], "Enforcement Guidelines": [[1, "enforcement-guidelines"]], "Enforcement Responsibilities": [[1, "enforcement-responsibilities"]], "Export to ONNX": [[17, "export-to-onnx"]], "Feature requests & bug report": [[2, "feature-requests-bug-report"]], "Feedback": [[2, "feedback"]], "File reading": [[7, "file-reading"]], "Half-precision": [[17, "half-precision"]], "Installation": [[3, null]], "Integrate contributions into your pipeline": [[15, null]], "Let\u2019s connect": [[2, "let-s-connect"]], "Line": [[7, "line"]], "Loading from Huggingface Hub": [[14, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[12, "loading-your-custom-trained-model"]], "Main Features": [[4, "main-features"]], "Model optimization": [[17, "model-optimization"]], "Model zoo": [[4, "model-zoo"]], "Modifying the documentation": [[2, "modifying-the-documentation"]], "Naming conventions": [[14, "naming-conventions"]], "Object Detection": [[16, "object-detection"]], "Our Pledge": [[1, "our-pledge"]], "Our Standards": [[1, "our-standards"]], "Page": [[7, "page"]], "Preparing your model for inference": [[17, null]], "Prerequisites": [[3, "prerequisites"]], "Pretrained community models": [[14, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[14, "pushing-to-the-huggingface-hub"]], "Questions": [[2, "questions"]], "Recognition": [[14, "recognition"], [16, "recognition"]], "Recognition predictors": [[18, "recognition-predictors"]], "Returns:": [[6, "returns"], [7, "returns"], [7, "id11"], [7, "id13"], [7, "id15"], [7, "id19"], [7, "id23"], [7, "id27"], [7, "id31"], [8, "returns"], [8, "id6"], [8, "id11"], [8, "id16"], [8, "id20"], [8, "id24"], [8, "id29"], [8, "id34"], [8, "id39"], [8, "id44"], [8, "id49"], [8, "id53"], [8, "id57"], [8, "id62"], [8, "id66"], [8, "id71"], [8, "id76"], [8, "id80"], [8, "id84"], [8, "id88"], [8, "id93"], [8, "id98"], [8, "id102"], [8, "id107"], [8, "id112"], [8, "id117"], [8, "id122"], [8, "id126"], [8, "id130"], [8, "id135"], [8, "id140"], [8, "id145"], [8, "id149"], [8, "id153"], [8, "id158"], [8, "id162"], [8, "id166"], [8, "id168"], [8, "id170"], [8, "id172"], [10, "returns"]], "Scope": [[1, "scope"]], "Share your model with the community": [[14, null]], "Supported Vocabs": [[6, "supported-vocabs"]], "Supported contribution modules": [[5, "supported-contribution-modules"]], "Supported datasets": [[4, "supported-datasets"]], "Supported transformations": [[9, "supported-transformations"]], "Synthetic dataset generator": [[6, "synthetic-dataset-generator"], [16, "synthetic-dataset-generator"]], "Task evaluation": [[10, "task-evaluation"]], "Text Detection": [[18, "text-detection"]], "Text Recognition": [[18, "text-recognition"]], "Text detection models": [[4, "text-detection-models"]], "Text recognition models": [[4, "text-recognition-models"]], "Train your own model": [[12, null]], "Two-stage approaches": [[18, "two-stage-approaches"]], "Unit tests": [[2, "unit-tests"]], "Use your own datasets": [[16, "use-your-own-datasets"]], "Using your ONNX exported model": [[17, "using-your-onnx-exported-model"]], "Via Conda (Only for Linux)": [[3, "via-conda-only-for-linux"]], "Via Git": [[3, "via-git"]], "Via Python Package": [[3, "via-python-package"]], "Visualization": [[10, "visualization"]], "What should I do with the output?": [[18, "what-should-i-do-with-the-output"]], "Word": [[7, "word"]], "docTR Notebooks": [[11, null]], "docTR Vocabs": [[6, "id62"]], "docTR: Document Text Recognition": [[4, null]], "doctr.contrib": [[5, null]], "doctr.datasets": [[6, null], [6, "datasets"]], "doctr.io": [[7, null]], "doctr.models": [[8, null]], "doctr.models.classification": [[8, "doctr-models-classification"]], "doctr.models.detection": [[8, "doctr-models-detection"]], "doctr.models.factory": [[8, "doctr-models-factory"]], "doctr.models.recognition": [[8, "doctr-models-recognition"]], "doctr.models.zoo": [[8, "doctr-models-zoo"]], "doctr.transforms": [[9, null]], "doctr.utils": [[10, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]], "v0.7.0 (2023-09-09)": [[0, "v0-7-0-2023-09-09"]], "v0.8.0 (2024-02-28)": [[0, "v0-8-0-2024-02-28"]], "v0.8.1 (2024-03-04)": [[0, "v0-8-1-2024-03-04"]]}, "docnames": ["changelog", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/contrib", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_contrib_modules", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 62, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/contrib.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_contrib_modules.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[7, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[7, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[9, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[6, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[9, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[9, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[6, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[8, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[6, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[8, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[8, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[7, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[8, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[6, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[10, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[6, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[7, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[7, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[6, "doctr.datasets.encode_sequences", false]], "fast_base() (in module doctr.models.detection)": [[8, "doctr.models.detection.fast_base", false]], "fast_small() (in module doctr.models.detection)": [[8, "doctr.models.detection.fast_small", false]], "fast_tiny() (in module doctr.models.detection)": [[8, "doctr.models.detection.fast_tiny", false]], "from_hub() (in module doctr.models.factory)": [[8, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[7, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[7, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[7, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[6, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[9, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[9, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[6, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[6, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[6, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[6, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[6, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[8, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[9, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[7, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[8, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet34() (in module doctr.models.detection)": [[8, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[8, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[10, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[8, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[8, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[6, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_crop_orientation() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_small_crop_orientation", false]], "mobilenet_v3_small_page_orientation() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_small_page_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[9, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[8, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[6, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[10, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[9, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[7, "doctr.io.Page", false]], "page_orientation_predictor() (in module doctr.models.classification)": [[8, "doctr.models.classification.page_orientation_predictor", false]], "parseq() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[8, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[9, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[9, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[9, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[9, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[9, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[9, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[9, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[9, "doctr.transforms.RandomJpegQuality", false]], "randomresize (class in doctr.transforms)": [[9, "doctr.transforms.RandomResize", false]], "randomrotate (class in doctr.transforms)": [[9, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[9, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[9, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[7, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[7, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[7, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[7, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[6, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[9, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[8, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[8, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[8, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[8, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[7, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[7, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[6, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[10, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[10, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[10, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[10, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[6, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[6, "doctr.datasets.SVT", false]], "synthtext (class in doctr.datasets)": [[6, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[10, "doctr.utils.metrics.TextMatch", false]], "textnet_base() (in module doctr.models.classification)": [[8, "doctr.models.classification.textnet_base", false]], "textnet_small() (in module doctr.models.classification)": [[8, "doctr.models.classification.textnet_small", false]], "textnet_tiny() (in module doctr.models.classification)": [[8, "doctr.models.classification.textnet_tiny", false]], "togray (class in doctr.transforms)": [[9, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[10, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[10, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[10, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[10, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[8, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[10, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[8, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[8, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.vitstr_small", false]], "wildreceipt (class in doctr.datasets)": [[6, "doctr.datasets.WILDRECEIPT", false]], "word (class in doctr.io)": [[7, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[6, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[6, 0, 1, "", "CORD"], [6, 0, 1, "", "CharacterGenerator"], [6, 0, 1, "", "DetectionDataset"], [6, 0, 1, "", "DocArtefacts"], [6, 0, 1, "", "FUNSD"], [6, 0, 1, "", "IC03"], [6, 0, 1, "", "IC13"], [6, 0, 1, "", "IIIT5K"], [6, 0, 1, "", "IIITHWS"], [6, 0, 1, "", "IMGUR5K"], [6, 0, 1, "", "MJSynth"], [6, 0, 1, "", "OCRDataset"], [6, 0, 1, "", "RecognitionDataset"], [6, 0, 1, "", "SROIE"], [6, 0, 1, "", "SVHN"], [6, 0, 1, "", "SVT"], [6, 0, 1, "", "SynthText"], [6, 0, 1, "", "WILDRECEIPT"], [6, 0, 1, "", "WordGenerator"], [6, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[6, 0, 1, "", "DataLoader"]], "doctr.io": [[7, 0, 1, "", "Artefact"], [7, 0, 1, "", "Block"], [7, 0, 1, "", "Document"], [7, 0, 1, "", "DocumentFile"], [7, 0, 1, "", "Line"], [7, 0, 1, "", "Page"], [7, 0, 1, "", "Word"], [7, 1, 1, "", "decode_img_as_tensor"], [7, 1, 1, "", "read_html"], [7, 1, 1, "", "read_img_as_numpy"], [7, 1, 1, "", "read_img_as_tensor"], [7, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[7, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[7, 2, 1, "", "from_images"], [7, 2, 1, "", "from_pdf"], [7, 2, 1, "", "from_url"]], "doctr.io.Page": [[7, 2, 1, "", "show"]], "doctr.models": [[8, 1, 1, "", "kie_predictor"], [8, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[8, 1, 1, "", "crop_orientation_predictor"], [8, 1, 1, "", "magc_resnet31"], [8, 1, 1, "", "mobilenet_v3_large"], [8, 1, 1, "", "mobilenet_v3_large_r"], [8, 1, 1, "", "mobilenet_v3_small"], [8, 1, 1, "", "mobilenet_v3_small_crop_orientation"], [8, 1, 1, "", "mobilenet_v3_small_page_orientation"], [8, 1, 1, "", "mobilenet_v3_small_r"], [8, 1, 1, "", "page_orientation_predictor"], [8, 1, 1, "", "resnet18"], [8, 1, 1, "", "resnet31"], [8, 1, 1, "", "resnet34"], [8, 1, 1, "", "resnet50"], [8, 1, 1, "", "textnet_base"], [8, 1, 1, "", "textnet_small"], [8, 1, 1, "", "textnet_tiny"], [8, 1, 1, "", "vgg16_bn_r"], [8, 1, 1, "", "vit_b"], [8, 1, 1, "", "vit_s"]], "doctr.models.detection": [[8, 1, 1, "", "db_mobilenet_v3_large"], [8, 1, 1, "", "db_resnet50"], [8, 1, 1, "", "detection_predictor"], [8, 1, 1, "", "fast_base"], [8, 1, 1, "", "fast_small"], [8, 1, 1, "", "fast_tiny"], [8, 1, 1, "", "linknet_resnet18"], [8, 1, 1, "", "linknet_resnet34"], [8, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[8, 1, 1, "", "from_hub"], [8, 1, 1, "", "login_to_hub"], [8, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[8, 1, 1, "", "crnn_mobilenet_v3_large"], [8, 1, 1, "", "crnn_mobilenet_v3_small"], [8, 1, 1, "", "crnn_vgg16_bn"], [8, 1, 1, "", "master"], [8, 1, 1, "", "parseq"], [8, 1, 1, "", "recognition_predictor"], [8, 1, 1, "", "sar_resnet31"], [8, 1, 1, "", "vitstr_base"], [8, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[9, 0, 1, "", "ChannelShuffle"], [9, 0, 1, "", "ColorInversion"], [9, 0, 1, "", "Compose"], [9, 0, 1, "", "GaussianBlur"], [9, 0, 1, "", "GaussianNoise"], [9, 0, 1, "", "LambdaTransformation"], [9, 0, 1, "", "Normalize"], [9, 0, 1, "", "OneOf"], [9, 0, 1, "", "RandomApply"], [9, 0, 1, "", "RandomBrightness"], [9, 0, 1, "", "RandomContrast"], [9, 0, 1, "", "RandomCrop"], [9, 0, 1, "", "RandomGamma"], [9, 0, 1, "", "RandomHorizontalFlip"], [9, 0, 1, "", "RandomHue"], [9, 0, 1, "", "RandomJpegQuality"], [9, 0, 1, "", "RandomResize"], [9, 0, 1, "", "RandomRotate"], [9, 0, 1, "", "RandomSaturation"], [9, 0, 1, "", "RandomShadow"], [9, 0, 1, "", "Resize"], [9, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[10, 0, 1, "", "DetectionMetric"], [10, 0, 1, "", "LocalizationConfusion"], [10, 0, 1, "", "OCRMetric"], [10, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[10, 2, 1, "", "summary"], [10, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[10, 2, 1, "", "summary"], [10, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[10, 2, 1, "", "summary"], [10, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[10, 2, 1, "", "summary"], [10, 2, 1, "", "update"]], "doctr.utils.visualization": [[10, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [1, 7, 8, 10, 14, 17], "0": [1, 3, 6, 9, 10, 12, 15, 16, 18], "00": 18, "01": 18, "0123456789": 6, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 6, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 6, "02": [], "02562": 8, "03": 18, "035": 18, "0361328125": 18, "04": 18, "05": 18, "06": 18, "06640625": 18, "07": 18, "08": [9, 18], "09": 18, "0966796875": 18, "1": [3, 6, 7, 8, 9, 10, 12, 16, 18], "10": [6, 10, 18], "100": [6, 9, 10, 16, 18], "1000": 18, "101": 6, "1024": [8, 12, 18], "104": 6, "106": 6, "108": 6, "1095": 16, "11": 18, "110": 10, "1107": 16, "114": 6, "115": [], "1156": 16, "116": 6, "118": 6, "11800h": 18, "11th": 18, "12": [3, 18], "120": 6, "123": 6, "126": 6, "1268": 16, "128": [8, 12, 17, 18], "13": 18, "130": 6, "13068": 16, "131": 6, "1337891": 16, "1357421875": 18, "1396484375": 18, "14": 18, "1420": 18, "14470v1": 6, "149": 16, "15": 18, "150": [10, 18], "154": [], "1552": 18, "16": [8, 17, 18], "160": [], "1630859375": 18, "1684": 18, "16x16": 8, "17": 18, "1778": 18, "1782": 18, "18": [8, 18], "185546875": 18, "19": [], "1900": 18, "1910": 8, "19342": 16, "19370": 16, "195": 6, "19598": 16, "199": 18, "1999": 18, "1m": [], "2": [3, 4, 6, 7, 9, 15, 18], "20": 18, "200": 10, "2000": 16, "2003": [4, 6], "2012": 6, "2013": [4, 6], "2015": 6, "2019": 4, "2021": [], "2023": [], "207901": 16, "21": 18, "2103": 6, "2186": 16, "21888": 16, "22": 18, "224": [8, 9], "225": 9, "22672": 16, "229": [9, 16], "23": 18, "233": 16, "234": 6, "236": [], "24": 18, "246": 16, "249": 16, "25": 18, "2504": 18, "255": [7, 8, 9, 10, 18], "256": 8, "257": 16, "26": 18, "26032": 16, "264": 12, "27": 18, "2700": 16, "2710": 18, "2749": 12, "28": 18, "287": 12, "29": 18, "296": 12, "299": 12, "2d": 18, "3": [3, 4, 7, 8, 9, 10, 17, 18], "30": 18, "300": 16, "3000": 16, "301": 12, "30595": 18, "30ghz": 18, "31": 8, "32": [6, 8, 9, 12, 16, 17, 18], "3232421875": 18, "33": [9, 18], "33402": 16, "33608": 16, "34": [8, 18], "340": 18, "3456": 18, "35": [], "3515625": 18, "36": 18, "360": 16, "37": [6, 18], "38": 18, "39": 18, "4": [8, 9, 10, 18], "40": 18, "406": 9, "41": 18, "42": 18, "43": 18, "44": 18, "45": 18, "456": 9, "46": 18, "47": 18, "472": 16, "48": [6, 18], "485": 9, "49": 18, "49377": 16, "5": [6, 9, 10, 15, 18], "50": [8, 16, 18], "51": 18, "51171875": 18, "512": 8, "52": [6, 18], "529": 18, "53": 18, "533": [], "54": 18, "540": 18, "5478515625": 18, "55": 18, "56": 18, "57": 18, "58": 18, "580": 18, "5810546875": 18, "583": 18, "59": 18, "595": [], "597": 18, "5k": [4, 6], "5m": 18, "6": [9, 18], "60": 9, "600": [8, 10, 18], "61": 18, "611": [], "62": 18, "625": [], "626": 16, "629": [], "63": 18, "630": [], "64": [8, 9, 18], "640": [], "641": 18, "647": 16, "65": 18, "66": 18, "660": [], "664": [], "666": [], "67": 18, "672": [], "68": 18, "689": [], "69": 18, "693": 12, "694": 12, "695": 12, "6m": 18, "7": 18, "70": [6, 10, 18], "700": [], "701": [], "702": [], "707470": 16, "71": [6, 18], "7100000": 16, "713": [], "7141797": 16, "7149": 16, "72": 18, "72dpi": 7, "73": 18, "73257": 16, "733": [], "74": 18, "745": [], "75": [9, 18], "753": [], "7581382": 16, "76": 18, "77": 18, "772": 12, "772875": 16, "78": 18, "780": [], "781": [], "783": [], "785": 12, "789": [], "79": 18, "793533": 16, "796": 16, "798": 12, "7m": 18, "8": [8, 9, 18], "80": 18, "800": [8, 10, 16, 18], "81": 18, "817": [], "82": 18, "8275l": [], "83": 18, "830": [], "84": 18, "849": 16, "85": 18, "8564453125": 18, "857": 18, "85875": 16, "86": 18, "860": [], "8603515625": 18, "862": [], "863": [], "87": 18, "8707": 16, "875": [], "88": 18, "89": 18, "8m": [], "9": [3, 9, 18], "90": 18, "90k": 6, "90kdict32px": 6, "91": 18, "913": [], "914085328578949": 18, "917": [], "92": 18, "921": [], "93": 18, "94": [6, 18], "95": [10, 18], "9578408598899841": 18, "96": 18, "97": 18, "98": 18, "99": 18, "9949972033500671": 18, "A": [1, 2, 4, 6, 7, 8, 11, 17], "And": [], "As": 2, "Be": 18, "Being": 1, "By": 13, "For": [1, 2, 3, 12, 18], "If": [2, 7, 8, 12, 18], "In": [2, 6, 16], "It": [9, 14, 15, 17], "Its": [4, 8], "No": [1, 18], "Of": 6, "Or": [15, 17], "The": [1, 2, 6, 7, 10, 13, 15, 17, 18], "Then": 8, "To": [2, 3, 13, 14, 15, 17, 18], "_": [1, 6, 8], "__call__": 18, "_build": 2, "_i": 10, "ab": 6, "abc": 17, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 6, "abdef": [6, 16], "abl": [16, 18], "about": [1, 16, 18], "abov": 18, "abstract": [], "abstractdataset": 6, "abus": 1, "accent": [], "accept": 1, "access": [4, 7, 16, 18], "account": [1, 14], "accur": 18, "accuraci": 10, "achiev": 17, "act": 1, "action": 1, "activ": 4, "ad": [2, 8, 9], "adapt": 1, "add": [9, 10, 14, 18], "add_hook": 18, "add_label": 10, "addit": [2, 3, 7, 15], "addition": [2, 18], "address": [1, 7], "adjust": 9, "advanc": 1, "advantag": 17, "advis": 2, "aesthet": [4, 6], "affect": 1, "after": [14, 18], "ag": 1, "again": 8, "aggreg": [10, 16], "aggress": 1, "align": [1, 7, 9], "all": [1, 2, 5, 6, 7, 9, 10, 15, 16, 18], "allow": [1, 17], "along": 18, "alreadi": [2, 17], "also": [1, 8, 14, 15, 16, 18], "alwai": 16, "an": [1, 2, 4, 6, 7, 8, 10, 15, 17, 18], "analysi": [7, 15], "ancient_greek": 6, "andrej": [], "angl": [7, 9], "ani": [1, 6, 7, 8, 9, 10, 17, 18], "annot": 6, "anot": 16, "anoth": [8, 12, 16], "answer": 1, "anyascii": 10, "anyon": 4, "anyth": 15, "api": [2, 4], "apolog": 1, "apologi": 1, "app": 2, "appear": 1, "appli": [1, 6, 9], "applic": [4, 8], "appoint": 1, "appreci": 14, "appropri": [1, 2, 18], "ar": [1, 2, 3, 5, 6, 7, 9, 10, 11, 15, 16, 18], "arab": 6, "arabic_diacrit": 6, "arabic_lett": 6, "arabic_punctu": 6, "arbitrarili": [4, 8], "arch": [8, 14], "architectur": [4, 8, 14, 15], "archiv": [], "area": 18, "arg": [], "argument": [6, 7, 8, 10, 18], "around": 1, "arrai": [7, 9, 10], "art": [4, 15], "artefact": [10, 11, 15, 18], "artefact_typ": 7, "articl": [], "artifici": [4, 6], "arxiv": [6, 8], "as_imag": [], "asarrai": 10, "ascii_lett": 6, "aspect": [4, 8, 9, 18], "assess": 10, "assign": 10, "associ": 7, "assum": 8, "assume_straight_pag": [8, 18], "astyp": [8, 10, 18], "attack": 1, "attend": [4, 8], "attent": [1, 8], "autoclass": [], "autom": 4, "automat": 18, "autoregress": [4, 8], "avail": [1, 4, 5, 9], "averag": [9, 18], "avoid": [1, 3], "aw": [4, 18], "awar": 18, "azur": 18, "b": [8, 10, 18], "b_j": 10, "back": 2, "backbon": 8, "backend": 18, "background": 16, "bangla": 6, "bar": 15, "bar_cod": 16, "baranovskij": [], "base": [4, 8, 15], "baselin": [4, 8, 18], "batch": [6, 8, 9, 15, 16, 18], "batch_siz": [6, 12, 15, 16, 17], "bblanchon": 3, "bbox": 18, "becaus": 13, "been": [2, 10, 16, 18], "befor": [6, 8, 9, 18], "begin": 10, "behavior": [1, 18], "being": [10, 18], "belong": 18, "benchmark": 18, "best": 1, "beta": [], "better": [11, 18], "between": [9, 10, 18], "bgr": 7, "bilinear": 9, "bin_thresh": 18, "binar": [4, 8, 18], "binari": [7, 17, 18], "bit": 17, "blank": [], "block": [10, 18], "block_1_1": 18, "blue": [], "blur": 9, "bmvc": 6, "bn": 14, "bodi": [1, 18], "bool": [6, 7, 8, 9, 10], "boolean": [8, 18], "both": [4, 6, 9, 16, 18], "bottom": [8, 18], "bound": [6, 7, 8, 9, 10, 15, 18], "box": [6, 7, 8, 9, 10, 15, 16, 18], "box_thresh": 18, "brew": [], "bright": 9, "broadcast": [], "browser": [2, 4], "build": [2, 3, 17], "built": 2, "byte": [7, 18], "c": [3, 7, 10], "c5": [], "c_j": 10, "cach": [2, 6, 13], "cache_sampl": 6, "cairo": [], "call": 17, "callabl": [6, 9], "can": [2, 3, 12, 13, 14, 15, 16, 18], "capabl": [2, 11, 18], "case": [6, 10], "cf": 18, "cfg": 18, "challeng": 6, "challenge2_test_task12_imag": 6, "challenge2_test_task1_gt": 6, "challenge2_training_task12_imag": 6, "challenge2_training_task1_gt": 6, "chang": [13, 18], "changelog": [], "channel": [1, 2, 7, 9], "channel_prior": 3, "channelshuffl": 9, "charact": [4, 6, 7, 10, 16, 18], "charactergener": [6, 16], "characterist": 1, "charg": 18, "charset": 18, "chart": 7, "check": [2, 14, 18], "checkpoint": 8, "chip": 3, "christian": [], "ci": 2, "clarifi": 1, "clariti": 1, "class": [1, 6, 7, 9, 10, 18], "class_nam": 12, "classif": 16, "classif_mobilenet_v3_smal": [], "classmethod": 7, "clear": 2, "clone": 3, "close": 2, "co": 14, "code": [4, 7, 15], "codecov": 2, "colab": 11, "collate_fn": 6, "collect": [7, 15], "color": 9, "colorinvers": 9, "column": 7, "com": [1, 3, 7, 8, 14], "combin": 18, "come": [], "command": [2, 15], "comment": 1, "commit": 1, "common": [1, 9, 10, 17], "commun": 1, "compar": 4, "comparison": [10, 18], "competit": 6, "compil": [11, 18], "complaint": 1, "complementari": 10, "complet": 2, "compon": 18, "compos": [6, 18], "comprehens": 18, "comput": [6, 10, 17, 18], "conf_threshold": 15, "confid": [7, 18], "config": [3, 8], "configur": 8, "confus": 10, "consecut": [9, 18], "consequ": 1, "consid": [1, 2, 6, 7, 10, 18], "consist": 18, "consolid": [4, 6], "constant": 9, "construct": 1, "consum": [], "contact": 1, "contain": [5, 6, 16], "content": [6, 7, 18], "context": 8, "contib": 3, "continu": 1, "contrast": 9, "contrast_factor": 9, "contrib": [3, 15], "contribut": 1, "contributor": 2, "conv_sequ": [], "convers": 7, "convert": [7, 9], "convert_page_to_numpi": [], "convert_to_fp16": [], "convert_to_tflit": [], "convolut": 8, "cool": [], "coordin": [7, 18], "cord": [4, 6, 16, 18], "core": [10, 18], "corner": 18, "correct": 9, "correspond": [3, 7, 9, 18], "could": [1, 15], "counterpart": 10, "cover": 2, "coverag": 2, "cpu": [4, 12, 17], "creat": 14, "crnn": [4, 8, 14], "crnn_mobilenet_v3_larg": [8, 14, 18], "crnn_mobilenet_v3_smal": [8, 17, 18], "crnn_resnet31": [], "crnn_vgg16_bn": [8, 12, 14, 18], "crop": [7, 8, 9, 16, 18], "crop_orient": [7, 18], "crop_orientation_predictor": 8, "crop_param": [], "croporientationpredictor": [], "cuda": 17, "currenc": 6, "current": [2, 18], "custom": [14, 15, 17, 18], "custom_crop_orientation_model": [], "custom_page_orientation_model": [], "customhook": 18, "cvit": 4, "czczup": 8, "czech": 6, "d": [6, 16], "daili": [], "danish": 6, "data": [4, 6, 7, 9, 10, 12, 14], "dataload": 16, "dataset": [8, 12, 18], "dataset_info": 6, "date": [12, 18], "db": 14, "db_crnn_resnet": [], "db_crnn_vgg": [], "db_mobilenet_v3_larg": [8, 14, 18], "db_resnet34": 18, "db_resnet50": [8, 12, 14, 18], "db_resnet50_rot": [], "db_sar_resnet": [], "db_sar_vgg": [], "dbnet": [4, 8], "deal": [], "decis": 1, "decod": 7, "decode_img_as_tensor": 7, "dedic": 17, "deem": 1, "deep": [8, 18], "def": 18, "default": [3, 7, 12, 13, 18], "defer": 16, "defin": [10, 17], "deform": [], "degre": [7, 9], "degress": 7, "delet": 2, "delimit": 18, "delta": 9, "demo": [2, 4], "demonstr": 1, "depend": [2, 3, 4, 18], "deploi": 2, "deploy": 4, "derogatori": 1, "describ": 8, "descript": 11, "design": 9, "desir": 7, "det_arch": [8, 12, 14, 17], "det_b": 18, "det_model": [12, 14, 17], "det_param": 12, "det_predictor": [12, 18], "detail": [12, 18], "detect": [6, 7, 10, 11, 12, 15], "detect_languag": 8, "detect_orient": 8, "detection_predictor": [8, 18], "detection_task": [], "detectiondataset": [6, 16], "detectionmetr": 10, "detectionpredictor": [8, 12], "detector": [4, 8, 15], "deterior": 8, "determin": 1, "dev": [2, 13], "develop": 3, "developp": [], "deviat": 9, "devic": 17, "dict": [7, 10, 18], "dictionari": [7, 10], "differ": 1, "differenti": [4, 8], "digit": [4, 6, 16], "dimens": [7, 10, 18], "dimension": 9, "direct": 6, "directli": [14, 18], "directori": [2, 13], "disabl": [1, 13, 18], "disable_crop_orient": [], "disable_page_orient": [], "disclaim": 18, "discuss": 2, "disk": [], "disparag": 1, "displai": [7, 10], "display_artefact": 10, "distanc": [], "distribut": 9, "div": 18, "divers": 1, "divid": 7, "do": [2, 3, 8], "doc": [2, 7, 15, 17, 18], "docartefact": [6, 16], "docstr": 2, "doctr": [3, 12, 13, 14, 15, 16, 17, 18], "doctr_cache_dir": 13, "doctr_multiprocessing_dis": 13, "document": [6, 8, 10, 11, 15, 16, 17, 18], "documentbuild": 18, "documentfil": [7, 14, 15, 17], "doesn": 17, "don": [12, 18], "done": 9, "download": [6, 16], "downsiz": 8, "draw": 9, "draw_proba": [], "drop": 6, "drop_last": 6, "dtype": [7, 8, 9, 10, 17], "dual": [4, 6], "dummi": 14, "dummy_img": 18, "dummy_input": 17, "dure": 1, "dutch": 6, "dynam": [6, 15], "dynamic_seq_length": 6, "e": [1, 2, 3, 7, 8], "each": [4, 6, 7, 8, 9, 10, 16, 18], "eas": 2, "easi": [4, 10, 14, 17], "easier": [], "easili": [7, 10, 12, 14, 16, 18], "econom": 1, "edit": 1, "educ": 1, "effect": [], "effici": [2, 4, 6, 8], "either": [10, 18], "element": [6, 7, 8, 18], "els": [2, 15], "email": 1, "empathi": 1, "en": 18, "enabl": [6, 7], "enclos": 7, "encod": [4, 6, 7, 8, 18], "encode_sequ": 6, "encount": 2, "encrypt": 7, "end": [4, 6, 8, 10], "english": [6, 16], "enough": [2, 18], "ensur": 2, "entir": [], "entri": 6, "environ": [1, 13], "eo": 6, "equiv": 18, "error": [], "estim": 8, "etc": [7, 15], "ethnic": 1, "evalu": [16, 18], "event": 1, "everyon": 1, "everyth": [2, 18], "exact": [10, 18], "exactmatch": [], "exampl": [1, 2, 4, 6, 8, 14, 18], "exchang": 17, "exclud": [], "execut": 18, "exist": 14, "expand": 9, "expect": [7, 9, 10], "experi": 1, "explan": [1, 18], "explicit": 1, "exploit": [4, 8], "export": [7, 8, 10, 11, 15, 18], "export_as_straight_box": [8, 18], "export_as_xml": 18, "export_model_to_onnx": 17, "express": [1, 9], "extens": 7, "extern": [1, 16], "extra": [], "extract": [4, 6], "extract_arch": [], "extractor": 8, "f_": 10, "f_a": 10, "factor": 9, "fair": 1, "fairli": 1, "fals": [6, 7, 8, 9, 10, 12, 18], "famili": [], "faq": 1, "fascan": 14, "fast": [4, 6, 8], "fast_bas": [8, 18], "fast_smal": [8, 18], "fast_tini": [8, 18], "faster": [4, 8, 17], "fasterrcnn_mobilenet_v3_large_fpn": 8, "favorit": 18, "featur": [3, 8, 10, 11, 15], "feed": [], "feedback": 1, "feel": [2, 14], "felix92": 14, "few": [17, 18], "figsiz": 10, "figur": [10, 15], "file": [2, 6], "file_hash": [], "file_nam": [], "final": 8, "find": [2, 16], "fine": [], "finnish": 6, "first": [2, 6], "firsthand": 6, "fit": [8, 18], "fitz": [], "flag": 18, "flexibl": [], "flip": 9, "float": [7, 9, 10, 17], "float32": [7, 8, 9, 17], "fn": 9, "focu": 14, "focus": [1, 6], "folder": 6, "follow": [1, 2, 3, 6, 9, 10, 12, 13, 14, 15, 18], "font": 6, "font_famili": 6, "font_siz": [], "foral": 10, "forc": 2, "forg": 3, "form": [4, 6, 18], "format": [7, 10, 12, 16, 17, 18], "forpost": [4, 6], "forum": 2, "found": [], "fp": [], "fp16": 17, "frac": 10, "frame": [], "framework": [3, 14, 16, 18], "free": [1, 2, 14], "french": [6, 12, 14, 18], "friendli": 4, "from": [1, 4, 6, 7, 8, 9, 10, 11, 12, 15, 16, 17, 18], "from_hub": [8, 14], "from_imag": [7, 14, 15, 17], "from_pdf": 7, "from_url": 7, "full": [6, 10, 18], "fulli": [], "function": [6, 9, 10, 15], "funsd": [4, 6, 16, 18], "further": 16, "futur": 6, "g": [7, 8], "g_": 10, "g_x": 10, "gallagh": [], "gamma": 9, "gaussian": 9, "gaussianblur": 9, "gaussiannois": 9, "gdk": [], "gen": 18, "gender": 1, "gener": [2, 4, 7, 8], "generic_cyrillic_lett": [], "geometri": [4, 7, 18], "geq": 10, "german": [6, 12, 14], "get": [17, 18], "get_artefact": [], "get_word": [], "gettextword": [], "git": 14, "github": [2, 3, 8, 14], "give": [1, 15], "given": [6, 7, 9, 10, 18], "global": 8, "go": 18, "good": 17, "googl": 2, "googlevis": 4, "gpu": [4, 15, 17], "gracefulli": 1, "graph": [4, 6, 7], "grayscal": 9, "ground": 10, "groung": 10, "group": [4, 18], "gt": 10, "gt_box": 10, "gt_label": 10, "gtk": [], "guid": 2, "guidanc": 16, "gvision": 18, "h": [7, 8, 9], "h_": 10, "ha": [2, 6, 10, 16], "half": [], "handl": [16, 18], "handwrit": 6, "handwritten": 16, "harass": 1, "hardwar": 18, "harm": 1, "hat": 10, "have": [1, 2, 10, 12, 14, 16, 17, 18], "head": [8, 18], "healthi": 1, "hebrew": 6, "height": [7, 9], "hello": [10, 18], "help": 17, "here": [5, 9, 11, 15, 16, 18], "hf": 8, "hf_hub_download": 8, "high": 7, "higher": [3, 6, 18], "hindi": 6, "hindi_digit": 6, "hocr": 18, "homebrew": [], "hook": 18, "horizont": [7, 9], "hous": 6, "how": [2, 12, 14, 16], "howev": 16, "hsv": 9, "html": [1, 2, 3, 7, 18], "http": [1, 3, 6, 7, 8, 14, 18], "hub": 8, "hue": 9, "huggingfac": 8, "hw": 6, "i": [1, 2, 6, 7, 8, 9, 10, 13, 14, 15, 16, 17], "i7": 18, "ibrahimov": [], "ic03": [4, 6, 16], "ic13": [4, 6, 16], "icdar": [4, 6], "icdar2019": 6, "id": 18, "ident": 1, "identifi": 4, "ignor": [], "ignore_acc": [], "ignore_cas": [], "iiit": [4, 6], "iiit5k": [6, 16], "iiithw": [4, 6, 16], "imag": [4, 6, 7, 8, 9, 10, 14, 15, 16, 18], "imagenet": 8, "imageri": 1, "images_90k_norm": 6, "img": [6, 9, 16, 17], "img_cont": 7, "img_fold": [6, 16], "img_path": 7, "img_transform": 6, "imgur5k": [4, 6, 16], "imgur5k_annot": 6, "imlist": 6, "impact": 1, "implement": [6, 7, 8, 9, 10, 18], "import": [6, 7, 8, 9, 10, 12, 14, 15, 16, 17, 18], "improv": 8, "inappropri": 1, "incid": 1, "includ": [1, 6, 16, 17], "inclus": 1, "increas": 9, "independ": 9, "index": [2, 7], "indic": 10, "individu": 1, "infer": [4, 8, 9, 15], "inform": [1, 2, 4, 6, 16], "inherit": [], "input": [2, 7, 8, 9, 17, 18], "input_crop": 8, "input_pag": [8, 10, 18], "input_shap": 17, "input_t": [], "input_tensor": 8, "inspir": [1, 9], "instal": [14, 15, 17], "instanc": [1, 18], "instanti": [8, 18], "instead": [6, 7, 8], "insult": 1, "int": [6, 7, 9], "int64": 10, "integ": 10, "integr": [4, 14, 16], "intel": 18, "interact": [1, 7, 10], "interfac": [14, 17], "interoper": 17, "interpol": 9, "interpret": [6, 7], "intersect": 10, "invert": 9, "investig": 1, "invis": 1, "invoic": [], "involv": [1, 18], "io": [14, 15, 17], "iou": 10, "iou_thresh": 10, "iou_threshold": 15, "irregular": [4, 8, 16], "isn": 6, "issu": [1, 2, 14], "italian": 6, "iter": [6, 9, 16, 18], "its": [7, 8, 9, 10, 16, 18], "itself": [8, 14], "j": 10, "jame": [], "job": 2, "join": 2, "jpeg": 9, "jpegqual": 9, "jpg": [6, 7, 14, 17], "json": [6, 16, 18], "json_output": 18, "jump": 2, "just": 1, "kei": [4, 6], "kera": [8, 17], "kernel": [4, 8, 9], "kernel_s": [], "kernel_shap": 9, "keywoard": 8, "keyword": [6, 7, 8, 10], "kie": [8, 12], "kie_predictor": [8, 12], "kiepredictor": 8, "kind": 1, "know": [2, 17], "kwarg": [6, 7, 8, 10], "l": 10, "l_j": 10, "label": [6, 10, 15, 16], "label_fil": [6, 16], "label_fold": 6, "label_path": [6, 16], "labels_path": [6, 16], "ladder": 1, "lambda": 9, "lambdatransform": 9, "lang": 18, "languag": [1, 4, 6, 7, 8, 14, 18], "larg": [8, 14], "largest": 10, "last": [3, 6], "latenc": 8, "later": 2, "latest": 18, "latin": 6, "layer": 17, "layout": 18, "lead": 1, "leader": 1, "learn": [1, 4, 8, 17, 18], "least": 3, "left": [10, 18], "legacy_french": 6, "length": [6, 18], "less": [17, 18], "let": [], "letter": [], "level": [1, 6, 10, 18], "levenshtein": [], "leverag": 11, "lf": 14, "libffi": [], "librari": [2, 3, 11, 12], "light": 4, "lightweight": 17, "like": 1, "limits_": 10, "line": [4, 8, 10, 18], "line_1_1": 18, "link": 12, "linknet": [4, 8], "linknet16": [], "linknet_resnet18": [8, 12, 17, 18], "linknet_resnet18_rot": [], "linknet_resnet34": [8, 17, 18], "linknet_resnet50": [8, 18], "linux": [], "list": [6, 7, 9, 10, 14], "ll": 10, "load": [4, 6, 8, 15, 17], "load_state_dict": 12, "load_weight": 12, "loader": [], "loc_pr": 18, "local": [2, 4, 6, 8, 10, 16, 18], "localis": 6, "localizationconfus": 10, "locat": [2, 7, 18], "login": 8, "login_to_hub": [8, 14], "logo": [7, 15, 16], "love": 14, "lower": [9, 10, 18], "m": [2, 10, 18], "m1": 3, "macbook": 3, "machin": 17, "maco": [], "made": 4, "magc_resnet31": 8, "mai": [1, 2], "mail": 1, "main": 11, "maintain": 4, "mainten": 2, "make": [1, 2, 10, 13, 14, 17, 18], "mani": [16, 18], "manipul": 18, "map": [6, 8], "map_loc": 12, "mask_shap": [], "master": [4, 8, 18], "match": [10, 18], "mathcal": 10, "matplotlib": [7, 10], "max": [6, 9, 10], "max_angl": 9, "max_area": 9, "max_char": [6, 16], "max_delta": 9, "max_dist": [], "max_gain": 9, "max_gamma": 9, "max_qual": 9, "max_ratio": 9, "maximum": [6, 9], "maxval": [8, 9], "mbox": 10, "mean": [9, 10, 12], "meaniou": 10, "meant": [7, 17], "measur": 18, "media": 1, "median": 8, "meet": 12, "member": 1, "memori": [13, 17], "mention": 18, "merg": 6, "messag": 2, "meta": 18, "metadata": 17, "metal": 3, "method": [7, 9, 18], "metric": [10, 18], "middl": 18, "might": [17, 18], "min": 9, "min_area": 9, "min_char": [6, 16], "min_gain": 9, "min_gamma": 9, "min_qual": 9, "min_ratio": 9, "min_val": 9, "minde": [1, 3, 4, 8], "minim": [2, 4], "minimalist": [4, 8], "minimum": [3, 6, 9, 10, 18], "minval": 9, "miss": 3, "mistak": 1, "mix": [], "mixed_float16": 17, "mixed_precis": 17, "mjsynth": [4, 6, 16], "mnt": 6, "mobilenet": [8, 14], "mobilenet_v3_larg": 8, "mobilenet_v3_large_r": 8, "mobilenet_v3_smal": 8, "mobilenet_v3_small_crop_orient": 8, "mobilenet_v3_small_orient": [], "mobilenet_v3_small_page_orient": 8, "mobilenet_v3_small_r": 8, "mobilenetv3": 8, "modal": [4, 6], "mode": 3, "model": [6, 10, 13, 15, 16], "model_nam": [8, 14, 17], "model_path": [15, 17], "moder": 1, "modif": 2, "modifi": [8, 13, 18], "modul": [3, 7, 8, 9, 10, 18], "moment": [], "more": [2, 16, 18], "moscardi": [], "most": 18, "mozilla": 1, "multi": [4, 8], "multilingu": [6, 14], "multipl": [6, 7, 9, 18], "multipli": 9, "multiprocess": 13, "my": 8, "my_awesome_model": 14, "my_hook": 18, "n": [6, 10], "na": [], "name": [6, 8, 17, 18], "nation": 1, "natur": [1, 4, 6], "nb": [], "ndarrai": [6, 7, 9, 10], "necessari": [3, 12, 13], "need": [2, 3, 6, 10, 12, 13, 14, 15, 18], "neg": 9, "nest": 18, "nestedobject": [], "netraj": [], "network": [4, 6, 8, 17], "neural": [4, 6, 8, 17], "new": [2, 10], "newer": [], "next": [6, 16], "nois": 9, "noisi": [4, 6], "non": [4, 6, 7, 8, 9, 10], "none": [6, 7, 8, 9, 10, 18], "normal": [8, 9], "norwegian": 6, "note": [0, 2, 6, 8, 14, 15, 17], "now": 2, "np": [8, 9, 10, 18], "num_output_channel": 9, "num_sampl": [6, 16], "num_work": [], "number": [6, 9, 10, 18], "numpi": [7, 8, 10, 18], "o": 3, "obb": 15, "obj_detect": 14, "object": [6, 7, 10, 11, 15, 18], "objectness_scor": [7, 18], "oblig": 1, "obtain": 18, "occupi": 17, "ocr": [4, 6, 8, 10, 14, 16], "ocr_carea": 18, "ocr_db_crnn": 10, "ocr_lin": 18, "ocr_pag": 18, "ocr_par": 18, "ocr_predictor": [8, 12, 14, 17, 18], "ocrdataset": [6, 16], "ocrmetr": 10, "ocrpredictor": [8, 12], "ocrx_word": 18, "offens": 1, "offici": [1, 8], "offlin": 1, "offset": 9, "onc": 18, "one": [2, 6, 8, 9, 12, 14, 18], "oneof": 9, "ones": [6, 10], "onli": [2, 8, 9, 10, 14, 16, 17, 18], "onlin": 1, "onnx": 15, "onnxruntim": [15, 17], "onnxtr": 17, "opac": 9, "opacity_rang": 9, "open": [1, 2, 14, 17], "opinion": 1, "optic": [4, 18], "optim": [4, 18], "option": [6, 8, 12], "order": [2, 6, 7, 9], "org": [1, 6, 8, 18], "organ": 7, "orient": [1, 7, 8, 15, 18], "orientationpredictor": 8, "other": [1, 2], "otherwis": [1, 7, 10], "our": [2, 8, 18], "out": [2, 8, 9, 10, 18], "outpout": 18, "output": [7, 9, 17], "output_s": [7, 9], "outsid": 13, "over": [6, 10, 18], "overal": [1, 8], "overlai": 7, "overview": 15, "overwrit": [], "overwritten": 14, "own": 4, "p": [9, 18], "packag": [2, 4, 10, 13, 15, 16, 17], "pad": [6, 8, 9, 18], "page": [3, 6, 8, 10, 18], "page1": 7, "page2": 7, "page_1": 18, "page_idx": [7, 18], "page_orientation_predictor": 8, "page_param": [], "pair": 10, "pango": [], "paper": 8, "par_1_1": 18, "paragraph": 18, "paragraph_break": 18, "parallel": [], "param": [9, 18], "paramet": [4, 7, 8, 17], "pars": [4, 6], "parseq": [4, 8, 14, 17, 18], "part": [6, 9, 18], "parti": 3, "partial": 18, "particip": 1, "pass": [6, 7, 8, 18], "password": 7, "patch": [8, 10], "path": [6, 7, 15, 16, 17], "path_to_checkpoint": 12, "path_to_custom_model": 17, "path_to_pt": 12, "patil": [], "pattern": 1, "pdf": [7, 8, 11], "pdfpage": 7, "peopl": 1, "per": [9, 18], "perform": [4, 7, 8, 9, 10, 13, 17, 18], "period": 1, "permiss": 1, "permut": [4, 8], "persian_lett": 6, "person": [1, 16], "phase": 18, "photo": 16, "physic": [1, 7], "pick": 9, "pictur": 7, "pip": [2, 3, 15, 17], "pipelin": 18, "pixbuf": [], "pixel": [7, 9, 18], "platinum": [], "pleas": 2, "plot": 10, "plt": 10, "plug": 14, "plugin": 3, "png": 7, "point": 17, "polici": 13, "polish": 6, "polit": 1, "polygon": [6, 10, 18], "pool": 8, "portugues": 6, "posit": [1, 10], "possibl": [2, 10, 14, 18], "post": [1, 18], "postprocessor": 18, "potenti": 8, "power": 4, "ppageno": 18, "pre": [2, 8, 17], "precis": [10, 18], "pred": 10, "pred_box": 10, "pred_label": 10, "predefin": 16, "predict": [7, 8, 10, 18], "predictor": [4, 7, 8, 12, 14, 17], "prefer": 16, "preinstal": 3, "preprocessor": [12, 18], "prerequisit": 14, "present": 11, "preserv": [8, 9, 18], "preserve_aspect_ratio": [7, 8, 9, 12, 18], "pretrain": [4, 8, 10, 12, 17, 18], "pretrained_backbon": [8, 12], "print": 18, "prior": 6, "privaci": 1, "privat": 1, "probabl": 9, "problem": 2, "procedur": 9, "process": [2, 4, 7, 12, 18], "processor": 18, "produc": [11, 18], "product": 17, "profession": 1, "project": [2, 16], "promptli": 1, "proper": 2, "properli": 6, "properti": [], "provid": [1, 2, 4, 14, 15, 16, 18], "public": [1, 4], "publicli": 18, "publish": 1, "pull": 14, "punctuat": 6, "pure": 6, "purpos": 2, "push_to_hf_hub": [8, 14], "py": 14, "pypdfium2": [3, 7], "pyplot": [7, 10], "python": [2, 15], "python3": 14, "pytorch": [3, 4, 8, 9, 12, 14, 17, 18], "q": 2, "qr": [7, 15], "qr_code": 16, "qualiti": 9, "quantiz": [], "quantize_model": [], "question": 1, "quickli": 4, "quicktour": 11, "r": 18, "race": 1, "ramdisk": 6, "rand": [8, 9, 10, 17, 18], "random": [8, 9, 10, 18], "randomappli": 9, "randombright": 9, "randomcontrast": 9, "randomcrop": 9, "randomgamma": 9, "randomhorizontalflip": 9, "randomhu": 9, "randomjpegqu": 9, "randomli": 9, "randomres": 9, "randomrot": 9, "randomsatur": 9, "randomshadow": 9, "rang": 9, "rassi": 14, "ratio": [8, 9, 18], "raw": [7, 10], "re": 17, "read": [4, 6, 8], "read_html": 7, "read_img": [], "read_img_as_numpi": 7, "read_img_as_tensor": 7, "read_pdf": 7, "readi": 17, "real": [4, 8, 9], "realli": [], "reason": [1, 4, 6], "rebuild": 2, "rebuilt": 2, "recal": [10, 18], "receipt": [4, 6, 18], "reco_arch": [8, 12, 14, 17], "reco_b": 18, "reco_model": [12, 14, 17], "reco_param": 12, "reco_predictor": 12, "recogn": 18, "recognit": [6, 10, 12], "recognition_predictor": [8, 18], "recognition_task": [6, 16], "recognitiondataset": [6, 16], "recognitionpredictor": [8, 12], "rectangular": 8, "recurr": [], "red": [], "reduc": [3, 9], "refer": [2, 3, 12, 14, 15, 16, 18], "regardless": 1, "region": 18, "regroup": 10, "regular": 16, "reject": 1, "rel": [7, 9, 10, 18], "relat": 7, "releas": [0, 3], "relev": 15, "religion": 1, "relu": [], "remov": 1, "render": [7, 18], "repo": 8, "repo_id": [8, 14], "report": 1, "repositori": [6, 8, 14], "repres": [1, 17, 18], "represent": [4, 8], "request": [1, 14], "requir": [3, 9, 17], "research": 4, "residu": 8, "resiz": [9, 18], "resnet": 8, "resnet18": [8, 14], "resnet31": 8, "resnet34": 8, "resnet50": [8, 14], "resolv": 7, "resolve_block": 18, "resolve_lin": 18, "resourc": 16, "respect": 1, "respons": [], "rest": [2, 9, 10], "restrict": 13, "result": [2, 6, 7, 11, 14, 17, 18], "return": 18, "reusabl": 18, "review": 1, "rgb": [7, 9], "rgb_mode": 7, "rgb_output": 7, "right": [1, 8, 10], "roboflow": [], "robust": [4, 6], "root": 6, "rotat": [6, 7, 8, 9, 10, 16, 18], "rotated_bbox": [], "run": [2, 3, 8], "same": [2, 7, 10, 16, 17, 18], "sampl": [6, 16, 18], "sample_transform": 6, "sanjin": [], "sar": [4, 8], "sar_resnet31": [8, 18], "sar_vgg16_bn": [], "satur": 9, "save": [8, 16], "saved_model": [], "scale": [7, 8, 9, 10], "scale_rang": 9, "scan": [4, 6], "scene": [4, 6, 8], "scheme": [], "score": [7, 10], "scratch": [], "script": [2, 16], "seamless": 4, "seamlessli": [4, 18], "search": 8, "searchabl": 11, "sec": 18, "second": 18, "section": [12, 14, 15, 17, 18], "secur": [1, 13], "see": [1, 2], "seemlessli": [], "seen": 18, "segment": [4, 8, 18], "self": 18, "semant": [4, 8], "send": 18, "sens": 10, "sensit": 16, "separ": 18, "sequenc": [4, 6, 7, 8, 10, 18], "sequenti": [9, 18], "seri": 1, "serial": [], "serialized_model": [], "seriou": 1, "set": [1, 3, 6, 8, 10, 13, 15, 18], "set_global_polici": 17, "sever": [7, 9, 18], "sex": 1, "sexual": 1, "sha256": [], "shade": 9, "shape": [4, 7, 8, 9, 10, 18], "share": [13, 16], "shift": 9, "shm": 13, "should": [2, 6, 7, 9, 10], "show": [4, 7, 8, 10, 12, 14, 15], "showcas": 2, "shuffl": [6, 9], "side": 10, "signatur": 7, "signific": 16, "simpl": [4, 8, 17], "simpler": 8, "sinc": [6, 16], "singl": [1, 2, 4, 6], "single_img_doc": 17, "size": [1, 6, 7, 9, 15, 18], "skew": 18, "slack": 2, "slightli": 8, "small": [2, 8], "smallest": 7, "snapshot_download": 8, "snippet": 18, "so": [2, 3, 6, 8, 14, 16], "social": 1, "socio": 1, "some": [3, 11, 14, 16], "someth": 2, "somewher": 2, "soon": [], "sort": 1, "sourc": [6, 7, 8, 9, 10, 14], "space": [1, 18], "span": 18, "spanish": 6, "spatial": [4, 6, 7], "special": [], "specif": [2, 3, 10, 12, 16, 18], "specifi": [1, 6, 7], "speed": [4, 8], "sphinx": 2, "sroie": [4, 6, 16], "stabl": 3, "stackoverflow": 2, "stage": 4, "standalon": [], "standard": 9, "start": 6, "state": [4, 10, 15], "static": 10, "statist": [], "statu": 1, "std": [9, 12], "step": 13, "still": 18, "str": [6, 7, 8, 9, 10], "straight": [6, 8, 16, 18], "straighten": [], "straighten_pag": 8, "straigten_pag": [], "stream": 7, "street": [4, 6], "strict": 3, "strictli": 10, "string": [6, 7, 10, 18], "strive": 3, "strong": [4, 8], "structur": [17, 18], "subset": [6, 18], "suggest": [2, 14], "sum": 10, "summari": 10, "support": [3, 15, 17, 18], "sustain": 1, "svhn": [4, 6, 16], "svt": [6, 16], "swedish": 6, "symbol": [], "symmetr": [8, 9, 18], "symmetric_pad": [8, 9, 18], "synthes": [], "synthesize_pag": [], "synthet": 4, "synthtext": [4, 6, 16], "system": 18, "t": [2, 6, 12, 17, 18], "tabl": [14, 15], "take": [1, 6, 18], "target": [6, 7, 9, 10, 16], "target_s": 6, "task": [4, 6, 8, 14, 16, 18], "task2": 6, "team": 3, "techminde": 3, "templat": [2, 4], "tensor": [6, 7, 9, 18], "tensorflow": [3, 4, 7, 8, 9, 12, 14, 17, 18], "tensorspec": 17, "term": 1, "test": [6, 16], "test_set": 6, "text": [6, 7, 8, 10, 16], "text_output": 18, "textmatch": 10, "textnet": 8, "textnet_bas": 8, "textnet_smal": 8, "textnet_tini": 8, "textract": [4, 18], "textstylebrush": [4, 6], "textual": [4, 6, 7, 8, 18], "tf": [3, 7, 8, 9, 14, 17], "tf_model": [], "tflite": [], "than": [2, 10, 14], "thank": 2, "thei": [1, 10], "them": [6, 18], "thi": [1, 2, 3, 5, 6, 9, 10, 12, 13, 14, 16, 17, 18], "thing": [17, 18], "third": 3, "those": [1, 7, 18], "threaten": 1, "threshold": 18, "through": [1, 9, 15, 16], "tilman": 14, "time": [1, 4, 8, 10, 16], "tini": 8, "titl": [7, 18], "tm": 18, "tmp": 13, "togeth": [2, 7], "tograi": 9, "tool": 16, "top": [10, 17, 18], "topic": 2, "torch": [3, 9, 12, 14, 17], "torchvis": 9, "total": 12, "toward": [1, 3], "train": [2, 6, 8, 9, 14, 15, 16, 17, 18], "train_it": [6, 16], "train_load": [6, 16], "train_pytorch": 14, "train_set": [6, 16], "train_tensorflow": 14, "trainabl": [4, 8], "tranform": 9, "transcrib": 18, "transfer": [4, 6], "transfo": 9, "transform": [4, 6, 8], "translat": 1, "troll": 1, "true": [6, 7, 8, 9, 10, 12, 13, 14, 16, 17, 18], "truth": 10, "tune": 17, "tupl": [6, 7, 9, 10], "turn": [], "two": [7, 13], "txt": 6, "type": [7, 10, 14, 17, 18], "typic": 18, "u": [1, 2], "ucsd": 6, "udac": 2, "uint8": [7, 8, 10, 18], "ukrainian": [], "unaccept": 1, "underli": [16, 18], "underneath": 7, "understand": [4, 6, 18], "unidecod": [], "uniform": [8, 9], "uniformli": 9, "uninterrupt": [7, 18], "union": 10, "unit": [], "unittest": 2, "unlock": 7, "unoffici": 8, "unprofession": 1, "unsolicit": 1, "unsupervis": 4, "unwelcom": 1, "up": [8, 18], "updat": 10, "upgrad": 2, "upper": [6, 9], "uppercas": 16, "url": 7, "us": [1, 2, 3, 6, 8, 10, 12, 13, 14, 15, 18], "usabl": 18, "usag": [13, 17], "use_broadcast": [], "use_polygon": [6, 10, 16], "useabl": 18, "user": [4, 7, 11], "utf": 18, "util": 17, "v0": [], "v1": 14, "v3": [8, 14, 18], "valid": 16, "valu": [2, 7, 9, 18], "valuabl": 4, "variabl": 13, "varieti": 6, "veri": 8, "verifi": [], "verma": [], "version": [1, 2, 3, 17, 18], "vgg": 8, "vgg16": 14, "vgg16_bn_r": 8, "via": 1, "video": [], "vietnames": 6, "view": [4, 6], "viewpoint": 1, "violat": 1, "visibl": 1, "vision": [4, 6, 8], "visiondataset": 6, "visiontransform": 8, "visual": [3, 4, 15], "visualize_pag": 10, "vit_": 8, "vit_b": 8, "vitstr": [4, 8, 17], "vitstr_bas": [8, 18], "vitstr_smal": [8, 12, 17, 18], "viz": 3, "vocab": [12, 14, 16, 17, 18], "vocabulari": [6, 12, 14], "w": [7, 8, 9, 10], "w3": 18, "wa": 1, "wai": [1, 4, 16], "want": [2, 17, 18], "warm": [], "warmup": 18, "wasn": 2, "we": [1, 2, 3, 4, 7, 9, 14, 16, 17, 18], "weasyprint": 7, "web": [2, 7], "websit": 6, "weight": 12, "welcom": 1, "well": [1, 17], "were": [1, 7, 18], "what": 1, "when": [1, 2, 8], "whenev": 2, "where": [2, 7, 9, 10], "whether": [2, 6, 7, 9, 10, 16, 18], "which": [1, 8, 13, 15, 16, 18], "whichev": 3, "while": [9, 18], "why": 1, "width": [7, 9], "wiki": 1, "wildreceipt": [4, 6, 16], "window": [8, 10], "wish": 2, "within": 1, "without": [1, 6, 8], "wonder": 2, "word": [4, 6, 8, 10, 18], "word_1_1": 18, "word_1_2": 18, "word_1_3": 18, "wordgener": [6, 16], "words_onli": 10, "work": [13, 18], "worker": [], "workflow": 2, "worklow": 2, "world": [10, 18], "worth": 8, "wrap": 18, "wrapper": [6, 9], "write": 13, "written": [1, 7], "www": [1, 7, 18], "x": [7, 9, 10], "x12larg": [], "x_ascend": 18, "x_descend": 18, "x_i": 10, "x_size": 18, "x_wconf": 18, "xeon": [], "xhtml": 18, "xmax": 7, "xmin": 7, "xml": 18, "xml_bytes_str": 18, "xml_element": 18, "xml_output": 18, "xmln": 18, "y": 10, "y_i": 10, "y_j": 10, "yet": 15, "ymax": 7, "ymin": 7, "yolov8": 15, "you": [2, 3, 6, 7, 8, 12, 13, 14, 15, 16, 17, 18], "your": [2, 4, 7, 10, 18], "yoursit": 7, "yugesh": [], "zero": [9, 10], "zoo": [], "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 6, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 6, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": 6, "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 6, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 6, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": [], "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 6, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 6, "\u00e4\u00f6\u00e4\u00f6": 6, "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 6, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": 6, "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": 6, "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": 6, "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 6, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": [], "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": [], "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": 6, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 6, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 6, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 6, "\u067e\u0686\u06a2\u06a4\u06af": 6, "\u0905": 6, "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": 6, "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": 6, "\u0950": 6, "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": 6, "\u09bd": 6, "\u09ce": 6, "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": 6}, "titles": ["Changelog", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.contrib", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Integrate contributions into your pipeline", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 2, "0": 0, "01": 0, "02": 0, "03": 0, "04": 0, "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 1], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 1], "2021": 0, "2022": 0, "2023": 0, "2024": 0, "21": [], "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 1], "31": 0, "4": [0, 1], "5": 0, "6": 0, "7": 0, "8": 0, "9": [], "advanc": 18, "approach": 18, "architectur": 18, "arg": [6, 7, 8, 9, 10], "artefact": 7, "artefactdetect": 15, "attribut": 1, "avail": [15, 16, 18], "aw": 13, "ban": 1, "block": 7, "bug": 2, "build": [], "changelog": 0, "choos": [16, 18], "classif": [8, 14], "code": [1, 2], "codebas": 2, "commit": 2, "commun": 14, "compos": 9, "compress": [], "conda": 3, "conduct": 1, "connect": 2, "content": [], "continu": 2, "contrib": 5, "contribut": [2, 5, 15], "contributor": 1, "convent": 14, "correct": 1, "coven": 1, "custom": [6, 12], "data": 16, "dataload": 6, "dataset": [4, 6, 16], "detect": [4, 8, 14, 16, 18], "develop": 2, "do": 18, "doctr": [2, 4, 5, 6, 7, 8, 9, 10, 11], "document": [2, 4, 7], "end": 18, "enforc": 1, "evalu": 10, "export": 17, "factori": 8, "featur": [2, 4], "feedback": 2, "file": 7, "from": 14, "gener": [6, 16], "get": [], "git": 3, "guidelin": 1, "half": 17, "hub": 14, "huggingfac": 14, "i": 18, "implement": [], "infer": 17, "instal": [2, 3], "integr": [2, 15], "io": 7, "lambda": 13, "let": 2, "line": 7, "linux": 3, "load": [12, 14, 16], "loader": 6, "main": 4, "mode": 2, "model": [4, 8, 12, 14, 17, 18], "modifi": 2, "modul": [5, 15], "name": 14, "note": [], "notebook": 11, "object": 16, "ocr": 18, "onli": 3, "onnx": 17, "optim": 17, "option": 18, "orient": [], "our": 1, "output": 18, "own": [12, 16], "packag": 3, "page": 7, "perman": 1, "pipelin": 15, "pledg": 1, "post": [], "pre": [], "precis": 17, "predictor": 18, "prepar": 17, "prerequisit": 3, "pretrain": 14, "process": [], "push": 14, "python": 3, "qualiti": 2, "question": 2, "read": 7, "readi": 16, "recognit": [4, 8, 14, 16, 18], "refer": [], "report": 2, "request": 2, "resourc": [], "respons": 1, "return": [6, 7, 8, 10], "right": 18, "savedmodel": [], "scope": 1, "share": 14, "should": 18, "stage": 18, "standard": 1, "start": [], "structur": [2, 7], "style": 2, "support": [4, 5, 6, 9], "synthet": [6, 16], "task": 10, "temporari": 1, "test": 2, "text": [4, 18], "train": 12, "transform": 9, "two": 18, "unit": 2, "us": [16, 17], "util": 10, "v0": 0, "verif": 2, "via": 3, "visual": 10, "vocab": 6, "warn": 1, "what": 18, "word": 7, "your": [12, 14, 15, 16, 17], "zoo": [4, 8]}}) \ No newline at end of file +Search.setIndex({"alltitles": {"1. Correction": [[1, "correction"]], "2. Warning": [[1, "warning"]], "3. Temporary Ban": [[1, "temporary-ban"]], "4. Permanent Ban": [[1, "permanent-ban"]], "AWS Lambda": [[13, null]], "Advanced options": [[18, "advanced-options"]], "Args:": [[6, "args"], [6, "id4"], [6, "id7"], [6, "id10"], [6, "id13"], [6, "id16"], [6, "id19"], [6, "id22"], [6, "id25"], [6, "id29"], [6, "id32"], [6, "id37"], [6, "id40"], [6, "id46"], [6, "id49"], [6, "id50"], [6, "id51"], [6, "id54"], [6, "id57"], [6, "id60"], [6, "id61"], [7, "args"], [7, "id2"], [7, "id3"], [7, "id4"], [7, "id5"], [7, "id6"], [7, "id7"], [7, "id10"], [7, "id12"], [7, "id14"], [7, "id16"], [7, "id20"], [7, "id24"], [7, "id28"], [8, "args"], [8, "id3"], [8, "id8"], [8, "id13"], [8, "id17"], [8, "id21"], [8, "id26"], [8, "id31"], [8, "id36"], [8, "id41"], [8, "id46"], [8, "id50"], [8, "id54"], [8, "id59"], [8, "id63"], [8, "id68"], [8, "id73"], [8, "id77"], [8, "id81"], [8, "id85"], [8, "id90"], [8, "id95"], [8, "id99"], [8, "id104"], [8, "id109"], [8, "id114"], [8, "id119"], [8, "id123"], [8, "id127"], [8, "id132"], [8, "id137"], [8, "id142"], [8, "id146"], [8, "id150"], [8, "id155"], [8, "id159"], [8, "id163"], [8, "id167"], [8, "id169"], [8, "id171"], [8, "id173"], [9, "args"], [9, "id1"], [9, "id2"], [9, "id3"], [9, "id4"], [9, "id5"], [9, "id6"], [9, "id7"], [9, "id8"], [9, "id9"], [9, "id10"], [9, "id11"], [9, "id12"], [9, "id13"], [9, "id14"], [9, "id15"], [9, "id16"], [9, "id17"], [9, "id18"], [9, "id19"], [10, "args"], [10, "id3"], [10, "id4"], [10, "id5"], [10, "id6"], [10, "id7"], [10, "id8"], [10, "id9"]], "Artefact": [[7, "artefact"]], "ArtefactDetection": [[15, "artefactdetection"]], "Attribution": [[1, "attribution"]], "Available Datasets": [[16, "available-datasets"]], "Available architectures": [[18, "available-architectures"], [18, "id1"], [18, "id2"]], "Available contribution modules": [[15, "available-contribution-modules"]], "Block": [[7, "block"]], "Changelog": [[0, null]], "Choose a ready to use dataset": [[16, null]], "Choosing the right model": [[18, null]], "Classification": [[14, "classification"]], "Code quality": [[2, "code-quality"]], "Code style verification": [[2, "code-style-verification"]], "Codebase structure": [[2, "codebase-structure"]], "Commits": [[2, "commits"]], "Composing transformations": [[9, "composing-transformations"]], "Continuous Integration": [[2, "continuous-integration"]], "Contributing to docTR": [[2, null]], "Contributor Covenant Code of Conduct": [[1, null]], "Custom dataset loader": [[6, "custom-dataset-loader"]], "Data Loading": [[16, "data-loading"]], "Dataloader": [[6, "dataloader"]], "Detection": [[14, "detection"], [16, "detection"]], "Detection predictors": [[18, "detection-predictors"]], "Developer mode installation": [[2, "developer-mode-installation"]], "Developing docTR": [[2, "developing-doctr"]], "Document": [[7, "document"]], "Document structure": [[7, "document-structure"]], "End-to-End OCR": [[18, "end-to-end-ocr"]], "Enforcement": [[1, "enforcement"]], "Enforcement Guidelines": [[1, "enforcement-guidelines"]], "Enforcement Responsibilities": [[1, "enforcement-responsibilities"]], "Export to ONNX": [[17, "export-to-onnx"]], "Feature requests & bug report": [[2, "feature-requests-bug-report"]], "Feedback": [[2, "feedback"]], "File reading": [[7, "file-reading"]], "Half-precision": [[17, "half-precision"]], "Installation": [[3, null]], "Integrate contributions into your pipeline": [[15, null]], "Let\u2019s connect": [[2, "let-s-connect"]], "Line": [[7, "line"]], "Loading from Huggingface Hub": [[14, "loading-from-huggingface-hub"]], "Loading your custom trained model": [[12, "loading-your-custom-trained-model"]], "Main Features": [[4, "main-features"]], "Model optimization": [[17, "model-optimization"]], "Model zoo": [[4, "model-zoo"]], "Modifying the documentation": [[2, "modifying-the-documentation"]], "Naming conventions": [[14, "naming-conventions"]], "Object Detection": [[16, "object-detection"]], "Our Pledge": [[1, "our-pledge"]], "Our Standards": [[1, "our-standards"]], "Page": [[7, "page"]], "Preparing your model for inference": [[17, null]], "Prerequisites": [[3, "prerequisites"]], "Pretrained community models": [[14, "pretrained-community-models"]], "Pushing to the Huggingface Hub": [[14, "pushing-to-the-huggingface-hub"]], "Questions": [[2, "questions"]], "Recognition": [[14, "recognition"], [16, "recognition"]], "Recognition predictors": [[18, "recognition-predictors"]], "Returns:": [[6, "returns"], [7, "returns"], [7, "id11"], [7, "id13"], [7, "id15"], [7, "id19"], [7, "id23"], [7, "id27"], [7, "id31"], [8, "returns"], [8, "id6"], [8, "id11"], [8, "id16"], [8, "id20"], [8, "id24"], [8, "id29"], [8, "id34"], [8, "id39"], [8, "id44"], [8, "id49"], [8, "id53"], [8, "id57"], [8, "id62"], [8, "id66"], [8, "id71"], [8, "id76"], [8, "id80"], [8, "id84"], [8, "id88"], [8, "id93"], [8, "id98"], [8, "id102"], [8, "id107"], [8, "id112"], [8, "id117"], [8, "id122"], [8, "id126"], [8, "id130"], [8, "id135"], [8, "id140"], [8, "id145"], [8, "id149"], [8, "id153"], [8, "id158"], [8, "id162"], [8, "id166"], [8, "id168"], [8, "id170"], [8, "id172"], [10, "returns"]], "Scope": [[1, "scope"]], "Share your model with the community": [[14, null]], "Supported Vocabs": [[6, "supported-vocabs"]], "Supported contribution modules": [[5, "supported-contribution-modules"]], "Supported datasets": [[4, "supported-datasets"]], "Supported transformations": [[9, "supported-transformations"]], "Synthetic dataset generator": [[6, "synthetic-dataset-generator"], [16, "synthetic-dataset-generator"]], "Task evaluation": [[10, "task-evaluation"]], "Text Detection": [[18, "text-detection"]], "Text Recognition": [[18, "text-recognition"]], "Text detection models": [[4, "text-detection-models"]], "Text recognition models": [[4, "text-recognition-models"]], "Train your own model": [[12, null]], "Two-stage approaches": [[18, "two-stage-approaches"]], "Unit tests": [[2, "unit-tests"]], "Use your own datasets": [[16, "use-your-own-datasets"]], "Using your ONNX exported model": [[17, "using-your-onnx-exported-model"]], "Via Conda (Only for Linux)": [[3, "via-conda-only-for-linux"]], "Via Git": [[3, "via-git"]], "Via Python Package": [[3, "via-python-package"]], "Visualization": [[10, "visualization"]], "What should I do with the output?": [[18, "what-should-i-do-with-the-output"]], "Word": [[7, "word"]], "docTR Notebooks": [[11, null]], "docTR Vocabs": [[6, "id62"]], "docTR: Document Text Recognition": [[4, null]], "doctr.contrib": [[5, null]], "doctr.datasets": [[6, null], [6, "datasets"]], "doctr.io": [[7, null]], "doctr.models": [[8, null]], "doctr.models.classification": [[8, "doctr-models-classification"]], "doctr.models.detection": [[8, "doctr-models-detection"]], "doctr.models.factory": [[8, "doctr-models-factory"]], "doctr.models.recognition": [[8, "doctr-models-recognition"]], "doctr.models.zoo": [[8, "doctr-models-zoo"]], "doctr.transforms": [[9, null]], "doctr.utils": [[10, null]], "v0.1.0 (2021-03-05)": [[0, "v0-1-0-2021-03-05"]], "v0.1.1 (2021-03-18)": [[0, "v0-1-1-2021-03-18"]], "v0.2.0 (2021-05-11)": [[0, "v0-2-0-2021-05-11"]], "v0.2.1 (2021-05-28)": [[0, "v0-2-1-2021-05-28"]], "v0.3.0 (2021-07-02)": [[0, "v0-3-0-2021-07-02"]], "v0.3.1 (2021-08-27)": [[0, "v0-3-1-2021-08-27"]], "v0.4.0 (2021-10-01)": [[0, "v0-4-0-2021-10-01"]], "v0.4.1 (2021-11-22)": [[0, "v0-4-1-2021-11-22"]], "v0.5.0 (2021-12-31)": [[0, "v0-5-0-2021-12-31"]], "v0.5.1 (2022-03-22)": [[0, "v0-5-1-2022-03-22"]], "v0.6.0 (2022-09-29)": [[0, "v0-6-0-2022-09-29"]], "v0.7.0 (2023-09-09)": [[0, "v0-7-0-2023-09-09"]], "v0.8.0 (2024-02-28)": [[0, "v0-8-0-2024-02-28"]], "v0.8.1 (2024-03-04)": [[0, "v0-8-1-2024-03-04"]]}, "docnames": ["changelog", "contributing/code_of_conduct", "contributing/contributing", "getting_started/installing", "index", "modules/contrib", "modules/datasets", "modules/io", "modules/models", "modules/transforms", "modules/utils", "notebooks", "using_doctr/custom_models_training", "using_doctr/running_on_aws", "using_doctr/sharing_models", "using_doctr/using_contrib_modules", "using_doctr/using_datasets", "using_doctr/using_model_export", "using_doctr/using_models"], "envversion": {"sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1}, "filenames": ["changelog.rst", "contributing/code_of_conduct.md", "contributing/contributing.md", "getting_started/installing.rst", "index.rst", "modules/contrib.rst", "modules/datasets.rst", "modules/io.rst", "modules/models.rst", "modules/transforms.rst", "modules/utils.rst", "notebooks.rst", "using_doctr/custom_models_training.rst", "using_doctr/running_on_aws.rst", "using_doctr/sharing_models.rst", "using_doctr/using_contrib_modules.rst", "using_doctr/using_datasets.rst", "using_doctr/using_model_export.rst", "using_doctr/using_models.rst"], "indexentries": {"artefact (class in doctr.io)": [[7, "doctr.io.Artefact", false]], "block (class in doctr.io)": [[7, "doctr.io.Block", false]], "channelshuffle (class in doctr.transforms)": [[9, "doctr.transforms.ChannelShuffle", false]], "charactergenerator (class in doctr.datasets)": [[6, "doctr.datasets.CharacterGenerator", false]], "colorinversion (class in doctr.transforms)": [[9, "doctr.transforms.ColorInversion", false]], "compose (class in doctr.transforms)": [[9, "doctr.transforms.Compose", false]], "cord (class in doctr.datasets)": [[6, "doctr.datasets.CORD", false]], "crnn_mobilenet_v3_large() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.crnn_mobilenet_v3_large", false]], "crnn_mobilenet_v3_small() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.crnn_mobilenet_v3_small", false]], "crnn_vgg16_bn() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.crnn_vgg16_bn", false]], "crop_orientation_predictor() (in module doctr.models.classification)": [[8, "doctr.models.classification.crop_orientation_predictor", false]], "dataloader (class in doctr.datasets.loader)": [[6, "doctr.datasets.loader.DataLoader", false]], "db_mobilenet_v3_large() (in module doctr.models.detection)": [[8, "doctr.models.detection.db_mobilenet_v3_large", false]], "db_resnet50() (in module doctr.models.detection)": [[8, "doctr.models.detection.db_resnet50", false]], "decode_img_as_tensor() (in module doctr.io)": [[7, "doctr.io.decode_img_as_tensor", false]], "detection_predictor() (in module doctr.models.detection)": [[8, "doctr.models.detection.detection_predictor", false]], "detectiondataset (class in doctr.datasets)": [[6, "doctr.datasets.DetectionDataset", false]], "detectionmetric (class in doctr.utils.metrics)": [[10, "doctr.utils.metrics.DetectionMetric", false]], "docartefacts (class in doctr.datasets)": [[6, "doctr.datasets.DocArtefacts", false]], "document (class in doctr.io)": [[7, "doctr.io.Document", false]], "documentfile (class in doctr.io)": [[7, "doctr.io.DocumentFile", false]], "encode_sequences() (in module doctr.datasets)": [[6, "doctr.datasets.encode_sequences", false]], "fast_base() (in module doctr.models.detection)": [[8, "doctr.models.detection.fast_base", false]], "fast_small() (in module doctr.models.detection)": [[8, "doctr.models.detection.fast_small", false]], "fast_tiny() (in module doctr.models.detection)": [[8, "doctr.models.detection.fast_tiny", false]], "from_hub() (in module doctr.models.factory)": [[8, "doctr.models.factory.from_hub", false]], "from_images() (doctr.io.documentfile class method)": [[7, "doctr.io.DocumentFile.from_images", false]], "from_pdf() (doctr.io.documentfile class method)": [[7, "doctr.io.DocumentFile.from_pdf", false]], "from_url() (doctr.io.documentfile class method)": [[7, "doctr.io.DocumentFile.from_url", false]], "funsd (class in doctr.datasets)": [[6, "doctr.datasets.FUNSD", false]], "gaussianblur (class in doctr.transforms)": [[9, "doctr.transforms.GaussianBlur", false]], "gaussiannoise (class in doctr.transforms)": [[9, "doctr.transforms.GaussianNoise", false]], "ic03 (class in doctr.datasets)": [[6, "doctr.datasets.IC03", false]], "ic13 (class in doctr.datasets)": [[6, "doctr.datasets.IC13", false]], "iiit5k (class in doctr.datasets)": [[6, "doctr.datasets.IIIT5K", false]], "iiithws (class in doctr.datasets)": [[6, "doctr.datasets.IIITHWS", false]], "imgur5k (class in doctr.datasets)": [[6, "doctr.datasets.IMGUR5K", false]], "kie_predictor() (in module doctr.models)": [[8, "doctr.models.kie_predictor", false]], "lambdatransformation (class in doctr.transforms)": [[9, "doctr.transforms.LambdaTransformation", false]], "line (class in doctr.io)": [[7, "doctr.io.Line", false]], "linknet_resnet18() (in module doctr.models.detection)": [[8, "doctr.models.detection.linknet_resnet18", false]], "linknet_resnet34() (in module doctr.models.detection)": [[8, "doctr.models.detection.linknet_resnet34", false]], "linknet_resnet50() (in module doctr.models.detection)": [[8, "doctr.models.detection.linknet_resnet50", false]], "localizationconfusion (class in doctr.utils.metrics)": [[10, "doctr.utils.metrics.LocalizationConfusion", false]], "login_to_hub() (in module doctr.models.factory)": [[8, "doctr.models.factory.login_to_hub", false]], "magc_resnet31() (in module doctr.models.classification)": [[8, "doctr.models.classification.magc_resnet31", false]], "master() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.master", false]], "mjsynth (class in doctr.datasets)": [[6, "doctr.datasets.MJSynth", false]], "mobilenet_v3_large() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_large", false]], "mobilenet_v3_large_r() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_large_r", false]], "mobilenet_v3_small() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_small", false]], "mobilenet_v3_small_crop_orientation() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_small_crop_orientation", false]], "mobilenet_v3_small_page_orientation() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_small_page_orientation", false]], "mobilenet_v3_small_r() (in module doctr.models.classification)": [[8, "doctr.models.classification.mobilenet_v3_small_r", false]], "normalize (class in doctr.transforms)": [[9, "doctr.transforms.Normalize", false]], "ocr_predictor() (in module doctr.models)": [[8, "doctr.models.ocr_predictor", false]], "ocrdataset (class in doctr.datasets)": [[6, "doctr.datasets.OCRDataset", false]], "ocrmetric (class in doctr.utils.metrics)": [[10, "doctr.utils.metrics.OCRMetric", false]], "oneof (class in doctr.transforms)": [[9, "doctr.transforms.OneOf", false]], "page (class in doctr.io)": [[7, "doctr.io.Page", false]], "page_orientation_predictor() (in module doctr.models.classification)": [[8, "doctr.models.classification.page_orientation_predictor", false]], "parseq() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.parseq", false]], "push_to_hf_hub() (in module doctr.models.factory)": [[8, "doctr.models.factory.push_to_hf_hub", false]], "randomapply (class in doctr.transforms)": [[9, "doctr.transforms.RandomApply", false]], "randombrightness (class in doctr.transforms)": [[9, "doctr.transforms.RandomBrightness", false]], "randomcontrast (class in doctr.transforms)": [[9, "doctr.transforms.RandomContrast", false]], "randomcrop (class in doctr.transforms)": [[9, "doctr.transforms.RandomCrop", false]], "randomgamma (class in doctr.transforms)": [[9, "doctr.transforms.RandomGamma", false]], "randomhorizontalflip (class in doctr.transforms)": [[9, "doctr.transforms.RandomHorizontalFlip", false]], "randomhue (class in doctr.transforms)": [[9, "doctr.transforms.RandomHue", false]], "randomjpegquality (class in doctr.transforms)": [[9, "doctr.transforms.RandomJpegQuality", false]], "randomresize (class in doctr.transforms)": [[9, "doctr.transforms.RandomResize", false]], "randomrotate (class in doctr.transforms)": [[9, "doctr.transforms.RandomRotate", false]], "randomsaturation (class in doctr.transforms)": [[9, "doctr.transforms.RandomSaturation", false]], "randomshadow (class in doctr.transforms)": [[9, "doctr.transforms.RandomShadow", false]], "read_html() (in module doctr.io)": [[7, "doctr.io.read_html", false]], "read_img_as_numpy() (in module doctr.io)": [[7, "doctr.io.read_img_as_numpy", false]], "read_img_as_tensor() (in module doctr.io)": [[7, "doctr.io.read_img_as_tensor", false]], "read_pdf() (in module doctr.io)": [[7, "doctr.io.read_pdf", false]], "recognition_predictor() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.recognition_predictor", false]], "recognitiondataset (class in doctr.datasets)": [[6, "doctr.datasets.RecognitionDataset", false]], "resize (class in doctr.transforms)": [[9, "doctr.transforms.Resize", false]], "resnet18() (in module doctr.models.classification)": [[8, "doctr.models.classification.resnet18", false]], "resnet31() (in module doctr.models.classification)": [[8, "doctr.models.classification.resnet31", false]], "resnet34() (in module doctr.models.classification)": [[8, "doctr.models.classification.resnet34", false]], "resnet50() (in module doctr.models.classification)": [[8, "doctr.models.classification.resnet50", false]], "sar_resnet31() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.sar_resnet31", false]], "show() (doctr.io.document method)": [[7, "doctr.io.Document.show", false]], "show() (doctr.io.page method)": [[7, "doctr.io.Page.show", false]], "sroie (class in doctr.datasets)": [[6, "doctr.datasets.SROIE", false]], "summary() (doctr.utils.metrics.detectionmetric method)": [[10, "doctr.utils.metrics.DetectionMetric.summary", false]], "summary() (doctr.utils.metrics.localizationconfusion method)": [[10, "doctr.utils.metrics.LocalizationConfusion.summary", false]], "summary() (doctr.utils.metrics.ocrmetric method)": [[10, "doctr.utils.metrics.OCRMetric.summary", false]], "summary() (doctr.utils.metrics.textmatch method)": [[10, "doctr.utils.metrics.TextMatch.summary", false]], "svhn (class in doctr.datasets)": [[6, "doctr.datasets.SVHN", false]], "svt (class in doctr.datasets)": [[6, "doctr.datasets.SVT", false]], "synthtext (class in doctr.datasets)": [[6, "doctr.datasets.SynthText", false]], "textmatch (class in doctr.utils.metrics)": [[10, "doctr.utils.metrics.TextMatch", false]], "textnet_base() (in module doctr.models.classification)": [[8, "doctr.models.classification.textnet_base", false]], "textnet_small() (in module doctr.models.classification)": [[8, "doctr.models.classification.textnet_small", false]], "textnet_tiny() (in module doctr.models.classification)": [[8, "doctr.models.classification.textnet_tiny", false]], "togray (class in doctr.transforms)": [[9, "doctr.transforms.ToGray", false]], "update() (doctr.utils.metrics.detectionmetric method)": [[10, "doctr.utils.metrics.DetectionMetric.update", false]], "update() (doctr.utils.metrics.localizationconfusion method)": [[10, "doctr.utils.metrics.LocalizationConfusion.update", false]], "update() (doctr.utils.metrics.ocrmetric method)": [[10, "doctr.utils.metrics.OCRMetric.update", false]], "update() (doctr.utils.metrics.textmatch method)": [[10, "doctr.utils.metrics.TextMatch.update", false]], "vgg16_bn_r() (in module doctr.models.classification)": [[8, "doctr.models.classification.vgg16_bn_r", false]], "visualize_page() (in module doctr.utils.visualization)": [[10, "doctr.utils.visualization.visualize_page", false]], "vit_b() (in module doctr.models.classification)": [[8, "doctr.models.classification.vit_b", false]], "vit_s() (in module doctr.models.classification)": [[8, "doctr.models.classification.vit_s", false]], "vitstr_base() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.vitstr_base", false]], "vitstr_small() (in module doctr.models.recognition)": [[8, "doctr.models.recognition.vitstr_small", false]], "wildreceipt (class in doctr.datasets)": [[6, "doctr.datasets.WILDRECEIPT", false]], "word (class in doctr.io)": [[7, "doctr.io.Word", false]], "wordgenerator (class in doctr.datasets)": [[6, "doctr.datasets.WordGenerator", false]]}, "objects": {"doctr.datasets": [[6, 0, 1, "", "CORD"], [6, 0, 1, "", "CharacterGenerator"], [6, 0, 1, "", "DetectionDataset"], [6, 0, 1, "", "DocArtefacts"], [6, 0, 1, "", "FUNSD"], [6, 0, 1, "", "IC03"], [6, 0, 1, "", "IC13"], [6, 0, 1, "", "IIIT5K"], [6, 0, 1, "", "IIITHWS"], [6, 0, 1, "", "IMGUR5K"], [6, 0, 1, "", "MJSynth"], [6, 0, 1, "", "OCRDataset"], [6, 0, 1, "", "RecognitionDataset"], [6, 0, 1, "", "SROIE"], [6, 0, 1, "", "SVHN"], [6, 0, 1, "", "SVT"], [6, 0, 1, "", "SynthText"], [6, 0, 1, "", "WILDRECEIPT"], [6, 0, 1, "", "WordGenerator"], [6, 1, 1, "", "encode_sequences"]], "doctr.datasets.loader": [[6, 0, 1, "", "DataLoader"]], "doctr.io": [[7, 0, 1, "", "Artefact"], [7, 0, 1, "", "Block"], [7, 0, 1, "", "Document"], [7, 0, 1, "", "DocumentFile"], [7, 0, 1, "", "Line"], [7, 0, 1, "", "Page"], [7, 0, 1, "", "Word"], [7, 1, 1, "", "decode_img_as_tensor"], [7, 1, 1, "", "read_html"], [7, 1, 1, "", "read_img_as_numpy"], [7, 1, 1, "", "read_img_as_tensor"], [7, 1, 1, "", "read_pdf"]], "doctr.io.Document": [[7, 2, 1, "", "show"]], "doctr.io.DocumentFile": [[7, 2, 1, "", "from_images"], [7, 2, 1, "", "from_pdf"], [7, 2, 1, "", "from_url"]], "doctr.io.Page": [[7, 2, 1, "", "show"]], "doctr.models": [[8, 1, 1, "", "kie_predictor"], [8, 1, 1, "", "ocr_predictor"]], "doctr.models.classification": [[8, 1, 1, "", "crop_orientation_predictor"], [8, 1, 1, "", "magc_resnet31"], [8, 1, 1, "", "mobilenet_v3_large"], [8, 1, 1, "", "mobilenet_v3_large_r"], [8, 1, 1, "", "mobilenet_v3_small"], [8, 1, 1, "", "mobilenet_v3_small_crop_orientation"], [8, 1, 1, "", "mobilenet_v3_small_page_orientation"], [8, 1, 1, "", "mobilenet_v3_small_r"], [8, 1, 1, "", "page_orientation_predictor"], [8, 1, 1, "", "resnet18"], [8, 1, 1, "", "resnet31"], [8, 1, 1, "", "resnet34"], [8, 1, 1, "", "resnet50"], [8, 1, 1, "", "textnet_base"], [8, 1, 1, "", "textnet_small"], [8, 1, 1, "", "textnet_tiny"], [8, 1, 1, "", "vgg16_bn_r"], [8, 1, 1, "", "vit_b"], [8, 1, 1, "", "vit_s"]], "doctr.models.detection": [[8, 1, 1, "", "db_mobilenet_v3_large"], [8, 1, 1, "", "db_resnet50"], [8, 1, 1, "", "detection_predictor"], [8, 1, 1, "", "fast_base"], [8, 1, 1, "", "fast_small"], [8, 1, 1, "", "fast_tiny"], [8, 1, 1, "", "linknet_resnet18"], [8, 1, 1, "", "linknet_resnet34"], [8, 1, 1, "", "linknet_resnet50"]], "doctr.models.factory": [[8, 1, 1, "", "from_hub"], [8, 1, 1, "", "login_to_hub"], [8, 1, 1, "", "push_to_hf_hub"]], "doctr.models.recognition": [[8, 1, 1, "", "crnn_mobilenet_v3_large"], [8, 1, 1, "", "crnn_mobilenet_v3_small"], [8, 1, 1, "", "crnn_vgg16_bn"], [8, 1, 1, "", "master"], [8, 1, 1, "", "parseq"], [8, 1, 1, "", "recognition_predictor"], [8, 1, 1, "", "sar_resnet31"], [8, 1, 1, "", "vitstr_base"], [8, 1, 1, "", "vitstr_small"]], "doctr.transforms": [[9, 0, 1, "", "ChannelShuffle"], [9, 0, 1, "", "ColorInversion"], [9, 0, 1, "", "Compose"], [9, 0, 1, "", "GaussianBlur"], [9, 0, 1, "", "GaussianNoise"], [9, 0, 1, "", "LambdaTransformation"], [9, 0, 1, "", "Normalize"], [9, 0, 1, "", "OneOf"], [9, 0, 1, "", "RandomApply"], [9, 0, 1, "", "RandomBrightness"], [9, 0, 1, "", "RandomContrast"], [9, 0, 1, "", "RandomCrop"], [9, 0, 1, "", "RandomGamma"], [9, 0, 1, "", "RandomHorizontalFlip"], [9, 0, 1, "", "RandomHue"], [9, 0, 1, "", "RandomJpegQuality"], [9, 0, 1, "", "RandomResize"], [9, 0, 1, "", "RandomRotate"], [9, 0, 1, "", "RandomSaturation"], [9, 0, 1, "", "RandomShadow"], [9, 0, 1, "", "Resize"], [9, 0, 1, "", "ToGray"]], "doctr.utils.metrics": [[10, 0, 1, "", "DetectionMetric"], [10, 0, 1, "", "LocalizationConfusion"], [10, 0, 1, "", "OCRMetric"], [10, 0, 1, "", "TextMatch"]], "doctr.utils.metrics.DetectionMetric": [[10, 2, 1, "", "summary"], [10, 2, 1, "", "update"]], "doctr.utils.metrics.LocalizationConfusion": [[10, 2, 1, "", "summary"], [10, 2, 1, "", "update"]], "doctr.utils.metrics.OCRMetric": [[10, 2, 1, "", "summary"], [10, 2, 1, "", "update"]], "doctr.utils.metrics.TextMatch": [[10, 2, 1, "", "summary"], [10, 2, 1, "", "update"]], "doctr.utils.visualization": [[10, 1, 1, "", "visualize_page"]]}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "function", "Python function"], "2": ["py", "method", "Python method"]}, "objtypes": {"0": "py:class", "1": "py:function", "2": "py:method"}, "terms": {"": [1, 7, 8, 10, 14, 17], "0": [1, 3, 6, 9, 10, 12, 15, 16, 18], "00": 18, "01": 18, "0123456789": 6, "0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 6, "0123456789\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 6, "02": [], "02562": 8, "03": 18, "035": 18, "0361328125": 18, "04": 18, "05": 18, "06": 18, "06640625": 18, "07": 18, "08": [9, 18], "09": 18, "0966796875": 18, "1": [3, 6, 7, 8, 9, 10, 12, 16, 18], "10": [6, 10, 18], "100": [6, 9, 10, 16, 18], "1000": 18, "101": 6, "1024": [8, 12, 18], "104": 6, "106": 6, "108": 6, "1095": 16, "11": 18, "110": 10, "1107": 16, "114": 6, "115": [], "1156": 16, "116": 6, "118": 6, "11800h": 18, "11th": 18, "12": [3, 18], "120": 6, "123": 6, "126": 6, "1268": 16, "128": [8, 12, 17, 18], "13": 18, "130": 6, "13068": 16, "131": 6, "1337891": 16, "1357421875": 18, "1396484375": 18, "14": 18, "1420": 18, "14470v1": 6, "149": 16, "15": 18, "150": [10, 18], "1552": 18, "16": [8, 17, 18], "1630859375": 18, "1684": 18, "16x16": 8, "17": 18, "1778": 18, "1782": 18, "18": [8, 18], "185546875": 18, "1900": 18, "1910": 8, "19342": 16, "19370": 16, "195": 6, "19598": 16, "199": 18, "1999": 18, "2": [3, 4, 6, 7, 9, 15, 18], "20": 18, "200": 10, "2000": 16, "2003": [4, 6], "2012": 6, "2013": [4, 6], "2015": 6, "2019": 4, "2023": [], "207901": 16, "21": 18, "2103": 6, "2186": 16, "21888": 16, "22": 18, "224": [8, 9], "225": 9, "22672": 16, "229": [9, 16], "23": 18, "233": 16, "234": 6, "236": [], "24": 18, "246": 16, "249": 16, "25": 18, "2504": 18, "255": [7, 8, 9, 10, 18], "256": 8, "257": 16, "26": 18, "26032": 16, "264": 12, "27": 18, "2700": 16, "2710": 18, "2749": 12, "28": 18, "287": 12, "29": 18, "296": 12, "299": 12, "2d": 18, "3": [3, 4, 7, 8, 9, 10, 17, 18], "30": 18, "300": 16, "3000": 16, "301": 12, "30595": 18, "30ghz": 18, "31": 8, "32": [6, 8, 9, 12, 16, 17, 18], "3232421875": 18, "33": [9, 18], "33402": 16, "33608": 16, "34": [8, 18], "340": 18, "3456": 18, "35": [], "3515625": 18, "36": 18, "360": 16, "37": [6, 18], "38": 18, "39": 18, "4": [8, 9, 10, 18], "40": 18, "406": 9, "41": 18, "42": 18, "43": 18, "44": 18, "45": 18, "456": 9, "46": 18, "47": 18, "472": 16, "48": [6, 18], "485": 9, "49": 18, "49377": 16, "5": [6, 9, 10, 15, 18], "50": [8, 16, 18], "51": 18, "51171875": 18, "512": 8, "52": [6, 18], "529": 18, "53": 18, "54": 18, "540": 18, "5478515625": 18, "55": 18, "56": 18, "57": 18, "58": 18, "580": 18, "5810546875": 18, "583": 18, "59": 18, "597": 18, "5k": [4, 6], "5m": 18, "6": [9, 18], "60": 9, "600": [8, 10, 18], "61": 18, "62": 18, "626": 16, "63": 18, "64": [8, 9, 18], "641": 18, "647": 16, "65": 18, "66": 18, "67": 18, "68": 18, "69": 18, "693": 12, "694": 12, "695": 12, "6m": 18, "7": 18, "70": [6, 10, 18], "707470": 16, "71": [6, 18], "7100000": 16, "7141797": 16, "7149": 16, "72": 18, "72dpi": 7, "73": 18, "73257": 16, "74": 18, "75": [9, 18], "7581382": 16, "76": 18, "77": 18, "772": 12, "772875": 16, "78": 18, "785": 12, "79": 18, "793533": 16, "796": 16, "798": 12, "7m": 18, "8": [8, 9, 18], "80": 18, "800": [8, 10, 16, 18], "81": 18, "82": 18, "83": 18, "84": 18, "849": 16, "85": 18, "8564453125": 18, "857": 18, "85875": 16, "86": 18, "8603515625": 18, "87": 18, "8707": 16, "88": 18, "89": 18, "9": [3, 9, 18], "90": 18, "90k": 6, "90kdict32px": 6, "91": 18, "914085328578949": 18, "92": 18, "93": 18, "94": [6, 18], "95": [10, 18], "9578408598899841": 18, "96": 18, "97": 18, "98": 18, "99": 18, "9949972033500671": 18, "A": [1, 2, 4, 6, 7, 8, 11, 17], "As": 2, "Be": 18, "Being": 1, "By": 13, "For": [1, 2, 3, 12, 18], "If": [2, 7, 8, 12, 18], "In": [2, 6, 16], "It": [9, 14, 15, 17], "Its": [4, 8], "No": [1, 18], "Of": 6, "Or": [15, 17], "The": [1, 2, 6, 7, 10, 13, 15, 17, 18], "Then": 8, "To": [2, 3, 13, 14, 15, 17, 18], "_": [1, 6, 8], "__call__": 18, "_build": 2, "_i": 10, "ab": 6, "abc": 17, "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz": 6, "abdef": [6, 16], "abl": [16, 18], "about": [1, 16, 18], "abov": 18, "abstract": [], "abstractdataset": 6, "abus": 1, "accept": 1, "access": [4, 7, 16, 18], "account": [1, 14], "accur": 18, "accuraci": 10, "achiev": 17, "act": 1, "action": 1, "activ": 4, "ad": [2, 8, 9], "adapt": 1, "add": [9, 10, 14, 18], "add_hook": 18, "add_label": 10, "addit": [2, 3, 7, 15], "addition": [2, 18], "address": [1, 7], "adjust": 9, "advanc": 1, "advantag": 17, "advis": 2, "aesthet": [4, 6], "affect": 1, "after": [14, 18], "ag": 1, "again": 8, "aggreg": [10, 16], "aggress": 1, "align": [1, 7, 9], "all": [1, 2, 5, 6, 7, 9, 10, 15, 16, 18], "allow": [1, 17], "along": 18, "alreadi": [2, 17], "also": [1, 8, 14, 15, 16, 18], "alwai": 16, "an": [1, 2, 4, 6, 7, 8, 10, 15, 17, 18], "analysi": [7, 15], "ancient_greek": 6, "andrej": [], "angl": [7, 9], "ani": [1, 6, 7, 8, 9, 10, 17, 18], "annot": 6, "anot": 16, "anoth": [8, 12, 16], "answer": 1, "anyascii": 10, "anyon": 4, "anyth": 15, "api": [2, 4], "apolog": 1, "apologi": 1, "app": 2, "appear": 1, "appli": [1, 6, 9], "applic": [4, 8], "appoint": 1, "appreci": 14, "appropri": [1, 2, 18], "ar": [1, 2, 3, 5, 6, 7, 9, 10, 11, 15, 16, 18], "arab": 6, "arabic_diacrit": 6, "arabic_lett": 6, "arabic_punctu": 6, "arbitrarili": [4, 8], "arch": [8, 14], "architectur": [4, 8, 14, 15], "area": 18, "arg": [], "argument": [6, 7, 8, 10, 18], "around": 1, "arrai": [7, 9, 10], "art": [4, 15], "artefact": [10, 11, 15, 18], "artefact_typ": 7, "articl": [], "artifici": [4, 6], "arxiv": [6, 8], "asarrai": 10, "ascii_lett": 6, "aspect": [4, 8, 9, 18], "assess": 10, "assign": 10, "associ": 7, "assum": 8, "assume_straight_pag": [8, 18], "astyp": [8, 10, 18], "attack": 1, "attend": [4, 8], "attent": [1, 8], "autom": 4, "automat": 18, "autoregress": [4, 8], "avail": [1, 4, 5, 9], "averag": [9, 18], "avoid": [1, 3], "aw": [4, 18], "awar": 18, "azur": 18, "b": [8, 10, 18], "b_j": 10, "back": 2, "backbon": 8, "backend": 18, "background": 16, "bangla": 6, "bar": 15, "bar_cod": 16, "baranovskij": [], "base": [4, 8, 15], "baselin": [4, 8, 18], "batch": [6, 8, 9, 15, 16, 18], "batch_siz": [6, 12, 15, 16, 17], "bblanchon": 3, "bbox": 18, "becaus": 13, "been": [2, 10, 16, 18], "befor": [6, 8, 9, 18], "begin": 10, "behavior": [1, 18], "being": [10, 18], "belong": 18, "benchmark": 18, "best": 1, "better": [11, 18], "between": [9, 10, 18], "bgr": 7, "bilinear": 9, "bin_thresh": 18, "binar": [4, 8, 18], "binari": [7, 17, 18], "bit": 17, "blank": [], "block": [10, 18], "block_1_1": 18, "blue": [], "blur": 9, "bmvc": 6, "bn": 14, "bodi": [1, 18], "bool": [6, 7, 8, 9, 10], "boolean": [8, 18], "both": [4, 6, 9, 16, 18], "bottom": [8, 18], "bound": [6, 7, 8, 9, 10, 15, 18], "box": [6, 7, 8, 9, 10, 15, 16, 18], "box_thresh": 18, "brew": [], "bright": 9, "broadcast": [], "browser": [2, 4], "build": [2, 3, 17], "built": 2, "byte": [7, 18], "c": [3, 7, 10], "c_j": 10, "cach": [2, 6, 13], "cache_sampl": 6, "cairo": [], "call": 17, "callabl": [6, 9], "can": [2, 3, 12, 13, 14, 15, 16, 18], "capabl": [2, 11, 18], "case": [6, 10], "cf": 18, "cfg": 18, "challeng": 6, "challenge2_test_task12_imag": 6, "challenge2_test_task1_gt": 6, "challenge2_training_task12_imag": 6, "challenge2_training_task1_gt": 6, "chang": [13, 18], "channel": [1, 2, 7, 9], "channel_prior": 3, "channelshuffl": 9, "charact": [4, 6, 7, 10, 16, 18], "charactergener": [6, 16], "characterist": 1, "charg": 18, "charset": 18, "chart": 7, "check": [2, 14, 18], "checkpoint": 8, "chip": 3, "christian": [], "ci": 2, "clarifi": 1, "clariti": 1, "class": [1, 6, 7, 9, 10, 18], "class_nam": 12, "classif": 16, "classif_mobilenet_v3_smal": [], "classmethod": 7, "clear": 2, "clone": 3, "close": 2, "co": 14, "code": [4, 7, 15], "codecov": 2, "colab": 11, "collate_fn": 6, "collect": [7, 15], "color": 9, "colorinvers": 9, "column": 7, "com": [1, 3, 7, 8, 14], "combin": 18, "come": [], "command": [2, 15], "comment": 1, "commit": 1, "common": [1, 9, 10, 17], "commun": 1, "compar": 4, "comparison": [10, 18], "competit": 6, "compil": [11, 18], "complaint": 1, "complementari": 10, "complet": 2, "compon": 18, "compos": [6, 18], "comprehens": 18, "comput": [6, 10, 17, 18], "conf_threshold": 15, "confid": [7, 18], "config": [3, 8], "configur": 8, "confus": 10, "consecut": [9, 18], "consequ": 1, "consid": [1, 2, 6, 7, 10, 18], "consist": 18, "consolid": [4, 6], "constant": 9, "construct": 1, "consum": [], "contact": 1, "contain": [5, 6, 16], "content": [6, 7, 18], "context": 8, "contib": 3, "continu": 1, "contrast": 9, "contrast_factor": 9, "contrib": [3, 15], "contribut": 1, "contributor": 2, "convers": 7, "convert": [7, 9], "convolut": 8, "cool": [], "coordin": [7, 18], "cord": [4, 6, 16, 18], "core": [10, 18], "corner": 18, "correct": 9, "correspond": [3, 7, 9, 18], "could": [1, 15], "counterpart": 10, "cover": 2, "coverag": 2, "cpu": [4, 12, 17], "creat": 14, "crnn": [4, 8, 14], "crnn_mobilenet_v3_larg": [8, 14, 18], "crnn_mobilenet_v3_smal": [8, 17, 18], "crnn_vgg16_bn": [8, 12, 14, 18], "crop": [7, 8, 9, 16, 18], "crop_orient": [7, 18], "crop_orientation_predictor": 8, "crop_param": [], "croporientationpredictor": [], "cuda": 17, "currenc": 6, "current": [2, 18], "custom": [14, 15, 17, 18], "custom_crop_orientation_model": [], "custom_page_orientation_model": [], "customhook": 18, "cvit": 4, "czczup": 8, "czech": 6, "d": [6, 16], "danish": 6, "data": [4, 6, 7, 9, 10, 12, 14], "dataload": 16, "dataset": [8, 12, 18], "dataset_info": 6, "date": [12, 18], "db": 14, "db_mobilenet_v3_larg": [8, 14, 18], "db_resnet34": 18, "db_resnet50": [8, 12, 14, 18], "db_resnet50_rot": [], "dbnet": [4, 8], "deal": [], "decis": 1, "decod": 7, "decode_img_as_tensor": 7, "dedic": 17, "deem": 1, "deep": [8, 18], "def": 18, "default": [3, 7, 12, 13, 18], "defer": 16, "defin": [10, 17], "degre": [7, 9], "degress": 7, "delet": 2, "delimit": 18, "delta": 9, "demo": [2, 4], "demonstr": 1, "depend": [2, 3, 4, 18], "deploi": 2, "deploy": 4, "derogatori": 1, "describ": 8, "descript": 11, "design": 9, "desir": 7, "det_arch": [8, 12, 14, 17], "det_b": 18, "det_model": [12, 14, 17], "det_param": 12, "det_predictor": [12, 18], "detail": [12, 18], "detect": [6, 7, 10, 11, 12, 15], "detect_languag": 8, "detect_orient": 8, "detection_predictor": [8, 18], "detection_task": [], "detectiondataset": [6, 16], "detectionmetr": 10, "detectionpredictor": [8, 12], "detector": [4, 8, 15], "deterior": 8, "determin": 1, "dev": [2, 13], "develop": 3, "deviat": 9, "devic": 17, "dict": [7, 10, 18], "dictionari": [7, 10], "differ": 1, "differenti": [4, 8], "digit": [4, 6, 16], "dimens": [7, 10, 18], "dimension": 9, "direct": 6, "directli": [14, 18], "directori": [2, 13], "disabl": [1, 13, 18], "disable_crop_orient": [], "disable_page_orient": [], "disclaim": 18, "discuss": 2, "disparag": 1, "displai": [7, 10], "display_artefact": 10, "distribut": 9, "div": 18, "divers": 1, "divid": 7, "do": [2, 3, 8], "doc": [2, 7, 15, 17, 18], "docartefact": [6, 16], "docstr": 2, "doctr": [3, 12, 13, 14, 15, 16, 17, 18], "doctr_cache_dir": 13, "doctr_multiprocessing_dis": 13, "document": [6, 8, 10, 11, 15, 16, 17, 18], "documentbuild": 18, "documentfil": [7, 14, 15, 17], "doesn": 17, "don": [12, 18], "done": 9, "download": [6, 16], "downsiz": 8, "draw": 9, "draw_proba": [], "drop": 6, "drop_last": 6, "dtype": [7, 8, 9, 10, 17], "dual": [4, 6], "dummi": 14, "dummy_img": 18, "dummy_input": 17, "dure": 1, "dutch": 6, "dynam": [6, 15], "dynamic_seq_length": 6, "e": [1, 2, 3, 7, 8], "each": [4, 6, 7, 8, 9, 10, 16, 18], "eas": 2, "easi": [4, 10, 14, 17], "easili": [7, 10, 12, 14, 16, 18], "econom": 1, "edit": 1, "educ": 1, "effect": [], "effici": [2, 4, 6, 8], "either": [10, 18], "element": [6, 7, 8, 18], "els": [2, 15], "email": 1, "empathi": 1, "en": 18, "enabl": [6, 7], "enclos": 7, "encod": [4, 6, 7, 8, 18], "encode_sequ": 6, "encount": 2, "encrypt": 7, "end": [4, 6, 8, 10], "english": [6, 16], "enough": [2, 18], "ensur": 2, "entri": 6, "environ": [1, 13], "eo": 6, "equiv": 18, "estim": 8, "etc": [7, 15], "ethnic": 1, "evalu": [16, 18], "event": 1, "everyon": 1, "everyth": [2, 18], "exact": [10, 18], "exampl": [1, 2, 4, 6, 8, 14, 18], "exchang": 17, "execut": 18, "exist": 14, "expand": 9, "expect": [7, 9, 10], "experi": 1, "explan": [1, 18], "explicit": 1, "exploit": [4, 8], "export": [7, 8, 10, 11, 15, 18], "export_as_straight_box": [8, 18], "export_as_xml": 18, "export_model_to_onnx": 17, "express": [1, 9], "extens": 7, "extern": [1, 16], "extra": [], "extract": [4, 6], "extractor": 8, "f_": 10, "f_a": 10, "factor": 9, "fair": 1, "fairli": 1, "fals": [6, 7, 8, 9, 10, 12, 18], "famili": [], "faq": 1, "fascan": 14, "fast": [4, 6, 8], "fast_bas": [8, 18], "fast_smal": [8, 18], "fast_tini": [8, 18], "faster": [4, 8, 17], "fasterrcnn_mobilenet_v3_large_fpn": 8, "favorit": 18, "featur": [3, 8, 10, 11, 15], "feedback": 1, "feel": [2, 14], "felix92": 14, "few": [17, 18], "figsiz": 10, "figur": [10, 15], "file": [2, 6], "final": 8, "find": [2, 16], "fine": [], "finnish": 6, "first": [2, 6], "firsthand": 6, "fit": [8, 18], "flag": 18, "flip": 9, "float": [7, 9, 10, 17], "float32": [7, 8, 9, 17], "fn": 9, "focu": 14, "focus": [1, 6], "folder": 6, "follow": [1, 2, 3, 6, 9, 10, 12, 13, 14, 15, 18], "font": 6, "font_famili": 6, "font_siz": [], "foral": 10, "forc": 2, "forg": 3, "form": [4, 6, 18], "format": [7, 10, 12, 16, 17, 18], "forpost": [4, 6], "forum": 2, "found": [], "fp16": 17, "frac": 10, "framework": [3, 14, 16, 18], "free": [1, 2, 14], "french": [6, 12, 14, 18], "friendli": 4, "from": [1, 4, 6, 7, 8, 9, 10, 11, 12, 15, 16, 17, 18], "from_hub": [8, 14], "from_imag": [7, 14, 15, 17], "from_pdf": 7, "from_url": 7, "full": [6, 10, 18], "function": [6, 9, 10, 15], "funsd": [4, 6, 16, 18], "further": 16, "futur": 6, "g": [7, 8], "g_": 10, "g_x": 10, "gallagh": [], "gamma": 9, "gaussian": 9, "gaussianblur": 9, "gaussiannois": 9, "gdk": [], "gen": 18, "gender": 1, "gener": [2, 4, 7, 8], "generic_cyrillic_lett": [], "geometri": [4, 7, 18], "geq": 10, "german": [6, 12, 14], "get": [17, 18], "git": 14, "github": [2, 3, 8, 14], "give": [1, 15], "given": [6, 7, 9, 10, 18], "global": 8, "go": 18, "good": 17, "googl": 2, "googlevis": 4, "gpu": [4, 15, 17], "gracefulli": 1, "graph": [4, 6, 7], "grayscal": 9, "ground": 10, "groung": 10, "group": [4, 18], "gt": 10, "gt_box": 10, "gt_label": 10, "gtk": [], "guid": 2, "guidanc": 16, "gvision": 18, "h": [7, 8, 9], "h_": 10, "ha": [2, 6, 10, 16], "handl": [16, 18], "handwrit": 6, "handwritten": 16, "harass": 1, "hardwar": 18, "harm": 1, "hat": 10, "have": [1, 2, 10, 12, 14, 16, 17, 18], "head": [8, 18], "healthi": 1, "hebrew": 6, "height": [7, 9], "hello": [10, 18], "help": 17, "here": [5, 9, 11, 15, 16, 18], "hf": 8, "hf_hub_download": 8, "high": 7, "higher": [3, 6, 18], "hindi": 6, "hindi_digit": 6, "hocr": 18, "homebrew": [], "hook": 18, "horizont": [7, 9], "hous": 6, "how": [2, 12, 14, 16], "howev": 16, "hsv": 9, "html": [1, 2, 3, 7, 18], "http": [1, 3, 6, 7, 8, 14, 18], "hub": 8, "hue": 9, "huggingfac": 8, "hw": 6, "i": [1, 2, 6, 7, 8, 9, 10, 13, 14, 15, 16, 17], "i7": 18, "ibrahimov": [], "ic03": [4, 6, 16], "ic13": [4, 6, 16], "icdar": [4, 6], "icdar2019": 6, "id": 18, "ident": 1, "identifi": 4, "iiit": [4, 6], "iiit5k": [6, 16], "iiithw": [4, 6, 16], "imag": [4, 6, 7, 8, 9, 10, 14, 15, 16, 18], "imagenet": 8, "imageri": 1, "images_90k_norm": 6, "img": [6, 9, 16, 17], "img_cont": 7, "img_fold": [6, 16], "img_path": 7, "img_transform": 6, "imgur5k": [4, 6, 16], "imgur5k_annot": 6, "imlist": 6, "impact": 1, "implement": [6, 7, 8, 9, 10, 18], "import": [6, 7, 8, 9, 10, 12, 14, 15, 16, 17, 18], "improv": 8, "inappropri": 1, "incid": 1, "includ": [1, 6, 16, 17], "inclus": 1, "increas": 9, "independ": 9, "index": [2, 7], "indic": 10, "individu": 1, "infer": [4, 8, 9, 15], "inform": [1, 2, 4, 6, 16], "input": [2, 7, 8, 9, 17, 18], "input_crop": 8, "input_pag": [8, 10, 18], "input_shap": 17, "input_tensor": 8, "inspir": [1, 9], "instal": [14, 15, 17], "instanc": [1, 18], "instanti": [8, 18], "instead": [6, 7, 8], "insult": 1, "int": [6, 7, 9], "int64": 10, "integ": 10, "integr": [4, 14, 16], "intel": 18, "interact": [1, 7, 10], "interfac": [14, 17], "interoper": 17, "interpol": 9, "interpret": [6, 7], "intersect": 10, "invert": 9, "investig": 1, "invis": 1, "involv": [1, 18], "io": [14, 15, 17], "iou": 10, "iou_thresh": 10, "iou_threshold": 15, "irregular": [4, 8, 16], "isn": 6, "issu": [1, 2, 14], "italian": 6, "iter": [6, 9, 16, 18], "its": [7, 8, 9, 10, 16, 18], "itself": [8, 14], "j": 10, "jame": [], "job": 2, "join": 2, "jpeg": 9, "jpegqual": 9, "jpg": [6, 7, 14, 17], "json": [6, 16, 18], "json_output": 18, "jump": 2, "just": 1, "kei": [4, 6], "kera": [8, 17], "kernel": [4, 8, 9], "kernel_shap": 9, "keywoard": 8, "keyword": [6, 7, 8, 10], "kie": [8, 12], "kie_predictor": [8, 12], "kiepredictor": 8, "kind": 1, "know": [2, 17], "kwarg": [6, 7, 8, 10], "l": 10, "l_j": 10, "label": [6, 10, 15, 16], "label_fil": [6, 16], "label_fold": 6, "label_path": [6, 16], "labels_path": [6, 16], "ladder": 1, "lambda": 9, "lambdatransform": 9, "lang": 18, "languag": [1, 4, 6, 7, 8, 14, 18], "larg": [8, 14], "largest": 10, "last": [3, 6], "latenc": 8, "later": 2, "latest": 18, "latin": 6, "layer": 17, "layout": 18, "lead": 1, "leader": 1, "learn": [1, 4, 8, 17, 18], "least": 3, "left": [10, 18], "legacy_french": 6, "length": [6, 18], "less": [17, 18], "level": [1, 6, 10, 18], "leverag": 11, "lf": 14, "libffi": [], "librari": [2, 3, 11, 12], "light": 4, "lightweight": 17, "like": 1, "limits_": 10, "line": [4, 8, 10, 18], "line_1_1": 18, "link": 12, "linknet": [4, 8], "linknet_resnet18": [8, 12, 17, 18], "linknet_resnet18_rot": [], "linknet_resnet34": [8, 17, 18], "linknet_resnet50": [8, 18], "linux": [], "list": [6, 7, 9, 10, 14], "ll": 10, "load": [4, 6, 8, 15, 17], "load_state_dict": 12, "load_weight": 12, "loc_pr": 18, "local": [2, 4, 6, 8, 10, 16, 18], "localis": 6, "localizationconfus": 10, "locat": [2, 7, 18], "login": 8, "login_to_hub": [8, 14], "logo": [7, 15, 16], "love": 14, "lower": [9, 10, 18], "m": [2, 10, 18], "m1": 3, "macbook": 3, "machin": 17, "maco": [], "made": 4, "magc_resnet31": 8, "mai": [1, 2], "mail": 1, "main": 11, "maintain": 4, "mainten": 2, "make": [1, 2, 10, 13, 14, 17, 18], "mani": [16, 18], "manipul": 18, "map": [6, 8], "map_loc": 12, "mask_shap": [], "master": [4, 8, 18], "match": [10, 18], "mathcal": 10, "matplotlib": [7, 10], "max": [6, 9, 10], "max_angl": 9, "max_area": 9, "max_char": [6, 16], "max_delta": 9, "max_gain": 9, "max_gamma": 9, "max_qual": 9, "max_ratio": 9, "maximum": [6, 9], "maxval": [8, 9], "mbox": 10, "mean": [9, 10, 12], "meaniou": 10, "meant": [7, 17], "measur": 18, "media": 1, "median": 8, "meet": 12, "member": 1, "memori": [13, 17], "mention": 18, "merg": 6, "messag": 2, "meta": 18, "metadata": 17, "metal": 3, "method": [7, 9, 18], "metric": [10, 18], "middl": 18, "might": [17, 18], "min": 9, "min_area": 9, "min_char": [6, 16], "min_gain": 9, "min_gamma": 9, "min_qual": 9, "min_ratio": 9, "min_val": 9, "minde": [1, 3, 4, 8], "minim": [2, 4], "minimalist": [4, 8], "minimum": [3, 6, 9, 10, 18], "minval": 9, "miss": 3, "mistak": 1, "mixed_float16": 17, "mixed_precis": 17, "mjsynth": [4, 6, 16], "mnt": 6, "mobilenet": [8, 14], "mobilenet_v3_larg": 8, "mobilenet_v3_large_r": 8, "mobilenet_v3_smal": 8, "mobilenet_v3_small_crop_orient": 8, "mobilenet_v3_small_orient": [], "mobilenet_v3_small_page_orient": 8, "mobilenet_v3_small_r": 8, "mobilenetv3": 8, "modal": [4, 6], "mode": 3, "model": [6, 10, 13, 15, 16], "model_nam": [8, 14, 17], "model_path": [15, 17], "moder": 1, "modif": 2, "modifi": [8, 13, 18], "modul": [3, 7, 8, 9, 10, 18], "moment": [], "more": [2, 16, 18], "moscardi": [], "most": 18, "mozilla": 1, "multi": [4, 8], "multilingu": [6, 14], "multipl": [6, 7, 9, 18], "multipli": 9, "multiprocess": 13, "my": 8, "my_awesome_model": 14, "my_hook": 18, "n": [6, 10], "name": [6, 8, 17, 18], "nation": 1, "natur": [1, 4, 6], "nb": [], "ndarrai": [6, 7, 9, 10], "necessari": [3, 12, 13], "need": [2, 3, 6, 10, 12, 13, 14, 15, 18], "neg": 9, "nest": 18, "netraj": [], "network": [4, 6, 8, 17], "neural": [4, 6, 8, 17], "new": [2, 10], "next": [6, 16], "nois": 9, "noisi": [4, 6], "non": [4, 6, 7, 8, 9, 10], "none": [6, 7, 8, 9, 10, 18], "normal": [8, 9], "norwegian": 6, "note": [0, 2, 6, 8, 14, 15, 17], "now": 2, "np": [8, 9, 10, 18], "num_output_channel": 9, "num_sampl": [6, 16], "num_work": [], "number": [6, 9, 10, 18], "numpi": [7, 8, 10, 18], "o": 3, "obb": 15, "obj_detect": 14, "object": [6, 7, 10, 11, 15, 18], "objectness_scor": [7, 18], "oblig": 1, "obtain": 18, "occupi": 17, "ocr": [4, 6, 8, 10, 14, 16], "ocr_carea": 18, "ocr_db_crnn": 10, "ocr_lin": 18, "ocr_pag": 18, "ocr_par": 18, "ocr_predictor": [8, 12, 14, 17, 18], "ocrdataset": [6, 16], "ocrmetr": 10, "ocrpredictor": [8, 12], "ocrx_word": 18, "offens": 1, "offici": [1, 8], "offlin": 1, "offset": 9, "onc": 18, "one": [2, 6, 8, 9, 12, 14, 18], "oneof": 9, "ones": [6, 10], "onli": [2, 8, 9, 10, 14, 16, 17, 18], "onlin": 1, "onnx": 15, "onnxruntim": [15, 17], "onnxtr": 17, "opac": 9, "opacity_rang": 9, "open": [1, 2, 14, 17], "opinion": 1, "optic": [4, 18], "optim": [4, 18], "option": [6, 8, 12], "order": [2, 6, 7, 9], "org": [1, 6, 8, 18], "organ": 7, "orient": [1, 7, 8, 15, 18], "orientationpredictor": 8, "other": [1, 2], "otherwis": [1, 7, 10], "our": [2, 8, 18], "out": [2, 8, 9, 10, 18], "outpout": 18, "output": [7, 9, 17], "output_s": [7, 9], "outsid": 13, "over": [6, 10, 18], "overal": [1, 8], "overlai": 7, "overview": 15, "overwrit": [], "overwritten": 14, "own": 4, "p": [9, 18], "packag": [2, 4, 10, 13, 15, 16, 17], "pad": [6, 8, 9, 18], "page": [3, 6, 8, 10, 18], "page1": 7, "page2": 7, "page_1": 18, "page_idx": [7, 18], "page_orientation_predictor": 8, "page_param": [], "pair": 10, "pango": [], "paper": 8, "par_1_1": 18, "paragraph": 18, "paragraph_break": 18, "parallel": [], "param": [9, 18], "paramet": [4, 7, 8, 17], "pars": [4, 6], "parseq": [4, 8, 14, 17, 18], "part": [6, 9, 18], "parti": 3, "partial": 18, "particip": 1, "pass": [6, 7, 8, 18], "password": 7, "patch": [8, 10], "path": [6, 7, 15, 16, 17], "path_to_checkpoint": 12, "path_to_custom_model": 17, "path_to_pt": 12, "patil": [], "pattern": 1, "pdf": [7, 8, 11], "pdfpage": 7, "peopl": 1, "per": [9, 18], "perform": [4, 7, 8, 9, 10, 13, 17, 18], "period": 1, "permiss": 1, "permut": [4, 8], "persian_lett": 6, "person": [1, 16], "phase": 18, "photo": 16, "physic": [1, 7], "pick": 9, "pictur": 7, "pip": [2, 3, 15, 17], "pipelin": 18, "pixbuf": [], "pixel": [7, 9, 18], "pleas": 2, "plot": 10, "plt": 10, "plug": 14, "plugin": 3, "png": 7, "point": 17, "polici": 13, "polish": 6, "polit": 1, "polygon": [6, 10, 18], "pool": 8, "portugues": 6, "posit": [1, 10], "possibl": [2, 10, 14, 18], "post": [1, 18], "postprocessor": 18, "potenti": 8, "power": 4, "ppageno": 18, "pre": [2, 8, 17], "precis": [10, 18], "pred": 10, "pred_box": 10, "pred_label": 10, "predefin": 16, "predict": [7, 8, 10, 18], "predictor": [4, 7, 8, 12, 14, 17], "prefer": 16, "preinstal": 3, "preprocessor": [12, 18], "prerequisit": 14, "present": 11, "preserv": [8, 9, 18], "preserve_aspect_ratio": [7, 8, 9, 12, 18], "pretrain": [4, 8, 10, 12, 17, 18], "pretrained_backbon": [8, 12], "print": 18, "prior": 6, "privaci": 1, "privat": 1, "probabl": 9, "problem": 2, "procedur": 9, "process": [2, 4, 7, 12, 18], "processor": 18, "produc": [11, 18], "product": 17, "profession": 1, "project": [2, 16], "promptli": 1, "proper": 2, "properli": 6, "provid": [1, 2, 4, 14, 15, 16, 18], "public": [1, 4], "publicli": 18, "publish": 1, "pull": 14, "punctuat": 6, "pure": 6, "purpos": 2, "push_to_hf_hub": [8, 14], "py": 14, "pypdfium2": [3, 7], "pyplot": [7, 10], "python": [2, 15], "python3": 14, "pytorch": [3, 4, 8, 9, 12, 14, 17, 18], "q": 2, "qr": [7, 15], "qr_code": 16, "qualiti": 9, "question": 1, "quickli": 4, "quicktour": 11, "r": 18, "race": 1, "ramdisk": 6, "rand": [8, 9, 10, 17, 18], "random": [8, 9, 10, 18], "randomappli": 9, "randombright": 9, "randomcontrast": 9, "randomcrop": 9, "randomgamma": 9, "randomhorizontalflip": 9, "randomhu": 9, "randomjpegqu": 9, "randomli": 9, "randomres": 9, "randomrot": 9, "randomsatur": 9, "randomshadow": 9, "rang": 9, "rassi": 14, "ratio": [8, 9, 18], "raw": [7, 10], "re": 17, "read": [4, 6, 8], "read_html": 7, "read_img": [], "read_img_as_numpi": 7, "read_img_as_tensor": 7, "read_pdf": 7, "readi": 17, "real": [4, 8, 9], "realli": [], "reason": [1, 4, 6], "rebuild": 2, "rebuilt": 2, "recal": [10, 18], "receipt": [4, 6, 18], "reco_arch": [8, 12, 14, 17], "reco_b": 18, "reco_model": [12, 14, 17], "reco_param": 12, "reco_predictor": 12, "recogn": 18, "recognit": [6, 10, 12], "recognition_predictor": [8, 18], "recognition_task": [6, 16], "recognitiondataset": [6, 16], "recognitionpredictor": [8, 12], "rectangular": 8, "red": [], "reduc": [3, 9], "refer": [2, 3, 12, 14, 15, 16, 18], "regardless": 1, "region": 18, "regroup": 10, "regular": 16, "reject": 1, "rel": [7, 9, 10, 18], "relat": 7, "releas": [0, 3], "relev": 15, "religion": 1, "remov": 1, "render": [7, 18], "repo": 8, "repo_id": [8, 14], "report": 1, "repositori": [6, 8, 14], "repres": [1, 17, 18], "represent": [4, 8], "request": [1, 14], "requir": [3, 9, 17], "research": 4, "residu": 8, "resiz": [9, 18], "resnet": 8, "resnet18": [8, 14], "resnet31": 8, "resnet34": 8, "resnet50": [8, 14], "resolv": 7, "resolve_block": 18, "resolve_lin": 18, "resourc": 16, "respect": 1, "respons": [], "rest": [2, 9, 10], "restrict": 13, "result": [2, 6, 7, 11, 14, 17, 18], "return": 18, "reusabl": 18, "review": 1, "rgb": [7, 9], "rgb_mode": 7, "rgb_output": 7, "right": [1, 8, 10], "roboflow": [], "robust": [4, 6], "root": 6, "rotat": [6, 7, 8, 9, 10, 16, 18], "run": [2, 3, 8], "same": [2, 7, 10, 16, 17, 18], "sampl": [6, 16, 18], "sample_transform": 6, "sanjin": [], "sar": [4, 8], "sar_resnet31": [8, 18], "satur": 9, "save": [8, 16], "scale": [7, 8, 9, 10], "scale_rang": 9, "scan": [4, 6], "scene": [4, 6, 8], "score": [7, 10], "script": [2, 16], "seamless": 4, "seamlessli": [4, 18], "search": 8, "searchabl": 11, "sec": 18, "second": 18, "section": [12, 14, 15, 17, 18], "secur": [1, 13], "see": [1, 2], "seen": 18, "segment": [4, 8, 18], "self": 18, "semant": [4, 8], "send": 18, "sens": 10, "sensit": 16, "separ": 18, "sequenc": [4, 6, 7, 8, 10, 18], "sequenti": [9, 18], "seri": 1, "seriou": 1, "set": [1, 3, 6, 8, 10, 13, 15, 18], "set_global_polici": 17, "sever": [7, 9, 18], "sex": 1, "sexual": 1, "shade": 9, "shape": [4, 7, 8, 9, 10, 18], "share": [13, 16], "shift": 9, "shm": 13, "should": [2, 6, 7, 9, 10], "show": [4, 7, 8, 10, 12, 14, 15], "showcas": 2, "shuffl": [6, 9], "side": 10, "signatur": 7, "signific": 16, "simpl": [4, 8, 17], "simpler": 8, "sinc": [6, 16], "singl": [1, 2, 4, 6], "single_img_doc": 17, "size": [1, 6, 7, 9, 15, 18], "skew": 18, "slack": 2, "slightli": 8, "small": [2, 8], "smallest": 7, "snapshot_download": 8, "snippet": 18, "so": [2, 3, 6, 8, 14, 16], "social": 1, "socio": 1, "some": [3, 11, 14, 16], "someth": 2, "somewher": 2, "soon": [], "sort": 1, "sourc": [6, 7, 8, 9, 10, 14], "space": [1, 18], "span": 18, "spanish": 6, "spatial": [4, 6, 7], "specif": [2, 3, 10, 12, 16, 18], "specifi": [1, 6, 7], "speed": [4, 8], "sphinx": 2, "sroie": [4, 6, 16], "stabl": 3, "stackoverflow": 2, "stage": 4, "standalon": [], "standard": 9, "start": 6, "state": [4, 10, 15], "static": 10, "statist": [], "statu": 1, "std": [9, 12], "step": 13, "still": 18, "str": [6, 7, 8, 9, 10], "straight": [6, 8, 16, 18], "straighten": [], "straighten_pag": 8, "straigten_pag": [], "stream": 7, "street": [4, 6], "strict": 3, "strictli": 10, "string": [6, 7, 10, 18], "strive": 3, "strong": [4, 8], "structur": [17, 18], "subset": [6, 18], "suggest": [2, 14], "sum": 10, "summari": 10, "support": [3, 15, 17, 18], "sustain": 1, "svhn": [4, 6, 16], "svt": [6, 16], "swedish": 6, "symmetr": [8, 9, 18], "symmetric_pad": [8, 9, 18], "synthes": [], "synthesize_pag": [], "synthet": 4, "synthtext": [4, 6, 16], "system": 18, "t": [2, 6, 12, 17, 18], "tabl": [14, 15], "take": [1, 6, 18], "target": [6, 7, 9, 10, 16], "target_s": 6, "task": [4, 6, 8, 14, 16, 18], "task2": 6, "team": 3, "techminde": 3, "templat": [2, 4], "tensor": [6, 7, 9, 18], "tensorflow": [3, 4, 7, 8, 9, 12, 14, 17, 18], "tensorspec": 17, "term": 1, "test": [6, 16], "test_set": 6, "text": [6, 7, 8, 10, 16], "text_output": 18, "textmatch": 10, "textnet": 8, "textnet_bas": 8, "textnet_smal": 8, "textnet_tini": 8, "textract": [4, 18], "textstylebrush": [4, 6], "textual": [4, 6, 7, 8, 18], "tf": [3, 7, 8, 9, 14, 17], "than": [2, 10, 14], "thank": 2, "thei": [1, 10], "them": [6, 18], "thi": [1, 2, 3, 5, 6, 9, 10, 12, 13, 14, 16, 17, 18], "thing": [17, 18], "third": 3, "those": [1, 7, 18], "threaten": 1, "threshold": 18, "through": [1, 9, 15, 16], "tilman": 14, "time": [1, 4, 8, 10, 16], "tini": 8, "titl": [7, 18], "tm": 18, "tmp": 13, "togeth": [2, 7], "tograi": 9, "tool": 16, "top": [10, 17, 18], "topic": 2, "torch": [3, 9, 12, 14, 17], "torchvis": 9, "total": 12, "toward": [1, 3], "train": [2, 6, 8, 9, 14, 15, 16, 17, 18], "train_it": [6, 16], "train_load": [6, 16], "train_pytorch": 14, "train_set": [6, 16], "train_tensorflow": 14, "trainabl": [4, 8], "tranform": 9, "transcrib": 18, "transfer": [4, 6], "transfo": 9, "transform": [4, 6, 8], "translat": 1, "troll": 1, "true": [6, 7, 8, 9, 10, 12, 13, 14, 16, 17, 18], "truth": 10, "tune": 17, "tupl": [6, 7, 9, 10], "two": [7, 13], "txt": 6, "type": [7, 10, 14, 17, 18], "typic": 18, "u": [1, 2], "ucsd": 6, "udac": 2, "uint8": [7, 8, 10, 18], "ukrainian": [], "unaccept": 1, "underli": [16, 18], "underneath": 7, "understand": [4, 6, 18], "unidecod": [], "uniform": [8, 9], "uniformli": 9, "uninterrupt": [7, 18], "union": 10, "unit": [], "unittest": 2, "unlock": 7, "unoffici": 8, "unprofession": 1, "unsolicit": 1, "unsupervis": 4, "unwelcom": 1, "up": [8, 18], "updat": 10, "upgrad": 2, "upper": [6, 9], "uppercas": 16, "url": 7, "us": [1, 2, 3, 6, 8, 10, 12, 13, 14, 15, 18], "usabl": 18, "usag": [13, 17], "use_broadcast": [], "use_polygon": [6, 10, 16], "useabl": 18, "user": [4, 7, 11], "utf": 18, "util": 17, "v1": 14, "v3": [8, 14, 18], "valid": 16, "valu": [2, 7, 9, 18], "valuabl": 4, "variabl": 13, "varieti": 6, "veri": 8, "verma": [], "version": [1, 2, 3, 17, 18], "vgg": 8, "vgg16": 14, "vgg16_bn_r": 8, "via": 1, "video": [], "vietnames": 6, "view": [4, 6], "viewpoint": 1, "violat": 1, "visibl": 1, "vision": [4, 6, 8], "visiondataset": 6, "visiontransform": 8, "visual": [3, 4, 15], "visualize_pag": 10, "vit_": 8, "vit_b": 8, "vitstr": [4, 8, 17], "vitstr_bas": [8, 18], "vitstr_smal": [8, 12, 17, 18], "viz": 3, "vocab": [12, 14, 16, 17, 18], "vocabulari": [6, 12, 14], "w": [7, 8, 9, 10], "w3": 18, "wa": 1, "wai": [1, 4, 16], "want": [2, 17, 18], "warmup": 18, "wasn": 2, "we": [1, 2, 3, 4, 7, 9, 14, 16, 17, 18], "weasyprint": 7, "web": [2, 7], "websit": 6, "weight": 12, "welcom": 1, "well": [1, 17], "were": [1, 7, 18], "what": 1, "when": [1, 2, 8], "whenev": 2, "where": [2, 7, 9, 10], "whether": [2, 6, 7, 9, 10, 16, 18], "which": [1, 8, 13, 15, 16, 18], "whichev": 3, "while": [9, 18], "why": 1, "width": [7, 9], "wiki": 1, "wildreceipt": [4, 6, 16], "window": [8, 10], "wish": 2, "within": 1, "without": [1, 6, 8], "wonder": 2, "word": [4, 6, 8, 10, 18], "word_1_1": 18, "word_1_2": 18, "word_1_3": 18, "wordgener": [6, 16], "words_onli": 10, "work": [13, 18], "worker": [], "workflow": 2, "worklow": 2, "world": [10, 18], "worth": 8, "wrap": 18, "wrapper": [6, 9], "write": 13, "written": [1, 7], "www": [1, 7, 18], "x": [7, 9, 10], "x_ascend": 18, "x_descend": 18, "x_i": 10, "x_size": 18, "x_wconf": 18, "xhtml": 18, "xmax": 7, "xmin": 7, "xml": 18, "xml_bytes_str": 18, "xml_element": 18, "xml_output": 18, "xmln": 18, "y": 10, "y_i": 10, "y_j": 10, "yet": 15, "ymax": 7, "ymin": 7, "yolov8": 15, "you": [2, 3, 6, 7, 8, 12, 13, 14, 15, 16, 17, 18], "your": [2, 4, 7, 10, 18], "yoursit": 7, "yugesh": [], "zero": [9, 10], "zoo": [], "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7\u00e0\u00e2\u00e9\u00e8\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00e7": 6, "\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7\u00e0\u00e2\u00e9\u00e8\u00ea\u00eb\u00ee\u00ef\u00f4\u00f9\u00fb\u00fc\u00e7": 6, "\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa\u00e0\u00e8\u00e9\u00ec\u00ed\u00ee\u00f2\u00f3\u00f9\u00fa": 6, "\u00e1\u00e0\u00e2\u00e3\u00e9\u00ea\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7\u00e1\u00e0\u00e2\u00e3\u00e9\u00eb\u00ed\u00ef\u00f3\u00f4\u00f5\u00fa\u00fc\u00e7": 6, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": 6, "\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5\u00e1\u00e0\u1ea3\u1ea1\u00e3\u0103\u1eaf\u1eb1\u1eb3\u1eb5\u1eb7\u00e2\u1ea5\u1ea7\u1ea9\u1eab\u1ead\u0111\u00e9\u00e8\u1ebb\u1ebd\u1eb9\u00ea\u1ebf\u1ec1\u1ec3\u1ec5\u1ec7\u00f3\u00f2\u1ecf\u00f5\u1ecd\u00f4\u1ed1\u1ed3\u1ed5\u1ed9\u1ed7\u01a1\u1edb\u1edd\u1edf\u1ee3\u1ee1\u00fa\u00f9\u1ee7\u0169\u1ee5\u01b0\u1ee9\u1eeb\u1eed\u1eef\u1ef1i\u00ed\u00ec\u1ec9\u0129\u1ecb\u00fd\u1ef3\u1ef7\u1ef9\u1ef5": [], "\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1\u00e1\u00e9\u00ed\u00f3\u00fa\u00fc\u00f1": 6, "\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e\u00e1\u010d\u010f\u00e9\u011b\u00ed\u0148\u00f3\u0159\u0161\u0165\u00fa\u016f\u00fd\u017e": 6, "\u00e4\u00f6\u00e4\u00f6": 6, "\u00e4\u00f6\u00fc\u00df\u00e4\u00f6\u00fc\u00df": 6, "\u00e5\u00e4\u00f6\u00e5\u00e4\u00f6": 6, "\u00e6\u00f8\u00e5\u00e6\u00f8\u00e5": 6, "\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c\u0105\u0107\u0119\u0142\u0144\u00f3\u015b\u017a\u017c": 6, "\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9\u03b1\u03b2\u03b3\u03b4\u03b5\u03b6\u03b7\u03b8\u03b9\u03ba\u03bb\u03bc\u03bd\u03be\u03bf\u03c0\u03c1\u03c3\u03c4\u03c5\u03c6\u03c7\u03c8\u03c9": 6, "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f": [], "\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f\u0430\u0431\u0432\u0433\u0434\u0435\u0436\u0437\u0438\u0439\u043a\u043b\u043c\u043d\u043e\u043f\u0440\u0441\u0442\u0443\u0444\u0445\u0446\u0447\u0448\u0449\u044c\u044e\u044f0123456789": [], "\u0491\u0456\u0457\u0454\u0491\u0456\u0457\u0454": [], "\u05d0\u05d1\u05d2\u05d3\u05d4\u05d5\u05d6\u05d7\u05d8\u05d9\u05db\u05dc\u05de\u05e0\u05e1\u05e2\u05e4\u05e6\u05e7\u05e8\u05e9\u05ea": 6, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a": 6, "\u0621\u0622\u0623\u0624\u0625\u0626\u0627\u0628\u0629\u062a\u062b\u062c\u062d\u062e\u062f\u0630\u0631\u0632\u0633\u0634\u0635\u0636\u0637\u0638\u0639\u063a\u0640\u0641\u0642\u0643\u0644\u0645\u0646\u0647\u0648\u0649\u064a\u067e\u0686\u06a2\u06a4\u06af": 6, "\u0660\u0661\u0662\u0663\u0664\u0665\u0666\u0667\u0668\u0669": 6, "\u067e\u0686\u06a2\u06a4\u06af": 6, "\u0905": 6, "\u0905\u0906\u0907\u0908\u0909\u090a\u090b\u0960\u090c\u0961\u090f\u0910\u0913\u0914\u0905": 6, "\u0915\u0916\u0917\u0918\u0919\u091a\u091b\u091c\u091d\u091e\u091f\u0920\u0921\u0922\u0923\u0924\u0925\u0926\u0927\u0928\u092a\u092b\u092c\u092d\u092e\u092f\u0930\u0932\u0935\u0936\u0937\u0938\u0939\u0966\u0967\u0968\u0969\u096a\u096b\u096c\u096d\u096e\u096f": 6, "\u0950": 6, "\u0985\u0986\u0987\u0988\u0989\u098a\u098b\u098f\u0990\u0993\u0994\u0995\u0996\u0997\u0998\u0999\u099a\u099b\u099c\u099d\u099e\u099f\u09a0\u09a1\u09a2\u09a3\u09a4\u09a5\u09a6\u09a7\u09a8\u09aa\u09ab\u09ac\u09ad\u09ae\u09af\u09b0\u09b2\u09b6\u09b7\u09b8\u09b9": 6, "\u09bd": 6, "\u09ce": 6, "\u09e6\u09e7\u09e8\u09e9\u09ea\u09eb\u09ec\u09ed\u09ee\u09ef": 6}, "titles": ["Changelog", "Contributor Covenant Code of Conduct", "Contributing to docTR", "Installation", "docTR: Document Text Recognition", "doctr.contrib", "doctr.datasets", "doctr.io", "doctr.models", "doctr.transforms", "doctr.utils", "docTR Notebooks", "Train your own model", "AWS Lambda", "Share your model with the community", "Integrate contributions into your pipeline", "Choose a ready to use dataset", "Preparing your model for inference", "Choosing the right model"], "titleterms": {"": 2, "0": 0, "01": 0, "02": 0, "03": 0, "04": 0, "05": 0, "07": 0, "08": 0, "09": 0, "1": [0, 1], "10": 0, "11": 0, "12": 0, "18": 0, "2": [0, 1], "2021": 0, "2022": 0, "2023": 0, "2024": 0, "21": [], "22": 0, "27": 0, "28": 0, "29": 0, "3": [0, 1], "31": 0, "4": [0, 1], "5": 0, "6": 0, "7": 0, "8": 0, "9": [], "advanc": 18, "approach": 18, "architectur": 18, "arg": [6, 7, 8, 9, 10], "artefact": 7, "artefactdetect": 15, "attribut": 1, "avail": [15, 16, 18], "aw": 13, "ban": 1, "block": 7, "bug": 2, "changelog": 0, "choos": [16, 18], "classif": [8, 14], "code": [1, 2], "codebas": 2, "commit": 2, "commun": 14, "compos": 9, "conda": 3, "conduct": 1, "connect": 2, "continu": 2, "contrib": 5, "contribut": [2, 5, 15], "contributor": 1, "convent": 14, "correct": 1, "coven": 1, "custom": [6, 12], "data": 16, "dataload": 6, "dataset": [4, 6, 16], "detect": [4, 8, 14, 16, 18], "develop": 2, "do": 18, "doctr": [2, 4, 5, 6, 7, 8, 9, 10, 11], "document": [2, 4, 7], "end": 18, "enforc": 1, "evalu": 10, "export": 17, "factori": 8, "featur": [2, 4], "feedback": 2, "file": 7, "from": 14, "gener": [6, 16], "git": 3, "guidelin": 1, "half": 17, "hub": 14, "huggingfac": 14, "i": 18, "infer": 17, "instal": [2, 3], "integr": [2, 15], "io": 7, "lambda": 13, "let": 2, "line": 7, "linux": 3, "load": [12, 14, 16], "loader": 6, "main": 4, "mode": 2, "model": [4, 8, 12, 14, 17, 18], "modifi": 2, "modul": [5, 15], "name": 14, "notebook": 11, "object": 16, "ocr": 18, "onli": 3, "onnx": 17, "optim": 17, "option": 18, "orient": [], "our": 1, "output": 18, "own": [12, 16], "packag": 3, "page": 7, "perman": 1, "pipelin": 15, "pledg": 1, "precis": 17, "predictor": 18, "prepar": 17, "prerequisit": 3, "pretrain": 14, "push": 14, "python": 3, "qualiti": 2, "question": 2, "read": 7, "readi": 16, "recognit": [4, 8, 14, 16, 18], "report": 2, "request": 2, "resourc": [], "respons": 1, "return": [6, 7, 8, 10], "right": 18, "scope": 1, "share": 14, "should": 18, "stage": 18, "standard": 1, "structur": [2, 7], "style": 2, "support": [4, 5, 6, 9], "synthet": [6, 16], "task": 10, "temporari": 1, "test": 2, "text": [4, 18], "train": 12, "transform": 9, "two": 18, "unit": 2, "us": [16, 17], "util": 10, "v0": 0, "verif": 2, "via": 3, "visual": 10, "vocab": 6, "warn": 1, "what": 18, "word": 7, "your": [12, 14, 15, 16, 17], "zoo": [4, 8]}}) \ No newline at end of file diff --git a/v0.9.0/transforms.html b/v0.9.0/transforms.html deleted file mode 100644 index 85e94d8a76..0000000000 --- a/v0.9.0/transforms.html +++ /dev/null @@ -1,684 +0,0 @@ - - - - - - - - - - - - - doctr.transforms - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    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:

    -
    -
    -class doctr.transforms.Resize(output_size: Tuple[int, int], method: str = 'bilinear', preserve_aspect_ratio: bool = False, symmetric_pad: bool = False)[source]
    -

    Resizes a tensor to a target size

    -
    -
    Example::
    >>> from doctr.transforms import Resize
    ->>> import tensorflow as tf
    ->>> transfo = Resize((32, 32))
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • output_size – expected output size

    • -
    • method – interpolation method

    • -
    • preserve_aspect_ratio – if True, preserve aspect ratio and pad the rest with zeros

    • -
    • symmetric_pad – if True while preserving aspect ratio, the padding will be done symmetrically

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.Normalize(mean: Tuple[float, float, float], std: Tuple[float, float, float])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • mean – average value per channel

    • -
    • std – standard deviation per channel

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.LambdaTransformation(fn: Callable[[Tensor], Tensor])[source]
    -

    Normalize a tensor to a Gaussian distribution for each channel

    -
    -
    Example::
    >>> from doctr.transforms import LambdaTransformation
    ->>> import tensorflow as tf
    ->>> transfo = LambdaTransformation(lambda x: x/ 255.)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    fn – the function to be applied to the input tensor

    -
    -
    -
    - -
    -
    -class doctr.transforms.ToGray[source]
    -

    Convert a RGB tensor (batch of images or image) to a 3-channels grayscale tensor

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ToGray()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    - -
    -
    -class doctr.transforms.ColorInversion(min_val: float = 0.5)[source]
    -

    Applies the following tranformation to a tensor (image or batch of images): -convert to grayscale, colorize (shift 0-values randomly), and then invert colors

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = ColorInversion(min_val=0.6)
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    min_val – range [min_val, 1] to colorize RGB pixels

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomBrightness(max_delta: float = 0.3)[source]
    -

    Randomly adjust brightness of a tensor (batch of images or image) by adding a delta -to all pixels

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Brightness()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    • -
    • p – probability to apply transformation

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomContrast(delta: float = 0.3)[source]
    -

    Randomly adjust contrast of a tensor (batch of images or image) by adjusting -each pixel: (img - mean) * contrast_factor + mean.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Contrast()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce contrast if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomSaturation(delta: float = 0.5)[source]
    -

    Randomly adjust saturation of a tensor (batch of images or image) by converting to HSV and -increasing saturation by a factor.

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Saturation()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -

    delta – multiplicative factor is picked in [1-delta, 1+delta] (reduce saturation if factor<1)

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomHue(max_delta: float = 0.3)[source]
    -

    Randomly adjust hue of a tensor (batch of images or image) by converting to HSV and adding a delta

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Hue()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    max_delta – offset to add to each pixel is randomly picked in [-max_delta, max_delta]

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomGamma(min_gamma: float = 0.5, max_gamma: float = 1.5, min_gain: float = 0.8, max_gain: float = 1.2)[source]
    -

    randomly performs gamma correction for a tensor (batch of images or image)

    -

    Example

    -
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = Gamma()
    ->>> out = transfo(tf.random.uniform(shape=[8, 64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_gamma – non-negative real number, lower bound for gamma param

    • -
    • max_gamma – non-negative real number, upper bound for gamma

    • -
    • min_gain – lower bound for constant multiplier

    • -
    • max_gain – upper bound for constant multiplier

    • -
    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomJpegQuality(min_quality: int = 60, max_quality: int = 100)[source]
    -

    Randomly adjust jpeg quality of a 3 dimensional RGB image

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = JpegQuality()
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • min_quality – int between [0, 100]

    • -
    • max_quality – int between [0, 100]

    • -
    -
    -
    -
    - -
    -
    -

    Composing transformations

    -

    It is common to require several transformations to be performed consecutively.

    -
    -
    -class doctr.transforms.Compose(transforms: List[Callable[[Any], Any]])[source]
    -

    Implements a wrapper that will apply transformations sequentially

    -
    -
    Example::
    >>> from doctr.transforms import Compose, Resize
    ->>> import tensorflow as tf
    ->>> transfos = Compose([Resize((32, 32))])
    ->>> out = transfos(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformation modules

    -
    -
    -
    - -
    -
    -class doctr.transforms.OneOf(transforms: List[Callable[[Any], Any]])[source]
    -

    Randomly apply one of the input transformations

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = OneOf([JpegQuality(), Gamma()])
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    transforms – list of transformations, one only will be picked

    -
    -
    -
    - -
    -
    -class doctr.transforms.RandomApply(transform: Callable[[Any], Any], p: float = 0.5)[source]
    -

    Apply with a probability p the input transformation

    -
    -
    Example::
    >>> from doctr.transforms import Normalize
    ->>> import tensorflow as tf
    ->>> transfo = RandomApply(Gamma(), p=.5)
    ->>> out = transfo(tf.random.uniform(shape=[64, 64, 3], minval=0, maxval=1))
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • transform – transformation to apply

    • -
    • p – probability to apply

    • -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - \ No newline at end of file diff --git a/v0.9.0/using_doctr/custom_models_training.html b/v0.9.0/using_doctr/custom_models_training.html index 2c79fb64b5..6ff20087da 100644 --- a/v0.9.0/using_doctr/custom_models_training.html +++ b/v0.9.0/using_doctr/custom_models_training.html @@ -14,7 +14,7 @@ - + Train your own model - docTR documentation @@ -547,7 +547,7 @@

    Loading your custom trained model - + diff --git a/v0.9.0/using_doctr/running_on_aws.html b/v0.9.0/using_doctr/running_on_aws.html index 2815ec9e23..09c81cbd17 100644 --- a/v0.9.0/using_doctr/running_on_aws.html +++ b/v0.9.0/using_doctr/running_on_aws.html @@ -14,7 +14,7 @@ - + AWS Lambda - docTR documentation @@ -358,7 +358,7 @@

    AWS Lambda - + diff --git a/v0.9.0/using_doctr/sharing_models.html b/v0.9.0/using_doctr/sharing_models.html index b1e1d03f67..a499929f5f 100644 --- a/v0.9.0/using_doctr/sharing_models.html +++ b/v0.9.0/using_doctr/sharing_models.html @@ -14,7 +14,7 @@ - + Share your model with the community - docTR documentation @@ -540,7 +540,7 @@

    Recognition - + diff --git a/v0.9.0/using_doctr/using_contrib_modules.html b/v0.9.0/using_doctr/using_contrib_modules.html index 7985749cdf..ae88bcf382 100644 --- a/v0.9.0/using_doctr/using_contrib_modules.html +++ b/v0.9.0/using_doctr/using_contrib_modules.html @@ -14,7 +14,7 @@ - + Integrate contributions into your pipeline - docTR documentation @@ -411,7 +411,7 @@

    ArtefactDetection - + diff --git a/v0.9.0/using_doctr/using_datasets.html b/v0.9.0/using_doctr/using_datasets.html index 1b309f8041..c44fa8bb73 100644 --- a/v0.9.0/using_doctr/using_datasets.html +++ b/v0.9.0/using_doctr/using_datasets.html @@ -14,7 +14,7 @@ - + Choose a ready to use dataset - docTR documentation @@ -625,7 +625,7 @@

    Data Loading - + diff --git a/v0.9.0/using_doctr/using_model_export.html b/v0.9.0/using_doctr/using_model_export.html index d24c97bba7..a8c3258f53 100644 --- a/v0.9.0/using_doctr/using_model_export.html +++ b/v0.9.0/using_doctr/using_model_export.html @@ -14,7 +14,7 @@ - + Preparing your model for inference - docTR documentation @@ -463,7 +463,7 @@

    Using your ONNX exported model - + diff --git a/v0.9.0/using_doctr/using_models.html b/v0.9.0/using_doctr/using_models.html index f7a717bff1..e638938baa 100644 --- a/v0.9.0/using_doctr/using_models.html +++ b/v0.9.0/using_doctr/using_models.html @@ -14,7 +14,7 @@ - + Choosing the right model - docTR documentation @@ -1229,7 +1229,7 @@

    Advanced options - + diff --git a/v0.9.0/utils.html b/v0.9.0/utils.html deleted file mode 100644 index e2f223f06a..0000000000 --- a/v0.9.0/utils.html +++ /dev/null @@ -1,574 +0,0 @@ - - - - - - - - - - - - - doctr.utils - docTR documentation - - - - - - - - - - - - - - - - - - Contents - - - - - - Menu - - - - - - - - Expand - - - - - - Light mode - - - - - - - - - - - - - - Dark mode - - - - - - - Auto light/dark, in light mode - - - - - - - - - - - - - - - Auto light/dark, in dark mode - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Skip to content - - - -
    -
    -
    - -
    - -
    -
    - -
    - -
    -
    - -
    -
    -
    - - - - - Back to top - -
    - -
    - -
    - -
    -
    -
    -

    doctr.utils

    -

    This module regroups non-core features that are complementary to the rest of the package.

    -
    -

    Visualization

    -

    Easy-to-use functions to make sense of your model’s predictions.

    -
    -
    -doctr.utils.visualization.visualize_page(page: Dict[str, Any], image: ndarray, words_only: bool = True, display_artefacts: bool = True, scale: float = 10, interactive: bool = True, add_labels: bool = True, **kwargs: Any) Figure[source]
    -

    Visualize a full page with predicted blocks, lines and words

    -
    -
    Example::
    >>> import numpy as np
    ->>> import matplotlib.pyplot as plt
    ->>> from doctr.utils.visualization import visualize_page
    ->>> from doctr.models import ocr_db_crnn
    ->>> model = ocr_db_crnn(pretrained=True)
    ->>> input_page = (255 * np.random.rand(600, 800, 3)).astype(np.uint8)
    ->>> out = model([[input_page]])
    ->>> visualize_page(out[0].pages[0].export(), input_page)
    ->>> plt.show()
    -
    -
    -
    -
    -
    -
    Parameters:
    -
      -
    • page – the exported Page of a Document

    • -
    • image – np array of the page, needs to have the same shape than page[‘dimensions’]

    • -
    • words_only – whether only words should be displayed

    • -
    • display_artefacts – whether artefacts should be displayed

    • -
    • scale – figsize of the largest windows side

    • -
    • interactive – whether the plot should be interactive

    • -
    • add_labels – for static plot, adds text labels on top of bounding box

    • -
    -
    -
    -
    - -
    -
    -

    Task evaluation

    -

    Implementations of task-specific metrics to easily assess your model performances.

    -
    -
    -class doctr.utils.metrics.TextMatch[source]
    -

    Implements text match metric (word-level accuracy) for recognition task.

    -

    The raw aggregated metric is computed as follows:

    -
    -
    -\[\forall X, Y \in \mathcal{W}^N, -TextMatch(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N f_{Y_i}(X_i)\]
    -
    -

    with the indicator function \(f_{a}\) defined as:

    -
    -
    -\[\begin{split}\forall a, x \in \mathcal{W}, -f_a(x) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } x = a \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{W}\) is the set of all possible character sequences, -\(N\) is a strictly positive integer.

    -
    -
    Example::
    >>> from doctr.utils import TextMatch
    ->>> metric = TextMatch()
    ->>> metric.update(['Hello', 'world'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    -summary() Dict[str, float][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a dictionary with the exact match score for the raw data, its lower-case counterpart, its unidecode -counterpart and its lower-case unidecode counterpart

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.LocalizationConfusion(iou_thresh: float = 0.5, rotated_bbox: bool = False, mask_shape: Tuple[int, int] = (1024, 1024))[source]
    -

    Implements common confusion metrics and mean IoU for localization evaluation.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall Y \in \mathcal{B}^N, \forall X \in \mathcal{B}^M, \\ -Recall(X, Y) = \frac{1}{N} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -Precision(X, Y) = \frac{1}{M} \sum\limits_{i=1}^N g_{X}(Y_i) \\ -meanIoU(X, Y) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(X_i, Y_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(g_{X}\) defined as:

    -
    -
    -\[\begin{split}\forall y \in \mathcal{B}, -g_X(y) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } y\mbox{ has been assigned to any }(X_i)_i\mbox{ with an }IoU \geq 0.5 \\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import LocalizationConfusion
    ->>> metric = LocalizationConfusion(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]))
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[float | None, float | None, float | None][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall, precision and meanIoU scores

    -
    -
    -
    - -
    - -
    -
    -class doctr.utils.metrics.OCRMetric(iou_thresh: float = 0.5, rotated_bbox: bool = False, mask_shape: Tuple[int, int] = (1024, 1024))[source]
    -

    Implements end-to-end OCR metric.

    -

    The aggregated metrics are computed as follows:

    -
    -
    -\[\begin{split}\forall (B, L) \in \mathcal{B}^N \times \mathcal{L}^N, -\forall (\hat{B}, \hat{L}) \in \mathcal{B}^M \times \mathcal{L}^M, \\ -Recall(B, \hat{B}, L, \hat{L}) = \frac{1}{N} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -Precision(B, \hat{B}, L, \hat{L}) = \frac{1}{M} \sum\limits_{i=1}^N h_{B,L}(\hat{B}_i, \hat{L}_i) \\ -meanIoU(B, \hat{B}) = \frac{1}{M} \sum\limits_{i=1}^M \max\limits_{j \in [1, N]} IoU(\hat{B}_i, B_j)\end{split}\]
    -
    -

    with the function \(IoU(x, y)\) being the Intersection over Union between bounding boxes \(x\) and -\(y\), and the function \(h_{B, L}\) defined as:

    -
    -
    -\[\begin{split}\forall (b, l) \in \mathcal{B} \times \mathcal{L}, -h_{B,L}(b, l) = \left\{ - \begin{array}{ll} - 1 & \mbox{if } b\mbox{ has been assigned to a given }B_j\mbox{ with an } \\ - & IoU \geq 0.5 \mbox{ and that for this assignment, } l = L_j\\ - 0 & \mbox{otherwise.} - \end{array} -\right.\end{split}\]
    -
    -

    where \(\mathcal{B}\) is the set of possible bounding boxes, -\(\mathcal{L}\) is the set of possible character sequences, -\(N\) (number of ground truths) and \(M\) (number of predictions) are strictly positive integers.

    -
    -
    Example::
    >>> import numpy as np
    ->>> from doctr.utils import OCRMetric
    ->>> metric = OCRMetric(iou_thresh=0.5)
    ->>> metric.update(np.asarray([[0, 0, 100, 100]]), np.asarray([[0, 0, 70, 70], [110, 95, 200, 150]]),
    -['hello'], ['hello', 'world'])
    ->>> metric.summary()
    -
    -
    -
    -
    -
    -
    Parameters:
    -

    iou_thresh – minimum IoU to consider a pair of prediction and ground truth as a match

    -
    -
    -
    -
    -summary() Tuple[Dict[str, float | None], Dict[str, float | None], float | None][source]
    -

    Computes the aggregated metrics

    -
    -
    Returns:
    -

    a tuple with the recall & precision for each string comparison flexibility and the mean IoU

    -
    -
    -
    - -
    - -
    -
    - -
    -
    - -
    - -
    -
    - - - - - - - - - \ No newline at end of file

    iR})ml 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�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 -{KbThs9Kdjb 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