muddasser commited on
Commit
a892593
·
verified ·
1 Parent(s): c567af2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +33 -4
app.py CHANGED
@@ -1,5 +1,6 @@
1
  # app.py - Hybrid RAG (FAISS semantic + BM25, fused with RRF) for multi-format documents.
2
- # Handles: PDFs (text + tables), DOCX (with tables), PPTX, XLSX/CSV, TXT/MD. Arabic + English.
 
3
  import io
4
  import os
5
  import re
@@ -24,6 +25,8 @@ CHUNK_WORDS = 80 # stays under the embedding model's 128-token limit
24
  CHUNK_OVERLAP = 15
25
  TABLE_CHUNK_WORDS = 60 # table/spreadsheet rows are token-heavy, so smaller groups
26
  RRF_K = 60
 
 
27
 
28
  # ========================= TEXT NORMALIZATION =========================
29
  DIACRITICS_RE = re.compile(r"[\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06ED]")
@@ -63,11 +66,29 @@ def pdf_tables(page):
63
  pass
64
  return out
65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  def extract_pdf(name, data):
67
  blocks = []
68
  stats = {"pages": 0, "tables": 0, "notes": []}
69
  doc = fitz.open(stream=data, filetype="pdf")
70
- empty_pages = []
71
  for i, page in enumerate(doc, 1):
72
  stats["pages"] += 1
73
  loc = f"p.{i}"
@@ -75,13 +96,21 @@ def extract_pdf(name, data):
75
  for tbl in pdf_tables(page):
76
  blocks.append(make_block(tbl, name, loc, "table"))
77
  stats["tables"] += 1
 
 
 
 
 
78
  if len(text) < 50:
79
  empty_pages.append(i)
80
  if text:
81
  blocks.append(make_block(text, name, loc, "text"))
 
 
 
82
  if empty_pages:
83
  shown = ", ".join(map(str, empty_pages[:15])) + ("..." if len(empty_pages) > 15 else "")
84
- stats["notes"].append(f"Little or no text on page(s) {shown}. Scanned pages cannot be read by this app.")
85
  return blocks, stats
86
 
87
  def extract_docx(name, data):
@@ -301,7 +330,7 @@ if uploaded_files:
301
  signature = tuple(sorted((f.name, f.size) for f in uploaded_files))
302
  if st.session_state.get("signature") != signature:
303
  all_blocks, report = [], {}
304
- with st.spinner("Reading documents..."):
305
  for f in uploaded_files:
306
  try:
307
  blocks, stats = extract_file(f.name, f.getvalue())
 
1
  # app.py - Hybrid RAG (FAISS semantic + BM25, fused with RRF) for multi-format documents.
2
+ # Handles: PDFs (text + tables, OCR for scanned pages), DOCX (with tables), PPTX, XLSX/CSV, TXT/MD. Arabic + English.
3
+ import glob
4
  import io
5
  import os
6
  import re
 
25
  CHUNK_OVERLAP = 15
26
  TABLE_CHUNK_WORDS = 60 # table/spreadsheet rows are token-heavy, so smaller groups
27
  RRF_K = 60
28
+ OCR_DPI = 300 # lower (e.g. 200) for faster OCR at some cost in accuracy
29
+ OCR_LANGS = "ara+eng"
30
 
31
  # ========================= TEXT NORMALIZATION =========================
32
  DIACRITICS_RE = re.compile(r"[\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06ED]")
 
66
  pass
67
  return out
68
 
69
+ def _set_tessdata():
70
+ """Point PyMuPDF at Tesseract's language data (path differs by distro/version)."""
71
+ if os.environ.get("TESSDATA_PREFIX"):
72
+ return
73
+ for p in glob.glob("/usr/share/tesseract-ocr/*/tessdata") + ["/usr/share/tessdata"]:
74
+ if os.path.isdir(p):
75
+ os.environ["TESSDATA_PREFIX"] = p
76
+ return
77
+
78
+ def ocr_page(page):
79
+ """OCR a scanned page. Returns "" if Tesseract or the language data is missing."""
80
+ try:
81
+ _set_tessdata()
82
+ tp = page.get_textpage_ocr(language=OCR_LANGS, dpi=OCR_DPI, full=True)
83
+ return unicodedata.normalize("NFKC", page.get_text("text", textpage=tp) or "").strip()
84
+ except Exception:
85
+ return ""
86
+
87
  def extract_pdf(name, data):
88
  blocks = []
89
  stats = {"pages": 0, "tables": 0, "notes": []}
90
  doc = fitz.open(stream=data, filetype="pdf")
91
+ empty_pages, ocr_pages = [], []
92
  for i, page in enumerate(doc, 1):
93
  stats["pages"] += 1
94
  loc = f"p.{i}"
 
96
  for tbl in pdf_tables(page):
97
  blocks.append(make_block(tbl, name, loc, "table"))
98
  stats["tables"] += 1
99
+ if len(text) < 50:
100
+ ocr_text = ocr_page(page)
101
+ if len(ocr_text) > len(text):
102
+ text = ocr_text
103
+ ocr_pages.append(i)
104
  if len(text) < 50:
105
  empty_pages.append(i)
106
  if text:
107
  blocks.append(make_block(text, name, loc, "text"))
108
+ if ocr_pages:
109
+ shown = ", ".join(map(str, ocr_pages[:15])) + ("..." if len(ocr_pages) > 15 else "")
110
+ stats["notes"].append(f"OCR used on page(s) {shown}. Text may contain recognition errors.")
111
  if empty_pages:
112
  shown = ", ".join(map(str, empty_pages[:15])) + ("..." if len(empty_pages) > 15 else "")
113
+ stats["notes"].append(f"No readable text found on page(s) {shown}, even after OCR.")
114
  return blocks, stats
115
 
116
  def extract_docx(name, data):
 
330
  signature = tuple(sorted((f.name, f.size) for f in uploaded_files))
331
  if st.session_state.get("signature") != signature:
332
  all_blocks, report = [], {}
333
+ with st.spinner("Reading documents (scanned pages are OCR'd and may take a while)..."):
334
  for f in uploaded_files:
335
  try:
336
  blocks, stats = extract_file(f.name, f.getvalue())