Asem75 commited on
Commit
dfdaa96
·
verified ·
1 Parent(s): 57409ff

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +79 -91
app.py CHANGED
@@ -1,7 +1,7 @@
1
  #!/usr/bin/env python3
2
  # -*- coding: utf-8 -*-
3
  """
4
- Sapphire OCR Engine - Ultra Safe Speed Route
5
  """
6
 
7
  import os
@@ -16,9 +16,6 @@ import cv2
16
  import torch
17
  import numpy as np
18
  import gradio as gr
19
- import requests
20
- from fastapi import FastAPI, Request
21
- from fastapi.responses import JSONResponse
22
  from huggingface_hub import HfApi, hf_hub_download
23
  import fitz # PyMuPDF
24
 
@@ -28,24 +25,25 @@ logger = logging.getLogger("SapphireOCR")
28
  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": "🟢 المحرك مستقر تماماً وبانتظار البيانات...",
36
  "processed_files_count": 0,
37
  "total_extracted_words": 0,
38
  "last_processed_file": "لا طلبات بعد",
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
 
@@ -55,12 +53,23 @@ def advanced_image_filter_from_matrix(img_np: np.ndarray) -> np.ndarray:
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:
@@ -75,16 +84,14 @@ def core_ocr_and_forward_to_golden(filename: str, file_path: str, is_vip: bool =
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()
@@ -95,117 +102,98 @@ def core_ocr_and_forward_to_golden(filename: str, file_path: str, is_vip: bool =
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:
124
  token = os.environ.get("AIOCR_KEY")
125
- if token and not processing_lock.locked():
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")
211
-
 
1
  #!/usr/bin/env python3
2
  # -*- coding: utf-8 -*-
3
  """
4
+ Sapphire OCR Engine - Pure Cloud Files Route (No API - No RAM Forwarding)
5
  """
6
 
7
  import os
 
16
  import torch
17
  import numpy as np
18
  import gradio as gr
 
 
 
19
  from huggingface_hub import HfApi, hf_hub_download
20
  import fitz # PyMuPDF
21
 
 
25
  UPLOAD_FOLDER = "upload_tmp"
26
  os.makedirs(UPLOAD_FOLDER, exist_ok=True)
27
 
28
+ # إعدادات مستودع الخزنة السحابية الثابتة والموحدة
29
+ REPO_ID = "Asem75/aiocr_asistant"
30
+ RAW_DIR = "raw_files" # مجلد الاستقبال من بوابات الرفع
31
+ PROCESS_DIR = "process_files" # مجلد الإرسال الناتجة للمعالج الذهبي
32
 
33
  ocr_stats = {
34
+ "status": "🟢 المحرك مستقر تماماً وبانتظار ملفات الداتا...",
35
  "processed_files_count": 0,
36
  "total_extracted_words": 0,
37
  "last_processed_file": "لا طلبات بعد",
38
+ "micro_logs": "🚀 الرادار جاهز لمراقبة الملفات السحابية بالتتابع..."
39
  }
40
 
 
41
  _ocr_reader = None
42
  def get_ocr_reader():
43
  global _ocr_reader
44
  if _ocr_reader is None:
45
  import easyocr
46
+ logger.info("📥 [Lazy Load] جاري شحن محرك EasyOCR في الرام...")
47
  _ocr_reader = easyocr.Reader(['ar', 'en'], gpu=torch.cuda.is_available())
48
  return _ocr_reader
49
 
 
53
  return cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
54
  except Exception: return img_np
55
 
56
+ # ============================================================
57
+ # ⚙️ الدالة المركزية الصافية لمعالجة الملفات وحفظ الأسماء الأصلية
58
+ # ============================================================
59
+ def core_ocr_process_and_upload(filename: str, file_path: str):
60
  global ocr_stats
61
+ token = os.environ.get("AIOCR_KEY")
62
+ api = HfApi(token=token)
63
+
64
+ # 🛑 استخراج اسم الملف الأصلي النقي القادم من البوابة (كما هو: مجاني أو VIP)
65
+ clean_name = os.path.splitext(filename)[0]
66
+ # تنظيف أي مخلفات داخلية في مسار المجلد المؤقت لضمان نقاء الاسم
67
+ clean_name = clean_name.replace("radar_", "")
68
+
69
+ final_cloud_filename = f"{clean_name}.txt"
70
+
71
+ logger.info(f"🔎 بدء معالجة مستند المستخدم: {clean_name}")
72
+ ocr_stats["status"] = f"🔎 جاري معالجة ملف المستخدم {clean_name}..."
73
 
74
  final_text = ""
75
  try:
 
84
  if native_text.strip():
85
  final_text = native_text
86
  else:
 
87
  for page_num in range(len(doc)):
88
  page = doc[page_num]
89
+ pix = page.get_pixmap(dpi=100)
90
  img_np = np.frombuffer(pix.samples, dtype=np.uint8).reshape(pix.h, pix.w, pix.n)
91
  open_cv_image = cv2.cvtColor(img_np, cv2.COLOR_RGB2GRAY) if pix.n >= 3 else img_np
92
  cleaned_matrix = advanced_image_filter_from_matrix(open_cv_image)
93
  ocr_results = reader.readtext(cleaned_matrix, detail=0)
94
  final_text += "\n".join(ocr_results) + "\n"
 
95
  del pix, img_np, open_cv_image, cleaned_matrix
96
  gc.collect()
97
  doc.close()
 
102
  ocr_results = reader.readtext(cleaned_matrix, detail=0)
103
  final_text += "\n".join(ocr_results)
104
 
105
+ # 📤 حفظ الملف ورفعه إلى الخزنة السحابية بنفس اسم الملف القادم تماماً
106
  if final_text.strip():
107
+ local_txt_output = f"/tmp/{final_cloud_filename}"
108
+ with open(local_txt_output, "w", encoding="utf-8") as f:
109
+ f.write(final_text)
110
+
111
+ logger.info(f"📤 رفع الملف النصي إلى: {PROCESS_DIR}/{final_cloud_filename}")
112
+ api.upload_file(
113
+ path_or_fileobj=local_txt_output,
114
+ path_in_repo=f"{PROCESS_DIR}/{final_cloud_filename}",
115
+ repo_id=REPO_ID,
116
+ repo_type="dataset",
117
+ token=token
118
+ )
119
+ if os.path.exists(local_txt_output): os.remove(local_txt_output)
120
 
121
  # تحديث لوحة التحكم
122
  ocr_stats["total_extracted_words"] += len(final_text.split())
123
  ocr_stats["processed_files_count"] += 1
124
+ ocr_stats["last_processed_file"] = f"{clean_name} ({time.strftime('%H:%M:%S')})"
125
+ ocr_stats["micro_logs"] = f"✅ تم ترحيل ملف: {clean_name} بنجاح إلى مجلد {PROCESS_DIR}"
126
 
127
  return final_text
128
  except Exception as e:
129
+ logger.error(f"❌ خطأ في معالجة {clean_name}: {e}")
130
  return None
131
  finally:
132
  ocr_stats["status"] = "🟢 المحرك مستقر وبانتظار دفع الملفات..."
133
  gc.collect()
134
 
135
+ # ============================================================
136
+ # 🧭 خيط الرادار النقي لمراقبة مجلد الـ raw_files
137
+ # ============================================================
138
  def cloud_folder_polling_worker():
 
139
  while True:
140
  token = os.environ.get("AIOCR_KEY")
141
+ if token:
142
  try:
143
  api = HfApi(token=token)
144
  all_files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset")
145
+
146
+ # فحص مجلد raw_files فقط عن أي ملفات جديدة قادمة من البوابات
147
+ valid_files = [f for f in all_files if f.lower().startswith(f"{RAW_DIR}/") and not f.endswith('/')]
148
 
149
  if valid_files:
150
+ # سحب أول ملف في الطابور لمعالجته
151
  target_file = valid_files[0]
152
+ filename = os.path.basename(target_file)
153
+
154
+ local_path = os.path.join(UPLOAD_FOLDER, f"radar_{filename}")
155
+ downloaded_path = hf_hub_download(repo_id=REPO_ID, filename=target_file, repo_type="dataset", token=token)
156
+ shutil.copy2(downloaded_path, local_path)
157
+
158
+ # استدعاء دالة الـ OCR والمعالجة النظيفة بدون خلط أسماء
159
+ output_result = core_ocr_process_and_upload(filename, local_path)
160
+
161
+ if output_result is not None:
162
+ try:
163
+ # حذف الملف الخام من مجلد raw_files لضمان عدم التكرار نهائياً
164
+ api.delete_file(path_in_repo=target_file, repo_id=REPO_ID, repo_type="dataset", token=token)
165
+ logger.info(f"🗑️ تم حذف الملف الخام ��ن الخزنة: {filename}")
166
+ except Exception as del_e:
167
+ logger.error(f"⚠️ فشل حذف الملف السحابي الخام: {del_e}")
168
 
169
+ if os.path.exists(local_path): os.remove(local_path)
 
 
 
 
 
 
170
  except Exception as e:
171
+ logger.error(f"⚠️ خطأ في دورة رادار الخزنة: {e}")
172
+ time.sleep(4)
173
 
174
+ # تشغيل الرادار السحابي النقي في الخلفية
175
  polling_thread = threading.Thread(target=cloud_folder_polling_worker, daemon=True)
176
  polling_thread.start()
177
 
178
+ # ============================================================
179
+ # 📊 واجهة العرض والمراقبة المبسطة (Gradio UI)
180
+ # ============================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  def get_dashboard_data():
182
  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"])
183
 
184
  with gr.Blocks(title="Sapphire OCR - Route Management") as demo:
185
+ gr.HTML("<div style='text-align:center; padding:10px;'><h1>🔮 Sapphire OCR Engine V3 (مسار الملفات النقي)</h1></div>")
186
+ gr.Markdown("تم إلغاء كافة مسارات الـ API والـ الرام؛ المحرك يعمل الآن بنظام الرادار السحابي الصافي لحفظ أسماء المستخدمين والملفات.")
187
+
188
  status_box = gr.Textbox(label="📡 حالة السيرفر الحالية", value=ocr_stats["status"], interactive=False)
189
  with gr.Row():
190
  count_box = gr.Textbox(label="✅ إجمالي الملفات المعالجة", value="0", interactive=False)
191
  words_box = gr.Textbox(label="🔍 إجمالي الكلمات المستخرجة", value="0", interactive=False)
192
+ last_file_box = gr.Textbox(label="📁 المستند الأخير وضخ الخزنة", value="لا يوجد طلبات بعد", interactive=False)
193
  logs_box = gr.Textbox(label="📝 سجل الحركات الحية للمحرك", value=ocr_stats["micro_logs"], lines=3, interactive=False)
194
+
195
  refresh_btn = gr.Button("🔄 تحديث مؤشرات السيرفر")
196
  refresh_btn.click(fn=get_dashboard_data, outputs=[status_box, count_box, words_box, last_file_box, logs_box])
197
 
 
 
198
  if __name__ == "__main__":
199
+ demo.launch(server_name="0.0.0.0", port=7860)