openocr-mlt 0.3.0__py3-none-any.whl
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.
- openocr_mlt/__init__.py +11 -0
- openocr_mlt/__main__.py +2 -0
- openocr_mlt/api.py +144 -0
- openocr_mlt/assets/THIRD_PARTY_NOTICES.txt +26 -0
- openocr_mlt/assets/detector-models.json +37 -0
- openocr_mlt/assets/dict.txt +19683 -0
- openocr_mlt/assets/manifest.json +21 -0
- openocr_mlt/assets/model-bundle.json +171 -0
- openocr_mlt/assets/onnx-model.json +1565 -0
- openocr_mlt/assets/ppocrv6_onnx-LICENSE.txt +21 -0
- openocr_mlt/assets.py +37 -0
- openocr_mlt/cli.py +59 -0
- openocr_mlt/config.py +45 -0
- openocr_mlt/detection/__init__.py +4 -0
- openocr_mlt/detection/config.py +34 -0
- openocr_mlt/detection/cuda_runtime.py +44 -0
- openocr_mlt/detection/db.py +268 -0
- openocr_mlt/detection/onnx_backend.py +85 -0
- openocr_mlt/geometry.py +37 -0
- openocr_mlt/model_assets.py +34 -0
- openocr_mlt/model_fetch.py +144 -0
- openocr_mlt/onnx_runtime.py +74 -0
- openocr_mlt/processing.py +82 -0
- openocr_mlt/recognition/__init__.py +1 -0
- openocr_mlt/recognition/cuda_stream.py +39 -0
- openocr_mlt/recognition/modeling.py +777 -0
- openocr_mlt/recognition/onnx_backend.py +100 -0
- openocr_mlt/recognition/partitioned_prefill.py +67 -0
- openocr_mlt/recognition/postprocess.py +87 -0
- openocr_mlt/recognition/torch_backend.py +31 -0
- openocr_mlt/types.py +71 -0
- openocr_mlt-0.3.0.dist-info/METADATA +149 -0
- openocr_mlt-0.3.0.dist-info/RECORD +36 -0
- openocr_mlt-0.3.0.dist-info/WHEEL +5 -0
- openocr_mlt-0.3.0.dist-info/entry_points.txt +2 -0
- openocr_mlt-0.3.0.dist-info/top_level.txt +1 -0
openocr_mlt/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Offline, serial, FP32 ScriptMoE recognition."""
|
|
2
|
+
from .api import OpenOCRMLT
|
|
3
|
+
from .config import RecognitionConfig
|
|
4
|
+
from .types import RecognitionResult, DetectionResult, OCRLine, OCRResult
|
|
5
|
+
from .detection import TextDetector, DetectionConfig
|
|
6
|
+
|
|
7
|
+
__version__ = "0.3.0"
|
|
8
|
+
__all__ = ["OpenOCRMLT", "RecognitionConfig", "RecognitionResult", "DetectionResult", "OCRLine", "OCRResult", "TextDetector", "DetectionConfig"]
|
|
9
|
+
|
|
10
|
+
from .model_fetch import fetch_models
|
|
11
|
+
__all__.append("fetch_models")
|
openocr_mlt/__main__.py
ADDED
openocr_mlt/api.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""ScriptMoE recognition, optionally composed with pinned ONNX detection."""
|
|
2
|
+
import os
|
|
3
|
+
from dataclasses import asdict
|
|
4
|
+
from PIL import Image
|
|
5
|
+
import numpy as np
|
|
6
|
+
from .assets import resolve_assets
|
|
7
|
+
from .config import MODEL_ID, RecognitionConfig, resolve_device
|
|
8
|
+
from .processing import read_image, resize_max_side
|
|
9
|
+
from .types import RecognitionResult, DetectionResult, OCRLine, OCRResult
|
|
10
|
+
from .geometry import crop_quad
|
|
11
|
+
|
|
12
|
+
class OpenOCRMLT:
|
|
13
|
+
def __init__(self, *, model_dir=None, weights_path=None, character_dict_path=None,
|
|
14
|
+
rec_backend="onnx", rec_device=None, rec_batch_size=1,
|
|
15
|
+
offline=True, preset="default", rec_threshold=None, max_side=None,
|
|
16
|
+
det_backend=None, det_model_dir=None,
|
|
17
|
+
det_model_id="ppocrv6-medium-det-bce-v1", det_device=None,
|
|
18
|
+
det_config=None, page_max_side=0, device=None, threads=4):
|
|
19
|
+
if rec_backend not in ("torch","onnx"):
|
|
20
|
+
raise ValueError("rec_backend must be torch or onnx")
|
|
21
|
+
if isinstance(threads,bool) or not isinstance(threads,int) or threads<1:
|
|
22
|
+
raise ValueError("threads must be a positive integer")
|
|
23
|
+
common = device if device is not None else "cpu"
|
|
24
|
+
rec_device = common if rec_device is None else rec_device
|
|
25
|
+
det_device = common if det_device is None else det_device
|
|
26
|
+
if det_backend is None and det_model_dir is not None:
|
|
27
|
+
det_backend = "onnx"
|
|
28
|
+
self.backend=rec_backend
|
|
29
|
+
self._closed=False
|
|
30
|
+
if rec_batch_size != 1:
|
|
31
|
+
raise ValueError("Only rec_batch_size=1 is supported; lists are processed serially")
|
|
32
|
+
if offline is not True:
|
|
33
|
+
raise ValueError("Only offline=True is supported; no online downloader is implemented")
|
|
34
|
+
if det_backend not in (None,"onnx"):
|
|
35
|
+
raise ValueError("det_backend must be None or 'onnx'")
|
|
36
|
+
if det_backend is None and det_model_dir is not None:
|
|
37
|
+
raise ValueError("det_model_dir requires det_backend='onnx'")
|
|
38
|
+
if det_backend=="onnx" and det_model_dir is None:
|
|
39
|
+
raise ValueError("det_backend='onnx' requires det_model_dir")
|
|
40
|
+
if isinstance(page_max_side,bool) or not isinstance(page_max_side,int) or page_max_side<0:
|
|
41
|
+
raise ValueError("page_max_side must be an integer >=0")
|
|
42
|
+
self.config = RecognitionConfig.from_preset(preset, rec_threshold=rec_threshold, max_side=max_side)
|
|
43
|
+
self.page_max_side=page_max_side
|
|
44
|
+
self._detector=None
|
|
45
|
+
if rec_backend=="torch":
|
|
46
|
+
from .recognition.torch_backend import TorchRecognizer
|
|
47
|
+
self.device=resolve_device(rec_device)
|
|
48
|
+
weights,dictionary=resolve_assets(model_dir,weights_path,character_dict_path)
|
|
49
|
+
self._recognizer=TorchRecognizer(weights,dictionary,self.device,self.config)
|
|
50
|
+
self.model_id=MODEL_ID
|
|
51
|
+
else:
|
|
52
|
+
if weights_path is not None:
|
|
53
|
+
raise ValueError("ONNX requires model_dir, not a safetensors weights_path")
|
|
54
|
+
from .recognition.onnx_backend import OnnxRecognizer
|
|
55
|
+
self._recognizer=OnnxRecognizer(model_dir,character_dict_path,rec_device,self.config,threads)
|
|
56
|
+
self.device=self._recognizer.device
|
|
57
|
+
self.model_id=self._recognizer.model_id
|
|
58
|
+
if det_backend=="onnx" and det_config is None:
|
|
59
|
+
from .detection import DetectionConfig
|
|
60
|
+
det_config=DetectionConfig(intra_threads=threads)
|
|
61
|
+
if det_backend=="onnx":
|
|
62
|
+
from .detection import TextDetector
|
|
63
|
+
self._detector=TextDetector(det_model_dir,model_id=det_model_id,device=det_device,config=det_config,offline=offline)
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def info(self):
|
|
67
|
+
info={"model_id":self.model_id,"backend":self.backend,"device":str(self.device),
|
|
68
|
+
"precision":"float32","effective_batch_size":1,"offline":True,"config":asdict(self.config)}
|
|
69
|
+
if self._detector is not None:
|
|
70
|
+
info["detector"]=self._detector.info
|
|
71
|
+
info["page_max_side"]=self.page_max_side
|
|
72
|
+
if self.backend=="onnx": info["recognizer"]=self._recognizer.info
|
|
73
|
+
return info
|
|
74
|
+
|
|
75
|
+
def _recognize_loaded(self,image,*,resize):
|
|
76
|
+
if self._closed: raise RuntimeError("OpenOCRMLT is closed")
|
|
77
|
+
height,width=image.shape[:2]
|
|
78
|
+
if resize:
|
|
79
|
+
image=resize_max_side(image,self.config.max_side)
|
|
80
|
+
raw,score,termination,steps=self._recognizer.recognize(image)
|
|
81
|
+
accepted=score>=self.config.rec_threshold
|
|
82
|
+
status="empty" if not raw else ("ok" if accepted else "filtered")
|
|
83
|
+
return RecognitionResult(raw if accepted else "",raw,score,status,termination,steps,
|
|
84
|
+
(width,height),self.model_id,self.backend,str(self.device))
|
|
85
|
+
|
|
86
|
+
def recognize(self,images,*,color_order="BGR"):
|
|
87
|
+
"""Unchanged rec-only semantics, independent of any configured detector."""
|
|
88
|
+
if isinstance(images,(str,os.PathLike,Image.Image,np.ndarray)):
|
|
89
|
+
images=[images]
|
|
90
|
+
if not isinstance(images,(list,tuple)):
|
|
91
|
+
raise TypeError("images must be an image or a list/tuple of images")
|
|
92
|
+
loaded=[read_image(image,color_order) for image in images]
|
|
93
|
+
return [self._recognize_loaded(image,resize=True) for image in loaded]
|
|
94
|
+
|
|
95
|
+
def _detect_page(self,image):
|
|
96
|
+
if self._detector is None:
|
|
97
|
+
raise RuntimeError("Configure det_backend='onnx' and det_model_dir before detect/ocr")
|
|
98
|
+
height,width=image.shape[:2]
|
|
99
|
+
working=resize_max_side(image,self.page_max_side)
|
|
100
|
+
detections=self._detector._detect_bgr(working)
|
|
101
|
+
sy,sx=height/working.shape[0],width/working.shape[1]
|
|
102
|
+
mapped=[DetectionResult(tuple((min(max(x*sx,0.0),float(width)),min(max(y*sy,0.0),float(height))) for x,y in d.points),
|
|
103
|
+
d.score,d.source_id,d.model_id,d.backend,d.device) for d in detections]
|
|
104
|
+
return working,detections,mapped
|
|
105
|
+
|
|
106
|
+
def detect(self,image,*,color_order="BGR"):
|
|
107
|
+
_,_,mapped=self._detect_page(read_image(image,color_order))
|
|
108
|
+
return mapped
|
|
109
|
+
|
|
110
|
+
def ocr(self,image,*,color_order="BGR"):
|
|
111
|
+
bgr=read_image(image,color_order)
|
|
112
|
+
height,width=bgr.shape[:2]
|
|
113
|
+
working,detections,mapped=self._detect_page(bgr)
|
|
114
|
+
items=[]
|
|
115
|
+
for detection,original in zip(detections,mapped):
|
|
116
|
+
crop,error=crop_quad(working,detection.points)
|
|
117
|
+
if error is not None:
|
|
118
|
+
items.append(OCRLine(original,None,error))
|
|
119
|
+
else:
|
|
120
|
+
# Legacy whole-page pipeline resizes the page, not every crop.
|
|
121
|
+
result=self._recognize_loaded(crop,resize=False)
|
|
122
|
+
items.append(OCRLine(original,result,None))
|
|
123
|
+
return OCRResult((width,height),tuple(items),self.info)
|
|
124
|
+
|
|
125
|
+
def __call__(self,image,*,color_order="BGR"):
|
|
126
|
+
"""One crop without a detector; one page with an explicitly configured detector."""
|
|
127
|
+
if getattr(self,"_detector",None) is not None:
|
|
128
|
+
return self.ocr(image,color_order=color_order)
|
|
129
|
+
return self.recognize([image],color_order=color_order)[0]
|
|
130
|
+
|
|
131
|
+
def close(self):
|
|
132
|
+
if self._detector is not None:
|
|
133
|
+
self._detector.close()
|
|
134
|
+
if self._recognizer is not None:
|
|
135
|
+
if hasattr(self._recognizer,"close"):self._recognizer.close()
|
|
136
|
+
elif hasattr(self._recognizer,"model"):self._recognizer.model=None
|
|
137
|
+
self._closed=True
|
|
138
|
+
|
|
139
|
+
def __enter__(self):
|
|
140
|
+
if self._closed:raise RuntimeError("OpenOCRMLT is closed")
|
|
141
|
+
return self
|
|
142
|
+
|
|
143
|
+
def __exit__(self,*args):
|
|
144
|
+
self.close()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Third-Party Model Sources and Licensing
|
|
2
|
+
|
|
3
|
+
## ScriptMoE Recognition
|
|
4
|
+
|
|
5
|
+
Recognition models are derived from [ScriptMoE](https://github.com/YesianRohn/ScriptMoE) at upstream revision `5dde99867aaee6b4af3e75a3020b225eab17ac53`. ONNX conversion preserves the character dictionary and autoregressive decoding rules and uses KV caching and optimized MoE execution.
|
|
6
|
+
|
|
7
|
+
| Artifact | SHA256 |
|
|
8
|
+
|---|---|
|
|
9
|
+
| Original recognition weights | 60c74a924404b652c3a9550ec370c74d78850151785341729bdac268c014650d |
|
|
10
|
+
| Character dictionary | 7b4eb2ca5fd6c9b6c6ecf423f69d35d8da48088697303e53624af196ee9ab6c0 |
|
|
11
|
+
|
|
12
|
+
## PP-OCRv6 Medium Detection
|
|
13
|
+
|
|
14
|
+
The detector is distributed by Paddle as [PP-OCRv6_medium_det_onnx.tar](https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/tmp/PP-OCRv6_medium_det_onnx.tar).
|
|
15
|
+
|
|
16
|
+
| Property | Value |
|
|
17
|
+
|---|---|
|
|
18
|
+
| File | inference.onnx |
|
|
19
|
+
| Size | 62,026,563 bytes |
|
|
20
|
+
| SHA256 | a0d14865d3a297970899b5c246f0e47c43879bb406c3a1fa4e505aba84b134e4 |
|
|
21
|
+
|
|
22
|
+
The accompanying Python package adapts detection preprocessing and postprocessing from [ppocrv6_onnx](https://github.com/AIwork4me/ppocrv6_onnx), revision `d638eb0bb5d8222522abcc1bae4394cbeeb636f0`, under its MIT license. The package includes the license text for that code.
|
|
23
|
+
|
|
24
|
+
## Licensing Status
|
|
25
|
+
|
|
26
|
+
A unified redistribution license has not been specified for this combined model bundle. The MIT license of the detection adapter applies to that code; model weights have separate licensing requirements.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"models": {
|
|
4
|
+
"ppocrv6-tiny-det-bce-v1": {
|
|
5
|
+
"filename": "inference.onnx",
|
|
6
|
+
"size": 1780621,
|
|
7
|
+
"sha256": "a56a3430a96a6c691f8bfbbb297208bb9573c3d70083d6382071cbd92d0a152e",
|
|
8
|
+
"source_url": "https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/tmp/PP-OCRv6_tiny_det_onnx.tar",
|
|
9
|
+
"source_revision": null,
|
|
10
|
+
"artifact_etag": "700445c8a6ebcaeac88d4622ddb68dba",
|
|
11
|
+
"runtime_profile": "ppocrv6-reference-v1",
|
|
12
|
+
"config_sha256": "626e4e3b45bd2f943d2282114b461ef5804f4620ae54d7ff7ff9fb6365f3a608",
|
|
13
|
+
"license_status": "official family model card states Apache-2.0; BCE binary redistribution not independently resolved"
|
|
14
|
+
},
|
|
15
|
+
"ppocrv5-server-det-hf-v1": {
|
|
16
|
+
"filename": "inference.onnx",
|
|
17
|
+
"size": 88116791,
|
|
18
|
+
"sha256": "10803475a591f7dc623e24670fb5752ec94d39a1f8cf069aac1b6f0ce19cfc85",
|
|
19
|
+
"source_url": "https://huggingface.co/PaddlePaddle/PP-OCRv5_server_det_onnx",
|
|
20
|
+
"source_revision": "dcf248c4dcc064c6d030db241255f9e8fbc6733a",
|
|
21
|
+
"runtime_profile": "ppocrv6-reference-v1",
|
|
22
|
+
"license_status": "official model card Apache-2.0; no public release in this task"
|
|
23
|
+
},
|
|
24
|
+
"ppocrv6-medium-det-bce-v1": {
|
|
25
|
+
"name": "inference.onnx",
|
|
26
|
+
"size": 62026563,
|
|
27
|
+
"sha256": "a0d14865d3a297970899b5c246f0e47c43879bb406c3a1fa4e505aba84b134e4",
|
|
28
|
+
"filename": "inference.onnx",
|
|
29
|
+
"source_url": "https://paddle-model-ecology.bj.bcebos.com/paddlex/official_inference_model/paddle3.0.0/tmp/PP-OCRv6_medium_det_onnx.tar",
|
|
30
|
+
"source_revision": null,
|
|
31
|
+
"source_etag": "\"33e0718cf119c8fcc545eb249bc69e06\"",
|
|
32
|
+
"archive_sha256": "46849788192d7882e52440d81cdfa437156f433368411c04b832ffa5433e5418",
|
|
33
|
+
"runtime_profile": "ppocrv6-reference-v1",
|
|
34
|
+
"license_status": "Source attribution preserved; no public redistribution performed"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|