docling 2.69.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of docling might be problematic. Click here for more details.
- docling/__init__.py +0 -0
- docling/backend/__init__.py +0 -0
- docling/backend/abstract_backend.py +84 -0
- docling/backend/asciidoc_backend.py +443 -0
- docling/backend/csv_backend.py +125 -0
- docling/backend/docling_parse_backend.py +237 -0
- docling/backend/docling_parse_v2_backend.py +276 -0
- docling/backend/docling_parse_v4_backend.py +260 -0
- docling/backend/docx/__init__.py +0 -0
- docling/backend/docx/drawingml/utils.py +131 -0
- docling/backend/docx/latex/__init__.py +0 -0
- docling/backend/docx/latex/latex_dict.py +274 -0
- docling/backend/docx/latex/omml.py +459 -0
- docling/backend/html_backend.py +1502 -0
- docling/backend/image_backend.py +188 -0
- docling/backend/json/__init__.py +0 -0
- docling/backend/json/docling_json_backend.py +58 -0
- docling/backend/md_backend.py +618 -0
- docling/backend/mets_gbs_backend.py +399 -0
- docling/backend/msexcel_backend.py +686 -0
- docling/backend/mspowerpoint_backend.py +398 -0
- docling/backend/msword_backend.py +1663 -0
- docling/backend/noop_backend.py +51 -0
- docling/backend/pdf_backend.py +82 -0
- docling/backend/pypdfium2_backend.py +417 -0
- docling/backend/webvtt_backend.py +572 -0
- docling/backend/xml/__init__.py +0 -0
- docling/backend/xml/jats_backend.py +819 -0
- docling/backend/xml/uspto_backend.py +1905 -0
- docling/chunking/__init__.py +12 -0
- docling/cli/__init__.py +0 -0
- docling/cli/main.py +974 -0
- docling/cli/models.py +196 -0
- docling/cli/tools.py +17 -0
- docling/datamodel/__init__.py +0 -0
- docling/datamodel/accelerator_options.py +69 -0
- docling/datamodel/asr_model_specs.py +494 -0
- docling/datamodel/backend_options.py +102 -0
- docling/datamodel/base_models.py +493 -0
- docling/datamodel/document.py +699 -0
- docling/datamodel/extraction.py +39 -0
- docling/datamodel/layout_model_specs.py +91 -0
- docling/datamodel/pipeline_options.py +457 -0
- docling/datamodel/pipeline_options_asr_model.py +78 -0
- docling/datamodel/pipeline_options_vlm_model.py +136 -0
- docling/datamodel/settings.py +65 -0
- docling/datamodel/vlm_model_specs.py +365 -0
- docling/document_converter.py +559 -0
- docling/document_extractor.py +327 -0
- docling/exceptions.py +10 -0
- docling/experimental/__init__.py +5 -0
- docling/experimental/datamodel/__init__.py +1 -0
- docling/experimental/datamodel/table_crops_layout_options.py +13 -0
- docling/experimental/datamodel/threaded_layout_vlm_pipeline_options.py +45 -0
- docling/experimental/models/__init__.py +3 -0
- docling/experimental/models/table_crops_layout_model.py +114 -0
- docling/experimental/pipeline/__init__.py +1 -0
- docling/experimental/pipeline/threaded_layout_vlm_pipeline.py +439 -0
- docling/models/__init__.py +0 -0
- docling/models/base_layout_model.py +39 -0
- docling/models/base_model.py +230 -0
- docling/models/base_ocr_model.py +241 -0
- docling/models/base_table_model.py +45 -0
- docling/models/extraction/__init__.py +0 -0
- docling/models/extraction/nuextract_transformers_model.py +305 -0
- docling/models/factories/__init__.py +47 -0
- docling/models/factories/base_factory.py +122 -0
- docling/models/factories/layout_factory.py +7 -0
- docling/models/factories/ocr_factory.py +11 -0
- docling/models/factories/picture_description_factory.py +11 -0
- docling/models/factories/table_factory.py +7 -0
- docling/models/picture_description_base_model.py +149 -0
- docling/models/plugins/__init__.py +0 -0
- docling/models/plugins/defaults.py +60 -0
- docling/models/stages/__init__.py +0 -0
- docling/models/stages/code_formula/__init__.py +0 -0
- docling/models/stages/code_formula/code_formula_model.py +342 -0
- docling/models/stages/layout/__init__.py +0 -0
- docling/models/stages/layout/layout_model.py +249 -0
- docling/models/stages/ocr/__init__.py +0 -0
- docling/models/stages/ocr/auto_ocr_model.py +132 -0
- docling/models/stages/ocr/easyocr_model.py +200 -0
- docling/models/stages/ocr/ocr_mac_model.py +145 -0
- docling/models/stages/ocr/rapid_ocr_model.py +328 -0
- docling/models/stages/ocr/tesseract_ocr_cli_model.py +331 -0
- docling/models/stages/ocr/tesseract_ocr_model.py +262 -0
- docling/models/stages/page_assemble/__init__.py +0 -0
- docling/models/stages/page_assemble/page_assemble_model.py +156 -0
- docling/models/stages/page_preprocessing/__init__.py +0 -0
- docling/models/stages/page_preprocessing/page_preprocessing_model.py +145 -0
- docling/models/stages/picture_classifier/__init__.py +0 -0
- docling/models/stages/picture_classifier/document_picture_classifier.py +246 -0
- docling/models/stages/picture_description/__init__.py +0 -0
- docling/models/stages/picture_description/picture_description_api_model.py +66 -0
- docling/models/stages/picture_description/picture_description_vlm_model.py +123 -0
- docling/models/stages/reading_order/__init__.py +0 -0
- docling/models/stages/reading_order/readingorder_model.py +431 -0
- docling/models/stages/table_structure/__init__.py +0 -0
- docling/models/stages/table_structure/table_structure_model.py +305 -0
- docling/models/utils/__init__.py +0 -0
- docling/models/utils/generation_utils.py +157 -0
- docling/models/utils/hf_model_download.py +45 -0
- docling/models/vlm_pipeline_models/__init__.py +1 -0
- docling/models/vlm_pipeline_models/api_vlm_model.py +180 -0
- docling/models/vlm_pipeline_models/hf_transformers_model.py +391 -0
- docling/models/vlm_pipeline_models/mlx_model.py +325 -0
- docling/models/vlm_pipeline_models/vllm_model.py +344 -0
- docling/pipeline/__init__.py +0 -0
- docling/pipeline/asr_pipeline.py +431 -0
- docling/pipeline/base_extraction_pipeline.py +72 -0
- docling/pipeline/base_pipeline.py +326 -0
- docling/pipeline/extraction_vlm_pipeline.py +207 -0
- docling/pipeline/legacy_standard_pdf_pipeline.py +262 -0
- docling/pipeline/simple_pipeline.py +55 -0
- docling/pipeline/standard_pdf_pipeline.py +859 -0
- docling/pipeline/threaded_standard_pdf_pipeline.py +5 -0
- docling/pipeline/vlm_pipeline.py +416 -0
- docling/py.typed +1 -0
- docling/utils/__init__.py +0 -0
- docling/utils/accelerator_utils.py +97 -0
- docling/utils/api_image_request.py +205 -0
- docling/utils/deepseekocr_utils.py +388 -0
- docling/utils/export.py +146 -0
- docling/utils/glm_utils.py +361 -0
- docling/utils/layout_postprocessor.py +683 -0
- docling/utils/locks.py +3 -0
- docling/utils/model_downloader.py +168 -0
- docling/utils/ocr_utils.py +69 -0
- docling/utils/orientation.py +65 -0
- docling/utils/profiling.py +65 -0
- docling/utils/utils.py +65 -0
- docling/utils/visualization.py +85 -0
- docling-2.69.0.dist-info/METADATA +237 -0
- docling-2.69.0.dist-info/RECORD +138 -0
- docling-2.69.0.dist-info/WHEEL +5 -0
- docling-2.69.0.dist-info/entry_points.txt +6 -0
- docling-2.69.0.dist-info/licenses/LICENSE +21 -0
- docling-2.69.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional, Type
|
|
6
|
+
|
|
7
|
+
from docling.datamodel.accelerator_options import AcceleratorOptions
|
|
8
|
+
from docling.datamodel.base_models import Page
|
|
9
|
+
from docling.datamodel.document import ConversionResult
|
|
10
|
+
from docling.datamodel.pipeline_options import (
|
|
11
|
+
EasyOcrOptions,
|
|
12
|
+
OcrAutoOptions,
|
|
13
|
+
OcrMacOptions,
|
|
14
|
+
OcrOptions,
|
|
15
|
+
RapidOcrOptions,
|
|
16
|
+
)
|
|
17
|
+
from docling.models.base_ocr_model import BaseOcrModel
|
|
18
|
+
from docling.models.stages.ocr.easyocr_model import EasyOcrModel
|
|
19
|
+
from docling.models.stages.ocr.ocr_mac_model import OcrMacModel
|
|
20
|
+
from docling.models.stages.ocr.rapid_ocr_model import RapidOcrModel
|
|
21
|
+
|
|
22
|
+
_log = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class OcrAutoModel(BaseOcrModel):
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
enabled: bool,
|
|
29
|
+
artifacts_path: Optional[Path],
|
|
30
|
+
options: OcrAutoOptions,
|
|
31
|
+
accelerator_options: AcceleratorOptions,
|
|
32
|
+
):
|
|
33
|
+
super().__init__(
|
|
34
|
+
enabled=enabled,
|
|
35
|
+
artifacts_path=artifacts_path,
|
|
36
|
+
options=options,
|
|
37
|
+
accelerator_options=accelerator_options,
|
|
38
|
+
)
|
|
39
|
+
self.options: OcrAutoOptions
|
|
40
|
+
|
|
41
|
+
self._engine: Optional[BaseOcrModel] = None
|
|
42
|
+
if self.enabled:
|
|
43
|
+
if "darwin" == sys.platform:
|
|
44
|
+
try:
|
|
45
|
+
from ocrmac import ocrmac
|
|
46
|
+
|
|
47
|
+
self._engine = OcrMacModel(
|
|
48
|
+
enabled=self.enabled,
|
|
49
|
+
artifacts_path=artifacts_path,
|
|
50
|
+
options=OcrMacOptions(
|
|
51
|
+
bitmap_area_threshold=self.options.bitmap_area_threshold,
|
|
52
|
+
force_full_page_ocr=self.options.force_full_page_ocr,
|
|
53
|
+
),
|
|
54
|
+
accelerator_options=accelerator_options,
|
|
55
|
+
)
|
|
56
|
+
_log.info("Auto OCR model selected ocrmac.")
|
|
57
|
+
except ImportError:
|
|
58
|
+
_log.info("ocrmac cannot be used because ocrmac is not installed.")
|
|
59
|
+
|
|
60
|
+
if self._engine is None:
|
|
61
|
+
try:
|
|
62
|
+
import onnxruntime
|
|
63
|
+
from rapidocr import EngineType, RapidOCR # type: ignore
|
|
64
|
+
|
|
65
|
+
self._engine = RapidOcrModel(
|
|
66
|
+
enabled=self.enabled,
|
|
67
|
+
artifacts_path=artifacts_path,
|
|
68
|
+
options=RapidOcrOptions(
|
|
69
|
+
backend="onnxruntime",
|
|
70
|
+
bitmap_area_threshold=self.options.bitmap_area_threshold,
|
|
71
|
+
force_full_page_ocr=self.options.force_full_page_ocr,
|
|
72
|
+
),
|
|
73
|
+
accelerator_options=accelerator_options,
|
|
74
|
+
)
|
|
75
|
+
_log.info("Auto OCR model selected rapidocr with onnxruntime.")
|
|
76
|
+
except ImportError:
|
|
77
|
+
_log.info(
|
|
78
|
+
"rapidocr cannot be used because onnxruntime is not installed."
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if self._engine is None:
|
|
82
|
+
try:
|
|
83
|
+
import easyocr
|
|
84
|
+
|
|
85
|
+
self._engine = EasyOcrModel(
|
|
86
|
+
enabled=self.enabled,
|
|
87
|
+
artifacts_path=artifacts_path,
|
|
88
|
+
options=EasyOcrOptions(
|
|
89
|
+
bitmap_area_threshold=self.options.bitmap_area_threshold,
|
|
90
|
+
force_full_page_ocr=self.options.force_full_page_ocr,
|
|
91
|
+
),
|
|
92
|
+
accelerator_options=accelerator_options,
|
|
93
|
+
)
|
|
94
|
+
_log.info("Auto OCR model selected easyocr.")
|
|
95
|
+
except ImportError:
|
|
96
|
+
_log.info("easyocr cannot be used because it is not installed.")
|
|
97
|
+
|
|
98
|
+
if self._engine is None:
|
|
99
|
+
try:
|
|
100
|
+
import torch
|
|
101
|
+
from rapidocr import EngineType, RapidOCR # type: ignore
|
|
102
|
+
|
|
103
|
+
self._engine = RapidOcrModel(
|
|
104
|
+
enabled=self.enabled,
|
|
105
|
+
artifacts_path=artifacts_path,
|
|
106
|
+
options=RapidOcrOptions(
|
|
107
|
+
backend="torch",
|
|
108
|
+
bitmap_area_threshold=self.options.bitmap_area_threshold,
|
|
109
|
+
force_full_page_ocr=self.options.force_full_page_ocr,
|
|
110
|
+
),
|
|
111
|
+
accelerator_options=accelerator_options,
|
|
112
|
+
)
|
|
113
|
+
_log.info("Auto OCR model selected rapidocr with torch.")
|
|
114
|
+
except ImportError:
|
|
115
|
+
_log.info(
|
|
116
|
+
"rapidocr cannot be used because rapidocr or torch is not installed."
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
if self._engine is None:
|
|
120
|
+
_log.warning("No OCR engine found. Please review the install details.")
|
|
121
|
+
|
|
122
|
+
def __call__(
|
|
123
|
+
self, conv_res: ConversionResult, page_batch: Iterable[Page]
|
|
124
|
+
) -> Iterable[Page]:
|
|
125
|
+
if not self.enabled or self._engine is None:
|
|
126
|
+
yield from page_batch
|
|
127
|
+
return
|
|
128
|
+
yield from self._engine(conv_res, page_batch)
|
|
129
|
+
|
|
130
|
+
@classmethod
|
|
131
|
+
def get_options_type(cls) -> Type[OcrOptions]:
|
|
132
|
+
return OcrAutoOptions
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import warnings
|
|
3
|
+
import zipfile
|
|
4
|
+
from collections.abc import Iterable
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import List, Optional, Type
|
|
7
|
+
|
|
8
|
+
import numpy
|
|
9
|
+
from docling_core.types.doc import BoundingBox, CoordOrigin
|
|
10
|
+
from docling_core.types.doc.page import BoundingRectangle, TextCell
|
|
11
|
+
|
|
12
|
+
from docling.datamodel.accelerator_options import AcceleratorDevice, AcceleratorOptions
|
|
13
|
+
from docling.datamodel.base_models import Page
|
|
14
|
+
from docling.datamodel.document import ConversionResult
|
|
15
|
+
from docling.datamodel.pipeline_options import (
|
|
16
|
+
EasyOcrOptions,
|
|
17
|
+
OcrOptions,
|
|
18
|
+
)
|
|
19
|
+
from docling.datamodel.settings import settings
|
|
20
|
+
from docling.models.base_ocr_model import BaseOcrModel
|
|
21
|
+
from docling.utils.accelerator_utils import decide_device
|
|
22
|
+
from docling.utils.profiling import TimeRecorder
|
|
23
|
+
from docling.utils.utils import download_url_with_progress
|
|
24
|
+
|
|
25
|
+
_log = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class EasyOcrModel(BaseOcrModel):
|
|
29
|
+
_model_repo_folder = "EasyOcr"
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
enabled: bool,
|
|
34
|
+
artifacts_path: Optional[Path],
|
|
35
|
+
options: EasyOcrOptions,
|
|
36
|
+
accelerator_options: AcceleratorOptions,
|
|
37
|
+
):
|
|
38
|
+
super().__init__(
|
|
39
|
+
enabled=enabled,
|
|
40
|
+
artifacts_path=artifacts_path,
|
|
41
|
+
options=options,
|
|
42
|
+
accelerator_options=accelerator_options,
|
|
43
|
+
)
|
|
44
|
+
self.options: EasyOcrOptions
|
|
45
|
+
|
|
46
|
+
self.scale = 3 # multiplier for 72 dpi == 216 dpi.
|
|
47
|
+
|
|
48
|
+
if self.enabled:
|
|
49
|
+
try:
|
|
50
|
+
import easyocr
|
|
51
|
+
except ImportError:
|
|
52
|
+
raise ImportError(
|
|
53
|
+
"EasyOCR is not installed. Please install it via `pip install easyocr` to use this OCR engine. "
|
|
54
|
+
"Alternatively, Docling has support for other OCR engines. See the documentation."
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if self.options.use_gpu is None:
|
|
58
|
+
device = decide_device(accelerator_options.device)
|
|
59
|
+
# Enable easyocr GPU if running on CUDA, MPS
|
|
60
|
+
use_gpu = any(
|
|
61
|
+
device.startswith(x)
|
|
62
|
+
for x in [
|
|
63
|
+
AcceleratorDevice.CUDA.value,
|
|
64
|
+
AcceleratorDevice.MPS.value,
|
|
65
|
+
]
|
|
66
|
+
)
|
|
67
|
+
else:
|
|
68
|
+
warnings.warn(
|
|
69
|
+
"Deprecated field. Better to set the `accelerator_options.device` in `pipeline_options`. "
|
|
70
|
+
"When `use_gpu and accelerator_options.device == AcceleratorDevice.CUDA` the GPU is used "
|
|
71
|
+
"to run EasyOCR. Otherwise, EasyOCR runs in CPU."
|
|
72
|
+
)
|
|
73
|
+
use_gpu = self.options.use_gpu
|
|
74
|
+
|
|
75
|
+
download_enabled = self.options.download_enabled
|
|
76
|
+
model_storage_directory = self.options.model_storage_directory
|
|
77
|
+
if artifacts_path is not None and model_storage_directory is None:
|
|
78
|
+
download_enabled = False
|
|
79
|
+
model_storage_directory = str(artifacts_path / self._model_repo_folder)
|
|
80
|
+
|
|
81
|
+
with warnings.catch_warnings():
|
|
82
|
+
if self.options.suppress_mps_warnings:
|
|
83
|
+
warnings.filterwarnings("ignore", message=".*pin_memory.*MPS.*")
|
|
84
|
+
self.reader = easyocr.Reader(
|
|
85
|
+
lang_list=self.options.lang,
|
|
86
|
+
gpu=use_gpu,
|
|
87
|
+
model_storage_directory=model_storage_directory,
|
|
88
|
+
recog_network=self.options.recog_network,
|
|
89
|
+
download_enabled=download_enabled,
|
|
90
|
+
verbose=False,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
def download_models(
|
|
95
|
+
detection_models: List[str] = ["craft"],
|
|
96
|
+
recognition_models: List[str] = ["english_g2", "latin_g2"],
|
|
97
|
+
local_dir: Optional[Path] = None,
|
|
98
|
+
force: bool = False,
|
|
99
|
+
progress: bool = False,
|
|
100
|
+
) -> Path:
|
|
101
|
+
# Models are located in https://github.com/JaidedAI/EasyOCR/blob/master/easyocr/config.py
|
|
102
|
+
from easyocr.config import (
|
|
103
|
+
detection_models as det_models_dict,
|
|
104
|
+
recognition_models as rec_models_dict,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if local_dir is None:
|
|
108
|
+
local_dir = settings.cache_dir / "models" / EasyOcrModel._model_repo_folder
|
|
109
|
+
|
|
110
|
+
local_dir.mkdir(parents=True, exist_ok=True)
|
|
111
|
+
|
|
112
|
+
# Collect models to download
|
|
113
|
+
download_list = []
|
|
114
|
+
for model_name in detection_models:
|
|
115
|
+
if model_name in det_models_dict:
|
|
116
|
+
download_list.append(det_models_dict[model_name])
|
|
117
|
+
for model_name in recognition_models:
|
|
118
|
+
if model_name in rec_models_dict["gen2"]:
|
|
119
|
+
download_list.append(rec_models_dict["gen2"][model_name])
|
|
120
|
+
|
|
121
|
+
# Download models
|
|
122
|
+
for model_details in download_list:
|
|
123
|
+
buf = download_url_with_progress(model_details["url"], progress=progress)
|
|
124
|
+
with zipfile.ZipFile(buf, "r") as zip_ref:
|
|
125
|
+
zip_ref.extractall(local_dir)
|
|
126
|
+
|
|
127
|
+
return local_dir
|
|
128
|
+
|
|
129
|
+
def __call__(
|
|
130
|
+
self, conv_res: ConversionResult, page_batch: Iterable[Page]
|
|
131
|
+
) -> Iterable[Page]:
|
|
132
|
+
if not self.enabled:
|
|
133
|
+
yield from page_batch
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
for page in page_batch:
|
|
137
|
+
assert page._backend is not None
|
|
138
|
+
if not page._backend.is_valid():
|
|
139
|
+
yield page
|
|
140
|
+
else:
|
|
141
|
+
with TimeRecorder(conv_res, "ocr"):
|
|
142
|
+
ocr_rects = self.get_ocr_rects(page)
|
|
143
|
+
|
|
144
|
+
all_ocr_cells = []
|
|
145
|
+
for ocr_rect in ocr_rects:
|
|
146
|
+
# Skip zero area boxes
|
|
147
|
+
if ocr_rect.area() == 0:
|
|
148
|
+
continue
|
|
149
|
+
high_res_image = page._backend.get_page_image(
|
|
150
|
+
scale=self.scale, cropbox=ocr_rect
|
|
151
|
+
)
|
|
152
|
+
im = numpy.array(high_res_image)
|
|
153
|
+
|
|
154
|
+
with warnings.catch_warnings():
|
|
155
|
+
if self.options.suppress_mps_warnings:
|
|
156
|
+
warnings.filterwarnings(
|
|
157
|
+
"ignore", message=".*pin_memory.*MPS.*"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
result = self.reader.readtext(im)
|
|
161
|
+
|
|
162
|
+
del high_res_image
|
|
163
|
+
del im
|
|
164
|
+
|
|
165
|
+
cells = [
|
|
166
|
+
TextCell(
|
|
167
|
+
index=ix,
|
|
168
|
+
text=line[1],
|
|
169
|
+
orig=line[1],
|
|
170
|
+
from_ocr=True,
|
|
171
|
+
confidence=line[2],
|
|
172
|
+
rect=BoundingRectangle.from_bounding_box(
|
|
173
|
+
BoundingBox.from_tuple(
|
|
174
|
+
coord=(
|
|
175
|
+
(line[0][0][0] / self.scale) + ocr_rect.l,
|
|
176
|
+
(line[0][0][1] / self.scale) + ocr_rect.t,
|
|
177
|
+
(line[0][2][0] / self.scale) + ocr_rect.l,
|
|
178
|
+
(line[0][2][1] / self.scale) + ocr_rect.t,
|
|
179
|
+
),
|
|
180
|
+
origin=CoordOrigin.TOPLEFT,
|
|
181
|
+
)
|
|
182
|
+
),
|
|
183
|
+
)
|
|
184
|
+
for ix, line in enumerate(result)
|
|
185
|
+
if line[2] >= self.options.confidence_threshold
|
|
186
|
+
]
|
|
187
|
+
all_ocr_cells.extend(cells)
|
|
188
|
+
|
|
189
|
+
# Post-process the cells
|
|
190
|
+
self.post_process_cells(all_ocr_cells, page)
|
|
191
|
+
|
|
192
|
+
# DEBUG code:
|
|
193
|
+
if settings.debug.visualize_ocr:
|
|
194
|
+
self.draw_ocr_rects_and_cells(conv_res, page, ocr_rects)
|
|
195
|
+
|
|
196
|
+
yield page
|
|
197
|
+
|
|
198
|
+
@classmethod
|
|
199
|
+
def get_options_type(cls) -> Type[OcrOptions]:
|
|
200
|
+
return EasyOcrOptions
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
import tempfile
|
|
4
|
+
from collections.abc import Iterable
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional, Type
|
|
7
|
+
|
|
8
|
+
from docling_core.types.doc import BoundingBox, CoordOrigin
|
|
9
|
+
from docling_core.types.doc.page import BoundingRectangle, TextCell
|
|
10
|
+
|
|
11
|
+
from docling.datamodel.accelerator_options import AcceleratorOptions
|
|
12
|
+
from docling.datamodel.base_models import Page
|
|
13
|
+
from docling.datamodel.document import ConversionResult
|
|
14
|
+
from docling.datamodel.pipeline_options import (
|
|
15
|
+
OcrMacOptions,
|
|
16
|
+
OcrOptions,
|
|
17
|
+
)
|
|
18
|
+
from docling.datamodel.settings import settings
|
|
19
|
+
from docling.models.base_ocr_model import BaseOcrModel
|
|
20
|
+
from docling.utils.profiling import TimeRecorder
|
|
21
|
+
|
|
22
|
+
_log = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class OcrMacModel(BaseOcrModel):
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
enabled: bool,
|
|
29
|
+
artifacts_path: Optional[Path],
|
|
30
|
+
options: OcrMacOptions,
|
|
31
|
+
accelerator_options: AcceleratorOptions,
|
|
32
|
+
):
|
|
33
|
+
super().__init__(
|
|
34
|
+
enabled=enabled,
|
|
35
|
+
artifacts_path=artifacts_path,
|
|
36
|
+
options=options,
|
|
37
|
+
accelerator_options=accelerator_options,
|
|
38
|
+
)
|
|
39
|
+
self.options: OcrMacOptions
|
|
40
|
+
|
|
41
|
+
self.scale = 3 # multiplier for 72 dpi == 216 dpi.
|
|
42
|
+
|
|
43
|
+
if self.enabled:
|
|
44
|
+
if "darwin" != sys.platform:
|
|
45
|
+
raise RuntimeError("OcrMac is only supported on Mac.")
|
|
46
|
+
install_errmsg = (
|
|
47
|
+
"ocrmac is not correctly installed. "
|
|
48
|
+
"Please install it via `pip install ocrmac` to use this OCR engine. "
|
|
49
|
+
"Alternatively, Docling has support for other OCR engines. See the documentation: "
|
|
50
|
+
"https://docling-project.github.io/docling/installation/"
|
|
51
|
+
)
|
|
52
|
+
try:
|
|
53
|
+
from ocrmac import ocrmac
|
|
54
|
+
except ImportError:
|
|
55
|
+
raise ImportError(install_errmsg)
|
|
56
|
+
|
|
57
|
+
self.reader_RIL = ocrmac.OCR
|
|
58
|
+
|
|
59
|
+
def __call__(
|
|
60
|
+
self, conv_res: ConversionResult, page_batch: Iterable[Page]
|
|
61
|
+
) -> Iterable[Page]:
|
|
62
|
+
if not self.enabled:
|
|
63
|
+
yield from page_batch
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
for page in page_batch:
|
|
67
|
+
assert page._backend is not None
|
|
68
|
+
if not page._backend.is_valid():
|
|
69
|
+
yield page
|
|
70
|
+
else:
|
|
71
|
+
with TimeRecorder(conv_res, "ocr"):
|
|
72
|
+
ocr_rects = self.get_ocr_rects(page)
|
|
73
|
+
|
|
74
|
+
all_ocr_cells = []
|
|
75
|
+
for ocr_rect in ocr_rects:
|
|
76
|
+
# Skip zero area boxes
|
|
77
|
+
if ocr_rect.area() == 0:
|
|
78
|
+
continue
|
|
79
|
+
high_res_image = page._backend.get_page_image(
|
|
80
|
+
scale=self.scale, cropbox=ocr_rect
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
with tempfile.NamedTemporaryFile(
|
|
84
|
+
suffix=".png", mode="w"
|
|
85
|
+
) as image_file:
|
|
86
|
+
fname = image_file.name
|
|
87
|
+
high_res_image.save(fname)
|
|
88
|
+
|
|
89
|
+
boxes = self.reader_RIL(
|
|
90
|
+
fname,
|
|
91
|
+
recognition_level=self.options.recognition,
|
|
92
|
+
framework=self.options.framework,
|
|
93
|
+
language_preference=self.options.lang,
|
|
94
|
+
).recognize()
|
|
95
|
+
|
|
96
|
+
im_width, im_height = high_res_image.size
|
|
97
|
+
cells = []
|
|
98
|
+
for ix, (text, confidence, box) in enumerate(boxes):
|
|
99
|
+
x = float(box[0])
|
|
100
|
+
y = float(box[1])
|
|
101
|
+
w = float(box[2])
|
|
102
|
+
h = float(box[3])
|
|
103
|
+
|
|
104
|
+
x1 = x * im_width
|
|
105
|
+
y2 = (1 - y) * im_height
|
|
106
|
+
|
|
107
|
+
x2 = x1 + w * im_width
|
|
108
|
+
y1 = y2 - h * im_height
|
|
109
|
+
|
|
110
|
+
left = x1 / self.scale
|
|
111
|
+
top = y1 / self.scale
|
|
112
|
+
right = x2 / self.scale
|
|
113
|
+
bottom = y2 / self.scale
|
|
114
|
+
|
|
115
|
+
cells.append(
|
|
116
|
+
TextCell(
|
|
117
|
+
index=ix,
|
|
118
|
+
text=text,
|
|
119
|
+
orig=text,
|
|
120
|
+
from_ocr=True,
|
|
121
|
+
confidence=confidence,
|
|
122
|
+
rect=BoundingRectangle.from_bounding_box(
|
|
123
|
+
BoundingBox.from_tuple(
|
|
124
|
+
coord=(left, top, right, bottom),
|
|
125
|
+
origin=CoordOrigin.TOPLEFT,
|
|
126
|
+
)
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# del high_res_image
|
|
132
|
+
all_ocr_cells.extend(cells)
|
|
133
|
+
|
|
134
|
+
# Post-process the cells
|
|
135
|
+
self.post_process_cells(all_ocr_cells, page)
|
|
136
|
+
|
|
137
|
+
# DEBUG code:
|
|
138
|
+
if settings.debug.visualize_ocr:
|
|
139
|
+
self.draw_ocr_rects_and_cells(conv_res, page, ocr_rects)
|
|
140
|
+
|
|
141
|
+
yield page
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def get_options_type(cls) -> Type[OcrOptions]:
|
|
145
|
+
return OcrMacOptions
|