KroZenDev commited on
Commit
42c4d70
·
verified ·
1 Parent(s): cac3832

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +152 -76
app.py CHANGED
@@ -1,95 +1,177 @@
1
  import os
2
  import io
3
- import tempfile
4
- import yaml
5
  import numpy as np
6
  from PIL import Image
7
  from fastapi import FastAPI, UploadFile, File
8
  from fastapi.responses import HTMLResponse
9
  from huggingface_hub import hf_hub_download
10
 
11
- # === Ограничиваем треды ONNX Runtime ДО импорта rapidocr ===
12
- # На HF Spaces CPU Basic всего 2 vCPU, лишние треды только мешают
13
  os.environ["OMP_NUM_THREADS"] = "2"
14
  os.environ["MKL_NUM_THREADS"] = "2"
15
 
16
- from rapidocr_onnxruntime import RapidOCR
17
-
18
  app = FastAPI()
19
  MODEL_DIR = os.path.join(os.path.dirname(__file__), "models")
20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  def ensure_models():
22
- """Скачивает модели при первом запуске, если их нет локально."""
23
  os.makedirs(MODEL_DIR, exist_ok=True)
24
- repo_id = "monkt/paddleocr-onnx"
25
- files = [
26
- "detection/v5/det.onnx",
27
- "languages/eslav/rec.onnx",
28
- "languages/eslav/dict.txt",
29
- ]
30
- for remote_path in files:
31
- local_path = os.path.join(MODEL_DIR, remote_path)
32
- if not os.path.exists(local_path):
33
- print(f"Downloading {remote_path}...")
34
- hf_hub_download(repo_id=repo_id, filename=remote_path, local_dir=MODEL_DIR)
35
- print(f" Saved {remote_path}")
36
- return {
37
- "det": os.path.join(MODEL_DIR, "detection/v5/det.onnx"),
38
- "rec": os.path.join(MODEL_DIR, "languages/eslav/rec.onnx"),
39
- "dict": os.path.join(MODEL_DIR, "languages/eslav/dict.txt"),
40
- }
41
-
42
- model_paths = ensure_models()
43
-
44
- # === Кастомный config для максимальной скорости на CPU ===
45
- config = {
46
- "Global": {
47
- "text_score": 0.5,
48
- "use_angle_cls": False,
49
- "use_text_det": True,
50
- "print_verbose": False,
51
- "min_height": 30,
52
- },
53
- "Det": {
54
- "model_path": model_paths["det"],
55
- "limit_side_len": 480, # ← меньше пикселей = быстрее детекция
56
- "limit_type": "max", # ← не растягивает маленькие, сжимает большие
57
- "thresh": 0.5, # ← меньше шумовых областей
58
- "box_thresh": 0.5,
59
- "max_candidates": 1000,
60
- "unclip_ratio": 1.6,
61
- "use_dilation": False, # ← отключено, дилатация тормозит на CPU
62
- "score_mode": "fast",
63
- },
64
- "Rec": {
65
- "model_path": model_paths["rec"],
66
- "keys_path": model_paths["dict"],
67
- "img_shape": [3, 48, 320],
68
- "batch_num": 1, # ← на CPU батчинг не ускоряет, а замедляет
69
- },
70
- "Cls": {
71
- "model_path": "",
72
- "img_shape": [3, 48, 192],
73
- "label_list": ["0", "180"],
74
- "batch_num": 1,
75
- "thresh": 0.9,
76
- },
77
- }
78
-
79
- with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
80
- yaml.dump(config, f, default_flow_style=False)
81
- config_path = f.name
82
-
83
- ocr = RapidOCR(config_path=config_path)
84
-
85
- # === Warm-up: разогреваем ONNX Runtime ===
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  print("Warming up OCR...")
87
  warmup_img = np.zeros((480, 480, 3), dtype=np.uint8)
88
  try:
89
  _ = ocr(warmup_img)
90
  except Exception:
91
  pass
92
- print("Warm-up done!")
93
 
94
  HTML_FORM = """<!DOCTYPE html>
95
  <html>
@@ -141,20 +223,14 @@ async def predict(file: UploadFile = File(...)):
141
  try:
142
  contents = await file.read()
143
  image = Image.open(io.BytesIO(contents))
144
-
145
  if image.mode != "RGB":
146
  image = image.convert("RGB")
147
-
148
- # Оптимизация: уменьшаем до 480px по длинной стороне
149
  if image.width > 480 or image.height > 480:
150
  image.thumbnail((480, 480))
151
-
152
  img_array = np.array(image)
153
  results, _ = ocr(img_array)
154
-
155
  if not results:
156
  return {"text": ""}
157
-
158
  text = " ".join([item[1] for item in results]).strip()
159
  return {"text": text}
160
  except Exception as e:
 
1
  import os
2
  import io
 
 
3
  import numpy as np
4
  from PIL import Image
5
  from fastapi import FastAPI, UploadFile, File
6
  from fastapi.responses import HTMLResponse
7
  from huggingface_hub import hf_hub_download
8
 
9
+ # Ограничиваем треды ONNX Runtime (на HF Spaces CPU Basic всего 2 vCPU)
 
10
  os.environ["OMP_NUM_THREADS"] = "2"
11
  os.environ["MKL_NUM_THREADS"] = "2"
12
 
 
 
13
  app = FastAPI()
14
  MODEL_DIR = os.path.join(os.path.dirname(__file__), "models")
15
 
16
+ def download_file(repo_id, filename, local_dir):
17
+ """Скачивает файл с HF Hub, если его нет локально."""
18
+ local_path = os.path.join(local_dir, filename)
19
+ if os.path.exists(local_path):
20
+ return local_path
21
+ try:
22
+ print(f"Downloading {repo_id}/{filename}...")
23
+ hf_hub_download(repo_id=repo_id, filename=filename, local_dir=local_dir)
24
+ print(f" Saved to {local_path}")
25
+ return local_path
26
+ except Exception as e:
27
+ print(f" Failed: {e}")
28
+ return None
29
+
30
  def ensure_models():
31
+ """Скачивает det, rec, dict. Cls скачаем отдельно если нужен config."""
32
  os.makedirs(MODEL_DIR, exist_ok=True)
33
+ det = download_file("monkt/paddleocr-onnx", "detection/v5/det.onnx", MODEL_DIR)
34
+ rec = download_file("monkt/paddleocr-onnx", "languages/eslav/rec.onnx", MODEL_DIR)
35
+ dict_path = download_file("monkt/paddleocr-onnx", "languages/eslav/dict.txt", MODEL_DIR)
36
+ return det, rec, dict_path
37
+
38
+ det_path, rec_path, dict_path = ensure_models()
39
+
40
+ from rapidocr_onnxruntime import RapidOCR
41
+
42
+ # === Попытка 1: использовать kwargs (новая версия 1.3.x) ===
43
+ try:
44
+ ocr = RapidOCR(
45
+ det_model_path=det_path,
46
+ rec_model_path=rec_path,
47
+ rec_keys_path=dict_path,
48
+ cls=False,
49
+ )
50
+ print("✓ RapidOCR initialized with kwargs")
51
+
52
+ # Monkey-patch параметров детекции для ускорения
53
+ if hasattr(ocr, "text_detector") and hasattr(ocr.text_detector, "preprocess_op"):
54
+ for op in ocr.text_detector.preprocess_op:
55
+ if op.__class__.__name__ == "DetResizeForTest":
56
+ op.limit_side_len = 480
57
+ op.limit_type = "max"
58
+ print(" Patched: limit_side_len=480, limit_type=max")
59
+
60
+ if hasattr(ocr, "text_detector") and hasattr(ocr.text_detector, "postprocess_op"):
61
+ ocr.text_detector.postprocess_op.thresh = 0.5
62
+ ocr.text_detector.postprocess_op.use_dilation = False
63
+ ocr.text_detector.postprocess_op.score_mode = "fast"
64
+ print(" Patched: thresh=0.5, use_dilation=False, score_mode=fast")
65
+
66
+ if hasattr(ocr, "text_recognizer"):
67
+ ocr.text_recognizer.rec_batch_num = 1
68
+ print(" Patched: rec_batch_num=1")
69
+
70
+ except TypeError:
71
+ # === Попытка 2: старая версия, нужен полный config.yaml ===
72
+ print("kwargs not supported, using full config.yaml")
73
+
74
+ # Скачиваем cls модель из нескольких возможных источников
75
+ cls_path = None
76
+ for repo, path in [
77
+ ("RapidAI/RapidOCR", "onnx/PP-OCRv4/cls/ch_ppocr_mobile_v2.0_cls_infer.onnx"),
78
+ ("RapidAI/RapidOCR", "onnx/PP-OCRv3/cls/ch_ppocr_mobile_v2.0_cls_infer.onnx"),
79
+ ("RapidAI/RapidOCR", "resources/models/ch_ppocr_mobile_v2.0_cls_infer.onnx"),
80
+ ]:
81
+ cls_path = download_file(repo, path, MODEL_DIR)
82
+ if cls_path:
83
+ break
84
+
85
+ # Если не нашли — запускаем стандартный RapidOCR, чтобы он скачал модели в кэш
86
+ if not cls_path:
87
+ print("Downloading standard models via RapidOCR()...")
88
+ temp_ocr = RapidOCR()
89
+ import rapidocr_onnxruntime
90
+ pkg_dir = os.path.dirname(rapidocr_onnxruntime.__file__)
91
+ for root, dirs, files in os.walk(pkg_dir):
92
+ for f in files:
93
+ if "cls" in f and f.endswith(".onnx"):
94
+ cls_path = os.path.join(root, f)
95
+ break
96
+ if cls_path:
97
+ break
98
+ # Ищем в кэше
99
+ if not cls_path:
100
+ cache_dir = os.path.expanduser("~/.cache/rapidocr_onnxruntime")
101
+ if os.path.exists(cache_dir):
102
+ for root, dirs, files in os.walk(cache_dir):
103
+ for f in files:
104
+ if "cls" in f and f.endswith(".onnx"):
105
+ cls_path = os.path.join(root, f)
106
+ break
107
+ if cls_path:
108
+ break
109
+
110
+ # Создаём полный config.yaml со ВСЕМИ обязательными полями
111
+ config_path = os.path.join(os.path.dirname(__file__), "config.yaml")
112
+ with open(config_path, "w") as f:
113
+ f.write(f"""
114
+ Global:
115
+ text_score: 0.5
116
+ use_angle_cls: false
117
+ print_verbose: false
118
+ min_height: 30
119
+ width_height_ratio: 8
120
+
121
+ Det:
122
+ module_name: ch_ppocr_v3_det
123
+ class_name: TextDetector
124
+ model_path: {det_path}
125
+ use_cuda: false
126
+ pre_process:
127
+ DetResizeForTest:
128
+ limit_side_len: 480
129
+ limit_type: max
130
+ NormalizeImage:
131
+ std: [0.229, 0.224, 0.225]
132
+ mean: [0.485, 0.456, 0.406]
133
+ scale: 1./255.
134
+ order: hwc
135
+ ToCHWImage:
136
+ KeepKeys:
137
+ keep_keys: ['image', 'shape']
138
+ post_process:
139
+ thresh: 0.5
140
+ box_thresh: 0.5
141
+ max_candidates: 1000
142
+ unclip_ratio: 1.6
143
+ use_dilation: false
144
+ score_mode: fast
145
+
146
+ Cls:
147
+ module_name: ch_ppocr_v2_cls
148
+ class_name: TextClassifier
149
+ model_path: {cls_path or det_path}
150
+ cls_img_shape: [3, 48, 192]
151
+ cls_batch_num: 1
152
+ cls_thresh: 0.9
153
+ label_list: [0, 180]
154
+
155
+ Rec:
156
+ module_name: ch_ppocr_v2_rec
157
+ class_name: TextRecognizer
158
+ model_path: {rec_path}
159
+ rec_img_shape: [3, 48, 320]
160
+ rec_batch_num: 1
161
+ keys_path: {dict_path}
162
+ """)
163
+
164
+ ocr = RapidOCR(config_path=config_path)
165
+ print("✓ RapidOCR initialized with config.yaml")
166
+
167
+ # Warm-up: разогреваем ONNX Runtime
168
  print("Warming up OCR...")
169
  warmup_img = np.zeros((480, 480, 3), dtype=np.uint8)
170
  try:
171
  _ = ocr(warmup_img)
172
  except Exception:
173
  pass
174
+ print("Ready!")
175
 
176
  HTML_FORM = """<!DOCTYPE html>
177
  <html>
 
223
  try:
224
  contents = await file.read()
225
  image = Image.open(io.BytesIO(contents))
 
226
  if image.mode != "RGB":
227
  image = image.convert("RGB")
 
 
228
  if image.width > 480 or image.height > 480:
229
  image.thumbnail((480, 480))
 
230
  img_array = np.array(image)
231
  results, _ = ocr(img_array)
 
232
  if not results:
233
  return {"text": ""}
 
234
  text = " ".join([item[1] for item in results]).strip()
235
  return {"text": text}
236
  except Exception as e: