rehan953 commited on
Commit
e4d155c
Β·
verified Β·
1 Parent(s): f4b7a0e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +197 -84
app.py CHANGED
@@ -49,23 +49,58 @@ print("βœ… result_formatter.py fixed")
49
 
50
  ABANDON = set(config["pipeline"]["result_formatter"]["abandon"])
51
 
52
- # ── STEP 3: Extract header/footer from PDF text layer ─────────
53
- def extract_pdf_headers(path):
 
 
 
54
  import fitz
55
- page_headers = []
56
- doc = fitz.open(path)
57
- for page in doc:
58
- page_rect = page.rect
59
- page_height = page_rect.height
60
- header_rect = fitz.Rect(0, 0, page_rect.width, page_height * 0.12)
61
- footer_rect = fitz.Rect(0, page_height * 0.88, page_rect.width, page_height)
62
- header_text = page.get_text(clip=header_rect).strip()
63
- footer_text = page.get_text(clip=footer_rect).strip()
64
- page_headers.append({"header": header_text, "footer": footer_text})
65
  doc.close()
66
- return page_headers
67
-
68
- # ── STEP 4: Parse one page result into regions list ───────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  def get_page_regions(page_result):
70
  if hasattr(page_result, "json_result"):
71
  jr = page_result.json_result
@@ -79,106 +114,180 @@ def get_page_regions(page_result):
79
  return md.strip(), "raw"
80
  return [], "empty"
81
 
82
- # ── STEP 5: Main OCR function ─────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  def run_ocr(uploaded_file):
84
  if uploaded_file is None:
85
- return "Please upload a file.", "No regions detected."
86
 
87
  try:
88
  from glmocr import parse
89
 
90
  path = uploaded_file.name if hasattr(uploaded_file, "name") else str(uploaded_file)
 
91
 
92
- if path.lower().endswith(".pdf"):
93
  import fitz
94
- pdf_headers = extract_pdf_headers(path)
95
  doc = fitz.open(path)
96
- page_images = []
 
 
97
  for i in range(len(doc)):
98
  pix = doc[i].get_pixmap(matrix=fitz.Matrix(1.5, 1.5), alpha=False)
99
  img_path = f"/tmp/maas_page_{i}.png"
100
  pix.save(img_path)
101
  page_images.append(img_path)
 
 
 
102
  doc.close()
103
  results = parse(page_images)
104
  if not isinstance(results, list):
105
  results = [results]
106
  else:
107
- pdf_headers = []
 
108
  results = parse(path)
109
  if not isinstance(results, list):
110
  results = [results]
111
 
112
- total_headers = 0
113
- total_footers = 0
114
- all_pages_json = [] # final JSON output β€” list of pages
115
  all_summary = []
116
 
117
  for page_num, page_result in enumerate(results):
118
- page_summary = [f"── PAGE {page_num + 1} of {len(results)} ──"]
119
- page_regions = [] # JSON regions for this page
120
-
121
- # Inject header from PDF text layer
122
- if pdf_headers and page_num < len(pdf_headers):
123
- hdr = pdf_headers[page_num]["header"]
124
- ftr = pdf_headers[page_num]["footer"]
125
- if hdr:
126
- total_headers += 1
127
- page_regions.append({
128
- "label": "header",
129
- "content": hdr,
130
- "source": "pdf_text_layer"
131
- })
132
- page_summary.append("πŸ”΅ HEADER: " + hdr[:100])
133
- if ftr:
134
- total_footers += 1
135
- page_regions.append({
136
- "label": "footer",
137
- "content": ftr,
138
- "source": "pdf_text_layer"
139
- })
140
- page_summary.append("🟒 FOOTER: " + ftr[:100])
141
-
142
- # Get regions from API result
143
  data, dtype = get_page_regions(page_result)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
 
 
145
  if dtype == "json":
146
- for region in data:
147
  if not isinstance(region, dict):
148
  continue
149
  label = region.get("label", "text")
150
  content = str(region.get("content", ""))
 
151
 
152
  if label in ABANDON:
153
  continue
154
 
 
 
 
 
 
 
 
 
 
155
  if label == "header":
156
  total_headers += 1
157
- page_summary.append("πŸ”΅ HEADER (API): " + content[:100])
158
- page_regions.append({
159
- "label": "header",
160
- "content": content,
161
- "source": "api"
162
- })
163
  elif label == "footer":
164
  total_footers += 1
165
- page_summary.append("🟒 FOOTER (API): " + content[:100])
166
- page_regions.append({
167
- "label": "footer",
168
- "content": content,
169
- "source": "api"
170
- })
171
  else:
172
- page_summary.append("[" + label + "]: " + content[:100])
173
- page_regions.append({
174
- "label": label,
175
- "content": content,
176
- "source": "api"
177
- })
178
 
179
  elif dtype == "raw":
180
- # Wrap raw markdown as a single text region
181
  page_regions.append({
 
182
  "label": "text",
183
  "content": data,
184
  "source": "api_raw"
@@ -195,7 +304,6 @@ def run_ocr(uploaded_file):
195
  all_summary.extend(page_summary)
196
  all_summary.append("")
197
 
198
- # Format final JSON output
199
  final_json = json.dumps(all_pages_json, indent=2, ensure_ascii=False)
200
 
201
  summary = (
@@ -206,18 +314,20 @@ def run_ocr(uploaded_file):
206
  + "\n".join(all_summary)
207
  )
208
 
209
- return final_json, summary
 
210
 
211
  except Exception as e:
212
  import traceback
213
- return "Error: " + str(e) + "\n\n" + traceback.format_exc(), "Failed."
 
214
 
215
- # ── STEP 6: Gradio UI ─────────────────────────────────────────
216
- with gr.Blocks(title="GLM-OCR β€” MaaS (JSON Output)") as demo:
217
  gr.Markdown("""
218
  # πŸ” GLM-OCR β€” Zhipu MaaS
219
- Upload PDF or image. Output is **JSON format** with all regions.
220
- Headers πŸ”΅ and Footers 🟒 included in every page.
221
  Multi-page PDFs fully supported.
222
  """)
223
 
@@ -227,15 +337,18 @@ with gr.Blocks(title="GLM-OCR β€” MaaS (JSON Output)") as demo:
227
  )
228
  run_btn = gr.Button("β–Ά Run OCR", variant="primary", size="lg")
229
 
230
- with gr.Row():
231
- with gr.Column():
232
- gr.Markdown("### πŸ“„ JSON Output")
233
- markdown_out = gr.Textbox(lines=30, label="")
234
- with gr.Column():
235
- gr.Markdown("### πŸ—‚οΈ Detected Regions")
236
  regions_out = gr.Textbox(lines=30, label="")
237
 
238
- run_btn.click(fn=run_ocr, inputs=file_input,
239
- outputs=[markdown_out, regions_out])
 
 
 
240
 
241
  demo.launch()
 
49
 
50
  ABANDON = set(config["pipeline"]["result_formatter"]["abandon"])
51
 
52
+ # ── STEP 3: Extract text from a specific zone of a PDF page ───
53
+ # We use this ONLY to recover text from zones the API missed.
54
+ # We figure out what zones were missed by looking at bbox_2d gaps.
55
+ def extract_zone_text(pdf_path, page_num, y_start_frac, y_end_frac):
56
+ """Extract text from a vertical fraction of a PDF page."""
57
  import fitz
58
+ doc = fitz.open(pdf_path)
59
+ page = doc[page_num]
60
+ h = page.rect.height
61
+ w = page.rect.width
62
+ rect = fitz.Rect(0, h * y_start_frac, w, h * y_end_frac)
63
+ text = page.get_text(clip=rect).strip()
 
 
 
 
64
  doc.close()
65
+ return text
66
+
67
+ def get_missed_zones(regions, page_height, page_width, min_gap_frac=0.05):
68
+ """
69
+ Look at bbox_2d of API regions and find vertical gaps at the
70
+ top and bottom of the page that were not covered by any region.
71
+ Returns: (top_gap_frac, bottom_gap_frac) or None if no gap.
72
+ """
73
+ if not regions:
74
+ return None, None
75
+
76
+ # Collect all y-coordinates from bbox_2d
77
+ y_tops = []
78
+ y_bottoms = []
79
+ for r in regions:
80
+ bbox = r.get("bbox_2d")
81
+ if bbox and len(bbox) == 4:
82
+ y_tops.append(bbox[1])
83
+ y_bottoms.append(bbox[3])
84
+
85
+ if not y_tops:
86
+ return None, None
87
+
88
+ first_region_y = min(y_tops)
89
+ last_region_y = max(y_bottoms)
90
+
91
+ # Top gap: if first region starts below 10% of page height
92
+ top_gap = None
93
+ if first_region_y > page_height * 0.10:
94
+ top_gap = first_region_y / page_height # as fraction
95
+
96
+ # Bottom gap: if last region ends above 90% of page height
97
+ bottom_gap = None
98
+ if last_region_y < page_height * 0.90:
99
+ bottom_gap = last_region_y / page_height # as fraction
100
+
101
+ return top_gap, bottom_gap
102
+
103
+ # ── STEP 4: Parse one page result ────────────────────────────
104
  def get_page_regions(page_result):
105
  if hasattr(page_result, "json_result"):
106
  jr = page_result.json_result
 
114
  return md.strip(), "raw"
115
  return [], "empty"
116
 
117
+ # ── STEP 5: Build readable HTML from JSON ────────────────────
118
+ def json_to_readable_html(pages_json):
119
+ html = """
120
+ <style>
121
+ .page-block { border: 2px solid #333; border-radius: 8px; margin-bottom: 24px; overflow: hidden; }
122
+ .page-title { background: #333; color: white; padding: 8px 16px; font-size: 16px; font-weight: bold; }
123
+ .region { padding: 10px 16px; border-bottom: 1px solid #eee; }
124
+ .region:last-child { border-bottom: none; }
125
+ .region-label { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 12px; font-weight: bold; margin-bottom: 6px; }
126
+ .label-header { background: #3b82f6; color: white; }
127
+ .label-footer { background: #22c55e; color: white; }
128
+ .label-table { background: #f59e0b; color: white; }
129
+ .label-text { background: #6b7280; color: white; }
130
+ .label-other { background: #8b5cf6; color: white; }
131
+ .region-content { font-size: 13px; color: #222; white-space: pre-wrap; word-break: break-word; }
132
+ .region-content table { border-collapse: collapse; width: 100%; margin-top: 4px; }
133
+ .region-content td, .region-content th { border: 1px solid #ccc; padding: 4px 8px; font-size: 12px; }
134
+ .region-content th { background: #f3f4f6; }
135
+ .source-badge { font-size: 10px; color: #999; margin-left: 8px; }
136
+ .empty-page { padding: 16px; color: #999; font-style: italic; }
137
+ </style>
138
+ """
139
+ for page in pages_json:
140
+ page_num = page.get("page", "?")
141
+ regions = page.get("regions", [])
142
+ html += f'<div class="page-block">'
143
+ html += f'<div class="page-title">πŸ“„ Page {page_num}</div>'
144
+ if not regions:
145
+ html += '<div class="empty-page">Empty page</div>'
146
+ else:
147
+ for region in regions:
148
+ label = region.get("label", "text")
149
+ content = region.get("content", "")
150
+ source = region.get("source", "")
151
+ label_class = {"header": "label-header", "footer": "label-footer",
152
+ "table": "label-table", "text": "label-text"}.get(label, "label-other")
153
+ source_text = "(PDF text layer)" if source == "pdf_text_layer" else "(API)"
154
+ html += f'<div class="region">'
155
+ html += f'<span class="region-label {label_class}">{label.upper()}</span>'
156
+ html += f'<span class="source-badge">{source_text}</span>'
157
+ html += f'<div class="region-content">{content}</div>'
158
+ html += f'</div>'
159
+ html += '</div>'
160
+ return html
161
+
162
+ # ── STEP 6: Main OCR function ─────────────────────────────────
163
  def run_ocr(uploaded_file):
164
  if uploaded_file is None:
165
+ return "Please upload a file.", "No regions detected.", "<p>No output yet.</p>"
166
 
167
  try:
168
  from glmocr import parse
169
 
170
  path = uploaded_file.name if hasattr(uploaded_file, "name") else str(uploaded_file)
171
+ is_pdf = path.lower().endswith(".pdf")
172
 
173
+ if is_pdf:
174
  import fitz
 
175
  doc = fitz.open(path)
176
+ page_images = []
177
+ page_heights = []
178
+ page_widths = []
179
  for i in range(len(doc)):
180
  pix = doc[i].get_pixmap(matrix=fitz.Matrix(1.5, 1.5), alpha=False)
181
  img_path = f"/tmp/maas_page_{i}.png"
182
  pix.save(img_path)
183
  page_images.append(img_path)
184
+ # Store original page dimensions for bbox math
185
+ page_heights.append(doc[i].rect.height)
186
+ page_widths.append(doc[i].rect.width)
187
  doc.close()
188
  results = parse(page_images)
189
  if not isinstance(results, list):
190
  results = [results]
191
  else:
192
+ page_heights = []
193
+ page_widths = []
194
  results = parse(path)
195
  if not isinstance(results, list):
196
  results = [results]
197
 
198
+ total_headers = 0
199
+ total_footers = 0
200
+ all_pages_json = []
201
  all_summary = []
202
 
203
  for page_num, page_result in enumerate(results):
204
+ page_summary = [f"── PAGE {page_num + 1} of {len(results)} ──"]
205
+ page_regions = []
206
+ index = 0
207
+
208
+ # Get API regions first so we can analyze bbox gaps
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  data, dtype = get_page_regions(page_result)
210
+ api_regions = data if dtype == "json" else []
211
+
212
+ # For PDFs: check if API missed a top or bottom zone
213
+ if is_pdf and page_num < len(page_heights):
214
+ ph = page_heights[page_num]
215
+ pw = page_widths[page_num]
216
+
217
+ # Scale bbox_2d: images were rendered at 1.5x, so bbox coords
218
+ # are in image pixels. Convert back to PDF page fractions.
219
+ # API bbox_2d uses the image dimensions (1.5x scaled).
220
+ # We need fractions of original page height.
221
+ top_gap, bottom_gap = get_missed_zones(
222
+ api_regions,
223
+ ph * 1.5, # image height = page height * 1.5
224
+ pw * 1.5,
225
+ )
226
+
227
+ # Top gap = missed header zone
228
+ if top_gap is not None:
229
+ hdr = extract_zone_text(path, page_num, 0, top_gap)
230
+ if hdr:
231
+ total_headers += 1
232
+ page_regions.append({
233
+ "index": index,
234
+ "label": "header",
235
+ "content": hdr,
236
+ "source": "pdf_text_layer"
237
+ })
238
+ page_summary.append("πŸ”΅ HEADER: " + hdr[:80])
239
+ index += 1
240
+
241
+ # Bottom gap = missed footer zone
242
+ if bottom_gap is not None:
243
+ ftr = extract_zone_text(path, page_num, bottom_gap, 1.0)
244
+ if ftr:
245
+ total_footers += 1
246
+ page_regions.append({
247
+ "index": index,
248
+ "label": "footer",
249
+ "content": ftr,
250
+ "source": "pdf_text_layer"
251
+ })
252
+ page_summary.append("🟒 FOOTER: " + ftr[:80])
253
+ index += 1
254
 
255
+ # Now add API regions
256
  if dtype == "json":
257
+ for region in api_regions:
258
  if not isinstance(region, dict):
259
  continue
260
  label = region.get("label", "text")
261
  content = str(region.get("content", ""))
262
+ bbox = region.get("bbox_2d", [])
263
 
264
  if label in ABANDON:
265
  continue
266
 
267
+ entry = {
268
+ "index": index,
269
+ "label": label,
270
+ "content": content,
271
+ "source": "api"
272
+ }
273
+ if bbox:
274
+ entry["bbox_2d"] = bbox
275
+
276
  if label == "header":
277
  total_headers += 1
278
+ page_summary.append("πŸ”΅ HEADER (API): " + content[:80])
 
 
 
 
 
279
  elif label == "footer":
280
  total_footers += 1
281
+ page_summary.append("🟒 FOOTER (API): " + content[:80])
 
 
 
 
 
282
  else:
283
+ page_summary.append("[" + label + "]: " + content[:80])
284
+
285
+ page_regions.append(entry)
286
+ index += 1
 
 
287
 
288
  elif dtype == "raw":
 
289
  page_regions.append({
290
+ "index": index,
291
  "label": "text",
292
  "content": data,
293
  "source": "api_raw"
 
304
  all_summary.extend(page_summary)
305
  all_summary.append("")
306
 
 
307
  final_json = json.dumps(all_pages_json, indent=2, ensure_ascii=False)
308
 
309
  summary = (
 
314
  + "\n".join(all_summary)
315
  )
316
 
317
+ readable = json_to_readable_html(all_pages_json)
318
+ return final_json, summary, readable
319
 
320
  except Exception as e:
321
  import traceback
322
+ err = "Error: " + str(e) + "\n\n" + traceback.format_exc()
323
+ return err, "Failed.", "<pre>" + err + "</pre>"
324
 
325
+ # ── STEP 7: Gradio UI ─────────────────────────────────────────
326
+ with gr.Blocks(title="GLM-OCR β€” MaaS") as demo:
327
  gr.Markdown("""
328
  # πŸ” GLM-OCR β€” Zhipu MaaS
329
+ Upload PDF or image. Headers πŸ”΅ and Footers 🟒 detected automatically.
330
+ Works for any PDF β€” no hard-coded assumptions.
331
  Multi-page PDFs fully supported.
332
  """)
333
 
 
337
  )
338
  run_btn = gr.Button("β–Ά Run OCR", variant="primary", size="lg")
339
 
340
+ with gr.Tabs():
341
+ with gr.Tab("πŸ“‹ Readable View"):
342
+ readable_out = gr.HTML(label="")
343
+ with gr.Tab("{ } JSON Output"):
344
+ json_out = gr.Textbox(lines=40, label="Raw JSON")
345
+ with gr.Tab("πŸ—‚οΈ Detected Regions"):
346
  regions_out = gr.Textbox(lines=30, label="")
347
 
348
+ run_btn.click(
349
+ fn=run_ocr,
350
+ inputs=file_input,
351
+ outputs=[json_out, regions_out, readable_out]
352
+ )
353
 
354
  demo.launch()