feat: OCR docTR par page — plus de seuil global, traite chaque page pauvre individuellement

L'OCR docTR est maintenant déclenché page par page (< 150 chars) au lieu
d'un seuil global sur tout le document. Permet de traiter les documents
mixtes (pages texte + pages scannées) sans pénaliser le temps de traitement
sur les pages déjà riches en texte.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-16 20:28:27 +01:00
parent 7c05ff9aaf
commit 2731bc1ce7

View File

@@ -1192,16 +1192,23 @@ def extract_text_with_fallback_ocr(pdf_path: Path) -> Tuple[List[str], List[List
except Exception: except Exception:
pass pass
# --- Passe 3 : OCR docTR si PDF scanné (très peu de texte) --- # --- Passe 3 : OCR docTR sur les pages pauvres en texte ---
# Pas de seuil global : on OCR uniquement les pages individuelles
# qui ont peu de texte (< 150 chars), puis on garde le meilleur résultat
# par page. Les pages déjà riches en texte ne sont pas touchées.
_OCR_PAGE_THRESHOLD = 150 # chars minimum pour considérer une page comme "texte OK"
total_chars = sum(len(x or "") for x in pages_text) total_chars = sum(len(x or "") for x in pages_text)
ocr_word_map: OcrWordMap = {} ocr_word_map: OcrWordMap = {}
if total_chars < 200 and _DOCTR_AVAILABLE and fitz is not None: sparse_pages = [i for i, p in enumerate(pages_text) if len(p or "") < _OCR_PAGE_THRESHOLD]
if sparse_pages and _DOCTR_AVAILABLE and fitz is not None:
try: try:
model = _get_doctr_model() model = _get_doctr_model()
doc = fitz.open(str(pdf_path)) doc = fitz.open(str(pdf_path))
ocr_pages: List[str] = []
import numpy as np import numpy as np
for i in range(len(doc)): ocr_replaced = 0
for i in sparse_pages:
if i >= len(doc):
continue
pix = doc[i].get_pixmap(dpi=300) pix = doc[i].get_pixmap(dpi=300)
img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples) img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
result = model([np.array(img)]) result = model([np.array(img)])
@@ -1213,15 +1220,17 @@ def extract_text_with_fallback_ocr(pdf_path: Path) -> Tuple[List[str], List[List
(x0, y0), (x1, y1) = w.geometry (x0, y0), (x1, y1) = w.geometry
page_words.append((w.value, x0, y0, x1, y1)) page_words.append((w.value, x0, y0, x1, y1))
page_text += " ".join(w.value for w in line.words) + "\n" page_text += " ".join(w.value for w in line.words) + "\n"
ocr_word_map[i] = page_words # Remplacer seulement si l'OCR produit plus de texte
ocr_pages.append(page_text) if len(page_text) > len(pages_text[i] or ""):
pages_text[i] = page_text
ocr_word_map[i] = page_words
ocr_replaced += 1
doc.close() doc.close()
if sum(len(p) for p in ocr_pages) > total_chars: if ocr_replaced > 0:
pages_text = ocr_pages
ocr_used = True ocr_used = True
else: log.info("OCR docTR : %d/%d pages remplacées", ocr_replaced, len(sparse_pages))
ocr_word_map = {} except Exception as e:
except Exception: log.warning("OCR docTR échoué : %s", e)
ocr_word_map = {} ocr_word_map = {}
return pages_text, tables_lines, ocr_used, ocr_word_map return pages_text, tables_lines, ocr_used, ocr_word_map