diff --git a/core/grounding/fast_detector.py b/core/grounding/fast_detector.py index 2a5984f60..63e39a2e5 100644 --- a/core/grounding/fast_detector.py +++ b/core/grounding/fast_detector.py @@ -141,18 +141,54 @@ class FastDetector: # OCR # ------------------------------------------------------------------ + _easyocr_reader = None # Singleton EasyOCR (chargé une fois) + def _ocr_extract(self, image) -> List[Dict[str, Any]]: - """Extrait les mots visibles via docTR.""" + """Extrait les mots visibles via EasyOCR (GPU, ~500ms). + + Fallback sur docTR si EasyOCR non disponible. + """ try: - import sys - sys.path.insert(0, 'visual_workflow_builder/backend') - from services.ocr_service import ocr_extract_words + import numpy as np + import easyocr - words = ocr_extract_words(image) - return words if words else [] + # Singleton : charger le reader une seule fois + if FastDetector._easyocr_reader is None: + print(f"🔍 [FAST/ocr] Chargement EasyOCR (GPU)...") + FastDetector._easyocr_reader = easyocr.Reader( + ['fr', 'en'], gpu=True, verbose=False + ) + results = FastDetector._easyocr_reader.readtext(np.array(image)) + + words = [] + for (bbox_pts, text, conf) in results: + if not text or len(text.strip()) < 1: + continue + # bbox_pts = [[x1,y1],[x2,y1],[x2,y2],[x1,y2]] + x1 = int(min(p[0] for p in bbox_pts)) + y1 = int(min(p[1] for p in bbox_pts)) + x2 = int(max(p[0] for p in bbox_pts)) + y2 = int(max(p[1] for p in bbox_pts)) + words.append({ + 'text': text.strip(), + 'bbox': [x1, y1, x2, y2], + 'confidence': float(conf), + }) + + return words + + except ImportError: + # Fallback docTR + try: + import sys + sys.path.insert(0, 'visual_workflow_builder/backend') + from services.ocr_service import ocr_extract_words + return ocr_extract_words(image) or [] + except Exception: + return [] except Exception as ex: - print(f"⚠️ [FAST/ocr] docTR erreur: {ex}") + print(f"⚠️ [FAST/ocr] EasyOCR erreur: {ex}") return [] # ------------------------------------------------------------------ diff --git a/core/grounding/title_verifier.py b/core/grounding/title_verifier.py index dce14b828..3a87e7169 100644 --- a/core/grounding/title_verifier.py +++ b/core/grounding/title_verifier.py @@ -137,11 +137,33 @@ class TitleVerifier: 'reason': 'Titre changé' if changed else 'Titre identique (acceptable)', } + _easyocr_reader = None # Singleton partagé + def _get_ocr(self): - """Lazy load de la fonction OCR.""" + """Lazy load de la fonction OCR (EasyOCR prioritaire, fallback docTR).""" if self._ocr_fn is not None: return self._ocr_fn + # EasyOCR (rapide, bonne qualité GUI) + try: + import easyocr + import numpy as np + + if TitleVerifier._easyocr_reader is None: + TitleVerifier._easyocr_reader = easyocr.Reader( + ['fr', 'en'], gpu=True, verbose=False + ) + + def _easyocr_extract_text(img): + results = TitleVerifier._easyocr_reader.readtext(np.array(img)) + return ' '.join(r[1] for r in results if r[1].strip()) + + self._ocr_fn = _easyocr_extract_text + return self._ocr_fn + except ImportError: + pass + + # Fallback docTR try: import sys sys.path.insert(0, 'visual_workflow_builder/backend') @@ -149,10 +171,4 @@ class TitleVerifier: self._ocr_fn = ocr_extract_text return self._ocr_fn except ImportError: - try: - from core.extraction.field_extractor import FieldExtractor - extractor = FieldExtractor() - self._ocr_fn = extractor.extract_text_from_image - return self._ocr_fn - except ImportError: - return None + return None