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.
@@ -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")
@@ -0,0 +1,2 @@
1
+ from .cli import main
2
+ raise SystemExit(main())
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
+ }