diff --git a/docling/datamodel/pipeline_options.py b/docling/datamodel/pipeline_options.py index 874e5d8435..23720ead2a 100644 --- a/docling/datamodel/pipeline_options.py +++ b/docling/datamodel/pipeline_options.py @@ -216,6 +216,20 @@ class OcrOptions(BaseOptions): ), ] + scale: Annotated[ + float, + Field( + description=( + "Image scale multiplier applied before running OCR. The page is " + "rendered at 72 DPI times this factor, so the default 3 yields " + "216 DPI. Lower it when the source image is already high " + "resolution and upscaling degrades recognition." + ), + examples=[1.0, 3.0], + gt=0.0, + ), + ] = 3.0 + # Deprecated: superseded by `OcrMode.FULL_PAGE`. Kept for backwards compatibility # When set to True it forces `mode` to FULL_PAGE force_full_page_ocr: Annotated[ diff --git a/docling/models/stages/ocr/easyocr_model.py b/docling/models/stages/ocr/easyocr_model.py index a2c122780a..4fa6c06915 100644 --- a/docling/models/stages/ocr/easyocr_model.py +++ b/docling/models/stages/ocr/easyocr_model.py @@ -88,7 +88,8 @@ def __init__( ) self.options: EasyOcrOptions - self.scale = 3 # multiplier for 72 dpi == 216 dpi. + # multiplier for 72 dpi; the default 3.0 == 216 dpi. + self.scale = self.options.scale if self.enabled: try: diff --git a/docling/models/stages/ocr/nemotron_ocr_model.py b/docling/models/stages/ocr/nemotron_ocr_model.py index b2908e44cd..cb094a108a 100644 --- a/docling/models/stages/ocr/nemotron_ocr_model.py +++ b/docling/models/stages/ocr/nemotron_ocr_model.py @@ -120,7 +120,8 @@ def __init__( accelerator_options=accelerator_options, ) self.options: NemotronOcrOptions - self.scale = 3 # multiplier for 72 dpi == 216 dpi. + # multiplier for 72 dpi; the default 3.0 == 216 dpi. + self.scale = self.options.scale if self.enabled: self.validate_runtime(accelerator_options=accelerator_options) @@ -246,7 +247,7 @@ def _prediction_to_cell( ocr_rect: BoundingBox, image_width: int, image_height: int, - scale: int, + scale: float, ) -> TextCell: # `nemotron_ocr` returns normalized `left/right` and an inverted # pair `lower/upper`, where `lower` is the top Y and `upper` is the diff --git a/docling/models/stages/ocr/ocr_mac_model.py b/docling/models/stages/ocr/ocr_mac_model.py index b28bbf7079..a954e8330b 100644 --- a/docling/models/stages/ocr/ocr_mac_model.py +++ b/docling/models/stages/ocr/ocr_mac_model.py @@ -38,7 +38,8 @@ def __init__( ) self.options: OcrMacOptions - self.scale = 3 # multiplier for 72 dpi == 216 dpi. + # multiplier for 72 dpi; the default 3.0 == 216 dpi. + self.scale = self.options.scale if self.enabled: if "darwin" != sys.platform: diff --git a/docling/models/stages/ocr/rapid_ocr_model.py b/docling/models/stages/ocr/rapid_ocr_model.py index 4730e83c26..db7d887bf2 100644 --- a/docling/models/stages/ocr/rapid_ocr_model.py +++ b/docling/models/stages/ocr/rapid_ocr_model.py @@ -325,7 +325,8 @@ def __init__( ) self.options: RapidOcrOptions - self.scale = 3 # multiplier for 72 dpi == 216 dpi. + # multiplier for 72 dpi; the default 3.0 == 216 dpi. + self.scale = self.options.scale if self.enabled: try: diff --git a/docling/models/stages/ocr/tesseract_ocr_cli_model.py b/docling/models/stages/ocr/tesseract_ocr_cli_model.py index 05948a6b89..3efa6a2ad7 100644 --- a/docling/models/stages/ocr/tesseract_ocr_cli_model.py +++ b/docling/models/stages/ocr/tesseract_ocr_cli_model.py @@ -52,7 +52,8 @@ def __init__( ) self.options: TesseractCliOcrOptions - self.scale = 3 # multiplier for 72 dpi == 216 dpi. + # multiplier for 72 dpi; the default 3.0 == 216 dpi. + self.scale = self.options.scale self._name: Optional[str] = None self._version: Optional[str] = None diff --git a/docling/models/stages/ocr/tesseract_ocr_model.py b/docling/models/stages/ocr/tesseract_ocr_model.py index 5486f9afda..8862edc1d1 100644 --- a/docling/models/stages/ocr/tesseract_ocr_model.py +++ b/docling/models/stages/ocr/tesseract_ocr_model.py @@ -42,7 +42,8 @@ def __init__( ) self.options: TesseractOcrOptions self._is_auto: bool = "auto" in self.options.lang - self.scale = 3 # multiplier for 72 dpi == 216 dpi. + # multiplier for 72 dpi; the default 3.0 == 216 dpi. + self.scale = self.options.scale self.reader = None self.script_readers: dict[str, tesserocr.PyTessBaseAPI] = {} diff --git a/tests/test_options.py b/tests/test_options.py index 1757723382..35a4f159b2 100644 --- a/tests/test_options.py +++ b/tests/test_options.py @@ -3,6 +3,7 @@ from unittest.mock import Mock, patch import pytest +from pydantic import ValidationError from docling.backend.docling_parse_backend import ( DoclingParseDocumentBackend, @@ -21,9 +22,11 @@ ApiKserveV2ImageClassificationEngineOptions, ) from docling.datamodel.pipeline_options import ( + EasyOcrOptions, NemotronOcrOptions, PdfPipelineOptions, TableFormerMode, + TesseractCliOcrOptions, ) from docling.document_converter import ( ConversionError, @@ -31,7 +34,9 @@ PdfFormatOption, ) from docling.models.factories import get_ocr_factory +from docling.models.stages.ocr.easyocr_model import EasyOcrModel from docling.models.stages.ocr.nemotron_ocr_model import NemotronOcrModel +from docling.models.stages.ocr.tesseract_ocr_cli_model import TesseractOcrCliModel from docling.pipeline.legacy_standard_pdf_pipeline import LegacyStandardPdfPipeline @@ -455,6 +460,33 @@ def test_page_error_carries_page_no(test_doc_path): assert not err.error_message.startswith("Page ") +def test_ocr_scale_is_configurable(): + """The OCR render scale comes from the options instead of a hardcoded 3.""" + assert TesseractCliOcrOptions().scale == 3.0 + assert EasyOcrOptions().scale == 3.0 + + accelerator_options = AcceleratorOptions() + + tesseract_model = TesseractOcrCliModel( + enabled=False, + artifacts_path=None, + options=TesseractCliOcrOptions(scale=1.0), + accelerator_options=accelerator_options, + ) + assert tesseract_model.scale == 1.0 + + easyocr_model = EasyOcrModel( + enabled=False, + artifacts_path=None, + options=EasyOcrOptions(scale=1.5), + accelerator_options=accelerator_options, + ) + assert easyocr_model.scale == 1.5 + + with pytest.raises(ValidationError): + TesseractCliOcrOptions(scale=0) + + def test_nemotron_ocr_backend_registration(): factory = get_ocr_factory(allow_external_plugins=False)