Baiyinyou commited on
Commit
657f8db
Β·
verified Β·
1 Parent(s): de2dda6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +77 -100
app.py CHANGED
@@ -3,22 +3,19 @@ import tempfile
3
  from pathlib import Path
4
  import json
5
  import torch
6
- import re
7
- from PIL import Image
8
 
9
  from detection_ocr import DetectionOCR
10
  from repair_head import RepairHead
11
  from semantic_head import ExplanationLLM, FormulaEvaluator
12
 
13
- # ============================================================
14
  # Device
15
- # ============================================================
16
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
17
- print(f"[System] Using device: {DEVICE}")
18
 
19
- # ============================================================
20
  # Lazy singletons
21
- # ============================================================
22
  _ocr = None
23
  _repair = None
24
  _semantic = None
@@ -40,15 +37,15 @@ def get_semantic():
40
  if _semantic is None:
41
  explainer = ExplanationLLM(
42
  base_model="Qwen/Qwen2.5-Math-1.5B-Instruct",
43
- lora_dir=None,
44
  device="cpu",
45
  )
46
  _semantic = FormulaEvaluator(explainer)
47
  return _semantic
48
 
49
- # ============================================================
50
  # Helpers
51
- # ============================================================
52
  def is_reasonable_math(expr: str) -> bool:
53
  if not expr:
54
  return False
@@ -58,31 +55,17 @@ def is_reasonable_math(expr: str) -> bool:
58
  return False
59
  return True
60
 
61
- def wrap_equation(expr: str) -> str:
62
- return f"\\begin{{equation}}\n{expr}\n\\end{{equation}}"
63
-
64
- def merge_bbox_images_to_pdf(output_dir: Path) -> Path:
65
- images = sorted(output_dir.glob("page_*_bbox.png"))
66
- if not images:
67
- return None
68
-
69
- pil_images = [Image.open(p).convert("RGB") for p in images]
70
- pdf_path = output_dir / "merged_bbox.pdf"
71
- pil_images[0].save(
72
- pdf_path,
73
- save_all=True,
74
- append_images=pil_images[1:],
75
- )
76
- return pdf_path
77
-
78
- # ============================================================
79
- # Core pipeline
80
- # ============================================================
81
  def run_pipeline(pdf_file):
82
- logs = []
 
83
 
 
84
  def log(msg):
85
  logs.append(msg)
 
86
 
87
  tmpdir = Path(tempfile.mkdtemp(prefix="pdf_ocr_"))
88
  outdir = tmpdir / "output"
@@ -90,129 +73,123 @@ def run_pipeline(pdf_file):
90
 
91
  log(f"πŸ“ Output dir: {outdir}")
92
 
93
- # ---------------- OCR ----------------
94
  log("\nπŸš€ Stage 1: OCR")
95
  ocr = get_ocr()
96
  ocr.process_pdf(pdf_file, output_dir=outdir)
 
97
 
98
- page_jsons = sorted(outdir.glob("page_*_results.json"))
99
- log(f"βœ… OCR done. Found {len(page_jsons)} pages")
100
-
101
- # ---------------- Merge raw OCR ----------------
102
  merged = {}
103
- for p in page_jsons:
 
 
104
  page = p.stem.replace("_results", "")
105
  data = json.loads(p.read_text(encoding="utf-8"))
106
- merged[page] = {
107
- "formulas": data.get("formulas", []),
108
- "total": len(data.get("formulas", [])),
109
- }
110
 
111
- overall_raw = outdir / "overall_ocr_results.json"
112
- overall_raw.write_text(json.dumps(merged, indent=2, ensure_ascii=False), "utf-8")
113
- log("πŸ’Ύ Saved overall_ocr_results.json")
 
 
114
 
115
- # ---------------- Repair ----------------
116
- log("\nπŸ”§ Stage 2: Repairing formulas")
117
  repair = get_repair()
118
 
119
  repaired_all = {}
120
- merged_equations = []
121
-
122
- for page, content in merged.items():
123
  repaired_all[page] = []
124
- for fm in content["formulas"]:
 
125
  raw = fm.get("raw_latex", "")
 
126
  repaired = repair.repair(raw)
127
-
128
  if is_reasonable_math(repaired):
 
129
  repaired_all[page].append({
130
  "raw_latex": raw,
131
  "repaired": repaired
132
  })
133
- merged_equations.append(repaired)
134
  else:
 
135
  repaired_all[page].append({
136
  "raw_latex": raw,
137
  "repaired": "noise"
138
  })
139
 
140
  repaired_json = outdir / "overall_ocr_repaired.json"
141
- repaired_json.write_text(json.dumps(repaired_all, indent=2, ensure_ascii=False), "utf-8")
142
- log("πŸ’Ύ Saved overall_ocr_repaired.json")
 
 
143
 
144
- # ---------------- Semantic ----------------
145
- log("\n🧠 Stage 3: Semantic evaluation")
146
- evaluator = get_semantic()
147
 
148
  semantic_results = []
149
  for page, items in repaired_all.items():
150
- for item in items:
151
- if item["repaired"] == "noise":
152
  continue
153
- res = evaluator.evaluate(item["repaired"])
154
- semantic_results.append({
155
- "page": page,
156
- **res
157
- })
158
 
159
  semantic_json = outdir / "semantic_results.json"
160
- semantic_json.write_text(json.dumps(semantic_results, indent=2, ensure_ascii=False), "utf-8")
161
- log("πŸ’Ύ Saved semantic_results.json")
162
-
163
- # ---------------- merge.tex ----------------
164
- merge_tex = outdir / "merge.tex"
165
- with merge_tex.open("w", encoding="utf-8") as f:
166
- f.write("\\documentclass{article}\n")
167
- f.write("\\usepackage{amsmath,amssymb}\n")
168
- f.write("\\begin{document}\n\n")
169
- for eq in merged_equations:
170
- f.write(wrap_equation(eq) + "\n\n")
171
- f.write("\\end{document}\n")
172
- log("πŸ’Ύ Saved merge.tex")
173
-
174
- # ---------------- bbox pdf ----------------
175
- bbox_pdf = merge_bbox_images_to_pdf(outdir)
176
- if bbox_pdf:
177
- log("πŸ’Ύ Saved merged_bbox.pdf")
178
 
179
  log("\nπŸŽ‰ DONE")
180
 
181
  return (
182
  "\n".join(logs),
 
183
  str(repaired_json),
184
  str(semantic_json),
185
- str(merge_tex),
186
- str(bbox_pdf) if bbox_pdf else None,
187
  )
188
 
189
- # ============================================================
190
- # Gradio UI
191
- # ============================================================
192
  with gr.Blocks() as demo:
193
- gr.Markdown("# πŸ“˜ PDF Formula OCR β†’ Repair β†’ Semantic")
194
 
195
  pdf = gr.File(label="Upload PDF", file_types=[".pdf"])
196
  run = gr.Button("Run")
197
 
198
- log_box = gr.Textbox(label="Log", lines=25)
199
-
200
- with gr.Row():
201
- repaired_file = gr.File(label="overall_ocr_repaired.json")
202
- semantic_file = gr.File(label="semantic_results.json")
203
 
204
  with gr.Row():
205
- tex_file = gr.File(label="merge.tex")
206
- bbox_file = gr.File(label="merged_bbox.pdf")
 
207
 
208
  run.click(
209
  fn=run_pipeline,
210
  inputs=pdf,
211
- outputs=[log_box, repaired_file, semantic_file, tex_file, bbox_file],
 
 
 
 
 
212
  )
213
 
214
- # ============================================================
215
- # Main
216
- # ============================================================
217
  if __name__ == "__main__":
218
- demo.launch(server_name="0.0.0.0", server_port=7860)
 
 
 
 
 
3
  from pathlib import Path
4
  import json
5
  import torch
 
 
6
 
7
  from detection_ocr import DetectionOCR
8
  from repair_head import RepairHead
9
  from semantic_head import ExplanationLLM, FormulaEvaluator
10
 
11
+ # =========================
12
  # Device
13
+ # =========================
14
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
 
15
 
16
+ # =========================
17
  # Lazy singletons
18
+ # =========================
19
  _ocr = None
20
  _repair = None
21
  _semantic = None
 
37
  if _semantic is None:
38
  explainer = ExplanationLLM(
39
  base_model="Qwen/Qwen2.5-Math-1.5B-Instruct",
40
+ lora_dir="src/repair/repair_training/Repair_improved_1.5B_v1",
41
  device="cpu",
42
  )
43
  _semantic = FormulaEvaluator(explainer)
44
  return _semantic
45
 
46
+ # =========================
47
  # Helpers
48
+ # =========================
49
  def is_reasonable_math(expr: str) -> bool:
50
  if not expr:
51
  return False
 
55
  return False
56
  return True
57
 
58
+ # =========================
59
+ # Main pipeline (simple & reliable)
60
+ # =========================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  def run_pipeline(pdf_file):
62
+ if pdf_file is None:
63
+ return "No PDF uploaded.", None, None, None
64
 
65
+ logs = []
66
  def log(msg):
67
  logs.append(msg)
68
+ return "\n".join(logs)
69
 
70
  tmpdir = Path(tempfile.mkdtemp(prefix="pdf_ocr_"))
71
  outdir = tmpdir / "output"
 
73
 
74
  log(f"πŸ“ Output dir: {outdir}")
75
 
76
+ # -------- OCR --------
77
  log("\nπŸš€ Stage 1: OCR")
78
  ocr = get_ocr()
79
  ocr.process_pdf(pdf_file, output_dir=outdir)
80
+ log("βœ… OCR done")
81
 
82
+ # -------- Merge OCR results --------
83
+ log("\nπŸ”„ Stage 2: Merge OCR results")
 
 
84
  merged = {}
85
+ page_files = sorted(outdir.glob("page_*_results.json"))
86
+
87
+ for p in page_files:
88
  page = p.stem.replace("_results", "")
89
  data = json.loads(p.read_text(encoding="utf-8"))
90
+ merged[page] = data["formulas"]
91
+ log(f" {page}: {len(data['formulas'])} formulas")
 
 
92
 
93
+ overall_ocr = outdir / "overall_ocr_results.json"
94
+ overall_ocr.write_text(
95
+ json.dumps(merged, indent=2, ensure_ascii=False),
96
+ encoding="utf-8",
97
+ )
98
 
99
+ # -------- Repair --------
100
+ log("\nπŸ”§ Stage 3: Repair formulas")
101
  repair = get_repair()
102
 
103
  repaired_all = {}
104
+ for page, formulas in merged.items():
 
 
105
  repaired_all[page] = []
106
+ log(f"\n==== {page} ====")
107
+ for i, fm in enumerate(formulas, 1):
108
  raw = fm.get("raw_latex", "")
109
+ log(f"[{i}/{len(formulas)}] RAW: {raw[:80]}")
110
  repaired = repair.repair(raw)
 
111
  if is_reasonable_math(repaired):
112
+ log(" βœ” repaired")
113
  repaired_all[page].append({
114
  "raw_latex": raw,
115
  "repaired": repaired
116
  })
 
117
  else:
118
+ log(" βœ– noise")
119
  repaired_all[page].append({
120
  "raw_latex": raw,
121
  "repaired": "noise"
122
  })
123
 
124
  repaired_json = outdir / "overall_ocr_repaired.json"
125
+ repaired_json.write_text(
126
+ json.dumps(repaired_all, indent=2, ensure_ascii=False),
127
+ encoding="utf-8",
128
+ )
129
 
130
+ # -------- Semantic --------
131
+ log("\n🧠 Stage 4: Semantic evaluation")
132
+ semantic = get_semantic()
133
 
134
  semantic_results = []
135
  for page, items in repaired_all.items():
136
+ for r in items:
137
+ if r["repaired"] == "noise":
138
  continue
139
+ res = semantic.evaluate(r["repaired"])
140
+ res["page"] = page
141
+ semantic_results.append(res)
142
+ log(f" [{page}] {res['semantic_verdict']} (conf={res['confidence']})")
 
143
 
144
  semantic_json = outdir / "semantic_results.json"
145
+ semantic_json.write_text(
146
+ json.dumps(semantic_results, indent=2, ensure_ascii=False),
147
+ encoding="utf-8",
148
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
 
150
  log("\nπŸŽ‰ DONE")
151
 
152
  return (
153
  "\n".join(logs),
154
+ str(overall_ocr),
155
  str(repaired_json),
156
  str(semantic_json),
 
 
157
  )
158
 
159
+ # =========================
160
+ # Gradio UI (simple, stable)
161
+ # =========================
162
  with gr.Blocks() as demo:
163
+ gr.Markdown("# πŸ“˜ PDF β†’ OCR β†’ Repair β†’ Semantic (Simple)")
164
 
165
  pdf = gr.File(label="Upload PDF", file_types=[".pdf"])
166
  run = gr.Button("Run")
167
 
168
+ live_log = gr.Textbox(
169
+ label="Live Log",
170
+ lines=22,
171
+ interactive=False
172
+ )
173
 
174
  with gr.Row():
175
+ ocr_json = gr.File(label="overall_ocr_results.json")
176
+ repaired_json = gr.File(label="overall_ocr_repaired.json")
177
+ semantic_json = gr.File(label="semantic_results.json")
178
 
179
  run.click(
180
  fn=run_pipeline,
181
  inputs=pdf,
182
+ outputs=[
183
+ live_log,
184
+ ocr_json,
185
+ repaired_json,
186
+ semantic_json,
187
+ ],
188
  )
189
 
 
 
 
190
  if __name__ == "__main__":
191
+ demo.launch(
192
+ server_name="0.0.0.0",
193
+ server_port=7860,
194
+ share=False,
195
+ )