Asem75 commited on
Commit
654ddda
·
verified ·
1 Parent(s): 8a989ee

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +150 -84
app.py CHANGED
@@ -2,6 +2,7 @@
2
  # -*- coding: utf-8 -*-
3
  """
4
  Sapphire OCR Engine - Ultra Safe Speed Route
 
5
  """
6
 
7
  import os
@@ -12,7 +13,16 @@ import logging
12
  import threading
13
  from PIL import Image
14
 
15
- import cv2
 
 
 
 
 
 
 
 
 
16
  import torch
17
  import numpy as np
18
  import gradio as gr
@@ -29,7 +39,9 @@ UPLOAD_FOLDER = "upload_tmp"
29
  os.makedirs(UPLOAD_FOLDER, exist_ok=True)
30
 
31
  processing_lock = threading.Lock()
32
- GOLDEN_ENGINE_API_URL = "https://asem75-aiarabicengen.hf.space/api/predict"
 
 
33
 
34
  ocr_stats = {
35
  "status": "🟢 المحرك مستقر تماماً وبانتظار البيانات...",
@@ -39,85 +51,131 @@ ocr_stats = {
39
  "micro_logs": "🚀 السيرفر جاهز لاستقبال نبضات الرام والخزنة بالتتابع السريع..."
40
  }
41
 
42
- # تفعيل التحميل الذكي (Lazy Loading) لحماية المعالج والرام من الاختناق
 
 
43
  _ocr_reader = None
 
44
  def get_ocr_reader():
45
  global _ocr_reader
46
  if _ocr_reader is None:
47
  import easyocr
48
- logger.info("📥 [Lazy Load] جاري شحن محرك EasyOCR في الرام الآن...")
49
  _ocr_reader = easyocr.Reader(['ar', 'en'], gpu=torch.cuda.is_available())
50
  return _ocr_reader
51
 
 
 
 
52
  def advanced_image_filter_from_matrix(img_np: np.ndarray) -> np.ndarray:
 
 
 
 
53
  try:
54
  blurred = cv2.GaussianBlur(img_np, (3, 3), 0)
55
- return cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
56
- except Exception: return img_np
 
 
 
 
 
 
57
 
58
- # الدالة المركزية لمعالجة المستندات صفحة بصفحة لحماية الـ CPU من التجمد
 
 
59
  def core_ocr_and_forward_to_golden(filename: str, file_path: str, is_vip: bool = False):
60
  global ocr_stats
61
- prefix = "👑 [VIP بالرام]" if is_vip else "🐢 [عادي من الخزنة]"
62
  logger.info(f"{prefix} بدء معالجة المستند: {filename}")
63
  ocr_stats["status"] = f"🔎 جاري معالجة {filename}..."
64
-
65
  final_text = ""
66
  try:
67
  reader = get_ocr_reader()
 
 
68
  if filename.lower().endswith('.pdf'):
69
  doc = fitz.open(file_path)
70
  native_text = ""
 
71
  for page in doc:
72
  t = page.get_text()
73
- if t and len(t.strip()) > 15: native_text += t + "\n"
74
-
 
75
  if native_text.strip():
76
  final_text = native_text
77
  else:
78
- # معالجة صفحة تلو الأخرى مع تنظيف فوري للذاكرة لمنع تعليق اللوج
79
  for page_num in range(len(doc)):
80
  page = doc[page_num]
81
- pix = page.get_pixmap(dpi=100) # تقليل الـ DPI قليلاً ليناسب الـ CPU ومنع الاختناق
82
  img_np = np.frombuffer(pix.samples, dtype=np.uint8).reshape(pix.h, pix.w, pix.n)
83
- open_cv_image = cv2.cvtColor(img_np, cv2.COLOR_RGB2GRAY) if pix.n >= 3 else img_np
84
- cleaned_matrix = advanced_image_filter_from_matrix(open_cv_image)
85
- ocr_results = reader.readtext(cleaned_matrix, detail=0)
 
 
 
 
 
86
  final_text += "\n".join(ocr_results) + "\n"
87
- # تنظيف جزئي أثناء المعالجة
88
- del pix, img_np, open_cv_image, cleaned_matrix
89
  gc.collect()
 
90
  doc.close()
 
 
91
  else:
92
  img_pil = Image.open(file_path)
93
- open_cv_image = np.array(img_pil.convert('L'))
94
- cleaned_matrix = advanced_image_filter_from_matrix(open_cv_image)
95
- ocr_results = reader.readtext(cleaned_matrix, detail=0)
96
  final_text += "\n".join(ocr_results)
97
 
98
- # ضخ الناتج فوراً بالرام للمعالج الذهبي
99
  if final_text.strip():
100
  try:
101
- logger.info(f"🚀 إرسال النص المستخرج لـ {filename} بالرام للمعالج الذهبي...")
102
- requests.post(GOLDEN_ENGINE_API_URL, json={"data": [final_text], "fn_index": 2}, timeout=8)
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  except Exception as forward_err:
104
- logger.error(f"⚠️ المعالج الذهبي لم يستجب بعد: {forward_err}")
105
 
106
- # تحديث لوحة التحكم
107
  ocr_stats["total_extracted_words"] += len(final_text.split())
108
  ocr_stats["processed_files_count"] += 1
109
- ocr_stats["last_processed_file"] = f"{prefix} {filename} ({time.strftime('%H:%M:%S')})"
110
- ocr_stats["micro_logs"] = f"✅ تم الانتهاء وضخ بيانات ملف: {filename}"
111
-
112
  return final_text
 
113
  except Exception as e:
114
  logger.error(f"❌ خطأ في معالجة {filename}: {e}")
115
  return None
 
116
  finally:
117
- ocr_stats["status"] = "🟢 المحرك مستقر وبانتظار دفع الملفات..."
118
  gc.collect()
119
 
120
- # خيط الرادار للمجانيين
 
 
121
  def cloud_folder_polling_worker():
122
  REPO_ID = "Asem75/aiocr_asistant"
123
  while True:
@@ -126,85 +184,93 @@ def cloud_folder_polling_worker():
126
  try:
127
  api = HfApi(token=token)
128
  all_files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset")
129
- valid_files = [f for f in all_files if f.lower().startswith("raw_files/") and not f.endswith('/')]
130
-
131
  if valid_files:
132
  target_file = valid_files[0]
133
  with processing_lock:
134
  filename = os.path.basename(target_file)
135
  local_path = os.path.join(UPLOAD_FOLDER, f"radar_{filename}")
136
- downloaded_path = hf_hub_download(repo_id=REPO_ID, filename=target_file, repo_type="dataset", token=token)
137
- shutil.copy2(downloaded_path, local_path)
138
-
139
- output_result = core_ocr_and_forward_to_golden(filename, local_path, is_vip=False)
140
- if output_result is not None:
141
- try:
142
- api.delete_file(path_in_repo=target_file, repo_id=REPO_ID, repo_type="dataset", token=token)
143
- logger.info(f"🗑️ تم تصفية الملف المجاني من الخزنة: {filename}")
144
- except Exception as del_e: logger.error(f"⚠️ فشل حذف السحاب: {del_e}")
145
- if os.path.exists(local_path): os.remove(local_path)
 
146
  except Exception as e:
147
- logger.error(f"⚠️ خطأ دورة الرادار: {e}")
 
148
  time.sleep(6)
149
 
150
- polling_thread = threading.Thread(target=cloud_folder_polling_worker, daemon=True)
151
- polling_thread.start()
152
 
153
- # --- إنشاء تطبيق FastAPI وفتح بوابة صريحة ومباشرة لاستقبال الـ VIP ---
 
 
154
  fastapi_app = FastAPI()
155
 
156
  @fastapi_app.post("/api/predict")
157
  async def handle_vip_direct_network_route(request: Request):
158
- """
159
- بوابة الرام الشريانية المصممة خصيصاً لاستقبال طلبات الـ VIP من واجهة الرفع بدون أي وسيط
160
- """
161
  try:
162
  payload = await request.json()
163
- # فك داتا الـ Base64 للملف القادم من واجهة الرفع مباشرة
164
- file_data_list = payload.get("data", [])
165
- if not file_data_list or not isinstance(file_data_list, list):
166
- return JSONResponse(content={"error": "Invalid payload format"}, status_code=400)
167
-
168
- file_info = file_data_list[0]
169
- filename = file_info.get("name", "vip_document.png")
170
- raw_b64_string = file_info.get("data", "")
171
-
172
- if "base64," in raw_b64_string:
173
- raw_b64_string = raw_b64_string.split("base64,")[1]
174
-
175
  import base64
176
- local_path = os.path.join(UPLOAD_FOLDER, f"network_vip_{filename}")
 
177
  with open(local_path, "wb") as fh:
178
- fh.write(base64.b64decode(raw_b64_string))
179
-
180
- # معالجة فورية وحجز المعالج
181
  with processing_lock:
182
- logger.info("👑 [قناة الرام] استقبال كبسولة الـ VIP مع حجز كامل المعالج للتنفيذ الفوري...")
183
- extracted_text = core_ocr_and_forward_to_golden(filename, local_path, is_vip=True)
184
- if os.path.exists(local_path):
185
- os.remove(local_path)
186
-
187
- return JSONResponse(content={"data": [extracted_text if extracted_text else "❌ فشلت معالجة الـ VIP"]})
188
- except Exception as api_err:
189
- logger.error(f"❌ فشل استقبال حزمة الـ VIP في بوابة الرام: {api_err}")
190
- return JSONResponse(content={"error": str(api_err)}, status_code=500)
191
 
 
 
 
 
 
 
 
192
  def get_dashboard_data():
193
- return (ocr_stats["status"], str(ocr_stats["processed_files_count"]), str(ocr_stats["total_extracted_words"]), ocr_stats["last_processed_file"], ocr_stats["micro_logs"])
 
 
 
 
 
 
194
 
195
  with gr.Blocks(title="Sapphire OCR - Route Management") as demo:
196
- gr.HTML("<div style='text-align:center; padding:10px;'><h1>🔮 Sapphire OCR Engine V2 (منظم مسارات الرام)</h1></div>")
197
- status_box = gr.Textbox(label="📡 حالة السيرفر الحالية", value=ocr_stats["status"], interactive=False)
 
198
  with gr.Row():
199
- count_box = gr.Textbox(label="✅ إجمالي الملفات المعالجة", value="0", interactive=False)
200
- words_box = gr.Textbox(label="🔍 إجمالي الكلمات المستخرجة", value="0", interactive=False)
201
- last_file_box = gr.Textbox(label="📁 المستند الأخير وضخ الرام", value="لا يوجد طلبات بعد", interactive=False)
202
- logs_box = gr.Textbox(label="📝 سجل الحركات الحية للمحرك", value=ocr_stats["micro_logs"], lines=3, interactive=False)
203
- refresh_btn = gr.Button("🔄 تحديث مؤشرات السيرفر")
 
204
  refresh_btn.click(fn=get_dashboard_data, outputs=[status_box, count_box, words_box, last_file_box, logs_box])
205
 
206
  app = gr.mount_gradio_app(fastapi_app, demo, path="/")
207
 
208
  if __name__ == "__main__":
209
  import uvicorn
210
- uvicorn.run(app, host="0.0.0.0", port=7860, log_level="info")
 
2
  # -*- coding: utf-8 -*-
3
  """
4
  Sapphire OCR Engine - Ultra Safe Speed Route
5
+ (مُعدَّل للربط المباشر مع المعالج الذهبي عبر RAM VIP)
6
  """
7
 
8
  import os
 
13
  import threading
14
  from PIL import Image
15
 
16
+ # ============================================================
17
+ # 🔧 إصلاح مشكلة cv2 — تحميل آمن مع بديل تلقائي
18
+ # ============================================================
19
+ try:
20
+ import cv2
21
+ CV2_AVAILABLE = True
22
+ except ImportError:
23
+ CV2_AVAILABLE = False
24
+ print("⚠️ مكتبة cv2 غير مثبتة — سيتم استخدام بديل مبسط تلقائيًا.")
25
+
26
  import torch
27
  import numpy as np
28
  import gradio as gr
 
39
  os.makedirs(UPLOAD_FOLDER, exist_ok=True)
40
 
41
  processing_lock = threading.Lock()
42
+
43
+ # --- رابط المعالج الذهبي ---
44
+ GOLDEN_ENGINE_API_URL = "https://asem75-aiarabicengen.hf.space/api/submit_ocr_data"
45
 
46
  ocr_stats = {
47
  "status": "🟢 المحرك مستقر تماماً وبانتظار البيانات...",
 
51
  "micro_logs": "🚀 السيرفر جاهز لاستقبال نبضات الرام والخزنة بالتتابع السريع..."
52
  }
53
 
54
+ # ============================================================
55
+ # 🧠 Lazy Loading لمحرك EasyOCR
56
+ # ============================================================
57
  _ocr_reader = None
58
+
59
  def get_ocr_reader():
60
  global _ocr_reader
61
  if _ocr_reader is None:
62
  import easyocr
63
+ logger.info("📥 [Lazy Load] جاري شحن محرك EasyOCR...")
64
  _ocr_reader = easyocr.Reader(['ar', 'en'], gpu=torch.cuda.is_available())
65
  return _ocr_reader
66
 
67
+ # ============================================================
68
+ # 🧹 فلتر الصور — يعمل سواء كانت cv2 موجودة أو لا
69
+ # ============================================================
70
  def advanced_image_filter_from_matrix(img_np: np.ndarray) -> np.ndarray:
71
+ if not CV2_AVAILABLE:
72
+ # بديل بسيط بدون OpenCV
73
+ return img_np
74
+
75
  try:
76
  blurred = cv2.GaussianBlur(img_np, (3, 3), 0)
77
+ return cv2.adaptiveThreshold(
78
+ blurred, 255,
79
+ cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
80
+ cv2.THRESH_BINARY,
81
+ 11, 2
82
+ )
83
+ except Exception:
84
+ return img_np
85
 
86
+ # ============================================================
87
+ # 🧠 الدالة المركزية للـ OCR
88
+ # ============================================================
89
  def core_ocr_and_forward_to_golden(filename: str, file_path: str, is_vip: bool = False):
90
  global ocr_stats
91
+ prefix = "👑 [VIP]" if is_vip else "🐢 [FREE]"
92
  logger.info(f"{prefix} بدء معالجة المستند: {filename}")
93
  ocr_stats["status"] = f"🔎 جاري معالجة {filename}..."
94
+
95
  final_text = ""
96
  try:
97
  reader = get_ocr_reader()
98
+
99
+ # ---------------- PDF ----------------
100
  if filename.lower().endswith('.pdf'):
101
  doc = fitz.open(file_path)
102
  native_text = ""
103
+
104
  for page in doc:
105
  t = page.get_text()
106
+ if t and len(t.strip()) > 15:
107
+ native_text += t + "\n"
108
+
109
  if native_text.strip():
110
  final_text = native_text
111
  else:
 
112
  for page_num in range(len(doc)):
113
  page = doc[page_num]
114
+ pix = page.get_pixmap(dpi=100)
115
  img_np = np.frombuffer(pix.samples, dtype=np.uint8).reshape(pix.h, pix.w, pix.n)
116
+
117
+ if CV2_AVAILABLE:
118
+ open_cv_image = cv2.cvtColor(img_np, cv2.COLOR_RGB2GRAY)
119
+ else:
120
+ open_cv_image = img_np.mean(axis=2).astype(np.uint8)
121
+
122
+ cleaned = advanced_image_filter_from_matrix(open_cv_image)
123
+ ocr_results = reader.readtext(cleaned, detail=0)
124
  final_text += "\n".join(ocr_results) + "\n"
125
+
126
+ del pix, img_np, open_cv_image, cleaned
127
  gc.collect()
128
+
129
  doc.close()
130
+
131
+ # ---------------- IMAGE ----------------
132
  else:
133
  img_pil = Image.open(file_path)
134
+ gray = np.array(img_pil.convert('L'))
135
+ cleaned = advanced_image_filter_from_matrix(gray)
136
+ ocr_results = reader.readtext(cleaned, detail=0)
137
  final_text += "\n".join(ocr_results)
138
 
139
+ # ---------------- إرسال للمعالج الذهبي ----------------
140
  if final_text.strip():
141
  try:
142
+ rank = "vip" if is_vip else "free"
143
+ username = "VIP_User" if is_vip else "Free_OCR"
144
+
145
+ logger.info(f"🚀 إرسال النص المستخرج لـ {filename} إلى المحرك الذهبي...")
146
+ response = requests.post(
147
+ GOLDEN_ENGINE_API_URL,
148
+ json={"data": [final_text, username, rank]},
149
+ timeout=10
150
+ )
151
+
152
+ if response.status_code == 200:
153
+ logger.info("✅ تم الإرسال بنجاح")
154
+ else:
155
+ logger.warning(f"⚠️ استجابة غير متوقعة: {response.status_code}")
156
+
157
  except Exception as forward_err:
158
+ logger.error(f"⚠️ فشل الإرسال للمعالج الذهبي: {forward_err}")
159
 
160
+ # ---------------- تحديث الإحصائيات ----------------
161
  ocr_stats["total_extracted_words"] += len(final_text.split())
162
  ocr_stats["processed_files_count"] += 1
163
+ ocr_stats["last_processed_file"] = f"{prefix} {filename}"
164
+ ocr_stats["micro_logs"] = f"تم الانتهاء من {filename}"
165
+
166
  return final_text
167
+
168
  except Exception as e:
169
  logger.error(f"❌ خطأ في معالجة {filename}: {e}")
170
  return None
171
+
172
  finally:
173
+ ocr_stats["status"] = "🟢 المحرك مستقر"
174
  gc.collect()
175
 
176
+ # ============================================================
177
+ # 🛰️ رادار المجانيين
178
+ # ============================================================
179
  def cloud_folder_polling_worker():
180
  REPO_ID = "Asem75/aiocr_asistant"
181
  while True:
 
184
  try:
185
  api = HfApi(token=token)
186
  all_files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset")
187
+ valid_files = [f for f in all_files if f.lower().startswith("raw_files/")]
188
+
189
  if valid_files:
190
  target_file = valid_files[0]
191
  with processing_lock:
192
  filename = os.path.basename(target_file)
193
  local_path = os.path.join(UPLOAD_FOLDER, f"radar_{filename}")
194
+ downloaded = hf_hub_download(repo_id=REPO_ID, filename=target_file, repo_type="dataset", token=token)
195
+
196
+ shutil.copy2(downloaded, local_path)
197
+ result = core_ocr_and_forward_to_golden(filename, local_path, is_vip=False)
198
+
199
+ if result is not None:
200
+ api.delete_file(path_in_repo=target_file, repo_id=REPO_ID, repo_type="dataset", token=token)
201
+
202
+ if os.path.exists(local_path):
203
+ os.remove(local_path)
204
+
205
  except Exception as e:
206
+ logger.error(f"⚠️ خطأ في الرادار: {e}")
207
+
208
  time.sleep(6)
209
 
210
+ threading.Thread(target=cloud_folder_polling_worker, daemon=True).start()
 
211
 
212
+ # ============================================================
213
+ # 🛡️ بوابة VIP عبر الشبكة
214
+ # ============================================================
215
  fastapi_app = FastAPI()
216
 
217
  @fastapi_app.post("/api/predict")
218
  async def handle_vip_direct_network_route(request: Request):
 
 
 
219
  try:
220
  payload = await request.json()
221
+ file_info = payload.get("data", [])[0]
222
+
223
+ filename = file_info.get("name", "vip_file")
224
+ raw_b64 = file_info.get("data", "")
225
+
226
+ if "base64," in raw_b64:
227
+ raw_b64 = raw_b64.split("base64,")[1]
228
+
 
 
 
 
229
  import base64
230
+ local_path = os.path.join(UPLOAD_FOLDER, f"vip_{filename}")
231
+
232
  with open(local_path, "wb") as fh:
233
+ fh.write(base64.b64decode(raw_b64))
234
+
 
235
  with processing_lock:
236
+ extracted = core_ocr_and_forward_to_golden(filename, local_path, is_vip=True)
237
+
238
+ if os.path.exists(local_path):
239
+ os.remove(local_path)
240
+
241
+ return JSONResponse(content={"data": [extracted or "❌ فشل استخراج النص"]})
 
 
 
242
 
243
+ except Exception as e:
244
+ logger.error(f"❌ خطأ VIP: {e}")
245
+ return JSONResponse(content={"error": str(e)}, status_code=500)
246
+
247
+ # ============================================================
248
+ # 🖥️ لوحة التحكم
249
+ # ============================================================
250
  def get_dashboard_data():
251
+ return (
252
+ ocr_stats["status"],
253
+ str(ocr_stats["processed_files_count"]),
254
+ str(ocr_stats["total_extracted_words"]),
255
+ ocr_stats["last_processed_file"],
256
+ ocr_stats["micro_logs"]
257
+ )
258
 
259
  with gr.Blocks(title="Sapphire OCR - Route Management") as demo:
260
+ gr.HTML("<h1 style='text-align:center;'>🔮 Sapphire OCR Engine V2</h1>")
261
+ status_box = gr.Textbox(label="📡 الحالة", value=ocr_stats["status"], interactive=False)
262
+
263
  with gr.Row():
264
+ count_box = gr.Textbox(label="📁 عدد الملفات", value="0", interactive=False)
265
+ words_box = gr.Textbox(label="🔤 عدد الكلمات", value="0", interactive=False)
266
+ last_file_box = gr.Textbox(label="📄 آخر ملف", value="لا يوجد", interactive=False)
267
+
268
+ logs_box = gr.Textbox(label="📝 السجل", value=ocr_stats["micro_logs"], lines=3, interactive=False)
269
+ refresh_btn = gr.Button("🔄 تحديث")
270
  refresh_btn.click(fn=get_dashboard_data, outputs=[status_box, count_box, words_box, last_file_box, logs_box])
271
 
272
  app = gr.mount_gradio_app(fastapi_app, demo, path="/")
273
 
274
  if __name__ == "__main__":
275
  import uvicorn
276
+ uvicorn.run(app, host="0.0.0.0", port=7860)