KroZenDev commited on
Commit
9593864
·
verified ·
1 Parent(s): 1887b4c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +41 -9
app.py CHANGED
@@ -1,9 +1,12 @@
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)
@@ -13,6 +16,12 @@ os.environ["MKL_NUM_THREADS"] = "2"
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)
@@ -27,14 +36,24 @@ def download_file(repo_id, filename, local_dir):
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
@@ -61,7 +80,10 @@ try:
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
@@ -71,7 +93,6 @@ 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"),
@@ -82,7 +103,6 @@ except TypeError:
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()
@@ -95,7 +115,6 @@ except TypeError:
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):
@@ -107,7 +126,6 @@ except TypeError:
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"""
@@ -138,7 +156,7 @@ Det:
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
@@ -214,10 +232,21 @@ HTML_FORM = """<!DOCTYPE html>
214
  </body>
215
  </html>"""
216
 
 
217
  @app.get("/", response_class=HTMLResponse)
218
  def read_root():
219
  return HTML_FORM
220
 
 
 
 
 
 
 
 
 
 
 
221
  @app.post("/predict")
222
  async def predict(file: UploadFile = File(...)):
223
  try:
@@ -228,7 +257,10 @@ async def predict(file: UploadFile = File(...)):
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()
 
1
  import os
2
  import io
3
+ import time
4
+ import asyncio
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 starlette.concurrency import run_in_threadpool
10
  from huggingface_hub import hf_hub_download
11
 
12
  # Ограничиваем треды ONNX Runtime (на HF Spaces CPU Basic всего 2 vCPU)
 
16
  app = FastAPI()
17
  MODEL_DIR = os.path.join(os.path.dirname(__file__), "models")
18
 
19
+ # Ограничиваем реальную конкурентность OCR-вызовов под число vCPU.
20
+ # На 2 vCPU параллельный запуск 2+ OCR одновременно делит ядра между ними
21
+ # и может быть МЕДЛЕННЕЕ, чем строгая очередь — поэтому 1, не 2.
22
+ ocr_semaphore = asyncio.Semaphore(1)
23
+
24
+
25
  def download_file(repo_id, filename, local_dir):
26
  """Скачивает файл с HF Hub, если его нет локально."""
27
  local_path = os.path.join(local_dir, filename)
 
36
  print(f" Failed: {e}")
37
  return None
38
 
39
+
40
  def ensure_models():
41
+ """
42
+ Скачивает det (mobile, лёгкий ~4.8 МБ вместо server-варианта ~88 МБ),
43
+ rec и dict для русского+английского (eslav покрывает кириллицу).
44
+ """
45
  os.makedirs(MODEL_DIR, exist_ok=True)
46
+
47
+ # Детекция language-agnostic — просто находит текстовые блоки,
48
+ # поэтому mobile-вариант той же PP-OCRv5 серии работает для любого языка.
49
+ det = download_file("ilaylow/PP_OCRv5_mobile_onnx", "ppocrv5_det.onnx", MODEL_DIR)
50
+
51
+ # Рекогнишн оставляем eslav — обучена именно под кириллицу + латиницу
52
  rec = download_file("monkt/paddleocr-onnx", "languages/eslav/rec.onnx", MODEL_DIR)
53
  dict_path = download_file("monkt/paddleocr-onnx", "languages/eslav/dict.txt", MODEL_DIR)
54
  return det, rec, dict_path
55
 
56
+
57
  det_path, rec_path, dict_path = ensure_models()
58
 
59
  from rapidocr_onnxruntime import RapidOCR
 
80
  ocr.text_detector.postprocess_op.thresh = 0.5
81
  ocr.text_detector.postprocess_op.use_dilation = False
82
  ocr.text_detector.postprocess_op.score_mode = "fast"
83
+ # Меньше кандидатов для NMS — на скрине редко >10-15 текстовых блоков
84
+ if hasattr(ocr.text_detector.postprocess_op, "max_candidates"):
85
+ ocr.text_detector.postprocess_op.max_candidates = 100
86
+ print(" Patched: thresh=0.5, use_dilation=False, score_mode=fast, max_candidates=100")
87
 
88
  if hasattr(ocr, "text_recognizer"):
89
  ocr.text_recognizer.rec_batch_num = 1
 
93
  # === Попытка 2: старая версия, нужен полный config.yaml ===
94
  print("kwargs not supported, using full config.yaml")
95
 
 
96
  cls_path = None
97
  for repo, path in [
98
  ("RapidAI/RapidOCR", "onnx/PP-OCRv4/cls/ch_ppocr_mobile_v2.0_cls_infer.onnx"),
 
103
  if cls_path:
104
  break
105
 
 
106
  if not cls_path:
107
  print("Downloading standard models via RapidOCR()...")
108
  temp_ocr = RapidOCR()
 
115
  break
116
  if cls_path:
117
  break
 
118
  if not cls_path:
119
  cache_dir = os.path.expanduser("~/.cache/rapidocr_onnxruntime")
120
  if os.path.exists(cache_dir):
 
126
  if cls_path:
127
  break
128
 
 
129
  config_path = os.path.join(os.path.dirname(__file__), "config.yaml")
130
  with open(config_path, "w") as f:
131
  f.write(f"""
 
156
  post_process:
157
  thresh: 0.5
158
  box_thresh: 0.5
159
+ max_candidates: 100
160
  unclip_ratio: 1.6
161
  use_dilation: false
162
  score_mode: fast
 
232
  </body>
233
  </html>"""
234
 
235
+
236
  @app.get("/", response_class=HTMLResponse)
237
  def read_root():
238
  return HTML_FORM
239
 
240
+
241
+ def _run_ocr_sync(img_array):
242
+ """Синхронный вызов OCR — выполняет��я в threadpool, не блокирует event loop."""
243
+ t0 = time.time()
244
+ results, elapse_list = ocr(img_array)
245
+ total = time.time() - t0
246
+ print(f"[TIMING] total={total:.2f}s breakdown(det,cls,rec)={elapse_list}")
247
+ return results
248
+
249
+
250
  @app.post("/predict")
251
  async def predict(file: UploadFile = File(...)):
252
  try:
 
257
  if image.width > 480 or image.height > 480:
258
  image.thumbnail((480, 480))
259
  img_array = np.array(image)
260
+
261
+ async with ocr_semaphore:
262
+ results = await run_in_threadpool(_run_ocr_sync, img_array)
263
+
264
  if not results:
265
  return {"text": ""}
266
  text = " ".join([item[1] for item in results]).strip()