failed09 commited on
Commit
c8cdee4
·
verified ·
1 Parent(s): ef213d0

Initial public release of BashkirTrOCR v3 (ONNX FP16 CUDA & INT8 CPU)

Browse files
LICENSE ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
META.json ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "bashkir_trocr_base",
3
+ "version": "v3.0-onnx-runtime.1",
4
+ "type": "onnx_ocr_model_bundle",
5
+ "language": "ba",
6
+ "license": "apache-2.0",
7
+ "status": "published",
8
+ "repo_id": "failed09/bashkir-trocr-base",
9
+ "intended_visibility": "public",
10
+ "architecture": "ViT-Base + TrOCR decoder",
11
+ "precision": {
12
+ "cpu": "dynamic_int8",
13
+ "gpu": "mixed_fp16_fp32_io"
14
+ },
15
+ "training_data_included": false,
16
+ "private_user_data_included": false,
17
+ "quality_claim": "No independent real-book CER/WER established. Manual review required.",
18
+ "artifacts": [
19
+ {
20
+ "file": "models/dbnet.onnx",
21
+ "bytes": 2432880,
22
+ "sha256": "3439588c030faea393a54515f51e983d8e155b19a2e8aba7891934c1cf0de526"
23
+ },
24
+ {
25
+ "file": "models/fp16/decoder.onnx",
26
+ "bytes": 598137053,
27
+ "sha256": "e55c720fd10ae8f290c28d06a150dc118a118d020f7405fef85a1b273d9ed7a3"
28
+ },
29
+ {
30
+ "file": "models/fp16/encoder.onnx",
31
+ "bytes": 172327813,
32
+ "sha256": "90a4ee3d331ea59cc2b60a860064ded38a3953163a33d4d16ce34f9b640103a5"
33
+ },
34
+ {
35
+ "file": "models/int8/decoder.onnx",
36
+ "bytes": 300449064,
37
+ "sha256": "e04bcbaaee07851e7b0a47c7b271fcd85a4d8b68623a3241e2c968c22808aecb"
38
+ },
39
+ {
40
+ "file": "models/int8/encoder.onnx",
41
+ "bytes": 89817188,
42
+ "sha256": "c9773380fd2e85ec4ccc07f570de7c2e63c88784570d3378a6af1ae744c7ca04"
43
+ }
44
+ ],
45
+ "validation": {
46
+ "cpu_clean_environment": true,
47
+ "gpu_clean_environment": true,
48
+ "pytorch_required": false,
49
+ "transformers_required": false,
50
+ "cuda_providers_confirmed": true,
51
+ "private_inputs_redistributed": false,
52
+ "public_examples": 1,
53
+ "independent_real_book_accuracy_established": false
54
+ },
55
+ "known_limitations": [
56
+ "Superscript 1 may be recognized as !; I/l/1/7 can be confused.",
57
+ "Capitals, Bashkir letters and punctuation can be wrong.",
58
+ "Dash types and word-splitting at wide-line boundaries are unreliable.",
59
+ "Decorative marks, columns, skew and poor scans require manual review.",
60
+ "No PDF rasterization, handwriting guarantee, spelling repair or independent real-book CER/WER claim."
61
+ ]
62
+ }
NOTICE ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Bashkir TrOCR v3
2
+ Fine-tuned from microsoft/trocr-base-stage1.
3
+
4
+ PP-OCRv3 detection model and DBNet preprocessing/postprocessing helpers:
5
+ Copyright (c) 2020 PaddlePaddle Authors. Apache License 2.0.
6
+ Selected from RapidOCR 1.2.3 (https://github.com/RapidAI/RapidOCR).
7
+ Modified: only detector helpers retained; no recognition/classifier models bundled.
8
+
9
+ No source books, training corpus, personal paths or training logs are redistributed.
10
+
11
+ Upstream TrOCR implementation: Copyright (c) Microsoft Corporation, MIT; see licenses/TrOCR-MIT.txt.
12
+ RapidOCR: Copyright (c) 2021 RapidOCR Authors, Apache-2.0; see licenses/RapidOCR-Apache-2.0.txt.
QUICKSTART_RU.md ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Быстрый запуск Bashkir OCR v3
2
+
3
+ 1. Установите Python 3.11–3.13 с python.org. На Windows включите «Add Python to PATH».
4
+ 2. Скачайте репозиторий целиком в обычную папку с правом записи.
5
+ 3. Запустите start_ocr.bat. По умолчанию выбран режим 3 (Авто — сам определит наличие видеокарты NVIDIA через nvidia-smi и поставит GPU FP16, иначе CPU INT8), либо выберите 1 — CPU INT8 или 2 — NVIDIA GPU FP16. Первый запуск скачает зависимости в локальное окружение. Дождитесь завершения установки.
6
+ 4. Откроется браузер. Выберите изображение, нажмите «Распознать». Переключайтесь между вкладками «По строкам» (нарезка строк и построчная правка) и «Страница и текст» (страница целиком слева и весь текст единым блоком справа).
7
+ 5. «Скопировать текст» копирует весь распознанный текст в буфер обмена; «Сохранить TXT» выгружает его в UTF-8 файл. Документы сервером на диск не записываются.
8
+ 6. Для остановки закройте окно запуска. Для следующего запуска снова откройте батник.
9
+
10
+ ## Режимы
11
+
12
+ - CPU INT8 — обычный процессор, отдельная видеокарта не нужна.
13
+ - GPU FP16 — NVIDIA CUDA. AMD/Intel GPU в этом релизе не проверены. CUDA/cuDNN зависимости ставятся в отдельное окружение; нужен установленный драйвер NVIDIA.
14
+ - Авто (по умолчанию) — проверяет наличие драйвера NVIDIA (nvidia-smi), автоматически устанавливает и запускает GPU-окружение (с откатом на CPU при сбое инициализации CUDA), либо использует CPU.
15
+
16
+ Не смешивайте onnxruntime и onnxruntime-gpu в одном окружении. Батник создаёт отдельные .venv-cpu и .venv-gpu. Пакет не содержит PyTorch/Transformers. Если установка прервана, следующий запуск повторит её. Если Python отсутствует, установите его и перезапустите батник.
17
+
18
+ ## Ограничения
19
+
20
+ Принимается одно изображение страницы (PNG/JPEG/WebP/TIFF/BMP), до 25 МБ и 25 миллионов пикселей. Для PDF сначала экспортируйте страницы в изображения. Для TIFF используется первая страница. Не подавайте целую книгу одним длинным изображением.
21
+
22
+ Проверяйте мелкие сноски 1/!, регистр, похожие буквы, тире и переносы. Расположение колонок и сильно наклонённые страницы могут обрабатываться неверно. Декоративные элементы могут стать мусорным текстом. Красивый результат не означает отсутствие ошибок.
23
+
24
+ Сервер слушает только 127.0.0.1; изображения не отправляются в облако. Не меняйте привязку на публичный адрес. В интерфейсе нет телеметрии и внешних шрифтов.
25
+
26
+ ## Если не запускается
27
+
28
+ - CUDA недоступна: обновите драйвер NVIDIA или выберите CPU. FP16 не предназначен для CPU.
29
+ - Ошибка DLL в Windows: проверьте Microsoft Visual C++ Redistributable и установку зависимостей выбранного окружения. PyTorch устанавливать не требуется.
30
+ - Порт 8001 занят: закройте предыдущий OCR либо запустите studio.py с --port 8002.
31
+ - Текст не найден: убедитесь, что изображение содержит текст, а не иллюстрацию; попробуйте более чёткую страницу.
32
+ - Видно предупреждение о длине строки: вручную проверьте обрезанный конец или разделите изображение на более короткие строки.
33
+
README.md ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ language:
3
+ - ba
4
+ license: apache-2.0
5
+ pretty_name: BashkirTrOCR
6
+ library_name: onnxruntime
7
+ pipeline_tag: image-to-text
8
+ tags:
9
+ - bashkir
10
+ - trocr
11
+ - ocr
12
+ - optical-character-recognition
13
+ - vision-encoder-decoder
14
+ - text-recognition
15
+ - onnx
16
+ - onnxruntime
17
+ - int8
18
+ - fp16
19
+ - low-resource
20
+ - experimental
21
+ ---
22
+
23
+ # BashkirTrOCR
24
+
25
+ > Optical Character Recognition for printed Bashkir books and documents, shipped as standalone ONNX models (FP16 CUDA GPU & dynamic INT8 CPU) with a local browser review studio.
26
+
27
+ ## Overview
28
+
29
+ This model transcribes printed Bashkir text lines and full document pages. It supports the complete 42-letter Bashkir Cyrillic alphabet, including all 9 specific letters (`ә, ө, ү, ғ, ҡ, ң, һ, ҫ, ҙ`), mixed Russian-Bashkir lines, punctuation, and dialogue formatting.
30
+
31
+ The release bundles a complete zero-PyTorch ONNX Runtime pipeline: `PP-OCRv3` (DBNet) text-line detection, wide-line splitting, `ViT-Base + TrOCR` recognition in both **mixed FP16** (for NVIDIA CUDA GPUs) and **dynamic INT8** (for CPUs), a CLI runner, and **Bashkir OCR Studio** — a local web interface with two review modes (line-by-line crop comparison and side-by-side full page + full text).
32
+
33
+ | At a glance | |
34
+ | --- | --- |
35
+ | Task | Page & line-level Optical Character Recognition (`image-to-text`) |
36
+ | Default artifact | **`models/fp16/`** (CUDA GPU) / **`models/int8/`** (CPU) |
37
+ | Architecture | Vision-Encoder-Decoder (`ViT-Base` + `TrOCR` Causal LM) + `PP-OCRv3` detector |
38
+ | Version / license | v3.0-onnx-runtime.1 / Apache-2.0 |
39
+
40
+ ## Contents
41
+
42
+ ### Files and Configurations
43
+
44
+ | File / Directory | Purpose | Size |
45
+ | --- | --- | ---: |
46
+ | **`models/fp16/encoder.onnx`** (recommended for GPU) | Mixed FP16 ViT encoder for NVIDIA CUDA | 172.3 MB |
47
+ | **`models/fp16/decoder.onnx`** (recommended for GPU) | Mixed FP16 TrOCR decoder for NVIDIA CUDA | 598.1 MB |
48
+ | **`models/int8/encoder.onnx`** (recommended for CPU) | Dynamic INT8 ViT encoder for CPU | 89.8 MB |
49
+ | **`models/int8/decoder.onnx`** (recommended for CPU) | Dynamic INT8 TrOCR decoder for CPU | 300.4 MB |
50
+ | `models/dbnet.onnx` | `PP-OCRv3` text-line detector graph | 2.4 MB |
51
+ | `tokenizer.json`, `tokenizer_config.json` | Tokenizer vocabulary (50,283 tokens) and settings | 1.8 MB |
52
+ | `config.json`, `preprocessor_config.json` | Runtime contract, generation limits and image normalization | — |
53
+ | `start_ocr.bat`, `studio.py`, `run.py`, `runtime/` | One-click Windows launcher, local browser studio and CLI | — |
54
+ | `requirements-cpu.txt`, `requirements-gpu.txt` | Isolated dependency manifests for CPU and CUDA runtimes | — |
55
+ | `QUICKSTART_RU.md` | Quickstart guide and troubleshooting in Russian | — |
56
+ | `META.json` | Release passport and artifact hashes | — |
57
+ | `LICENSE`, `NOTICE`, `licenses/` | Full Apache-2.0 license text and upstream attributions | — |
58
+ | `SHA256SUMS` | Checksums of the public files | — |
59
+
60
+ Raw training corpora, rendered synthetic image archives and full PyTorch checkpoints are not included.
61
+
62
+ ### Model Architecture
63
+
64
+ | Property | Value |
65
+ |---|---|
66
+ | Base architecture | Vision-Encoder-Decoder (`VisionEncoderDecoderModel`) |
67
+ | Encoder | Vision Transformer (`ViT-Base`, 12 layers, 12 attention heads, hidden size 768) |
68
+ | Decoder | TrOCR Autoregressive Transformer Decoder (12 layers, 16 heads, hidden size 1024) |
69
+ | Line detector | `PP-OCRv3` (DBNet) polygon detector + horizontal line merger |
70
+ | Parameters | ~334M |
71
+ | Vocabulary | 50,283 tokens |
72
+ | Image resolution | 384 × 384 RGB |
73
+
74
+ ### Examples
75
+
76
+ Side-by-side line recognition in **Bashkir OCR Studio** (cropped book lines on the left, recognized text on the right):
77
+
78
+ ![Bashkir OCR Studio line recognition comparison](examples/line_comparison.png)
79
+
80
+ ## Method
81
+
82
+ ```text
83
+ BashkirTrOCR v3 pipeline
84
+ ├── 1. page text-line detection (PP-OCRv3 / DBNet) & horizontal box merging
85
+ ├── 2. wide-line splitting at whitespace valleys (aspect ratio > 20:1)
86
+ ├── 3. ViT-Base visual encoding (384 × 384 RGB) & autoregressive TrOCR decoding
87
+ └── 4. interactive browser review (line-by-line crops or full-page side-by-side)
88
+ ```
89
+
90
+ The recognizer was adapted from [`microsoft/trocr-base-stage1`](https://huggingface.co/microsoft/trocr-base-stage1) with full Bashkir Cyrillic vocabulary coverage and fine-tuned on a multi-domain collection of printed text lines (books, vintage scans, and digital typography). The trained v3 checkpoint was exported to ONNX and converted into two deployment variants:
91
+ - **Mixed FP16 (`models/fp16/`)** for NVIDIA CUDA GPUs, keeping graph inputs/outputs and numerically sensitive reductions in FP32 for exact parity with the PyTorch v3 checkpoint.
92
+ - **Dynamic INT8 (`models/int8/`)** for standalone CPU execution without a dedicated GPU.
93
+
94
+ ### Benchmark
95
+
96
+ Runtime parity and speed comparison between the bundled ONNX variants on the 133-line multi-page diagnostic suite (NVIDIA RTX 4060 vs CPU):
97
+
98
+ | Runtime variant | Precision | Lines tested | Exact match vs PyTorch v3 | Full poem page latency | 133-line suite total time |
99
+ | --- | --- | ---: | ---: | ---: | ---: |
100
+ | **NVIDIA CUDA GPU** | Mixed FP16 | 133 | **133 / 133 (100%)** | **8.8 s** | **66.9 s** |
101
+ | **CPU** | Dynamic INT8 | 133 | 114 / 133 (85.7%) | 49.7 s | 392.4 s |
102
+
103
+ Differences between INT8 CPU and FP16 GPU outputs consist primarily of dash variants (`—` vs `–`) and minor punctuation on degraded scans.
104
+
105
+ ## Quality and Use
106
+
107
+ Use this release for assisted digitization of printed Bashkir books, articles, and dictionary pages. The bundled **Bashkir OCR Studio** (`studio.py` / `start_ocr.bat`) serves as a ready-to-run reference application for single-page interactive review with two view modes:
108
+ - **Line-by-line review** — inspect each detected line crop alongside its editable transcription.
109
+ - **Full page & text** — view the full page scan on the left and edit the complete page text as a single block on the right, with one-click clipboard copy and UTF-8 text export.
110
+
111
+ For large-scale book digitization, the standalone `OCR` engine (`runtime/engine.py`) or the raw ONNX graphs can be directly integrated into custom batch, PDF, or multi-page processing pipelines.
112
+
113
+ ### Limitations
114
+
115
+ - Tiny superscript footnote digits can be confused with punctuation (e.g. `1 → !`), and visually similar glyphs (`1`, `I`, `l`, `7`) require attention.
116
+ - Multi-column layouts, heavy page skew, decorative dividers, and low-contrast scans can affect reading order or line boundaries.
117
+ - Wide-line splitting may introduce spaces or split hyphenated words across chunk boundaries.
118
+ - Designed for printed Cyrillic Bashkir text; not intended for handwriting or historical Arabic-script manuscripts.
119
+
120
+ ## Related Resources
121
+
122
+ - [Bashkir Diacritics Restorer](https://huggingface.co/failed09/bashkir-diacritics-restorer) — character-level restorer for Bashkir-specific letters (`ә, ө, ү, ғ, ҡ, ң, һ, ҫ, ҙ`), useful for post-correcting characters confused on degraded scans.
123
+ - [BashkirRoBERTa](https://huggingface.co/failed09/bashkir-roberta) — pretrained Bashkir masked language model suitable for scoring OCR hypotheses and reconstructing damaged words or broken line fragments.
124
+
125
+ ## Usage
126
+
127
+ > **Note:** The bundled `Bashkir OCR Studio` web interface is provided as an interactive single-page usage example. For automated multi-page or full-book processing, import `runtime.engine.OCR` (or run the ONNX sessions directly) in your own batch pipeline.
128
+
129
+ ### 1. Local Browser Studio (Windows One-Click Example)
130
+
131
+ Download the repository and double-click `start_ocr.bat`. By default (`[3] Auto`), the launcher automatically detects an NVIDIA GPU via `nvidia-smi`, creates an isolated `.venv-gpu` environment (or `.venv-cpu` on CPU-only machines), installs dependencies, and opens **Bashkir OCR Studio** at `http://127.0.0.1:8001`:
132
+
133
+ ```bash
134
+ pip install huggingface_hub
135
+ hf download failed09/bashkir-trocr-base --local-dir bashkir-ocr
136
+ ```
137
+
138
+ ### 2. Command-Line & Custom Pipeline API
139
+
140
+ ```bash
141
+ cd bashkir-ocr
142
+ python -m venv .venv-gpu
143
+ .venv-gpu\Scripts\python -m pip install -r requirements-gpu.txt
144
+
145
+ # Transcribe a page image from CLI (use --backend auto, gpu, or cpu)
146
+ .venv-gpu\Scripts\python run.py page.png --backend auto --output result.txt
147
+
148
+ # Launch the web studio manually
149
+ .venv-gpu\Scripts\python studio.py --backend auto
150
+ ```
151
+
152
+ ```python
153
+ from PIL import Image
154
+ from runtime.engine import OCR
155
+
156
+ ocr = OCR(".", backend="auto") # selects CUDA FP16 if available, else CPU INT8
157
+ result = ocr.recognize(Image.open("page.png"))
158
+ print(result["full_text"])
159
+ ```
160
+
161
+ ## License
162
+
163
+ The model weights, ONNX graphs and runtime code are distributed under [Apache-2.0](https://huggingface.co/failed09/bashkir-trocr-base/blob/main/LICENSE). Upstream attributions for TrOCR (`MIT`) and RapidOCR (`Apache-2.0`) are recorded in [NOTICE](https://huggingface.co/failed09/bashkir-trocr-base/blob/main/NOTICE). Rights in original source publications remain with their respective owners; raw book scans and training texts are not redistributed.
164
+
165
+ ## Citation
166
+
167
+ ```bibtex
168
+ @software{failed09_bashkir_trocr_2026,
169
+ title = {BashkirTrOCR: Printed Bashkir OCR with ONNX Runtime (FP16 CUDA & INT8 CPU)},
170
+ author = {failed09},
171
+ year = {2026},
172
+ publisher = {Hugging Face},
173
+ url = {https://huggingface.co/failed09/bashkir-trocr-base},
174
+ note = {Optical Character Recognition model and local review studio for Bashkir}
175
+ }
176
+ ```
177
+
178
+ ## Open Bashkir Data and Sources 🐝
179
+
180
+ This release is part of an open-source effort to support the development,
181
+ preservation and practical use of the Bashkir language. Other related models,
182
+ datasets and tools are available on the author's Hugging Face profile.
183
+
184
+ The author does not claim ownership or authorship of the source texts or other
185
+ materials used to derive this release; rights and licensing remain with the
186
+ original authors, publishers and dataset providers. Source texts are not
187
+ redistributed in this repository, so users should follow the licenses and
188
+ attribution requirements of the relevant upstream resources.
SHA256SUMS ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 3ddf9be5c28fe27dad143a5dc76eea25222ad1dd68934a047064e56ed2fa40c5 LICENSE
2
+ 6eb2c965307dc568082b8e63bef68828e571f37d9aadd19a1b8d9f0fbff6e6b0 META.json
3
+ a7269948dafa3fd519facba67890b90704d566609ba86c3555d02ce62f4c9c24 NOTICE
4
+ b0d20c0131758208f59219b5aac7910c08147cac9a42789ab7952b9c10f4c520 QUICKSTART_RU.md
5
+ 8c0b2fba7bbfd16caa92bccbc50e85050a379e13a3cf8972fc4fdb30290aa290 README.md
6
+ 6af91e2db60bc88f047eebb53414e1de8bd1015d2566e801f34ef9f5ea71e6c2 config.json
7
+ 58fb88778d56947e0929d8fb5d85e60e8fb18485061c465b9d3b3f3ed26e4219 examples/line_comparison.png
8
+ 3e0af25fdd06aa9586ae97adb00ea927ebe5a3805ac77d2d3a81ce5f55693333 licenses/RapidOCR-Apache-2.0.txt
9
+ 904dc4d8749877f1dba1cda48200d2462dccbeb7c134d5e4ef6fa75e0198c8fe licenses/TrOCR-MIT.txt
10
+ 3439588c030faea393a54515f51e983d8e155b19a2e8aba7891934c1cf0de526 models/dbnet.onnx
11
+ e55c720fd10ae8f290c28d06a150dc118a118d020f7405fef85a1b273d9ed7a3 models/fp16/decoder.onnx
12
+ 90a4ee3d331ea59cc2b60a860064ded38a3953163a33d4d16ce34f9b640103a5 models/fp16/encoder.onnx
13
+ e04bcbaaee07851e7b0a47c7b271fcd85a4d8b68623a3241e2c968c22808aecb models/int8/decoder.onnx
14
+ c9773380fd2e85ec4ccc07f570de7c2e63c88784570d3378a6af1ae744c7ca04 models/int8/encoder.onnx
15
+ 1918ad875a1cd08aa9ca0ecb3e4ae13adabe89f850a038f35f485d916bbd6e10 preprocessor_config.json
16
+ 77feb31f7f3f15a17fd569064c23abe9fa718f4a399dde268559ab644fae6512 requirements-common.txt
17
+ d1fc9108e0de2d3186caf337a97971c14032ef66001c2ec0024642b51eaa40c6 requirements-cpu.txt
18
+ a91cea3236c184653692f95944fa2c4967a7b431f7fa84029ef6efb2f26eac75 requirements-gpu.txt
19
+ 8492c5f8e6048e769bd2a4ed1e51b57d4cfad036e30c6e7f4ec7bd3799b23ec5 run.py
20
+ 089c2e7f646157f37640ac8dced5f20aa417728aa24d0ffc1c5cc4e37f9de21d runtime/__init__.py
21
+ 7447d83ba6825d95db99fb4b561a223d94dcd4f32de7c4670d4ee82df2b6af81 runtime/dbnet_utils.py
22
+ 380642fbed945044c7ae3f18a7565b569c32146ec5f13d5ca090f7e68354b617 runtime/engine.py
23
+ 2eae00b66dff26e0a28c7d7c7b4ad8bbe7619926cc33a4f5c4111af2c8d28dfc runtime/index.html
24
+ af060a2df181d242311694ee5053fe52e4b1cf2a9758a666abb0488f37403cfa runtime/line_merger.py
25
+ 293d3851b50aa16bf4f966d48a785d66187153ca86b8011a5b2ef1a2811e2b58 start_ocr.bat
26
+ 32ea287ac8d514ccc0dafec12c293e604d328ed69b32453754ff6340b5be1762 studio.py
27
+ 04ac1e6dec3f09a8cd17adbcc1d4a7809dbb3bdd2f800cbc8e9f33ec4155b28a tokenizer.json
28
+ 416c94e5774d3bdf841aeea13f526ec76fe137b18db3acd56f65e7bc4d399ea3 tokenizer_config.json
config.json ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architecture": "ViT-Base + TrOCR decoder",
3
+ "image_size": 384,
4
+ "decoder_start_token_id": 0,
5
+ "eos_token_id": 2,
6
+ "pad_token_id": 1,
7
+ "max_new_tokens": 256,
8
+ "variants": {
9
+ "int8": {
10
+ "encoder": "models/int8/encoder.onnx",
11
+ "decoder": "models/int8/decoder.onnx"
12
+ },
13
+ "fp16": {
14
+ "encoder": "models/fp16/encoder.onnx",
15
+ "decoder": "models/fp16/decoder.onnx"
16
+ }
17
+ }
18
+ }
examples/line_comparison.png ADDED
licenses/RapidOCR-Apache-2.0.txt ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright (c) 2021 RapidOCR Authors. All rights reserved.
2
+
3
+ Apache License
4
+ Version 2.0, January 2004
5
+ http://www.apache.org/licenses/
6
+
7
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
8
+
9
+ 1. Definitions.
10
+
11
+ "License" shall mean the terms and conditions for use, reproduction,
12
+ and distribution as defined by Sections 1 through 9 of this document.
13
+
14
+ "Licensor" shall mean the copyright owner or entity authorized by
15
+ the copyright owner that is granting the License.
16
+
17
+ "Legal Entity" shall mean the union of the acting entity and all
18
+ other entities that control, are controlled by, or are under common
19
+ control with that entity. For the purposes of this definition,
20
+ "control" means (i) the power, direct or indirect, to cause the
21
+ direction or management of such entity, whether by contract or
22
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
23
+ outstanding shares, or (iii) beneficial ownership of such entity.
24
+
25
+ "You" (or "Your") shall mean an individual or Legal Entity
26
+ exercising permissions granted by this License.
27
+
28
+ "Source" form shall mean the preferred form for making modifications,
29
+ including but not limited to software source code, documentation
30
+ source, and configuration files.
31
+
32
+ "Object" form shall mean any form resulting from mechanical
33
+ transformation or translation of a Source form, including but
34
+ not limited to compiled object code, generated documentation,
35
+ and conversions to other media types.
36
+
37
+ "Work" shall mean the work of authorship, whether in Source or
38
+ Object form, made available under the License, as indicated by a
39
+ copyright notice that is included in or attached to the work
40
+ (an example is provided in the Appendix below).
41
+
42
+ "Derivative Works" shall mean any work, whether in Source or Object
43
+ form, that is based on (or derived from) the Work and for which the
44
+ editorial revisions, annotations, elaborations, or other modifications
45
+ represent, as a whole, an original work of authorship. For the purposes
46
+ of this License, Derivative Works shall not include works that remain
47
+ separable from, or merely link (or bind by name) to the interfaces of,
48
+ the Work and Derivative Works thereof.
49
+
50
+ "Contribution" shall mean any work of authorship, including
51
+ the original version of the Work and any modifications or additions
52
+ to that Work or Derivative Works thereof, that is intentionally
53
+ submitted to Licensor for inclusion in the Work by the copyright owner
54
+ or by an individual or Legal Entity authorized to submit on behalf of
55
+ the copyright owner. For the purposes of this definition, "submitted"
56
+ means any form of electronic, verbal, or written communication sent
57
+ to the Licensor or its representatives, including but not limited to
58
+ communication on electronic mailing lists, source code control systems,
59
+ and issue tracking systems that are managed by, or on behalf of, the
60
+ Licensor for the purpose of discussing and improving the Work, but
61
+ excluding communication that is conspicuously marked or otherwise
62
+ designated in writing by the copyright owner as "Not a Contribution."
63
+
64
+ "Contributor" shall mean Licensor and any individual or Legal Entity
65
+ on behalf of whom a Contribution has been received by Licensor and
66
+ subsequently incorporated within the Work.
67
+
68
+ 2. Grant of Copyright License. Subject to the terms and conditions of
69
+ this License, each Contributor hereby grants to You a perpetual,
70
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
71
+ copyright license to reproduce, prepare Derivative Works of,
72
+ publicly display, publicly perform, sublicense, and distribute the
73
+ Work and such Derivative Works in Source or Object form.
74
+
75
+ 3. Grant of Patent License. Subject to the terms and conditions of
76
+ this License, each Contributor hereby grants to You a perpetual,
77
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
78
+ (except as stated in this section) patent license to make, have made,
79
+ use, offer to sell, sell, import, and otherwise transfer the Work,
80
+ where such license applies only to those patent claims licensable
81
+ by such Contributor that are necessarily infringed by their
82
+ Contribution(s) alone or by combination of their Contribution(s)
83
+ with the Work to which such Contribution(s) was submitted. If You
84
+ institute patent litigation against any entity (including a
85
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
86
+ or a Contribution incorporated within the Work constitutes direct
87
+ or contributory patent infringement, then any patent licenses
88
+ granted to You under this License for that Work shall terminate
89
+ as of the date such litigation is filed.
90
+
91
+ 4. Redistribution. You may reproduce and distribute copies of the
92
+ Work or Derivative Works thereof in any medium, with or without
93
+ modifications, and in Source or Object form, provided that You
94
+ meet the following conditions:
95
+
96
+ (a) You must give any other recipients of the Work or
97
+ Derivative Works a copy of this License; and
98
+
99
+ (b) You must cause any modified files to carry prominent notices
100
+ stating that You changed the files; and
101
+
102
+ (c) You must retain, in the Source form of any Derivative Works
103
+ that You distribute, all copyright, patent, trademark, and
104
+ attribution notices from the Source form of the Work,
105
+ excluding those notices that do not pertain to any part of
106
+ the Derivative Works; and
107
+
108
+ (d) If the Work includes a "NOTICE" text file as part of its
109
+ distribution, then any Derivative Works that You distribute must
110
+ include a readable copy of the attribution notices contained
111
+ within such NOTICE file, excluding those notices that do not
112
+ pertain to any part of the Derivative Works, in at least one
113
+ of the following places: within a NOTICE text file distributed
114
+ as part of the Derivative Works; within the Source form or
115
+ documentation, if provided along with the Derivative Works; or,
116
+ within a display generated by the Derivative Works, if and
117
+ wherever such third-party notices normally appear. The contents
118
+ of the NOTICE file are for informational purposes only and
119
+ do not modify the License. You may add Your own attribution
120
+ notices within Derivative Works that You distribute, alongside
121
+ or as an addendum to the NOTICE text from the Work, provided
122
+ that such additional attribution notices cannot be construed
123
+ as modifying the License.
124
+
125
+ You may add Your own copyright statement to Your modifications and
126
+ may provide additional or different license terms and conditions
127
+ for use, reproduction, or distribution of Your modifications, or
128
+ for any such Derivative Works as a whole, provided Your use,
129
+ reproduction, and distribution of the Work otherwise complies with
130
+ the conditions stated in this License.
131
+
132
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
133
+ any Contribution intentionally submitted for inclusion in the Work
134
+ by You to the Licensor shall be under the terms and conditions of
135
+ this License, without any additional terms or conditions.
136
+ Notwithstanding the above, nothing herein shall supersede or modify
137
+ the terms of any separate license agreement you may have executed
138
+ with Licensor regarding such Contributions.
139
+
140
+ 6. Trademarks. This License does not grant permission to use the trade
141
+ names, trademarks, service marks, or product names of the Licensor,
142
+ except as required for reasonable and customary use in describing the
143
+ origin of the Work and reproducing the content of the NOTICE file.
144
+
145
+ 7. Disclaimer of Warranty. Unless required by applicable law or
146
+ agreed to in writing, Licensor provides the Work (and each
147
+ Contributor provides its Contributions) on an "AS IS" BASIS,
148
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
149
+ implied, including, without limitation, any warranties or conditions
150
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
151
+ PARTICULAR PURPOSE. You are solely responsible for determining the
152
+ appropriateness of using or redistributing the Work and assume any
153
+ risks associated with Your exercise of permissions under this License.
154
+
155
+ 8. Limitation of Liability. In no event and under no legal theory,
156
+ whether in tort (including negligence), contract, or otherwise,
157
+ unless required by applicable law (such as deliberate and grossly
158
+ negligent acts) or agreed to in writing, shall any Contributor be
159
+ liable to You for damages, including any direct, indirect, special,
160
+ incidental, or consequential damages of any character arising as a
161
+ result of this License or out of the use or inability to use the
162
+ Work (including but not limited to damages for loss of goodwill,
163
+ work stoppage, computer failure or malfunction, or any and all
164
+ other commercial damages or losses), even if such Contributor
165
+ has been advised of the possibility of such damages.
166
+
167
+ 9. Accepting Warranty or Additional Liability. While redistributing
168
+ the Work or Derivative Works thereof, You may choose to offer,
169
+ and charge a fee for, acceptance of support, warranty, indemnity,
170
+ or other liability obligations and/or rights consistent with this
171
+ License. However, in accepting such obligations, You may act only
172
+ on Your own behalf and on Your sole responsibility, not on behalf
173
+ of any other Contributor, and only if You agree to indemnify,
174
+ defend, and hold each Contributor harmless for any liability
175
+ incurred by, or claims asserted against, such Contributor by reason
176
+ of your accepting any such warranty or additional liability.
177
+
178
+ END OF TERMS AND CONDITIONS
179
+
180
+ APPENDIX: How to apply the Apache License to your work.
181
+
182
+ To apply the Apache License to your work, attach the following
183
+ boilerplate notice, with the fields enclosed by brackets "[]"
184
+ replaced with your own identifying information. (Don't include
185
+ the brackets!) The text should be enclosed in the appropriate
186
+ comment syntax for the file format. We also recommend that a
187
+ file or class name and description of purpose be included on the
188
+ same "printed page" as the copyright notice for easier
189
+ identification within third-party archives.
190
+
191
+ Copyright (c) 2021 RapidAI. All rights reserved.
192
+
193
+ Licensed under the Apache License, Version 2.0 (the "License");
194
+ you may not use this file except in compliance with the License.
195
+ You may obtain a copy of the License at
196
+
197
+ http://www.apache.org/licenses/LICENSE-2.0
198
+
199
+ Unless required by applicable law or agreed to in writing, software
200
+ distributed under the License is distributed on an "AS IS" BASIS,
201
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
202
+ See the License for the specific language governing permissions and
203
+ limitations under the License.
licenses/TrOCR-MIT.txt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) Microsoft Corporation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
models/dbnet.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3439588c030faea393a54515f51e983d8e155b19a2e8aba7891934c1cf0de526
3
+ size 2432880
models/fp16/decoder.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e55c720fd10ae8f290c28d06a150dc118a118d020f7405fef85a1b273d9ed7a3
3
+ size 598137053
models/fp16/encoder.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:90a4ee3d331ea59cc2b60a860064ded38a3953163a33d4d16ce34f9b640103a5
3
+ size 172327813
models/int8/decoder.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e04bcbaaee07851e7b0a47c7b271fcd85a4d8b68623a3241e2c968c22808aecb
3
+ size 300449064
models/int8/encoder.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c9773380fd2e85ec4ccc07f570de7c2e63c88784570d3378a6af1ae744c7ca04
3
+ size 89817188
preprocessor_config.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "do_normalize": true,
3
+ "do_rescale": true,
4
+ "do_resize": true,
5
+ "image_mean": [
6
+ 0.5,
7
+ 0.5,
8
+ 0.5
9
+ ],
10
+ "image_processor_type": "ViTImageProcessor",
11
+ "image_std": [
12
+ 0.5,
13
+ 0.5,
14
+ 0.5
15
+ ],
16
+ "processor_class": "TrOCRProcessor",
17
+ "resample": 2,
18
+ "rescale_factor": 0.00392156862745098,
19
+ "size": {
20
+ "height": 384,
21
+ "width": 384
22
+ }
23
+ }
requirements-common.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ numpy>=1.26,<3
2
+ Pillow>=11,<13
3
+ tokenizers>=0.22,<0.23
4
+ opencv-python-headless>=4.10,<5
5
+ pyclipper>=1.3,<2
6
+ shapely>=2,<3
7
+ fastapi>=0.115,<1
8
+ uvicorn>=0.30,<1
9
+ python-multipart>=0.0.20,<0.1
requirements-cpu.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ -r requirements-common.txt
2
+ onnxruntime==1.22.0
requirements-gpu.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ -r requirements-common.txt
2
+ onnxruntime-gpu[cuda,cudnn]==1.22.0
3
+ # ORT 1.22 convolution frontend was validated with cuDNN 9.1; 9.27 failed.
4
+ nvidia-cudnn-cu12==9.1.0.70
run.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Recognize one image locally and write UTF-8 text and JSON."""
2
+ import argparse
3
+ import json
4
+ from pathlib import Path
5
+ from PIL import Image
6
+ from runtime.engine import OCR
7
+
8
+
9
+ def main():
10
+ p=argparse.ArgumentParser(description=__doc__)
11
+ p.add_argument('image',type=Path)
12
+ p.add_argument('--backend',choices=['cpu','gpu','auto'],default='cpu')
13
+ p.add_argument('--output',type=Path,default=Path('result.txt'))
14
+ args=p.parse_args()
15
+ with Image.open(args.image) as image:r=OCR(backend=args.backend).recognize(image,include_images=False)
16
+ args.output.write_text(r['full_text']+'\n',encoding='utf-8')
17
+ args.output.with_suffix('.json').write_text(json.dumps(r,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
18
+ print(f'Saved {args.output}; backend={r["backend"]}; lines={len(r["lines"])}')
19
+
20
+
21
+ if __name__=='__main__':main()
runtime/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Standalone Bashkir OCR; no PyTorch or Transformers dependency."""
runtime/dbnet_utils.py ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ # Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ """
16
+ # -*- encoding: utf-8 -*-
17
+ # @Author: SWHL
18
+ # Modified: selected detector helpers only; no recognition/classifier runtime.
19
+ import cv2
20
+ import numpy as np
21
+ import pyclipper
22
+ from shapely.geometry import Polygon
23
+
24
+ class DetResizeForTest():
25
+ def __init__(self, **kwargs):
26
+ super(DetResizeForTest, self).__init__()
27
+ self.resize_type = 0
28
+ if 'image_shape' in kwargs:
29
+ self.image_shape = kwargs['image_shape']
30
+ self.resize_type = 1
31
+ elif 'limit_side_len' in kwargs:
32
+ self.limit_side_len = kwargs.get('limit_side_len', 736)
33
+ self.limit_type = kwargs.get('limit_type', 'min')
34
+
35
+ if 'resize_long' in kwargs:
36
+ self.resize_type = 2
37
+ self.resize_long = kwargs.get('resize_long', 960)
38
+ else:
39
+ self.limit_side_len = kwargs.get('limit_side_len', 736)
40
+ self.limit_type = kwargs.get('limit_type', 'min')
41
+
42
+ def __call__(self, data):
43
+ img = data['image']
44
+ src_h, src_w = img.shape[:2]
45
+
46
+ if self.resize_type == 0:
47
+ # img, shape = self.resize_image_type0(img)
48
+ img, [ratio_h, ratio_w] = self.resize_image_type0(img)
49
+ elif self.resize_type == 2:
50
+ img, [ratio_h, ratio_w] = self.resize_image_type2(img)
51
+ else:
52
+ # img, shape = self.resize_image_type1(img)
53
+ img, [ratio_h, ratio_w] = self.resize_image_type1(img)
54
+ data['image'] = img
55
+ data['shape'] = np.array([src_h, src_w, ratio_h, ratio_w])
56
+ return data
57
+
58
+ def resize_image_type1(self, img):
59
+ resize_h, resize_w = self.image_shape
60
+ ori_h, ori_w = img.shape[:2] # (h, w, c)
61
+ ratio_h = float(resize_h) / ori_h
62
+ ratio_w = float(resize_w) / ori_w
63
+ img = cv2.resize(img, (int(resize_w), int(resize_h)))
64
+ # return img, np.array([ori_h, ori_w])
65
+ return img, [ratio_h, ratio_w]
66
+
67
+ def resize_image_type0(self, img):
68
+ """
69
+ resize image to a size multiple of 32 which is required by the network
70
+ args:
71
+ img(array): array with shape [h, w, c]
72
+ return(tuple):
73
+ img, (ratio_h, ratio_w)
74
+ """
75
+ limit_side_len = self.limit_side_len
76
+ h, w = img.shape[:2]
77
+
78
+ # limit the max side
79
+ if self.limit_type == 'max':
80
+ if max(h, w) > limit_side_len:
81
+ if h > w:
82
+ ratio = float(limit_side_len) / h
83
+ else:
84
+ ratio = float(limit_side_len) / w
85
+ else:
86
+ ratio = 1.
87
+ else:
88
+ if min(h, w) < limit_side_len:
89
+ if h < w:
90
+ ratio = float(limit_side_len) / h
91
+ else:
92
+ ratio = float(limit_side_len) / w
93
+ else:
94
+ ratio = 1.
95
+ resize_h = int(h * ratio)
96
+ resize_w = int(w * ratio)
97
+
98
+ resize_h = int(round(resize_h / 32) * 32)
99
+ resize_w = int(round(resize_w / 32) * 32)
100
+
101
+ try:
102
+ if int(resize_w) <= 0 or int(resize_h) <= 0:
103
+ return None, (None, None)
104
+ img = cv2.resize(img, (int(resize_w), int(resize_h)))
105
+ except:
106
+ print(img.shape, resize_w, resize_h)
107
+ sys.exit(0)
108
+ ratio_h = resize_h / float(h)
109
+ ratio_w = resize_w / float(w)
110
+ return img, [ratio_h, ratio_w]
111
+
112
+ def resize_image_type2(self, img):
113
+ h, w = img.shape[:2]
114
+
115
+ resize_w = w
116
+ resize_h = h
117
+
118
+ # Fix the longer side
119
+ if resize_h > resize_w:
120
+ ratio = float(self.resize_long) / resize_h
121
+ else:
122
+ ratio = float(self.resize_long) / resize_w
123
+
124
+ resize_h = int(resize_h * ratio)
125
+ resize_w = int(resize_w * ratio)
126
+
127
+ max_stride = 128
128
+ resize_h = (resize_h + max_stride - 1) // max_stride * max_stride
129
+ resize_w = (resize_w + max_stride - 1) // max_stride * max_stride
130
+ img = cv2.resize(img, (int(resize_w), int(resize_h)))
131
+ ratio_h = resize_h / float(h)
132
+ ratio_w = resize_w / float(w)
133
+
134
+ return img, [ratio_h, ratio_w]
135
+
136
+
137
+ class DBPostProcess():
138
+ """The post process for Differentiable Binarization (DB)."""
139
+
140
+ def __init__(self,
141
+ thresh=0.3,
142
+ box_thresh=0.7,
143
+ max_candidates=1000,
144
+ unclip_ratio=2.0,
145
+ score_mode="fast",
146
+ use_dilation=False):
147
+ self.thresh = thresh
148
+ self.box_thresh = box_thresh
149
+ self.max_candidates = max_candidates
150
+ self.unclip_ratio = unclip_ratio
151
+ self.min_size = 3
152
+ self.score_mode = score_mode
153
+
154
+ if use_dilation:
155
+ self.dilation_kernel = np.array([[1, 1], [1, 1]])
156
+ else:
157
+ self.dilation_kernel = None
158
+
159
+ def boxes_from_bitmap(self, pred, _bitmap, dest_width, dest_height):
160
+ '''
161
+ _bitmap: single map with shape (1, H, W),
162
+ whose values are binarized as {0, 1}
163
+ '''
164
+
165
+ bitmap = _bitmap
166
+ height, width = bitmap.shape
167
+
168
+ outs = cv2.findContours((bitmap * 255).astype(np.uint8), cv2.RETR_LIST,
169
+ cv2.CHAIN_APPROX_SIMPLE)
170
+ if len(outs) == 3:
171
+ img, contours, _ = outs[0], outs[1], outs[2]
172
+ elif len(outs) == 2:
173
+ contours, _ = outs[0], outs[1]
174
+
175
+ num_contours = min(len(contours), self.max_candidates)
176
+
177
+ boxes = []
178
+ scores = []
179
+ for index in range(num_contours):
180
+ contour = contours[index]
181
+ points, sside = self.get_mini_boxes(contour)
182
+ if sside < self.min_size:
183
+ continue
184
+ points = np.array(points)
185
+ if self.score_mode == "fast":
186
+ score = self.box_score_fast(pred, points.reshape(-1, 2))
187
+ else:
188
+ score = self.box_score_slow(pred, contour)
189
+ if self.box_thresh > score:
190
+ continue
191
+
192
+ box = self.unclip(points).reshape(-1, 1, 2)
193
+ box, sside = self.get_mini_boxes(box)
194
+ if sside < self.min_size + 2:
195
+ continue
196
+ box = np.array(box)
197
+
198
+ box[:, 0] = np.clip(
199
+ np.round(box[:, 0] / width * dest_width), 0, dest_width)
200
+ box[:, 1] = np.clip(
201
+ np.round(box[:, 1] / height * dest_height), 0, dest_height)
202
+ boxes.append(box.astype(np.int16))
203
+ scores.append(score)
204
+ return np.array(boxes, dtype=np.int16), scores
205
+
206
+ def unclip(self, box):
207
+ unclip_ratio = self.unclip_ratio
208
+ poly = Polygon(box)
209
+ distance = poly.area * unclip_ratio / poly.length
210
+ offset = pyclipper.PyclipperOffset()
211
+ offset.AddPath(box, pyclipper.JT_ROUND, pyclipper.ET_CLOSEDPOLYGON)
212
+ expanded = np.array(offset.Execute(distance))
213
+ return expanded
214
+
215
+ def get_mini_boxes(self, contour):
216
+ bounding_box = cv2.minAreaRect(contour)
217
+ points = sorted(list(cv2.boxPoints(bounding_box)), key=lambda x: x[0])
218
+
219
+ index_1, index_2, index_3, index_4 = 0, 1, 2, 3
220
+ if points[1][1] > points[0][1]:
221
+ index_1 = 0
222
+ index_4 = 1
223
+ else:
224
+ index_1 = 1
225
+ index_4 = 0
226
+ if points[3][1] > points[2][1]:
227
+ index_2 = 2
228
+ index_3 = 3
229
+ else:
230
+ index_2 = 3
231
+ index_3 = 2
232
+
233
+ box = [
234
+ points[index_1], points[index_2], points[index_3], points[index_4]
235
+ ]
236
+ return box, min(bounding_box[1])
237
+
238
+ def box_score_fast(self, bitmap, _box):
239
+ h, w = bitmap.shape[:2]
240
+ box = _box.copy()
241
+ xmin = np.clip(np.floor(box[:, 0].min()).astype(np.int32), 0, w - 1)
242
+ xmax = np.clip(np.ceil(box[:, 0].max()).astype(np.int32), 0, w - 1)
243
+ ymin = np.clip(np.floor(box[:, 1].min()).astype(np.int32), 0, h - 1)
244
+ ymax = np.clip(np.ceil(box[:, 1].max()).astype(np.int32), 0, h - 1)
245
+
246
+ mask = np.zeros((ymax - ymin + 1, xmax - xmin + 1), dtype=np.uint8)
247
+ box[:, 0] = box[:, 0] - xmin
248
+ box[:, 1] = box[:, 1] - ymin
249
+ cv2.fillPoly(mask, box.reshape(1, -1, 2).astype(np.int32), 1)
250
+ return cv2.mean(bitmap[ymin:ymax + 1, xmin:xmax + 1], mask)[0]
251
+
252
+ def box_score_slow(self, bitmap, contour):
253
+ '''
254
+ box_score_slow: use polyon mean score as the mean score
255
+ '''
256
+ h, w = bitmap.shape[:2]
257
+ contour = contour.copy()
258
+ contour = np.reshape(contour, (-1, 2))
259
+
260
+ xmin = np.clip(np.min(contour[:, 0]), 0, w - 1)
261
+ xmax = np.clip(np.max(contour[:, 0]), 0, w - 1)
262
+ ymin = np.clip(np.min(contour[:, 1]), 0, h - 1)
263
+ ymax = np.clip(np.max(contour[:, 1]), 0, h - 1)
264
+
265
+ mask = np.zeros((ymax - ymin + 1, xmax - xmin + 1), dtype=np.uint8)
266
+
267
+ contour[:, 0] = contour[:, 0] - xmin
268
+ contour[:, 1] = contour[:, 1] - ymin
269
+
270
+ cv2.fillPoly(mask, contour.reshape(1, -1, 2).astype(np.int32), 1)
271
+ return cv2.mean(bitmap[ymin:ymax + 1, xmin:xmax + 1], mask)[0]
272
+
273
+ def __call__(self, pred, shape_list):
274
+ pred = pred[:, 0, :, :]
275
+ segmentation = pred > self.thresh
276
+
277
+ boxes_batch = []
278
+ for batch_index in range(pred.shape[0]):
279
+ src_h, src_w, ratio_h, ratio_w = shape_list[batch_index]
280
+ if self.dilation_kernel is not None:
281
+ mask = cv2.dilate(
282
+ np.array(segmentation[batch_index]).astype(np.uint8),
283
+ self.dilation_kernel)
284
+ else:
285
+ mask = segmentation[batch_index]
286
+ boxes, scores = self.boxes_from_bitmap(pred[batch_index], mask,
287
+ src_w, src_h)
288
+
289
+ boxes_batch.append({'points': boxes})
290
+ return boxes_batch
runtime/engine.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Local ONNX OCR with shared detection and line assembly for CPU/GPU."""
2
+ import base64
3
+ import io
4
+ import json
5
+ import time
6
+ from pathlib import Path
7
+
8
+ import numpy as np
9
+ import cv2
10
+ from PIL import Image, ImageOps
11
+ from tokenizers import Tokenizer
12
+
13
+ from .dbnet_utils import DBPostProcess, DetResizeForTest
14
+ from .line_merger import merge_line_boxes
15
+
16
+
17
+ def pixels(image, config):
18
+ size=config['size']
19
+ image=image.convert('RGB').resize((size['width'],size['height']),Image.Resampling(config['resample']))
20
+ x=np.asarray(image,dtype=np.float32)*np.float32(config['rescale_factor'])
21
+ x=(x-np.asarray(config['image_mean'],dtype=np.float32))/np.asarray(config['image_std'],dtype=np.float32)
22
+ return np.ascontiguousarray(x.transpose(2,0,1)[None])
23
+
24
+
25
+ def split_line(image, ratio=20):
26
+ w,h=image.size
27
+ if w/max(h,1)<=ratio:return [image]
28
+ ink=(np.asarray(image.convert('L'))<210).sum(axis=0)
29
+ runs=[];start=None
30
+ for x,value in enumerate(ink):
31
+ if value==0 and start is None:start=x
32
+ elif value!=0 and start is not None:runs.append((start,x));start=None
33
+ if start is not None:runs.append((start,w))
34
+ gaps=[(a,b) for a,b in runs if a>40 and b<w-40 and b-a>=8]
35
+ if gaps:
36
+ a,b=min(gaps,key=lambda r:abs((r[0]+r[1])/2-w/2));cut=(a+b)//2
37
+ else:
38
+ left,right=int(w*.35),int(w*.65)
39
+ cut=left+int(np.argmin(ink[left:right])) if right>left else w//2
40
+ cut=max(1,min(w-1,cut))
41
+ return split_line(image.crop((0,0,cut,h)),ratio)+split_line(image.crop((cut,0,w,h)),ratio)
42
+
43
+
44
+ def preview(image):
45
+ image=image.copy();image.thumbnail((1100,180))
46
+ out=io.BytesIO();image.save(out,format='PNG')
47
+ return 'data:image/png;base64,'+base64.b64encode(out.getvalue()).decode('ascii')
48
+
49
+
50
+ class OCR:
51
+ def __init__(self, root=None, backend='cpu', threads=4):
52
+ import onnxruntime as ort
53
+ ort.disable_telemetry_events()
54
+ self.root=Path(root or Path(__file__).resolve().parents[1])
55
+ self.config=json.loads((self.root/'config.json').read_text(encoding='utf-8'))
56
+ self.preprocess=json.loads((self.root/'preprocessor_config.json').read_text(encoding='utf-8'))
57
+ self.tokenizer=Tokenizer.from_file(str(self.root/'tokenizer.json'))
58
+ self.tokenizer.no_padding();self.tokenizer.no_truncation()
59
+ requested_backend=backend
60
+ if backend not in ('cpu','gpu','auto'):raise ValueError('backend must be cpu, gpu or auto')
61
+ if backend in ('gpu','auto') and hasattr(ort,'preload_dlls'):
62
+ ort.preload_dlls()
63
+ if backend=='auto':backend='gpu' if 'CUDAExecutionProvider' in ort.get_available_providers() else 'cpu'
64
+ if backend=='gpu' and 'CUDAExecutionProvider' not in ort.get_available_providers():
65
+ raise RuntimeError('CUDA недоступна. Установите GPU-зависимости или выберите CPU.')
66
+ options=ort.SessionOptions();options.intra_op_num_threads=threads;options.inter_op_num_threads=1
67
+ if backend=='gpu':
68
+ variant=self.config['variants']['fp16']
69
+ providers=['CUDAExecutionProvider','CPUExecutionProvider']
70
+ try:
71
+ self.encoder=ort.InferenceSession(str(self.root/variant['encoder']),sess_options=options,providers=providers)
72
+ self.decoder=ort.InferenceSession(str(self.root/variant['decoder']),sess_options=options,providers=providers)
73
+ if any('CUDAExecutionProvider' not in s.get_providers() for s in (self.encoder,self.decoder)):
74
+ raise RuntimeError('Модель не загрузилась на CUDA. Выберите CPU; скрытая смена GPU на CPU запрещена.')
75
+ self.encoder.disable_fallback()
76
+ self.decoder.disable_fallback()
77
+ except Exception:
78
+ if requested_backend!='auto':raise
79
+ backend='cpu'
80
+ if backend=='cpu':
81
+ variant=self.config['variants']['int8']
82
+ providers=['CPUExecutionProvider']
83
+ self.encoder=ort.InferenceSession(str(self.root/variant['encoder']),sess_options=options,providers=providers)
84
+ self.decoder=ort.InferenceSession(str(self.root/variant['decoder']),sess_options=options,providers=providers)
85
+ self.backend=backend
86
+ self.detector=ort.InferenceSession(str(self.root/'models/dbnet.onnx'),sess_options=options,providers=['CPUExecutionProvider'])
87
+ self.resize=DetResizeForTest(limit_side_len=736,limit_type='min')
88
+ self.post=DBPostProcess(thresh=.2,box_thresh=.2,max_candidates=1000,unclip_ratio=1.3,use_dilation=True,score_mode='fast')
89
+
90
+ def recognize_line(self,image):
91
+ hidden=self.encoder.run(None,{'pixel_values':pixels(image,self.preprocess)})[0]
92
+ ids=np.array([[self.config['decoder_start_token_id']]],dtype=np.int64)
93
+ truncated=True
94
+ for _ in range(self.config['max_new_tokens']):
95
+ logits=self.decoder.run(None,{'input_ids':ids,'encoder_hidden_states':hidden})[0]
96
+ if not np.isfinite(logits).all():raise RuntimeError('Non-finite decoder output')
97
+ token=int(np.argmax(logits[0,-1]))
98
+ ids=np.concatenate((ids,np.array([[token]],dtype=np.int64)),axis=1)
99
+ if token==self.config['eos_token_id']:truncated=False;break
100
+ return self.tokenizer.decode(ids[0].tolist(),skip_special_tokens=True).strip(),truncated
101
+
102
+ def detect(self,image):
103
+ # The original detector upsizes the short side. Bound unusually narrow
104
+ # images so a one-line crop cannot allocate a gigantic detector tensor.
105
+ scale=max(1.,736/min(image.size))
106
+ if max(image.size)*scale>4096:
107
+ scale=4096/max(image.size)
108
+ width=max(32,round(image.width*scale/32)*32)
109
+ height=max(32,round(image.height*scale/32)*32)
110
+ data=dict(image=cv2.resize(np.asarray(image),(width,height)),
111
+ shape=np.array([image.height,image.width,height/image.height,width/image.width]))
112
+ else:data=self.resize({'image':np.asarray(image)})
113
+ x=np.asarray(data['image'],dtype=np.float32)*np.float32(1/255)
114
+ x=(x-np.array([.485,.456,.406],dtype=np.float32))/np.array([.229,.224,.225],dtype=np.float32)
115
+ x=np.ascontiguousarray(x.transpose(2,0,1)[None])
116
+ output=self.detector.run(None,{self.detector.get_inputs()[0].name:x})[0]
117
+ boxes=self.post(output,np.asarray([data['shape']]))[0]['points']
118
+ valid=[]
119
+ for box in boxes:
120
+ box=np.asarray(box,dtype=np.float32)
121
+ box[:,0]=np.clip(box[:,0],0,image.width-1);box[:,1]=np.clip(box[:,1],0,image.height-1)
122
+ if np.linalg.norm(box[0]-box[1])>3 and np.linalg.norm(box[0]-box[3])>3:valid.append(box)
123
+ return merge_line_boxes(valid,x_margin_ratio=3.5,y_overlap_thresh=.45)
124
+
125
+ def recognize(self,image,boxes=None,include_images=True):
126
+ t=time.perf_counter()
127
+ image=ImageOps.exif_transpose(image).convert('RGB')
128
+ if np.asarray(image.convert('L')).mean()<125:image=ImageOps.invert(image)
129
+ boxes=self.detect(image) if boxes is None else boxes
130
+ lines=[]
131
+ for box in boxes:
132
+ a,b,c,d=[int(v) for v in box]
133
+ if c-a<4 or d-b<4:continue
134
+ crop=image.crop((max(0,a-4),max(0,b-2),min(image.width,c+4),min(image.height,d+2)))
135
+ parts=[self.recognize_line(ch) for ch in split_line(crop)]
136
+ text=' '.join(s for s,_ in parts if s).strip()
137
+ line=dict(bbox=[a,b,c,d],text=text,truncated=any(end for _,end in parts),chunks=len(parts))
138
+ if include_images:line['image']=preview(crop)
139
+ lines.append(line)
140
+ return dict(backend=self.backend,lines=lines,full_text='\n'.join(row['text'] for row in lines),
141
+ seconds=time.perf_counter()-t,no_text_detected=not lines)
runtime/index.html ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="ru"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
3
+ <title>Bashkir OCR Studio</title>
4
+ <style>
5
+ :root{color-scheme:light;--ink:#183333;--muted:#637778;--border:#d8e3df;--accent:#196c56}*{box-sizing:border-box}body{margin:0;background:#f3f6f2;color:var(--ink);font:16px/1.55 system-ui,sans-serif}main{max-width:1300px;margin:auto;padding:32px 24px}header{display:flex;justify-content:space-between;align-items:start;gap:20px}h1{font-size:30px;letter-spacing:-1px;margin:0}p{max-width:850px}.muted{color:var(--muted)}.card{background:white;border:1px solid var(--border);border-radius:16px;padding:24px;margin:22px 0}.controls{display:flex;gap:12px;align-items:center;flex-wrap:wrap}button,.file{border:1px solid var(--border);background:white;border-radius:9px;padding:11px 17px;font:inherit;color:var(--ink);cursor:pointer}button.primary{background:var(--accent);color:white;border-color:var(--accent)}button:disabled{opacity:.45;cursor:wait}input[type=file]{max-width:400px}#original{max-height:420px;max-width:100%;object-fit:contain;margin:18px 0}.tabs{display:inline-flex;background:#e8efec;padding:4px;border-radius:11px;border:1px solid var(--border);gap:4px}.tab{border:none;background:transparent;border-radius:8px;padding:8px 15px;font-weight:600;color:var(--muted);cursor:pointer}.tab.active{background:var(--accent);color:white;box-shadow:0 1px 3px rgba(0,0,0,.12)}.row{display:grid;grid-template-columns:42px minmax(0,1fr) minmax(0,1fr);gap:16px;padding:18px 0;border-bottom:1px solid var(--border);align-items:start}.row img{max-width:100%;background:#fff}.num{color:var(--muted);font-size:13px}textarea{width:100%;min-height:62px;resize:vertical;border:1px solid var(--border);border-radius:8px;padding:10px;font:17px/1.5 system-ui;color:var(--ink);background:#fcfdfb}.page-split{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:20px;align-items:start;margin-top:12px}.page-pane{position:sticky;top:16px}.page-pane img{max-width:100%;max-height:78vh;object-fit:contain;border:1px solid var(--border);border-radius:10px;background:#fff;display:block}#fullText{min-height:520px;height:72vh;font:17px/1.6 system-ui;padding:14px}.warning{color:#9b5b12;font-size:13px}.rowhead{font-weight:600;color:var(--muted);font-size:13px}pre{white-space:pre-wrap;font:inherit}.badge{white-space:nowrap;border:1px solid var(--border);padding:7px 12px;border-radius:30px}#message[role=alert]{color:#9b3612}@media(max-width:700px){main{padding:22px 14px}.card{padding:16px}.row{grid-template-columns:25px minmax(0,1fr)}.row textarea,.row .result{grid-column:2}.rowhead{display:none}.page-split{grid-template-columns:1fr}.page-pane{position:static}header{display:block}.badge{display:inline-block;margin-top:12px}}
6
+ </style>
7
+ <main><header><div><h1>Bashkir OCR Studio</h1><div class="muted">Распознайте. Сверьте со сканом. Сохраните текст.</div></div><span class="badge" id="backend">Загрузка…</span></header>
8
+ <section class="card"><div class="controls"><input id="file" type="file" accept="image/png,image/jpeg,image/webp,image/tiff,image/bmp"><button class="primary" id="recognize" disabled>Распознать</button><div style="flex:1"></div><div class="tabs" role="tablist" aria-label="Режим просмотра"><button type="button" class="tab active" id="tabLines" role="tab" aria-selected="true">По строкам</button><button type="button" class="tab" id="tabPage" role="tab" aria-selected="false">Страница и текст</button></div></div><p class="muted">Изображение обрабатывается на этом компьютере. Оно не отправляется в облако и не сохраняется сервером. Поддерживается одно изображение за раз.</p><details><summary>На что обратить внимание</summary><p class="muted">Проверьте мелкие сноски: <b>1</b> может превратиться в <b>!</b>. Возможны ошибки регистра, похожих букв и тире. Длинные строки разделяются на части; сложные колонки, наклон и декоративные элементы могут нарушать порядок и полноту текста. Результат требует вычитки.</p></details><img id="original" alt="Выбранное изображение" hidden><p id="message" aria-live="polite">Выберите изображение со страницы книги или документа.</p></section>
9
+ <section class="card" id="results" hidden><div class="controls"><div class="tabs" role="tablist" aria-label="Режим просмотра результатов"><button type="button" class="tab active" id="tabLines2" role="tab" aria-selected="true">По строкам</button><button type="button" class="tab" id="tabPage2" role="tab" aria-selected="false">Страница и текст</button></div><div style="flex:1"></div><button id="copyText">Скопировать текст</button><button id="saveText">Сохранить TXT</button></div><p class="muted" id="summary"></p><div id="viewLines"><div class="row rowhead"><span>№</span><span>Оригинальная строка</span><span>Распознанный текст — можно исправить</span></div><div id="rows"></div></div><div id="viewPage" class="page-split" hidden><div class="page-pane"><div class="rowhead" style="margin-bottom:8px">Страница</div><img id="pageImage" alt="Исходная страница"></div><div class="text-pane"><div class="rowhead" style="margin-bottom:8px">Весь распознанный текст — можно исправить</div><textarea id="fullText" aria-label="Весь распознанный текст"></textarea></div></div></section><footer class="muted">TrOCR v3 · Без словарной автозамены · Закройте окно запуска, чтобы остановить OCR.</footer></main>
10
+ <script>
11
+ const $=id=>document.getElementById(id);let chosen=null,objectUrl=null,answer=null,mode='lines';
12
+ function setMode(next){mode=next;const isPage=mode==='page';['tabLines','tabLines2'].forEach(id=>{$(id).classList.toggle('active',!isPage);$(id).setAttribute('aria-selected',String(!isPage))});['tabPage','tabPage2'].forEach(id=>{$(id).classList.toggle('active',isPage);$(id).setAttribute('aria-selected',String(isPage))});$('viewLines').hidden=isPage;$('viewPage').hidden=!isPage}
13
+ $('tabLines').onclick=$('tabLines2').onclick=()=>setMode('lines');
14
+ $('tabPage').onclick=$('tabPage2').onclick=()=>setMode('page');
15
+ function syncFullFromRows(){$('fullText').value=[...$('rows').querySelectorAll('textarea')].map(x=>x.value).join('\n')}
16
+ $('fullText').addEventListener('input',()=>{const boxes=[...$('rows').querySelectorAll('textarea')];const parts=$('fullText').value.split('\n');if(parts.length===boxes.length)boxes.forEach((b,i)=>{b.value=parts[i]})});
17
+ fetch('/status').then(r=>r.json()).then(x=>{$('backend').textContent=x.backend==='gpu'?'NVIDIA GPU · FP16':'CPU · INT8'}).catch(()=>{$('backend').textContent='Сервер недоступен'});
18
+ $('file').addEventListener('change',()=>{chosen=$('file').files[0];$('recognize').disabled=!chosen;if(objectUrl)URL.revokeObjectURL(objectUrl);if(chosen){objectUrl=URL.createObjectURL(chosen);$('original').src=objectUrl;$('pageImage').src=objectUrl;$('original').hidden=false;$('results').hidden=true;$('message').textContent='Нажмите «Распознать».'}});
19
+ $('recognize').onclick=async()=>{if(!chosen)return;$('recognize').disabled=true;$('message').removeAttribute('role');$('message').textContent='Идёт распознавание. На CPU это может занять несколько минут.';$('results').hidden=true;try{const data=new FormData();data.append('file',chosen);const response=await fetch('/recognize',{method:'POST',body:data});if(!response.ok){const e=await response.json();throw new Error(e.detail||'Не удалось распознать изображение')}answer=await response.json();$('rows').replaceChildren();answer.lines.forEach((line,index)=>{const row=document.createElement('div');row.className='row';const n=document.createElement('span');n.className='num';n.textContent=index+1;const img=document.createElement('img');img.src=line.image;img.alt='Строка '+(index+1);const result=document.createElement('div');result.className='result';const edit=document.createElement('textarea');edit.value=line.text;edit.setAttribute('aria-label','Текст строки '+(index+1));edit.addEventListener('input',syncFullFromRows);result.append(edit);if(line.truncated){const note=document.createElement('div');note.className='warning';note.textContent='Достигнут предел длины: проверьте, не обрезана ли строка.';result.append(note)}row.append(n,img,result);$('rows').append(row)});syncFullFromRows();$('summary').textContent=`Строк: ${answer.lines.length}. Время: ${answer.seconds.toFixed(1)} с. ${answer.backend==='gpu'?'GPU FP16':'CPU INT8'}.`;$('message').textContent=answer.no_text_detected?'Текст не обнаружен. Проверьте, есть ли он на изображении; модель не распознаёт иллюстрацию как целую строку.':'Готово. Переключайте вкладки «По строкам» и «Страница и текст» для удобной сверки.';$('results').hidden=answer.no_text_detected;if(!answer.no_text_detected)$('original').hidden=true}catch(e){$('message').setAttribute('role','alert');$('message').textContent=e.message}finally{$('recognize').disabled=!chosen}};
20
+ function currentText(){return($('fullText').value||[...$('rows').querySelectorAll('textarea')].map(x=>x.value).join('\n'))}
21
+ function save(content,type,name){const url=URL.createObjectURL(new Blob([content],{type}));const a=document.createElement('a');a.href=url;a.download=name;a.click();setTimeout(()=>URL.revokeObjectURL(url),1000)}
22
+ $('copyText').onclick=async()=>{await navigator.clipboard.writeText(currentText());const old=$('copyText').textContent;$('copyText').textContent='Скопировано!';setTimeout(()=>{$('copyText').textContent=old},1500)};
23
+ $('saveText').onclick=()=>save(currentText()+'\n','text/plain;charset=utf-8','ocr-result.txt');
24
+ </script></html>
25
+
26
+
runtime/line_merger.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+
3
+ def merge_line_boxes(det_res, x_margin_ratio=2.5, y_overlap_thresh=0.45):
4
+ """
5
+ Умное объединение разрезанных боксов одной строки (Horizontal Line Merging).
6
+
7
+ :param det_res: список кортежей (box, text, score) от PaddleOCR/RapidOCR
8
+ :param x_margin_ratio: максимальное расстояние между словами одной строки (в долях высоты)
9
+ :param y_overlap_thresh: минимальный коэффициент вертикального перекрытия для принадлежности к одной строке
10
+ """
11
+ if det_res is None or len(det_res) == 0:
12
+ return []
13
+
14
+ # Преобразуем в структурированные прямоугольники [xmin, ymin, xmax, ymax]
15
+ rects = []
16
+ for item in det_res:
17
+ if isinstance(item, (list, tuple)) and len(item) > 0 and isinstance(item[0], (list, tuple, np.ndarray)):
18
+ box = np.array(item[0]).astype(int)
19
+ else:
20
+ box = np.array(item).astype(int)
21
+
22
+ if box.ndim == 2 and box.shape[0] >= 4:
23
+ xmin = int(np.min(box[:, 0]))
24
+ xmax = int(np.max(box[:, 0]))
25
+ ymin = int(np.min(box[:, 1]))
26
+ ymax = int(np.max(box[:, 1]))
27
+ elif len(box) == 4:
28
+ xmin, ymin, xmax, ymax = int(box[0]), int(box[1]), int(box[2]), int(box[3])
29
+ else:
30
+ continue
31
+
32
+ h = max(1, ymax - ymin)
33
+ w = max(1, xmax - xmin)
34
+ rects.append({
35
+ "box": [xmin, ymin, xmax, ymax],
36
+ "xmin": xmin, "xmax": xmax,
37
+ "ymin": ymin, "ymax": ymax,
38
+ "h": h, "w": w,
39
+ "cy": (ymin + ymax) / 2.0
40
+ })
41
+
42
+ if not rects:
43
+ return []
44
+
45
+ # Сортируем сначала по вертикали Y
46
+ rects.sort(key=lambda r: (r["ymin"], r["xmin"]))
47
+
48
+ # Группируем боксы по строкам с проверкой близости вертикальных центров
49
+ lines = []
50
+ for r in rects:
51
+ placed = False
52
+ for line in lines:
53
+ line_ymin = min(x["ymin"] for x in line)
54
+ line_ymax = max(x["ymax"] for x in line)
55
+ line_cy = (line_ymin + line_ymax) / 2.0
56
+ line_h = max(1, line_ymax - line_ymin)
57
+
58
+ # Проверяем расстояние между центрами строк
59
+ center_diff = abs(r["cy"] - line_cy)
60
+ if center_diff <= min(r["h"], line_h) * 0.45:
61
+ line.append(r)
62
+ placed = True
63
+ break
64
+ if not placed:
65
+ lines.append([r])
66
+
67
+ # Внутри каждой строки сортируем слева направо и объединяем
68
+ merged_boxes = []
69
+ for line in lines:
70
+ line.sort(key=lambda r: r["xmin"])
71
+
72
+ cur_xmin = line[0]["xmin"]
73
+ cur_xmax = line[0]["xmax"]
74
+ cur_ymin = line[0]["ymin"]
75
+ cur_ymax = line[0]["ymax"]
76
+
77
+ for next_r in line[1:]:
78
+ avg_h = ((cur_ymax - cur_ymin) + next_r["h"]) / 2.0
79
+ dist_x = next_r["xmin"] - cur_xmax
80
+
81
+ # Если боксы достаточно близко по горизонтали — объединяем в один
82
+ if dist_x <= avg_h * x_margin_ratio:
83
+ cur_xmax = max(cur_xmax, next_r["xmax"])
84
+ cur_ymin = min(cur_ymin, next_r["ymin"])
85
+ cur_ymax = max(cur_ymax, next_r["ymax"])
86
+ else:
87
+ # Слишком далеко (например, две колонки текста) — сохраняем отдельно
88
+ merged_boxes.append([cur_xmin, cur_ymin, cur_xmax, cur_ymax])
89
+ cur_xmin = next_r["xmin"]
90
+ cur_xmax = next_r["xmax"]
91
+ cur_ymin = next_r["ymin"]
92
+ cur_ymax = next_r["ymax"]
93
+
94
+ merged_boxes.append([cur_xmin, cur_ymin, cur_xmax, cur_ymax])
95
+
96
+ # Сортируем итоговые строки строго сверху вниз
97
+ merged_boxes.sort(key=lambda b: (b[1], b[0]))
98
+ return merged_boxes
start_ocr.bat ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @echo off
2
+ setlocal
3
+ cd /d "%~dp0"
4
+ title Bashkir OCR Studio v3
5
+ echo [1] CPU - ONNX INT8
6
+ echo [2] NVIDIA GPU - ONNX FP16
7
+ echo [3] Auto - detect NVIDIA GPU (FP16) or fall back to CPU (INT8)
8
+ set "OCR_MODE="
9
+ set /p "OCR_MODE=Choose mode [1/2/3, default 3]: "
10
+ if "%OCR_MODE%"=="" set "OCR_MODE=3"
11
+ set "OCR_BACKEND=cpu"
12
+ set "OCR_RUN_BACKEND=cpu"
13
+ if "%OCR_MODE%"=="2" (
14
+ set "OCR_BACKEND=gpu"
15
+ set "OCR_RUN_BACKEND=gpu"
16
+ )
17
+ if "%OCR_MODE%"=="3" (
18
+ set "OCR_RUN_BACKEND=auto"
19
+ if exist ".venv-gpu\.ready" (
20
+ set "OCR_BACKEND=gpu"
21
+ ) else (
22
+ nvidia-smi >nul 2>&1
23
+ if not errorlevel 1 set "OCR_BACKEND=gpu"
24
+ )
25
+ )
26
+ set "OCR_ENV=.venv-%OCR_BACKEND%"
27
+ if exist "%OCR_ENV%\.ready" goto LAUNCH
28
+ echo First start (%OCR_BACKEND%): Python 3.11-3.13 and internet access are needed to install dependencies.
29
+ python --version >nul 2>&1
30
+ if errorlevel 1 (
31
+ echo Install Python from python.org and enable Add Python to PATH.
32
+ goto FINISH
33
+ )
34
+ if not exist "%OCR_ENV%\Scripts\python.exe" python -m venv "%OCR_ENV%"
35
+ if errorlevel 1 goto FINISH
36
+ "%OCR_ENV%\Scripts\python.exe" -m pip install -r "requirements-%OCR_BACKEND%.txt"
37
+ if errorlevel 1 (
38
+ echo Installation failed. Check your connection, then run install again:
39
+ echo %OCR_ENV%\Scripts\python.exe -m pip install -r requirements-%OCR_BACKEND%.txt
40
+ goto FINISH
41
+ )
42
+ type nul > "%OCR_ENV%\.ready"
43
+ :LAUNCH
44
+ "%OCR_ENV%\Scripts\python.exe" studio.py --backend %OCR_RUN_BACKEND%
45
+ :FINISH
46
+ pause
47
+
studio.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Local browser UI. Uploaded images live only in request memory."""
2
+ import argparse
3
+ import io
4
+ import threading
5
+ import time
6
+ import webbrowser
7
+ from pathlib import Path
8
+
9
+ from fastapi import FastAPI, HTTPException, UploadFile
10
+ from fastapi.responses import FileResponse
11
+ from PIL import Image, UnidentifiedImageError
12
+ from starlette.concurrency import run_in_threadpool
13
+ from runtime.engine import OCR
14
+
15
+
16
+ def create_app(ocr):
17
+ app=FastAPI(title='Bashkir OCR Studio',docs_url=None,redoc_url=None)
18
+ lock=threading.Lock()
19
+ @app.get('/')
20
+ def index():return FileResponse(Path(__file__).parent/'runtime/index.html',headers={'Cache-Control':'no-store, no-cache, must-revalidate','Pragma':'no-cache','Expires':'0'})
21
+ @app.get('/status')
22
+ def status():return dict(ready=True,backend=ocr.backend)
23
+ @app.post('/recognize')
24
+ async def recognize(file:UploadFile):
25
+ data=await file.read(25*1024*1024+1)
26
+ if len(data)>25*1024*1024:raise HTTPException(413,'Изображение слишком большое: предел 25 МБ.')
27
+ try:
28
+ image=Image.open(io.BytesIO(data))
29
+ if image.width*image.height>25_000_000:raise HTTPException(413,'Предел — 25 миллионов пикселей.')
30
+ image.load()
31
+ except (UnidentifiedImageError,OSError,Image.DecompressionBombError):raise HTTPException(400,'Не удалось открыть изображение.')
32
+ if not lock.acquire(blocking=False):raise HTTPException(409,'Дождитесь завершения текущего изображения.')
33
+ try:return await run_in_threadpool(ocr.recognize,image)
34
+ finally:lock.release()
35
+ return app
36
+
37
+
38
+ def main():
39
+ parser=argparse.ArgumentParser(description=__doc__)
40
+ parser.add_argument('--backend',choices=['cpu','gpu','auto'],default='cpu')
41
+ parser.add_argument('--port',type=int,default=8001)
42
+ parser.add_argument('--no-browser',action='store_true')
43
+ parser.add_argument('--model-dir',type=Path,help='Optional directory containing config.json and model files')
44
+ args=parser.parse_args()
45
+ ocr=OCR(root=args.model_dir,backend=args.backend)
46
+ if not args.no_browser:
47
+ def open_ready():
48
+ import urllib.request
49
+ for _ in range(60):
50
+ try:
51
+ with urllib.request.urlopen(f'http://127.0.0.1:{args.port}/status',timeout=1) as r:
52
+ if r.status==200:webbrowser.open(f'http://127.0.0.1:{args.port}/?v={int(time.time())}');return
53
+ except OSError:time.sleep(.5)
54
+ threading.Thread(target=open_ready,daemon=True).start()
55
+ import uvicorn
56
+ uvicorn.run(create_app(ocr),host='127.0.0.1',port=args.port,access_log=False)
57
+
58
+
59
+ if __name__=='__main__':main()
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"add_prefix_space": false, "added_tokens_decoder": {"0": {"content": "<s>", "lstrip": false, "normalized": true, "rstrip": false, "single_word": false, "special": true}, "1": {"content": "<pad>", "lstrip": false, "normalized": true, "rstrip": false, "single_word": false, "special": true}, "2": {"content": "</s>", "lstrip": false, "normalized": true, "rstrip": false, "single_word": false, "special": true}, "3": {"content": "<unk>", "lstrip": false, "normalized": true, "rstrip": false, "single_word": false, "special": true}, "50264": {"content": "<mask>", "lstrip": true, "normalized": true, "rstrip": false, "single_word": false, "special": true}, "50265": {"content": "Ғ", "lstrip": false, "normalized": true, "rstrip": false, "single_word": false, "special": false}, "50266": {"content": "ғ", "lstrip": false, "normalized": true, "rstrip": false, "single_word": false, "special": false}, "50267": {"content": "Ҙ", "lstrip": false, "normalized": true, "rstrip": false, "single_word": false, "special": false}, "50268": {"content": "ҙ", "lstrip": false, "normalized": true, "rstrip": false, "single_word": false, "special": false}, "50269": {"content": "Ҡ", "lstrip": false, "normalized": true, "rstrip": false, "single_word": false, "special": false}, "50270": {"content": "ҡ", "lstrip": false, "normalized": true, "rstrip": false, "single_word": false, "special": false}, "50271": {"content": "Ң", "lstrip": false, "normalized": true, "rstrip": false, "single_word": false, "special": false}, "50272": {"content": "ң", "lstrip": false, "normalized": true, "rstrip": false, "single_word": false, "special": false}, "50273": {"content": "Ө", "lstrip": false, "normalized": true, "rstrip": false, "single_word": false, "special": false}, "50274": {"content": "ө", "lstrip": false, "normalized": true, "rstrip": false, "single_word": false, "special": false}, "50275": {"content": "Ҫ", "lstrip": false, "normalized": true, "rstrip": false, "single_word": false, "special": false}, "50276": {"content": "ҫ", "lstrip": false, "normalized": true, "rstrip": false, "single_word": false, "special": false}, "50277": {"content": "Ү", "lstrip": false, "normalized": true, "rstrip": false, "single_word": false, "special": false}, "50278": {"content": "ү", "lstrip": false, "normalized": true, "rstrip": false, "single_word": false, "special": false}, "50279": {"content": "Һ", "lstrip": false, "normalized": true, "rstrip": false, "single_word": false, "special": false}, "50280": {"content": "һ", "lstrip": false, "normalized": true, "rstrip": false, "single_word": false, "special": false}, "50281": {"content": "Ә", "lstrip": false, "normalized": true, "rstrip": false, "single_word": false, "special": false}, "50282": {"content": "ә", "lstrip": false, "normalized": true, "rstrip": false, "single_word": false, "special": false}}, "backend": "tokenizers", "bos_token": "<s>", "clean_up_tokenization_spaces": false, "cls_token": "<s>", "eos_token": "</s>", "errors": "replace", "is_local": false, "local_files_only": false, "mask_token": "<mask>", "model_max_length": 512, "pad_token": "<pad>", "processor_class": "TrOCRProcessor", "sep_token": "</s>", "tokenizer_class": "RobertaTokenizer", "trim_offsets": true, "unk_token": "<unk>"}