c6308dc822
* Add OCR test data and implement tests for various document formats - Created HTML file with multiple images for testing OCR extraction. - Added several PDF files with different layouts and image placements to validate OCR functionality. - Introduced PPTX files with complex layouts and images at various positions for comprehensive testing. - Included XLSX files with multiple images and complex layouts to ensure accurate OCR extraction. - Implemented a new test suite in `test_ocr.py` to validate OCR functionality across all document types, ensuring context preservation and accuracy. * Enhance OCR functionality and validation in document converters - Refactor image extraction and processing in PDF, PPTX, and XLSX converters for improved readability and consistency. - Implement detailed validation for OCR text positioning relative to surrounding text in test cases. - Introduce comprehensive tests for expected OCR results across various document types, ensuring no base64 images are present. - Improve error handling and logging for better debugging during OCR extraction. * Add support for scanned PDFs with full-page OCR fallback and implement tests * Bump version to 0.1.6b1 in __about__.py * Refactor OCR services to support LLM Vision, update README and tests accordingly * Add OCR-enabled converters and ensure consistent OCR format across document types * Refactor converters to improve import organization and enhance OCR functionality across DOCX, PDF, PPTX, and XLSX converters * Refactor exception imports for consistency across converters and tests * Fix OCR tests to match MockOCRService output and fix cross-platform file URI handling * Bump version to 0.1.6b1 in __about__.py * Skip DOCX/XLSX/PPTX OCR tests when optional dependencies are missing * Add comprehensive OCR test suite for various document formats - Introduced multiple test documents for PDF, DOCX, XLSX, and PPTX formats, covering scenarios with images at the start, middle, and end. - Implemented tests for complex layouts, multi-page documents, and documents with multiple images. - Created a new test script `test_ocr.py` to validate OCR functionality, ensuring context preservation and accurate text extraction. - Added expected OCR results for validation against ground truth. - Included tests for scanned documents to verify OCR fallback mechanisms. * Remove obsolete HTML test files and refactor test cases for file URIs and OCR format consistency - Deleted `html_image_start.html` and `html_multiple_images.html` as they are no longer needed. - Updated `test_file_uris` in `test_module_misc.py` to simplify assertions by removing unnecessary `url2pathname` usage. - Removed `test_ocr_format_consistency.py` as it is no longer relevant to the current testing framework. * Refactor OCR processing in PdfConverterWithOCR and enhance unit tests for multipage PDFs * Revert * Revert * Update REDMEs * Refactor import statements for consistency and improve formatting in converter and test files
111 lines
3.3 KiB
Python
111 lines
3.3 KiB
Python
"""
|
|
OCR Service Layer for MarkItDown
|
|
Provides LLM Vision-based image text extraction.
|
|
"""
|
|
|
|
import base64
|
|
from typing import Any, BinaryIO
|
|
from dataclasses import dataclass
|
|
|
|
from markitdown import StreamInfo
|
|
|
|
|
|
@dataclass
|
|
class OCRResult:
|
|
"""Result from OCR extraction."""
|
|
|
|
text: str
|
|
confidence: float | None = None
|
|
backend_used: str | None = None
|
|
error: str | None = None
|
|
|
|
|
|
class LLMVisionOCRService:
|
|
"""OCR service using LLM vision models (OpenAI-compatible)."""
|
|
|
|
def __init__(
|
|
self,
|
|
client: Any,
|
|
model: str,
|
|
default_prompt: str | None = None,
|
|
) -> None:
|
|
"""
|
|
Initialize LLM Vision OCR service.
|
|
|
|
Args:
|
|
client: OpenAI-compatible client
|
|
model: Model name (e.g., 'gpt-4o', 'gemini-2.0-flash')
|
|
default_prompt: Default prompt for OCR extraction
|
|
"""
|
|
self.client = client
|
|
self.model = model
|
|
self.default_prompt = default_prompt or (
|
|
"Extract all text from this image. "
|
|
"Return ONLY the extracted text, maintaining the original "
|
|
"layout and order. Do not add any commentary or description."
|
|
)
|
|
|
|
def extract_text(
|
|
self,
|
|
image_stream: BinaryIO,
|
|
prompt: str | None = None,
|
|
stream_info: StreamInfo | None = None,
|
|
**kwargs: Any,
|
|
) -> OCRResult:
|
|
"""Extract text using LLM vision."""
|
|
if self.client is None:
|
|
return OCRResult(
|
|
text="",
|
|
backend_used="llm_vision",
|
|
error="LLM client not configured",
|
|
)
|
|
|
|
try:
|
|
image_stream.seek(0)
|
|
|
|
content_type: str | None = None
|
|
if stream_info:
|
|
content_type = stream_info.mimetype
|
|
|
|
if not content_type:
|
|
try:
|
|
from PIL import Image
|
|
|
|
image_stream.seek(0)
|
|
img = Image.open(image_stream)
|
|
fmt = img.format.lower() if img.format else "png"
|
|
content_type = f"image/{fmt}"
|
|
except Exception:
|
|
content_type = "image/png"
|
|
|
|
image_stream.seek(0)
|
|
base64_image = base64.b64encode(image_stream.read()).decode("utf-8")
|
|
data_uri = f"data:{content_type};base64,{base64_image}"
|
|
|
|
actual_prompt = prompt or self.default_prompt
|
|
response = self.client.chat.completions.create(
|
|
model=self.model,
|
|
messages=[
|
|
{
|
|
"role": "user",
|
|
"content": [
|
|
{"type": "text", "text": actual_prompt},
|
|
{
|
|
"type": "image_url",
|
|
"image_url": {"url": data_uri},
|
|
},
|
|
],
|
|
}
|
|
],
|
|
)
|
|
|
|
text = response.choices[0].message.content
|
|
return OCRResult(
|
|
text=text.strip() if text else "",
|
|
backend_used="llm_vision",
|
|
)
|
|
except Exception as e:
|
|
return OCRResult(text="", backend_used="llm_vision", error=str(e))
|
|
finally:
|
|
image_stream.seek(0)
|