Spaces:
Sleeping
Sleeping
File size: 8,244 Bytes
900df0b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | """
HandwrittenOCR - الوحدة الرئيسية v6.0 Unified
========================================
نقطة الدخول الرئيسية - تجمع بين جميع المكونات.
يدعم: التشغيل المحلي (Offline)، المزامنة بين الأجهزة، واجهة الشبكة المحلية.
Gradio UI كاملة (7 تبويبات) + FP16 + memory_mode + HuggingFace Spaces.
"""
import time
import logging
import os
from config import Config
from src.logger import setup_logging
from src.recognition import OCREngine
from src.correction import init_correctors
from src.database import HandwritingDB
from src.pdf_processor import PDFProcessor
from src.review_ui import ReviewUI
from src.metrics import compute_metrics
def _check_available_memory() -> tuple[int, int]:
"""كشف الذاكرة المتاحة (RAM + Swap) بالميجابايت."""
try:
with open("/proc/meminfo", "r") as f:
meminfo = {}
for line in f:
parts = line.split()
if len(parts) >= 2:
key = parts[0].rstrip(":")
value = int(parts[1]) # كيلوبايت
meminfo[key] = value
mem_total = meminfo.get("MemTotal", 0) // 1024 # MB
mem_available = meminfo.get("MemAvailable", 0) // 1024 # MB
swap_total = meminfo.get("SwapTotal", 0) // 1024 # MB
return mem_available, swap_total
except Exception:
return 0, 0
def _auto_detect_low_memory(config: Config) -> bool:
"""كشف تلقائي: هل يجب تفعيل الوضع الخفيف؟
يُفعّل تلقائياً إذا كانت الذاكرة المتاحة < 3 GB.
يمكن تجاوزه يدوياً بـ config.low_memory.
"""
mem_available_mb, swap_mb = _check_available_memory()
total_available = mem_available_mb + swap_mb
logger = logging.getLogger("HandwrittenOCR")
logger.info(f"الذاكرة المتاحة: {mem_available_mb} MB RAM + {swap_mb} MB Swap")
# إذا الذاكرة < 3 GB: فعّل الوضع الخفيف تلقائياً
if total_available < 3072 and not config.low_memory:
logger.warning(
f"الذاكرة المتاحة ({total_available} MB) أقل من 3 GB — "
f"يُفعَّل الوضع الخفيف تلقائياً. استخدم --low-memory لتخطي هذا التحذير."
)
config.low_memory = True
return config.low_memory
def main(config: Config | None = None):
if config is None:
config = Config()
# إعداد شامل
config.setup()
config.apply_hf_token()
config.apply_cache_env()
if not config.is_colab:
# لا حاجة لربط EasyOCR بالـ Drive محلياً
pass
else:
config.setup_easyocr_symlink()
logger = setup_logging(config)
logger.info(f"بدء تشغيل HandwrittenOCR v6.0 Unified")
logger.info(f"ملف PDF: {config.pdf_path}")
logger.info(f"مجلد الإخراج: {config.output_dir or config.project_root}")
if config.model_cache_dir or config.cache_dir:
logger.info(f"تخزين مؤقت: {config.model_cache_dir or config.cache_dir}")
# === كشف الذاكرة وتفعيل الوضع الخفيف تلقائياً ===
low_mem = _auto_detect_low_memory(config)
if low_mem or config.low_memory:
config.apply_low_memory()
print("\n ⚠️ الوضع الخفيف مفعّل (توفير الذاكرة)")
print(f" DPI={config.dpi}, EasyOCR={config.ocr_languages}, batch={config.trocr_batch_size}")
print(" استخدم: python run.py --local --pdf FILE (بدون --low-memory) للوضع الكامل\n")
# عرض معلومات المزامنة
if config.sync_enabled:
from src.sync import SyncManager
sync_mgr = SyncManager(config)
network = sync_mgr.get_network_info()
print("\n" + "=" * 50)
print(" نظام المزامنة: مفعّل")
print("=" * 50)
print(f" معرف الجهاز: {sync_mgr.device_id}")
print(f" شبكة محلية: {network.get('local_ip', 'N/A')}")
print(f" واجهة المراجعة: {network.get('server_url', 'N/A')}")
print(f" API: {network.get('api_url', 'N/A')}")
print("=" * 50)
# كشف التعارضات
conflicts = sync_mgr.detect_conflicts()
if conflicts:
for c in conflicts:
print(f" تحذير: {c['message']}")
# تحميل المدققات الإملائية (اختياري — يمكن تخطيه في الوضع الخفيف)
if not config.skip_spellcheck:
init_correctors()
else:
logger.info("تخطي المدققات الإملائية (توفير الذاكرة)")
# تحميل محرك التعرف (مع LoRA تلقائي)
start = time.time()
ocr_engine = OCREngine(
trocr_model_name=config.trocr_model_name,
ocr_languages=config.ocr_languages,
max_text_length=config.max_text_length,
cache_dir=config.model_cache_dir or config.cache_dir,
hf_token=config.hf_token,
trocr_default_confidence=config.trocr_default_confidence,
easy_conf_threshold=config.easy_conf_threshold,
num_beams=config.num_beams,
trocr_batch_size=config.trocr_batch_size,
lora_save_path=config.lora_save_path,
skip_trocr=config.skip_trocr,
)
logger.info(f"تم تحميل النماذج في {time.time() - start:.2f} ثانية")
if ocr_engine.lora_loaded:
print("تم تحميل النموذج المُحسَّن (LoRA)")
else:
print("يستخدم النموذج الأساسي")
# تهيئة قاعدة البيانات
db = HandwritingDB(config.db_path)
# معالجة PDF
processor = PDFProcessor(config, ocr_engine, db)
stats = processor.process()
if stats.get("error"):
logger.error("فشلت المعالجة!")
if isinstance(stats.get("error"), str):
logger.error(f"السبب: {stats['error']}")
if stats.get("error") == "lock_timeout":
print("\nتعذر الحصول على قفل المعالجة - جهاز آخر يعمل حالياً.")
print("انتظر حتى يكتمل الجهاز الآخر أو أوقف المعالجة عليه.")
return
# عرض الإحصائيات
print("\n" + "=" * 50)
print(" إحصائيات المعالجة v6.0 Unified")
print("=" * 50)
print(f" Run ID: {stats.get('run_id', 'N/A')}")
print(f" الصفحات: {stats.get('pages', 0)}")
print(f" الكلمات: {stats.get('words', 0)}")
print(f" متوسط الثقة: {stats.get('avg_confidence', 0):.2%}")
print(f" الوقت: {stats.get('duration_sec', 0):.1f} ثانية")
print("=" * 50)
# حساب المقاييس (WER/CER)
if config.metrics_log:
try:
m = compute_metrics(db, metrics_log=config.metrics_log)
if m.get("wer") is not None:
print(f"\n WER: {m['wer']:.2%} | CER: {m['cer']:.2%} ({m['samples']} عينة)")
except Exception as e:
logger.debug(f"Metrics: {e}")
# ملفات المراقبة
print(f"\nملفات المراقبة:")
print(f" سجل الأحداث: {config.log_file}")
print(f" إحصائيات: {config.stats_json}")
print(f" تصحيحات: {config.feedback_csv}")
print(f" قاموس التصحيح: {config.correction_dict_path}")
if config.sync_enabled:
print(f" حالة المزامنة: {config.sync_status_path}")
# تشغيل واجهة المراجعة
print("\nتشغيل واجهة المراجعة...")
review_ui = ReviewUI(db, config.feedback_csv)
review_ui.launch()
# تشغيل Gradio UI كاملة (7 تبويبات) في بيئة Colab أو عند تفعيل المشاركة
if config.is_colab or config.gradio_share:
from src.gradio_ui import launch_gradio
launch_gradio(config)
if __name__ == "__main__":
main()
|