mathcraft-ocr 0.2.7__tar.gz → 0.2.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mathcraft-ocr might be problematic. Click here for more details.

Files changed (46) hide show
  1. {mathcraft_ocr-0.2.7/mathcraft_ocr.egg-info → mathcraft_ocr-0.2.9}/PKG-INFO +60 -13
  2. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/README_MATHCRAFT_OCR.md +48 -5
  3. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/__init__.py +7 -1
  4. mathcraft_ocr-0.2.9/mathcraft_ocr/adapters/common.py +129 -0
  5. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/adapters/text_recognizer.py +13 -56
  6. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/cli.py +7 -4
  7. mathcraft_ocr-0.2.9/mathcraft_ocr/devices.py +360 -0
  8. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/doctor.py +4 -2
  9. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/hardware.py +28 -27
  10. mathcraft_ocr-0.2.9/mathcraft_ocr/providers.py +279 -0
  11. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/runtime.py +12 -3
  12. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/serialization.py +8 -0
  13. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/worker.py +4 -1
  14. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9/mathcraft_ocr.egg-info}/PKG-INFO +60 -13
  15. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr.egg-info/SOURCES.txt +1 -0
  16. mathcraft_ocr-0.2.9/mathcraft_ocr.egg-info/requires.txt +33 -0
  17. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/pyproject.toml +15 -8
  18. mathcraft_ocr-0.2.7/mathcraft_ocr/adapters/common.py +0 -54
  19. mathcraft_ocr-0.2.7/mathcraft_ocr/providers.py +0 -89
  20. mathcraft_ocr-0.2.7/mathcraft_ocr.egg-info/requires.txt +0 -30
  21. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/LICENSE +0 -0
  22. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/MANIFEST.in +0 -0
  23. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/__main__.py +0 -0
  24. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/adapters/__init__.py +0 -0
  25. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/adapters/formula_detector.py +0 -0
  26. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/adapters/formula_recognizer.py +0 -0
  27. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/adapters/text_detector.py +0 -0
  28. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/api.py +0 -0
  29. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/cache.py +0 -0
  30. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/debug_blocks.py +0 -0
  31. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/downloader.py +0 -0
  32. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/error_patterns.py +0 -0
  33. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/errors.py +0 -0
  34. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/formula_lines.py +0 -0
  35. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/image.py +0 -0
  36. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/latex_alignment.py +0 -0
  37. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/latex_quality.py +0 -0
  38. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/layout.py +0 -0
  39. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/manifest.py +0 -0
  40. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/manifests/models.v1.json +0 -0
  41. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/profiles.py +0 -0
  42. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr/results.py +0 -0
  43. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr.egg-info/dependency_links.txt +0 -0
  44. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr.egg-info/entry_points.txt +0 -0
  45. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/mathcraft_ocr.egg-info/top_level.txt +0 -0
  46. {mathcraft_ocr-0.2.7 → mathcraft_ocr-0.2.9}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mathcraft-ocr
3
- Version: 0.2.7
3
+ Version: 0.2.9
4
4
  Summary: ONNX-only OCR runtime for mathematical documents
5
5
  Author: SakuraMathcraft
6
6
  License-Expression: GPL-3.0-only
@@ -21,21 +21,25 @@ Classifier: Programming Language :: Python :: 3.12
21
21
  Classifier: Programming Language :: Python :: 3.13
22
22
  Classifier: Topic :: Scientific/Engineering :: Image Recognition
23
23
  Classifier: Topic :: Text Processing
24
- Requires-Python: >=3.10
24
+ Requires-Python: <3.14,>=3.10
25
25
  Description-Content-Type: text/markdown
26
26
  License-File: LICENSE
27
- Requires-Dist: numpy<3,>=1.26
28
- Requires-Dist: pillow<12,>=10
27
+ Requires-Dist: numpy<3,>=2
28
+ Requires-Dist: pillow<13,>=12
29
29
  Requires-Dist: opencv-python<5,>=4.10
30
30
  Requires-Dist: rapidocr==3.5.0
31
31
  Requires-Dist: transformers==4.55.4
32
32
  Requires-Dist: tokenizers==0.21.4
33
33
  Provides-Extra: cpu
34
- Requires-Dist: onnxruntime<1.26,>=1.19.2; python_version < "3.13" and extra == "cpu"
35
- Requires-Dist: onnxruntime<1.26,>=1.20; python_version >= "3.13" and extra == "cpu"
34
+ Requires-Dist: onnxruntime<1.30,>=1.20; extra == "cpu"
36
35
  Provides-Extra: gpu
37
- Requires-Dist: onnxruntime-gpu<1.26,>=1.19.2; python_version < "3.13" and extra == "gpu"
38
- Requires-Dist: onnxruntime-gpu<1.26,>=1.20; python_version >= "3.13" and extra == "gpu"
36
+ Requires-Dist: onnxruntime-gpu<1.30,>=1.27; extra == "gpu"
37
+ Provides-Extra: gpu-cu12
38
+ Requires-Dist: onnxruntime-gpu<1.27,>=1.21; extra == "gpu-cu12"
39
+ Provides-Extra: directml
40
+ Requires-Dist: onnxruntime-directml<1.30,>=1.20; platform_system == "Windows" and extra == "directml"
41
+ Provides-Extra: openvino
42
+ Requires-Dist: onnxruntime-openvino<1.30,>=1.20; (platform_system == "Windows" or platform_system == "Linux") and extra == "openvino"
39
43
  Provides-Extra: dev
40
44
  Requires-Dist: ruff>=0.8; extra == "dev"
41
45
  Requires-Dist: build>=1.2; extra == "dev"
@@ -76,9 +80,27 @@ GPU backend:
76
80
  pip install "mathcraft-ocr[gpu]"
77
81
  ```
78
82
 
79
- Install only one backend extra in a clean environment. `onnxruntime` and `onnxruntime-gpu` should not be mixed in the same environment.
83
+ CUDA 12 backend:
80
84
 
81
- LaTeXSnipper's dependency wizard selects the ONNX Runtime GPU wheel line from the detected CUDA toolkit. CUDA 11.x uses the ONNX Runtime CUDA 11 package feed, CUDA 12.x uses the stable PyPI GPU wheels, and CUDA 13.x uses the ONNX Runtime CUDA 13 nightly feed. Static `mathcraft-ocr[gpu]` package metadata cannot inspect the local CUDA toolkit, so it keeps a broad stable PyPI range; CUDA 11.x users installing manually should use the CUDA 11 feed shown by the wizard.
85
+ ```powershell
86
+ pip install "mathcraft-ocr[gpu-cu12]"
87
+ ```
88
+
89
+ Windows DirectML backend:
90
+
91
+ ```powershell
92
+ pip install "mathcraft-ocr[directml]"
93
+ ```
94
+
95
+ Intel OpenVINO backend on Windows or Linux:
96
+
97
+ ```powershell
98
+ pip install "mathcraft-ocr[openvino]"
99
+ ```
100
+
101
+ Install only one backend extra in a clean environment. The CPU, CUDA/TensorRT, DirectML, and OpenVINO ONNX Runtime distributions must not be mixed in the same environment. The standard `onnxruntime` macOS wheel includes CoreML EP support.
102
+
103
+ LaTeXSnipper's dependency wizard selects the ONNX Runtime GPU line from the detected CUDA toolkit: CUDA 11.x uses ONNX Runtime 1.20 from the official CUDA 11 feed, CUDA 12.x uses ONNX Runtime 1.21-1.26 from PyPI, and CUDA 13.x uses ONNX Runtime 1.27-1.29 from PyPI. The static `mathcraft-ocr[gpu]` extra follows the current PyPI default (CUDA 13 and Python 3.11+); use `gpu-cu12` for CUDA 12. CUDA 11 installations must also select the official CUDA 11 package feed, so LaTeXSnipper's dependency wizard is recommended for that configuration.
82
104
 
83
105
  ## Quick Start
84
106
 
@@ -190,11 +212,36 @@ Model artifacts are downloaded from the MathCraft-Models release assets declared
190
212
 
191
213
  `provider_preference` accepts:
192
214
 
193
- - `auto`: prefer CUDA when available and valid, otherwise CPU.
215
+ - `auto`: prefer CUDA, then a platform accelerator, and finally CPU; TensorRT remains explicit because of its first-use engine build cost.
194
216
  - `cpu`: force CPU.
195
- - `gpu`: request CUDA-capable ONNX Runtime.
217
+ - `gpu`: request the first available supported accelerator.
218
+ - `cuda`: request CUDA.
219
+ - `tensorrt` or `trt`: request TensorRT, followed by CUDA and CPU for unsupported nodes.
220
+ - `directml` or `dml`: request DirectML.
221
+ - `coreml`: request CoreML.
222
+ - `openvino`: request OpenVINO.
223
+
224
+ Advanced callers can pass an explicit ordered provider list and provider options:
225
+
226
+ ```python
227
+ runtime = MathCraftRuntime(
228
+ providers=[
229
+ (
230
+ "TensorrtExecutionProvider",
231
+ {
232
+ "device_id": 0,
233
+ "trt_engine_cache_enable": True,
234
+ "trt_engine_cache_path": "./trt-cache",
235
+ },
236
+ ),
237
+ ("CUDAExecutionProvider", {"device_id": 0}),
238
+ ]
239
+ )
240
+ ```
241
+
242
+ `auto` deliberately prefers CUDA over TensorRT because TensorRT can spend substantial time building an engine on first use. Select `tensorrt` explicitly when its native runtime is installed; enable its engine cache for repeated startup performance.
196
243
 
197
- The actual provider is available on results through the `provider` field.
244
+ The actual provider is available on recognition results through the `provider` field. Doctor and warmup reports also expose `device_id`, `device_name`, `device_uuid`, and `device_verified` under `provider_info`. GPU sessions bind the reported `device_id` explicitly; `device_verified` becomes true only after the runtime confirms the device used by initialized inference sessions.
198
245
 
199
246
  ## Development
200
247
 
@@ -30,9 +30,27 @@ GPU backend:
30
30
  pip install "mathcraft-ocr[gpu]"
31
31
  ```
32
32
 
33
- Install only one backend extra in a clean environment. `onnxruntime` and `onnxruntime-gpu` should not be mixed in the same environment.
33
+ CUDA 12 backend:
34
34
 
35
- LaTeXSnipper's dependency wizard selects the ONNX Runtime GPU wheel line from the detected CUDA toolkit. CUDA 11.x uses the ONNX Runtime CUDA 11 package feed, CUDA 12.x uses the stable PyPI GPU wheels, and CUDA 13.x uses the ONNX Runtime CUDA 13 nightly feed. Static `mathcraft-ocr[gpu]` package metadata cannot inspect the local CUDA toolkit, so it keeps a broad stable PyPI range; CUDA 11.x users installing manually should use the CUDA 11 feed shown by the wizard.
35
+ ```powershell
36
+ pip install "mathcraft-ocr[gpu-cu12]"
37
+ ```
38
+
39
+ Windows DirectML backend:
40
+
41
+ ```powershell
42
+ pip install "mathcraft-ocr[directml]"
43
+ ```
44
+
45
+ Intel OpenVINO backend on Windows or Linux:
46
+
47
+ ```powershell
48
+ pip install "mathcraft-ocr[openvino]"
49
+ ```
50
+
51
+ Install only one backend extra in a clean environment. The CPU, CUDA/TensorRT, DirectML, and OpenVINO ONNX Runtime distributions must not be mixed in the same environment. The standard `onnxruntime` macOS wheel includes CoreML EP support.
52
+
53
+ LaTeXSnipper's dependency wizard selects the ONNX Runtime GPU line from the detected CUDA toolkit: CUDA 11.x uses ONNX Runtime 1.20 from the official CUDA 11 feed, CUDA 12.x uses ONNX Runtime 1.21-1.26 from PyPI, and CUDA 13.x uses ONNX Runtime 1.27-1.29 from PyPI. The static `mathcraft-ocr[gpu]` extra follows the current PyPI default (CUDA 13 and Python 3.11+); use `gpu-cu12` for CUDA 12. CUDA 11 installations must also select the official CUDA 11 package feed, so LaTeXSnipper's dependency wizard is recommended for that configuration.
36
54
 
37
55
  ## Quick Start
38
56
 
@@ -144,11 +162,36 @@ Model artifacts are downloaded from the MathCraft-Models release assets declared
144
162
 
145
163
  `provider_preference` accepts:
146
164
 
147
- - `auto`: prefer CUDA when available and valid, otherwise CPU.
165
+ - `auto`: prefer CUDA, then a platform accelerator, and finally CPU; TensorRT remains explicit because of its first-use engine build cost.
148
166
  - `cpu`: force CPU.
149
- - `gpu`: request CUDA-capable ONNX Runtime.
167
+ - `gpu`: request the first available supported accelerator.
168
+ - `cuda`: request CUDA.
169
+ - `tensorrt` or `trt`: request TensorRT, followed by CUDA and CPU for unsupported nodes.
170
+ - `directml` or `dml`: request DirectML.
171
+ - `coreml`: request CoreML.
172
+ - `openvino`: request OpenVINO.
173
+
174
+ Advanced callers can pass an explicit ordered provider list and provider options:
150
175
 
151
- The actual provider is available on results through the `provider` field.
176
+ ```python
177
+ runtime = MathCraftRuntime(
178
+ providers=[
179
+ (
180
+ "TensorrtExecutionProvider",
181
+ {
182
+ "device_id": 0,
183
+ "trt_engine_cache_enable": True,
184
+ "trt_engine_cache_path": "./trt-cache",
185
+ },
186
+ ),
187
+ ("CUDAExecutionProvider", {"device_id": 0}),
188
+ ]
189
+ )
190
+ ```
191
+
192
+ `auto` deliberately prefers CUDA over TensorRT because TensorRT can spend substantial time building an engine on first use. Select `tensorrt` explicitly when its native runtime is installed; enable its engine cache for repeated startup performance.
193
+
194
+ The actual provider is available on recognition results through the `provider` field. Doctor and warmup reports also expose `device_id`, `device_name`, `device_uuid`, and `device_verified` under `provider_info`. GPU sessions bind the reported `device_id` explicitly; `device_verified` becomes true only after the runtime confirms the device used by initialized inference sessions.
152
195
 
153
196
  ## Development
154
197
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- __version__ = "0.2.7"
5
+ __version__ = "0.2.9"
6
6
 
7
7
  __all__ = [
8
8
  "DoctorReport",
@@ -12,6 +12,8 @@ __all__ = [
12
12
  "MathCraftRuntime",
13
13
  "MixedRecognitionResult",
14
14
  "OCRRegion",
15
+ "ProviderInfo",
16
+ "ProviderSpec",
15
17
  "__version__",
16
18
  "run_doctor",
17
19
  ]
@@ -32,6 +34,10 @@ def __getattr__(name: str) -> object:
32
34
  from . import doctor
33
35
 
34
36
  return getattr(doctor, name)
37
+ if name in {"ProviderInfo", "ProviderSpec"}:
38
+ from . import providers
39
+
40
+ return getattr(providers, name)
35
41
  if name == "MathCraftError":
36
42
  from .errors import MathCraftError
37
43
 
@@ -0,0 +1,129 @@
1
+ # coding: utf-8
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ from functools import lru_cache
7
+ from pathlib import Path
8
+
9
+ from ..providers import ACCELERATOR_PROVIDER_NAMES, ProviderInfo
10
+
11
+
12
+ ProviderOptions = tuple[tuple[str, str], ...]
13
+ ProviderConfig = tuple[str, ProviderOptions]
14
+
15
+
16
+ def _ort():
17
+ return importlib.import_module("onnxruntime")
18
+
19
+
20
+ def session_providers(provider_info: ProviderInfo) -> list[str | tuple[str, dict[str, object]]]:
21
+ if provider_info.provider_specs:
22
+ return [spec.as_ort_provider() for spec in provider_info.provider_specs]
23
+ available = list(provider_info.available_providers)
24
+ active = provider_info.active_provider
25
+ if active and active in ACCELERATOR_PROVIDER_NAMES and "CPUExecutionProvider" in available:
26
+ return [(active, {"device_id": int(provider_info.device_id or 0)}), "CPUExecutionProvider"]
27
+ if active and active in ACCELERATOR_PROVIDER_NAMES:
28
+ return [(active, {"device_id": int(provider_info.device_id or 0)})]
29
+ if "CPUExecutionProvider" in available:
30
+ return ["CPUExecutionProvider"]
31
+ return available
32
+
33
+
34
+ def create_session(model_path: str | Path, provider_info: ProviderInfo):
35
+ model_path = str(Path(model_path).resolve())
36
+ provider_config = _freeze_provider_config(session_providers(provider_info))
37
+ session = _create_session_cached(model_path, provider_config)
38
+ enforce_session_provider(session, provider_info)
39
+ return session
40
+
41
+
42
+ @lru_cache(maxsize=16)
43
+ def _create_session_cached(model_path: str, providers: tuple[ProviderConfig, ...]):
44
+ ort = _ort()
45
+ configured = [
46
+ (name, {key: value for key, value in options}) if options else name
47
+ for name, options in providers
48
+ ]
49
+ kwargs = {
50
+ "providers": configured,
51
+ "enable_fallback": False,
52
+ }
53
+ session_options = _session_options(ort, providers)
54
+ if session_options is not None:
55
+ kwargs["sess_options"] = session_options
56
+ return ort.InferenceSession(model_path, **kwargs)
57
+
58
+
59
+ def _session_options(ort, providers: tuple[ProviderConfig, ...]):
60
+ create_options = getattr(ort, "SessionOptions", None)
61
+ if not callable(create_options):
62
+ return None
63
+ session_options = create_options()
64
+ session_options.log_severity_level = 4
65
+ names = {name for name, _options in providers}
66
+ if "DmlExecutionProvider" not in names:
67
+ return session_options
68
+ session_options.enable_mem_pattern = False
69
+ execution_mode = getattr(getattr(ort, "ExecutionMode", None), "ORT_SEQUENTIAL", None)
70
+ if execution_mode is not None:
71
+ session_options.execution_mode = execution_mode
72
+ return session_options
73
+
74
+
75
+ def _freeze_provider_config(
76
+ providers: list[str | tuple[str, dict[str, object]]],
77
+ ) -> tuple[ProviderConfig, ...]:
78
+ frozen: list[ProviderConfig] = []
79
+ for item in providers:
80
+ if isinstance(item, str):
81
+ frozen.append((item, ()))
82
+ continue
83
+ name, options = item
84
+ frozen.append((name, tuple(sorted((str(key), str(value)) for key, value in options.items()))))
85
+ return tuple(frozen)
86
+
87
+
88
+ def enforce_session_provider(session, provider_info: ProviderInfo) -> None:
89
+ validate_session_provider(
90
+ session,
91
+ str(provider_info.active_provider or ""),
92
+ int(provider_info.device_id or 0),
93
+ )
94
+
95
+
96
+ def validate_session_provider(
97
+ session,
98
+ active_provider: str,
99
+ device_id: int = 0,
100
+ *,
101
+ runtime_name: str = "ONNX Runtime",
102
+ ) -> None:
103
+ actual = list(session.get_providers() or [])
104
+ active = str(active_provider or "")
105
+ if not actual or actual[0] != active:
106
+ provider_kind = "ONNX accelerator provider" if active in ACCELERATOR_PROVIDER_NAMES else "ONNX provider"
107
+ raise RuntimeError(
108
+ f"requested {provider_kind} {active or '<none>'}, "
109
+ f"but {runtime_name} session providers are {actual}"
110
+ )
111
+ if active in {"CUDAExecutionProvider", "TensorrtExecutionProvider", "DmlExecutionProvider"}:
112
+ get_options = getattr(session, "get_provider_options", None)
113
+ if callable(get_options):
114
+ options_by_provider = get_options() or {}
115
+ active_options = options_by_provider.get(active, {}) if isinstance(options_by_provider, dict) else {}
116
+ actual_device_id = active_options.get("device_id") if isinstance(active_options, dict) else None
117
+ if actual_device_id not in (None, "") and int(actual_device_id) != int(device_id):
118
+ raise RuntimeError(
119
+ f"requested ONNX provider {active} device_id={int(device_id)}, "
120
+ f"but {runtime_name} session uses device_id={actual_device_id}"
121
+ )
122
+ disable_fallback = getattr(session, "disable_fallback", None)
123
+ if not callable(disable_fallback):
124
+ raise RuntimeError(f"{runtime_name} session cannot disable provider fallback")
125
+ disable_fallback()
126
+
127
+
128
+ def clear_session_cache() -> None:
129
+ _create_session_cached.cache_clear()
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import logging
5
6
  from functools import lru_cache
6
7
  from pathlib import Path
7
8
 
@@ -10,6 +11,12 @@ from rapidocr import EngineType, LangRec, ModelType, OCRVersion
10
11
  from rapidocr.ch_ppocr_rec import TextRecInput, TextRecognizer
11
12
  from rapidocr.utils.typings import TaskType
12
13
 
14
+ from .common import create_session
15
+ from ..providers import ProviderInfo
16
+
17
+
18
+ logging.getLogger("RapidOCR").setLevel(logging.WARNING)
19
+
13
20
 
14
21
  class _Config(dict):
15
22
  def __init__(self, *args, **kwargs):
@@ -54,23 +61,13 @@ def recognize_pp_text_lines(
54
61
 
55
62
  def _create_pp_text_recognizer(model_dir: Path, provider_info) -> TextRecognizer:
56
63
  model_dir = model_dir.resolve()
57
- active_provider = str(getattr(provider_info, "active_provider", "") or "")
58
- use_cuda = active_provider in {"CUDAExecutionProvider", "TensorrtExecutionProvider"}
59
- use_dml = active_provider == "DmlExecutionProvider"
60
- return _create_pp_text_recognizer_cached(
61
- str(model_dir),
62
- active_provider,
63
- use_cuda,
64
- use_dml,
65
- )
64
+ return _create_pp_text_recognizer_cached(str(model_dir), provider_info)
66
65
 
67
66
 
68
67
  @lru_cache(maxsize=8)
69
68
  def _create_pp_text_recognizer_cached(
70
69
  model_dir: str,
71
- active_provider: str,
72
- use_cuda: bool,
73
- use_dml: bool,
70
+ provider_info: ProviderInfo,
74
71
  ) -> TextRecognizer:
75
72
  model_dir = Path(model_dir)
76
73
  model_candidates = sorted(model_dir.glob("**/*rec*.onnx"))
@@ -84,6 +81,7 @@ def _create_pp_text_recognizer_cached(
84
81
  is_server = "server" in model_name or "server" in model_dir.name
85
82
  is_v5 = "v5" in model_name or "v5" in model_dir.name
86
83
  is_english = dict_path.name == "en_dict.txt"
84
+ session = create_session(model_path, provider_info)
87
85
  config = _Config({
88
86
  "engine_type": EngineType.ONNXRUNTIME,
89
87
  "lang_type": LangRec.EN if is_english else LangRec.CH,
@@ -96,51 +94,10 @@ def _create_pp_text_recognizer_cached(
96
94
  "rec_img_shape": [3, 48, 320],
97
95
  "rec_batch_num": 6,
98
96
  "font_path": None,
99
- "engine_cfg": {
100
- "intra_op_num_threads": -1,
101
- "inter_op_num_threads": -1,
102
- "enable_cpu_mem_arena": False,
103
- "cpu_ep_cfg": {"arena_extend_strategy": "kSameAsRequested"},
104
- "use_cuda": use_cuda,
105
- "cuda_ep_cfg": {
106
- "device_id": 0,
107
- "arena_extend_strategy": "kNextPowerOfTwo",
108
- "cudnn_conv_algo_search": "EXHAUSTIVE",
109
- "do_copy_in_default_stream": True,
110
- },
111
- "use_dml": use_dml,
112
- "dm_ep_cfg": None,
113
- "use_cann": False,
114
- "cann_ep_cfg": {
115
- "device_id": 0,
116
- "arena_extend_strategy": "kNextPowerOfTwo",
117
- "npu_mem_limit": 21474836480,
118
- "op_select_impl_mode": "high_performance",
119
- "optypelist_for_implmode": "Gelu",
120
- "enable_cann_graph": True,
121
- },
122
- },
97
+ "session": session,
98
+ "engine_cfg": {},
123
99
  })
124
- recognizer = TextRecognizer(config)
125
- _enforce_strict_provider(recognizer, active_provider)
126
- return recognizer
127
-
128
-
129
- def _enforce_strict_provider(recognizer: TextRecognizer, active_provider: str) -> None:
130
- engine = getattr(recognizer, "session", None)
131
- session = getattr(engine, "session", None)
132
- if session is None:
133
- raise RuntimeError("RapidOCR did not expose its ONNX Runtime session")
134
- actual = list(session.get_providers() or [])
135
- if not active_provider or not actual or actual[0] != active_provider:
136
- raise RuntimeError(
137
- f"requested ONNX provider {active_provider or '<none>'}, "
138
- f"but RapidOCR session providers are {actual}"
139
- )
140
- disable_fallback = getattr(session, "disable_fallback", None)
141
- if not callable(disable_fallback):
142
- raise RuntimeError("RapidOCR ONNX Runtime session cannot disable provider fallback")
143
- disable_fallback()
100
+ return TextRecognizer(config)
144
101
 
145
102
 
146
103
  def clear_text_recognizer_cache() -> None:
@@ -16,6 +16,9 @@ from .serialization import (
16
16
  )
17
17
 
18
18
 
19
+ _PROVIDER_HELP = "auto, cpu, gpu, cuda, tensorrt, directml, coreml, or openvino"
20
+
21
+
19
22
  def _resolve_ocr_output_path(args: argparse.Namespace) -> Path | None:
20
23
  output = str(getattr(args, "output", "") or "").strip()
21
24
  output_dir = str(getattr(args, "output_dir", "") or "").strip()
@@ -45,22 +48,22 @@ def build_parser() -> argparse.ArgumentParser:
45
48
  models_sub.add_parser("check")
46
49
 
47
50
  doctor = sub.add_parser("doctor")
48
- doctor.add_argument("--provider", default="auto")
51
+ doctor.add_argument("--provider", default="auto", help=_PROVIDER_HELP)
49
52
 
50
53
  warmup = sub.add_parser("warmup")
51
54
  warmup.add_argument("--profile", default="formula")
52
- warmup.add_argument("--provider", default="auto")
55
+ warmup.add_argument("--provider", default="auto", help=_PROVIDER_HELP)
53
56
 
54
57
  ocr = sub.add_parser("ocr")
55
58
  ocr.add_argument("image")
56
59
  ocr.add_argument("--profile", choices=("formula", "text", "mixed"), default="mixed")
57
- ocr.add_argument("--provider", default="auto")
60
+ ocr.add_argument("--provider", default="auto", help=_PROVIDER_HELP)
58
61
  ocr.add_argument("--output", "-o", default="")
59
62
  ocr.add_argument("--output-dir", default="")
60
63
  ocr.add_argument("--json", action="store_true", dest="as_json")
61
64
 
62
65
  worker = sub.add_parser("worker")
63
- worker.add_argument("--provider", default="auto")
66
+ worker.add_argument("--provider", default="auto", help=_PROVIDER_HELP)
64
67
  return parser
65
68
 
66
69