Spaces:
Sleeping
Sleeping
| """ | |
| GLM-OCR Hugging Face Space β MaaS mode with header extraction. | |
| Header is extracted from PDF text layer using bbox gap detection. | |
| Footer is NOT extracted separately β API already captures it as text regions. | |
| """ | |
| import asyncio | |
| try: | |
| _orig_close = asyncio.BaseEventLoop.close | |
| def _safe_close(self): | |
| try: | |
| _orig_close(self) | |
| except (ValueError, OSError): | |
| pass | |
| asyncio.BaseEventLoop.close = _safe_close | |
| except Exception: | |
| pass | |
| import logging | |
| import os | |
| import re | |
| import tempfile | |
| import yaml | |
| import gradio as gr | |
| import glmocr | |
| log = logging.getLogger("glmocr_app") | |
| GLMOCR_BASE = os.path.dirname(glmocr.__file__) | |
| CONFIG_PATH = os.path.join(GLMOCR_BASE, "config.yaml") | |
| FORMATTER_PATH = os.path.join(GLMOCR_BASE, "postprocess", "result_formatter.py") | |
| DEFAULT_HEADER_FRAC = float(os.environ.get("GLMOCR_DEFAULT_HEADER_FRAC", "0.12")) | |
| # ββ STEP 1: Fix config ββββββββββββββββββββββββββββββββββββββββ | |
| try: | |
| with open(CONFIG_PATH, "r") as f: | |
| config = yaml.safe_load(f) | |
| config["pipeline"]["maas"]["enabled"] = True | |
| config["pipeline"]["maas"]["api_key"] = os.environ.get( | |
| "GLMOCR_API_KEY", "4570c28bdea5493c9efae9dae68edc66.sGbA9DLlcX1GlvqV" | |
| ) | |
| to_include = {"header", "footer"} | |
| formatter_section = config.get("pipeline", {}).get("result_formatter", {}) | |
| if "abandon" in formatter_section and isinstance(formatter_section["abandon"], list): | |
| formatter_section["abandon"] = [ | |
| x for x in formatter_section["abandon"] if x not in to_include | |
| ] | |
| with open(CONFIG_PATH, "w") as f: | |
| yaml.dump(config, f, default_flow_style=False, sort_keys=False) | |
| except Exception: | |
| pass | |
| # ββ STEP 2: Fix result_formatter.py ββββββββββββββββββββββββββ | |
| try: | |
| with open(FORMATTER_PATH, "r") as f: | |
| source = f.read() | |
| for label in ('"header"', "'header'", '"footer"', "'footer'", | |
| '"doc_header"', "'doc_header'", '"doc_footer"', "'doc_footer'"): | |
| source = re.sub(r",\s*" + re.escape(label), "", source) | |
| source = re.sub(re.escape(label) + r"\s*,", "", source) | |
| source = re.sub(re.escape(label), "", source) | |
| with open(FORMATTER_PATH, "w") as f: | |
| f.write(source) | |
| except Exception: | |
| pass | |
| # ββ Single shared parser ββββββββββββββββββββββββββββββββββββββ | |
| _parser = None | |
| def get_parser(): | |
| global _parser | |
| if _parser is None: | |
| from glmocr import GlmOcr | |
| _parser = GlmOcr( | |
| api_key=os.environ.get("GLMOCR_API_KEY", "4570c28bdea5493c9efae9dae68edc66.sGbA9DLlcX1GlvqV"), | |
| mode="maas", | |
| ) | |
| return _parser | |
| # ββ STEP 3: Header extraction helpers ββββββββββββββββββββββββ | |
| def get_top_gap_frac(regions, img_height): | |
| """ | |
| Find the fraction of image height that the API missed at the top. | |
| Uses bbox_2d of the first (topmost) region. | |
| Returns a fraction (0β1) or None if no gap detected. | |
| """ | |
| y_tops = [] | |
| for r in regions: | |
| bbox = r.get("bbox_2d") if isinstance(r, dict) else getattr(r, "bbox_2d", None) | |
| if bbox and len(bbox) >= 4: | |
| y_tops.append(bbox[1]) | |
| if not y_tops: | |
| return DEFAULT_HEADER_FRAC # no bbox info β use default band | |
| first_y = min(y_tops) | |
| # Only treat as missed header if gap > 8% of image height | |
| return (first_y / img_height) if first_y > img_height * 0.08 else None | |
| def extract_header_text(pdf_path, page_num, y_end_frac): | |
| """Extract text from the top zone of a PDF page using PyMuPDF.""" | |
| try: | |
| import pymupdf as fitz | |
| doc = fitz.open(pdf_path) | |
| page = doc[page_num] | |
| h, w = page.rect.height, page.rect.width | |
| text = page.get_text(clip=fitz.Rect(0, 0, w, h * y_end_frac)).strip() | |
| doc.close() | |
| return text | |
| except Exception: | |
| return "" | |
| def get_page_data(page_result): | |
| """Extract markdown and regions from one PipelineResult.""" | |
| md = "" | |
| if hasattr(page_result, "markdown_result") and page_result.markdown_result: | |
| md = (page_result.markdown_result or "").strip() | |
| regions = [] | |
| if hasattr(page_result, "json_result"): | |
| jr = page_result.json_result | |
| if isinstance(jr, list) and len(jr) > 0: | |
| r = jr[0] if isinstance(jr[0], list) else jr | |
| if isinstance(r, list): | |
| regions = r | |
| return md, regions | |
| # ββ STEP 4: Main OCR function βββββββββββββββββββββββββββββββββ | |
| def run_ocr(uploaded_file): | |
| if uploaded_file is None: | |
| return "Please upload a file." | |
| try: | |
| import pymupdf as fitz | |
| path = uploaded_file.name if hasattr(uploaded_file, "name") else str(uploaded_file) | |
| is_pdf = path.lower().endswith(".pdf") | |
| parser = get_parser() | |
| if is_pdf: | |
| doc = fitz.open(path) | |
| page_images = [] | |
| page_heights = [] | |
| for i in range(len(doc)): | |
| pix = doc[i].get_pixmap(matrix=fitz.Matrix(1.5, 1.5), alpha=False) | |
| img_path = os.path.join(tempfile.gettempdir(), f"maas_page_{i}.png") | |
| pix.save(img_path) | |
| page_images.append(img_path) | |
| # Image height at 1.5x matches bbox_2d coordinate space | |
| page_heights.append(doc[i].rect.height * 1.5) | |
| doc.close() | |
| results = parser.parse(page_images) | |
| else: | |
| page_images = [path] | |
| page_heights = [] | |
| results = parser.parse(path) | |
| if not isinstance(results, list): | |
| results = [results] | |
| all_pages = [] | |
| for page_num, page_result in enumerate(results): | |
| page_md, regions = get_page_data(page_result) | |
| parts = [] | |
| # ββ HEADER at TOP βββββββββββββββββββββββββββββββββ | |
| # Only for PDFs β extract top zone the API missed. | |
| # We do NOT extract footer β the API already captures | |
| # footer text (copyright, address) as regular text regions. | |
| if is_pdf and page_num < len(page_heights): | |
| top_gap = get_top_gap_frac(regions, page_heights[page_num]) | |
| if top_gap is not None: | |
| hdr = extract_header_text(path, page_num, top_gap) | |
| if hdr: | |
| parts.append(hdr) | |
| # ββ BODY in MIDDLE ββββββββββββββββββββββββββββββββ | |
| if page_md: | |
| parts.append(page_md) | |
| # No footer extraction β avoids duplicating body content. | |
| # The API captures real footer text as text regions in page_md. | |
| if parts: | |
| all_pages.append("\n\n".join(parts)) | |
| return "\n\n---\n\n".join(all_pages) if all_pages else "(No content)" | |
| except Exception as e: | |
| import traceback | |
| log.exception("run_ocr failed: %s", e) | |
| return f"Error: {e}\n\n{traceback.format_exc()}" | |
| # ββ STEP 5: Gradio UI βββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Blocks(title="GLM-OCR") as demo: | |
| gr.Markdown("# π GLM-OCR\nUpload a PDF or image. Headers included in correct position.") | |
| file_in = gr.File( | |
| label="Upload PDF or image", | |
| file_types=[".pdf", ".png", ".jpg", ".jpeg", ".tiff", ".bmp"] | |
| ) | |
| run_btn = gr.Button("βΆ Run OCR", variant="primary") | |
| out = gr.Textbox(lines=40, label="Output") | |
| run_btn.click(fn=run_ocr, inputs=file_in, outputs=out) | |
| if __name__ == "__main__": | |
| demo.launch() | |