Skip to content

VLM4OCR v0.6.0

Latest

Choose a tag to compare

@daviden1013 daviden1013 released this 12 Jul 05:07

⚡Highlights

OCR Pipelines for heterogeneous documents

OCREngine applies one prompt to every page. That breaks down when a single PDF or TIFF interleaves different page types — for example a packet of cognitive-assessment forms where each form needs its own JSON schema. v0.6.0 adds OCR pipelines: you decide, per page, how that page is processed, while the pipeline owns file loading, concurrency, page ordering, error handling, and OCRResult assembly.

OCREngine.ocr_image_async — the atomic building block

Run a single, already-loaded image through an engine's prompt and get back a standalone OCRPage. No preprocessing is applied (the caller owns rotate/resize), so a page can be OCR'd once and reused across calls.

import asyncio
from PIL import Image
from vlm4ocr import VLLMVLMEngine, OCREngine

engine = OCREngine(VLLMVLMEngine(model="Qwen/Qwen3-VL-30B-A3B-Instruct"), output_mode="markdown")
page = asyncio.run(engine.ocr_image_async(Image.open("page.png")))
print(page.text)

IndependentPagePipeline — per-page processing, concurrently

Supply a process_page(image, *, messages_logger=None) -> OCRPage function and get a full pipeline whose concurrent_ocr mirrors OCREngine.concurrent_ocr (same arguments, same first-complete-first-out AsyncGenerator[OCRResult]). process_page receives only its own page image, so page independence is guaranteed by construction.

import json, asyncio
from vlm4ocr import OCREngine, IndependentPagePipeline, OCRPage

# One classifier + per-type extractors + a fallback — each engine is an atom (one prompt).
classifier = OCREngine(vlm_engine, output_mode="JSON",
                       user_prompt='Classify this page. Respond as JSON: {"page_type": "<one_token>"}')
extractors = {"form_a": OCREngine(vlm_engine, output_mode="JSON", user_prompt="<form A schema>"),
              "form_b": OCREngine(vlm_engine, output_mode="JSON", user_prompt="<form B schema>")}
default = OCREngine(vlm_engine, output_mode="JSON", user_prompt="<generic schema>")

async def classify_and_extract(image, *, messages_logger=None) -> OCRPage:
    label = json.loads((await classifier.ocr_image_async(image, messages_logger=messages_logger)).text)
    page_type = (label[0] if isinstance(label, list) else label).get("page_type", "unknown")
    page = await extractors.get(page_type, default).ocr_image_async(image, messages_logger=messages_logger)
    page.metadata["page_type"] = page_type
    return page

pipeline = IndependentPagePipeline(classify_and_extract, output_mode="JSON")

async def run():
    async for result in pipeline.concurrent_ocr(pdf_paths, concurrent_batch_size=8):
        for page in result.pages:
            print(page.metadata["page_type"], page.text)

asyncio.run(run())

All routing logic — classifier prompt, schemas, type labels — lives in your code; the pipeline only orchestrates. See the OCR Pipelines guide for the full walkthrough and the independence assumption.

OCRPage.metadata

OCRPage now carries a free-form metadata dict (e.g. a page type assigned by a routing pipeline), preserved on each page of the resulting OCRResult. Dict-style access continues to work.

Changes

  • New: IndependentPagePipeline for per-page custom processing (routing, classification, multi-pass refinement) with concurrent, files-in / OCRResult-out execution. Exported from the top-level vlm4ocr namespace.
  • New: OCREngine.ocr_image_async(image, ...) — the atomic, preprocessing-free single-image OCR call, returning a standalone OCRPage.
  • New: OCRPage.metadata free-form dict, and OCRResult.add_page(..., metadata=...).
  • New: OCRResult and OCRPage exported from the top-level vlm4ocr namespace.
  • Internal: loader dispatch consolidated into vlm4ocr.utils.get_data_loader; SUPPORTED_IMAGE_EXTS now lives in vlm4ocr.utils.
  • Compat: fully backward compatible — OCREngine and its existing methods are unchanged.