CoolFace
Apppublic

prithivMLmods/POINTS-Reader-OCR

sourceHugging Faceapache-2.0updated 5mo agoView on Hugging Face
18likes
app.py400 linesDownload Raw Back to root
1import spaces2import json3import math4import os5import traceback6from io import BytesIO7from typing import Any, Dict, List, Optional, Tuple8from typing import Iterable9import re10import time11from threading import Thread12from io import BytesIO13import subprocess14import uuid15import tempfile16 17import gradio as gr18import requests19import torch20from PIL import Image21import fitz22import numpy as np23 24from transformers import AutoModelForCausalLM, AutoTokenizer, Qwen2VLImageProcessor25 26from reportlab.lib.pagesizes import A427from reportlab.lib.styles import getSampleStyleSheet28from reportlab.platypus import SimpleDocTemplate, Image as RLImage, Paragraph, Spacer29from reportlab.lib.units import inch30 31from transformers.image_utils import load_image32from gradio.themes import Soft33from gradio.themes.utils import colors, fonts, sizes34 35colors.steel_blue = colors.Color(36    name="steel_blue",37    c50="#EBF3F8",38    c100="#D3E5F0",39    c200="#A8CCE1",40    c300="#7DB3D2",41    c400="#529AC3",42    c500="#4682B4",43    c600="#3E72A0",44    c700="#36638C",45    c800="#2E5378",46    c900="#264364",47    c950="#1E3450",48)49 50class SteelBlueTheme(Soft):51    def __init__(52        self,53        *,54        primary_hue: colors.Color | str = colors.gray,55        secondary_hue: colors.Color | str = colors.steel_blue,56        neutral_hue: colors.Color | str = colors.slate,57        text_size: sizes.Size | str = sizes.text_lg,58        font: fonts.Font | str | Iterable[fonts.Font | str] = (59            fonts.GoogleFont("Outfit"), "Arial", "sans-serif",60        ),61        font_mono: fonts.Font | str | Iterable[fonts.Font | str] = (62            fonts.GoogleFont("IBM Plex Mono"), "ui-monospace", "monospace",63        ),64    ):65        super().__init__(66            primary_hue=primary_hue,67            secondary_hue=secondary_hue,68            neutral_hue=neutral_hue,69            text_size=text_size,70            font=font,71            font_mono=font_mono,72        )73        super().set(74            background_fill_primary="*primary_50",75            background_fill_primary_dark="*primary_900",76            body_background_fill="linear-gradient(135deg, *primary_200, *primary_100)",77            body_background_fill_dark="linear-gradient(135deg, *primary_900, *primary_800)",78            button_primary_text_color="white",79            button_primary_text_color_hover="white",80            button_primary_background_fill="linear-gradient(90deg, *secondary_500, *secondary_600)",81            button_primary_background_fill_hover="linear-gradient(90deg, *secondary_600, *secondary_700)",82            button_primary_background_fill_dark="linear-gradient(90deg, *secondary_600, *secondary_800)",83            button_primary_background_fill_hover_dark="linear-gradient(90deg, *secondary_500, *secondary_500)",84            button_secondary_text_color="black",85            button_secondary_text_color_hover="white",86            button_secondary_background_fill="linear-gradient(90deg, *primary_300, *primary_300)",87            button_secondary_background_fill_hover="linear-gradient(90deg, *primary_400, *primary_400)",88            button_secondary_background_fill_dark="linear-gradient(90deg, *primary_500, *primary_600)",89            button_secondary_background_fill_hover_dark="linear-gradient(90deg, *primary_500, *primary_500)",90            slider_color="*secondary_500",91            slider_color_dark="*secondary_600",92            block_title_text_weight="600",93            block_border_width="3px",94            block_shadow="*shadow_drop_lg",95            button_primary_shadow="*shadow_drop_lg",96            button_large_padding="11px",97            color_accent_soft="*primary_100",98            block_label_background_fill="*primary_200",99        )100 101steel_blue_theme = SteelBlueTheme()102 103# --- Constants and Model Setup ---104MAX_INPUT_TOKEN_LENGTH = 4096105device = torch.device("cuda" if torch.cuda.is_available() else "cpu")106 107print("CUDA_VISIBLE_DEVICES=", os.environ.get("CUDA_VISIBLE_DEVICES"))108print("torch.__version__ =", torch.__version__)109print("torch.version.cuda =", torch.version.cuda)110print("cuda available:", torch.cuda.is_available())111print("cuda device count:", torch.cuda.device_count())112if torch.cuda.is_available():113    print("current device:", torch.cuda.current_device())114    print("device name:", torch.cuda.get_device_name(torch.cuda.current_device()))115 116print("Using device:", device)117 118 119# --- Model Loading: tencent/POINTS-Reader ---120MODEL_PATH = 'tencent/POINTS-Reader'121 122print(f"Loading model: {MODEL_PATH}")123model = AutoModelForCausalLM.from_pretrained(124    MODEL_PATH,125    trust_remote_code=True,126    torch_dtype=torch.bfloat16,127    device_map='auto'128)129tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH, trust_remote_code=True)130image_processor = Qwen2VLImageProcessor.from_pretrained(MODEL_PATH)131print("Model loaded successfully.")132 133 134# --- PDF Generation and Preview Utility Function ---135def generate_and_preview_pdf(image: Image.Image, text_content: str, font_size: int, line_spacing: float, alignment: str, image_size: str):136    """137    Generates a PDF, saves it, and then creates image previews of its pages.138    Returns the path to the PDF and a list of paths to the preview images.139    """140    if image is None or not text_content or not text_content.strip():141        raise gr.Error("Cannot generate PDF. Image or text content is missing.")142 143    # --- 1. Generate the PDF ---144    temp_dir = tempfile.gettempdir()145    pdf_filename = os.path.join(temp_dir, f"output_{uuid.uuid4()}.pdf")146    doc = SimpleDocTemplate(147        pdf_filename,148        pagesize=A4,149        rightMargin=inch, leftMargin=inch,150        topMargin=inch, bottomMargin=inch151    )152    styles = getSampleStyleSheet()153    style_normal = styles["Normal"]154    style_normal.fontSize = int(font_size)155    style_normal.leading = int(font_size) * line_spacing156    style_normal.alignment = {"Left": 0, "Center": 1, "Right": 2, "Justified": 4}[alignment]157 158    story = []159 160    img_buffer = BytesIO()161    image.save(img_buffer, format='PNG')162    img_buffer.seek(0)163    164    page_width, _ = A4165    available_width = page_width - 2 * inch166    image_widths = {167        "Small": available_width * 0.3,168        "Medium": available_width * 0.6,169        "Large": available_width * 0.9,170    }171    img_width = image_widths[image_size]172    img = RLImage(img_buffer, width=img_width, height=image.height * (img_width / image.width))173    story.append(img)174    story.append(Spacer(1, 12))175 176    cleaned_text = re.sub(r'#+\s*', '', text_content).replace("*", "")177    text_paragraphs = cleaned_text.split('\n')178    179    for para in text_paragraphs:180        if para.strip():181            story.append(Paragraph(para, style_normal))182 183    doc.build(story)184 185    # --- 2. Render PDF pages as images for preview ---186    preview_images = []187    try:188        pdf_doc = fitz.open(pdf_filename)189        for page_num in range(len(pdf_doc)):190            page = pdf_doc.load_page(page_num)191            pix = page.get_pixmap(dpi=150)192            preview_img_path = os.path.join(temp_dir, f"preview_{uuid.uuid4()}_p{page_num}.png")193            pix.save(preview_img_path)194            preview_images.append(preview_img_path)195        pdf_doc.close()196    except Exception as e:197        print(f"Error generating PDF preview: {e}")198        199    return pdf_filename, preview_images200 201 202# --- Core Application Logic ---203@spaces.GPU204def process_document_stream(205    image: Image.Image, 206    prompt_input: str,207    image_scale_factor: float, # New parameter for image scaling208    max_new_tokens: int,209    temperature: float,210    top_p: float,211    top_k: int,212    repetition_penalty: float213):214    """215    Main function that handles model inference using tencent/POINTS-Reader.216    """217    if image is None:218        yield "Please upload an image.", ""219        return220    if not prompt_input or not prompt_input.strip():221        yield "Please enter a prompt.", ""222        return223 224    # --- IMPLEMENTATION: Image Scaling based on user input ---225    if image_scale_factor > 1.0:226        try:227            original_width, original_height = image.size228            new_width = int(original_width * image_scale_factor)229            new_height = int(original_height * image_scale_factor)230            print(f"Scaling image from {image.size} to ({new_width}, {new_height}) with factor {image_scale_factor}.")231            # Use a high-quality resampling filter for better results232            image = image.resize((new_width, new_height), Image.Resampling.LANCZOS)233        except Exception as e:234            print(f"Error during image scaling: {e}")235            # Continue with the original image if scaling fails236            pass237    # --- END IMPLEMENTATION ---238 239    temp_image_path = None240    try:241        # --- FIX: Save the PIL Image to a temporary file ---242        # The model expects a file path, not a PIL object.243        temp_dir = tempfile.gettempdir()244        temp_image_path = os.path.join(temp_dir, f"temp_image_{uuid.uuid4()}.png")245        image.save(temp_image_path)246        247        # Prepare content for the model using the temporary file path248        content = [249            dict(type='image', image=temp_image_path),250            dict(type='text', text=prompt_input)251        ]252        messages = [253            {254                'role': 'user',255                'content': content256            }257        ]258        259        # Prepare generation configuration from UI inputs260        generation_config = {261            'max_new_tokens': max_new_tokens,262            'repetition_penalty': repetition_penalty,263            'temperature': temperature,264            'top_p': top_p,265            'top_k': top_k,266            'do_sample': True if temperature > 0 else False267        }268 269        # Run inference270        response = model.chat(271            messages,272            tokenizer,273            image_processor,274            generation_config275        )276        # Yield the full response at once277        yield response, response278 279    except Exception as e:280        traceback.print_exc()281        yield f"An error occurred during processing: {str(e)}", ""282    finally:283        # --- Clean up the temporary image file ---284        if temp_image_path and os.path.exists(temp_image_path):285            os.remove(temp_image_path)286 287css = """288.main-container { max-width: 1400px; margin: 0 auto; }289.process-button { border: none !important; color: white !important; font-weight: bold !important; background-color: blue !important;}290.process-button:hover { background-color: darkblue !important; transform: translateY(-2px) !important; box-shadow: 0 4px 8px rgba(0,0,0,0.2) !important; }291#gallery { min-height: 400px; }292"""293 294def create_gradio_interface():295    with gr.Blocks() as demo:296        gr.HTML(f"""297        <div class="title" style="text-align: center">298            <h1>Document Conversion with POINTS Reader ๐Ÿ“–</h1>299            <p style="font-size: 1.1em; color: #6b7280; margin-bottom: 0.6em;">300                Using tencent/POINTS-Reader Multimodal for Image Content Extraction301            </p>302        </div>303        """)304 305        with gr.Row():306            # Left Column (Inputs)307            with gr.Column(scale=1):308                gr.Textbox(309                    label="Model in Use",310                    value="tencent/POINTS-Reader",311                    interactive=False312                )313                prompt_input = gr.Textbox(314                    label="Query Input",315                    placeholder="โœฆ๏ธŽ Enter the prompt",316                    value="Perform OCR on the image precisely.",317                )318                image_input = gr.Image(label="Upload Image", type="pil", sources=['upload'])319                320                with gr.Accordion("Advanced Settings", open=False):321                    # --- NEW UI ELEMENT: Image Scaling Slider ---322                    image_scale_factor = gr.Slider(323                        minimum=1.0, 324                        maximum=3.0, 325                        value=1.0, 326                        step=0.1, 327                        label="Image Upscale Factor",328                        info="Increases image size before processing. Can improve OCR on small text. Default: 1.0 (no change)."329                    )330                    # --- END NEW UI ELEMENT ---331                    max_new_tokens = gr.Slider(minimum=512, maximum=8192, value=2048, step=256, label="Max New Tokens")332                    temperature = gr.Slider(label="Temperature", minimum=0.1, maximum=1.0, step=0.05, value=0.7)333                    top_p = gr.Slider(label="Top-p (nucleus sampling)", minimum=0.05, maximum=1.0, step=0.05, value=0.8)334                    top_k = gr.Slider(label="Top-k", minimum=1, maximum=100, step=1, value=20)335                    repetition_penalty = gr.Slider(label="Repetition penalty", minimum=1.0, maximum=2.0, step=0.05, value=1.05)336                    337                    gr.Markdown("### PDF Export Settings")338                    font_size = gr.Dropdown(choices=["8", "10", "12", "14", "16", "18"], value="12", label="Font Size")339                    line_spacing = gr.Dropdown(choices=[1.0, 1.15, 1.5, 2.0], value=1.15, label="Line Spacing")340                    alignment = gr.Dropdown(choices=["Left", "Center", "Right", "Justified"], value="Justified", label="Text Alignment")341                    image_size = gr.Dropdown(choices=["Small", "Medium", "Large"], value="Medium", label="Image Size in PDF")342 343                process_btn = gr.Button("๐Ÿš€ Process Image", variant="primary", elem_classes=["process-button"], size="lg")344                clear_btn = gr.Button("๐Ÿ—‘๏ธ Clear All", variant="secondary")345 346            # Right Column (Outputs)347            with gr.Column(scale=2):348                with gr.Tabs() as tabs:349                    with gr.Tab("๐Ÿ“ Extracted Content"):350                        raw_output_stream = gr.Textbox(label="Raw Model Output (max T โ‰ค 120s)", interactive=True, lines=15)351                        with gr.Row():352                            examples = gr.Examples(353                                examples=["examples/1.jpeg", 354                                          "examples/2.jpeg", 355                                          "examples/3.jpeg",356                                          "examples/4.jpeg", 357                                          "examples/5.jpeg"],358                                inputs=image_input, label="Examples"359                            )360                        gr.Markdown("[Report-Bug๐Ÿ’ป](https://huggingface.co/spaces/prithivMLmods/POINTS-Reader-OCR/discussions) | [prithivMLmods๐Ÿค—](https://huggingface.co/prithivMLmods)")361                    362                    with gr.Tab("๐Ÿ“ฐ README.md"):363                        with gr.Accordion("(Result.md)", open=True): 364                            # --- FIX: Added latex_delimiters to enable LaTeX rendering ---365                            markdown_output = gr.Markdown(latex_delimiters=[366                                {"left": "$$", "right": "$$", "display": True},367                                {"left": "$", "right": "$", "display": False}368                            ])369 370                    with gr.Tab("๐Ÿ“‹ PDF Preview"):371                        generate_pdf_btn = gr.Button("๐Ÿ“„ Generate PDF & Render", variant="primary")372                        pdf_output_file = gr.File(label="Download Generated PDF", interactive=False)373                        pdf_preview_gallery = gr.Gallery(label="PDF Page Preview", show_label=True, elem_id="gallery", columns=2, object_fit="contain", height="auto")374 375        # Event Handlers376        def clear_all_outputs():377            return None, "", "Raw output will appear here.", "", None, None378 379        process_btn.click(380            fn=process_document_stream,381            # --- UPDATE: Add the new slider to the inputs list ---382            inputs=[image_input, prompt_input, image_scale_factor, max_new_tokens, temperature, top_p, top_k, repetition_penalty],383            outputs=[raw_output_stream, markdown_output]384        )385        386        generate_pdf_btn.click(387            fn=generate_and_preview_pdf,388            inputs=[image_input, raw_output_stream, font_size, line_spacing, alignment, image_size],389            outputs=[pdf_output_file, pdf_preview_gallery]390        )391 392        clear_btn.click(393            clear_all_outputs,394            outputs=[image_input, prompt_input, raw_output_stream, markdown_output, pdf_output_file, pdf_preview_gallery]395        )396    return demo397 398if __name__ == "__main__":399    demo = create_gradio_interface()400    demo.queue(max_size=50).launch(theme=steel_blue_theme, css=css, mcp_server=True, ssr_mode=False, show_error=True)