{"slug": "how-to-build-an-end-to-end-ocr-pipeline-with-baidus-unlimited-ocr-for-high-and", "title": "How to Build an End-to-End OCR Pipeline with Baidu’s Unlimited-OCR for High-Resolution Images and Multi-Page PDF Parsing", "summary": "Baidu's Unlimited-OCR, a 3B-parameter vision-language model, enables end-to-end OCR for high-resolution images and multi-page PDFs without separate layout analysis, according to a tutorial published on Hugging Face. The tutorial demonstrates a complete pipeline using the model's tiled Gundam and Base inference modes, supporting structured output for dense layouts, tables, and cross-page content.", "body_md": "In this tutorial, we build a complete workflow for running[ Baidu’s Unlimited-OCR](https://huggingface.co/baidu/Unlimited-OCR) model on document images and multi-page PDFs. We configure the GPU environment, install the required dependencies, load the 3B-parameter vision-language model with automatic selection of bfloat16 or float16, and generate structured sample documents for testing. We then evaluate both the tiled Gundam inference mode and the faster Base mode for single-page OCR before extending the pipeline to multi-page PDF parsing with PyMuPDF and infer_multi(). Throughout the workflow, we preserve long-context generation settings, repetition controls, and structured output handling to process dense layouts, tables, paragraphs, and cross-page content in a reproducible end-to-end pipeline.\n\n``` python\nimport subprocess, sys\ndef pip_install(*pkgs):\n   subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", *pkgs])\nprint(\">> Installing dependencies (1-2 min)...\")\npip_install(\n   \"transformers==4.57.1\",\n   \"Pillow\",\n   \"matplotlib\",\n   \"einops\",\n   \"addict\",\n   \"easydict\",\n   \"pymupdf\",\n   \"psutil\",\n   \"accelerate\",\n)\nprint(\">> Done.\")\nimport os\nimport torch\nfrom transformers import AutoModel, AutoTokenizer\nassert torch.cuda.is_available(), (\n   \"No GPU detected! In Colab: Runtime -> Change runtime type -> GPU.\"\n)\ngpu_name = torch.cuda.get_device_name(0)\nprint(f\">> GPU: {gpu_name}\")\nuse_bf16 = torch.cuda.is_bf16_supported()\nDTYPE = torch.bfloat16 if use_bf16 else torch.float16\nprint(f\">> Using dtype: {DTYPE}\")\nMODEL_NAME = \"baidu/Unlimited-OCR\"\nprint(\">> Downloading model (~6 GB for 3B params in BF16). First run takes a while...\")\ntokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True)\nmodel = AutoModel.from_pretrained(\n   MODEL_NAME,\n   trust_remote_code=True,\n   use_safetensors=True,\n   torch_dtype=DTYPE,\n)\nmodel = model.eval().cuda()\nprint(\">> Model loaded and moved to GPU.\")\n```\n\nWe install the required libraries and prepare the Google Colab environment for Unlimited-OCR inference. We verify that a CUDA-enabled GPU is available and automatically choose bfloat16 or float16 based on hardware support. We then load the tokenizer and the 3B-parameter model from Hugging Face, switch them to evaluation mode, and move them to the GPU.\n\n``` python\nfrom PIL import Image, ImageDraw, ImageFont\nimport textwrap\nos.makedirs(\"inputs\", exist_ok=True)\nos.makedirs(\"outputs/single_gundam\", exist_ok=True)\nos.makedirs(\"outputs/single_base\", exist_ok=True)\nos.makedirs(\"outputs/multi_page\", exist_ok=True)\ndef load_font(size):\n   for path in [\n       \"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf\",\n       \"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf\",\n   ]:\n       if os.path.exists(path):\n           return ImageFont.truetype(path, size)\n   return ImageFont.load_default()\ndef make_sample_page(path, page_no):\n   W, H = 1240, 1754\n   img = Image.new(\"RGB\", (W, H), \"white\")\n   d = ImageDraw.Draw(img)\n   title_f, head_f, body_f = load_font(48), load_font(34), load_font(26)\n   d.text((80, 70), f\"Quarterly Operations Report — Page {page_no}\",\n          fill=\"black\", font=title_f)\n   d.line([(80, 145), (W - 80, 145)], fill=\"black\", width=3)\n   body = (\n       \"This document demonstrates Unlimited-OCR's one-shot long-horizon \"\n       \"parsing. The model reads an entire page — headings, paragraphs, \"\n       \"and tables — and emits structured text in a single decoding pass. \"\n       \"Unlike classic OCR pipelines, no separate layout-analysis stage \"\n       \"is required.\"\n   )\n   y = 190\n   for line in textwrap.wrap(body, width=72):\n       d.text((80, y), line, fill=\"black\", font=body_f)\n       y += 40\n   y += 30\n   d.text((80, y), f\"Table {page_no}: Regional Revenue (USD, millions)\",\n          fill=\"black\", font=head_f)\n   y += 60\n   rows = [\n       [\"Region\",  \"Q1\",   \"Q2\",   \"Q3\"],\n       [\"North\",   \"12.4\", \"13.1\", \"15.0\"],\n       [\"South\",   \"9.8\",  \"10.2\", \"11.7\"],\n       [\"East\",    \"14.3\", \"13.9\", \"16.2\"],\n       [\"West\",    \"11.1\", \"12.5\", \"12.9\"],\n   ]\n   col_w, row_h, x0 = 260, 56, 80\n   for r, row in enumerate(rows):\n       for c, cell in enumerate(row):\n           x = x0 + c * col_w\n           d.rectangle([x, y, x + col_w, y + row_h], outline=\"black\", width=2)\n           d.text((x + 14, y + 12), cell, fill=\"black\", font=body_f)\n       y += row_h\n   y += 50\n   footer = (\n       f\"Note {page_no}: Figures are illustrative. Multi-page mode stitches \"\n       \"context across pages, so cross-page references remain coherent.\"\n   )\n   for line in textwrap.wrap(footer, width=72):\n       d.text((80, y), line, fill=\"black\", font=body_f)\n       y += 40\n   img.save(path)\n   return path\nIMAGE_PATH = make_sample_page(\"inputs/sample_page_1.png\", 1)\nPAGE_2     = make_sample_page(\"inputs/sample_page_2.png\", 2)\nPAGE_3     = make_sample_page(\"inputs/sample_page_3.png\", 3)\nprint(f\">> Sample pages written: {IMAGE_PATH}, {PAGE_2}, {PAGE_3}\")\nimport matplotlib.pyplot as plt\nplt.figure(figsize=(6, 8))\nplt.imshow(Image.open(IMAGE_PATH))\nplt.axis(\"off\")\nplt.title(\"Input document (page 1)\")\nplt.show()\n```\n\nWe create the required input and output directories and generate three realistic sample document pages with PIL. We add headings, paragraphs, tables, and footnotes to test the model on structured, layout-rich content. We also preview the first generated page with Matplotlib before sending it to the OCR pipeline.\n\n```\nprint(\"\\n\" + \"=\" * 76)\nprint(\"STEP 4: Single image — GUNDAM mode (tiled, high detail)\")\nprint(\"=\" * 76)\nmodel.infer(\n   tokenizer,\n   prompt=\"<image>document parsing.\",\n   image_file=IMAGE_PATH,\n   output_path=\"outputs/single_gundam\",\n   base_size=1024,\n   image_size=640,\n   crop_mode=True,\n   max_length=32768,\n   no_repeat_ngram_size=35,\n   ngram_window=128,\n   save_results=True,\n)\n```\n\nWe run single-image OCR using Gundam mode, which combines a global document view with tiled image crops. We enable crop_mode and use a smaller tile size to preserve fine text and improve recognition on dense document layouts. We also configure long-output generation and repetition controls to ensure the model produces stable, structured results.\n\n```\nprint(\"\\n\" + \"=\" * 76)\nprint(\"STEP 5: Single image — BASE mode (single view, faster)\")\nprint(\"=\" * 76)\nmodel.infer(\n   tokenizer,\n   prompt=\"<image>document parsing.\",\n   image_file=IMAGE_PATH,\n   output_path=\"outputs/single_base\",\n   base_size=1024,\n   image_size=1024,\n   crop_mode=False,\n   max_length=32768,\n   no_repeat_ngram_size=35,\n   ngram_window=128,\n   save_results=True,\n)\n```\n\nWe process the same document using Base mode with a single 1024-pixel image view. We turn off image cropping to reduce inference complexity and improve processing speed for clean, clearly printed pages. We retain the same output length and repetition-control settings to directly compare Base mode with Gundam mode.\n\n```\nprint(\"\\n\" + \"=\" * 76)\nprint(\"STEP 6: Multi-page / PDF parsing\")\nprint(\"=\" * 76)\nimport tempfile\nimport fitz\ndef pdf_to_images(pdf_path, dpi=300):\n   \"\"\"Rasterize every PDF page to a PNG; return the list of image paths.\"\"\"\n   doc = fitz.open(pdf_path)\n   tmp_dir = tempfile.mkdtemp(prefix=\"pdf_ocr_\")\n   mat = fitz.Matrix(dpi / 72, dpi / 72)\n   paths = []\n   for i, page in enumerate(doc):\n       out = os.path.join(tmp_dir, f\"page_{i + 1:04d}.png\")\n       page.get_pixmap(matrix=mat).save(out)\n       paths.append(out)\n   doc.close()\n   return paths\nSAMPLE_PDF = \"inputs/sample_doc.pdf\"\npdf = fitz.open()\nfor p in [IMAGE_PATH, PAGE_2, PAGE_3]:\n   img_doc = fitz.open(p)\n   rect = img_doc[0].rect\n   pdf_bytes = img_doc.convert_to_pdf()\n   img_pdf = fitz.open(\"pdf\", pdf_bytes)\n   page = pdf.new_page(width=rect.width, height=rect.height)\n   page.show_pdf_page(rect, img_pdf, 0)\npdf.save(SAMPLE_PDF)\npdf.close()\nprint(f\">> Built sample PDF: {SAMPLE_PDF}\")\npage_images = pdf_to_images(SAMPLE_PDF, dpi=300)\nprint(f\">> Rasterized {len(page_images)} pages\")\nmodel.infer_multi(\n   tokenizer,\n   prompt=\"<image>Multi page parsing.\",\n   image_files=page_images,\n   output_path=\"outputs/multi_page\",\n   image_size=1024,\n   max_length=32768,\n   no_repeat_ngram_size=35,\n   ngram_window=1024,\n   save_results=True,\n)\n```\n\nWe create a three-page PDF from the generated document images and rasterize each page of the PDF into a high-resolution PNG using PyMuPDF. We pass the resulting page-image sequence to infer_multi() so that the model can parse the complete document in a single long-horizon inference operation. We also widen the n-gram repetition window to maintain stable decoding across multiple pages.\n\n```\nprint(\"\\n\" + \"=\" * 76)\nprint(\"STEP 7: Saved outputs\")\nprint(\"=\" * 76)\nTEXT_EXTS = {\".txt\", \".md\", \".mmd\", \".json\"}\ndef show_outputs(root):\n   print(f\"\\n--- {root} ---\")\n   if not os.path.isdir(root):\n       print(\"  (no output directory found)\")\n       return\n   for dirpath, _, files in os.walk(root):\n       for fn in sorted(files):\n           fp = os.path.join(dirpath, fn)\n           size = os.path.getsize(fp)\n           print(f\"  {fp}  ({size:,} bytes)\")\n           if os.path.splitext(fn)[1].lower() in TEXT_EXTS:\n               with open(fp, \"r\", encoding=\"utf-8\", errors=\"replace\") as f:\n                   content = f.read()\n               preview = content[:1500]\n               print(\"  \" + \"-\" * 60)\n               print(\"\\n\".join(\"  | \" + ln for ln in preview.splitlines()))\n               if len(content) > 1500:\n                   print(f\"  | ... [{len(content) - 1500:,} more chars]\")\n               print(\"  \" + \"-\" * 60)\nfor out_dir in [\"outputs/single_gundam\", \"outputs/single_base\", \"outputs/multi_page\"]:\n   show_outputs(out_dir)\nprint(\"\"\"\n============================================================================\nDONE — CHEAT SHEET\n============================================================================\nSingle image, dense/small text .... infer(), gundam (640 + crop_mode=True)\nSingle image, clean print ......... infer(), base   (1024, crop_mode=False)\nMulti-page or PDF ................. infer_multi(), image_size=1024,\n                                    ngram_window=1024\nLong documents .................... keep max_length=32768 and the\n                                    no_repeat_ngram settings — they prevent\n                                    degeneration on long outputs.\nYour own files .................... upload via Colab sidebar, point\n                                    image_file / pdf_to_images() at them.\n============================================================================\n\"\"\")\n```\n\nWe inspect the output directories created by the single-page and multi-page inference runs. We list every generated file and display previews of supported text, Markdown, MMD, and JSON artifacts. We conclude the workflow with a concise reference summarizing the recommended inference modes for dense images, clean pages, and multi-page PDFs.\n\nIn conclusion, we completed a practical OCR pipeline that handles both high-detail single-page documents and long multi-page PDFs within Google Colab. We compared Gundam and Base inference modes, rasterized PDFs into model-ready page images, ran long-horizon document parsing, and inspected the generated text, Markdown, and auxiliary artifacts directly from the output directories. We also configured the workflow to adapt to different GPU capabilities while retaining the generation parameters required for stable long-document decoding. It provides us with a reusable foundation for applying Unlimited-OCR to reports, scanned forms, technical documents, tables, and other layout-rich content without relying on a separate traditional OCR and layout-analysis stack.\n\nCheck out the ** Full Code here. **Also, feel free to follow us on\n\n**and don’t forget to join our**[Twitter](https://x.com/intent/follow?screen_name=marktechpost)\n\n**and Subscribe to**\n\n[150k+ML SubReddit](https://www.reddit.com/r/machinelearningnews/)**. Wait! are you on telegram?**\n\n[our Newsletter](https://www.aidevsignals.com/)\n\n[now you can join us on telegram as well.](https://t.me/machinelearningresearchnews)Need to partner with us for promoting your GitHub Repo OR Hugging Face Page OR Product Release OR Webinar etc.? [Connect with us](https://forms.gle/wbash1wF6efRj8G58)\n\nSana Hassan, a consulting intern at Marktechpost and dual-degree student at IIT Madras, is passionate about applying technology and AI to address real-world challenges. With a keen interest in solving practical problems, he brings a fresh perspective to the intersection of AI and real-life solutions.", "url": "https://wpnews.pro/news/how-to-build-an-end-to-end-ocr-pipeline-with-baidus-unlimited-ocr-for-high-and", "canonical_source": "https://www.marktechpost.com/2026/07/23/how-to-build-an-end-to-end-ocr-pipeline-with-baidus-unlimited-ocr-for-high-resolution-images-and-multi-page-pdf-parsing/", "published_at": "2026-07-24 05:16:28+00:00", "updated_at": "2026-07-24 05:27:45.492741+00:00", "lang": "en", "topics": ["artificial-intelligence", "computer-vision", "large-language-models", "ai-tools", "natural-language-processing"], "entities": ["Baidu", "Unlimited-OCR", "Hugging Face", "PyMuPDF", "Google Colab"], "alternates": {"html": "https://wpnews.pro/news/how-to-build-an-end-to-end-ocr-pipeline-with-baidus-unlimited-ocr-for-high-and", "markdown": "https://wpnews.pro/news/how-to-build-an-end-to-end-ocr-pipeline-with-baidus-unlimited-ocr-for-high-and.md", "text": "https://wpnews.pro/news/how-to-build-an-end-to-end-ocr-pipeline-with-baidus-unlimited-ocr-for-high-and.txt", "jsonld": "https://wpnews.pro/news/how-to-build-an-end-to-end-ocr-pipeline-with-baidus-unlimited-ocr-for-high-and.jsonld"}}