|
6 | 6 | from images of documents. |
7 | 7 | """ |
8 | 8 |
|
9 | | -import importlib |
10 | | -import importlib.util |
11 | 9 | import json |
12 | 10 | import logging |
13 | 11 | import os |
14 | 12 | import re |
15 | | -import subprocess |
16 | | -import sys |
17 | 13 | from typing import TYPE_CHECKING, Any |
18 | 14 |
|
19 | 15 | from .image_downloader import ImageDownloader |
@@ -43,13 +39,12 @@ def __init__(self, model_path="naver-clova-ix/donut-base-finetuned-cord-v2"): |
43 | 39 | self.model_path = model_path |
44 | 40 | self.downloader = ImageDownloader() |
45 | 41 |
|
46 | | - def ensure_installed(self, package_name): |
47 | | - try: |
48 | | - importlib.import_module(package_name) |
49 | | - except ImportError: |
50 | | - subprocess.check_call( |
51 | | - [sys.executable, "-m", "pip", "install", package_name] |
52 | | - ) |
| 42 | + @staticmethod |
| 43 | + def _missing_dependency_message(package_name: str) -> str: |
| 44 | + return ( |
| 45 | + f"Donut OCR requires {package_name}. " |
| 46 | + "Install with: pip install datafog[nlp-advanced,ocr]" |
| 47 | + ) |
53 | 48 |
|
54 | 49 | def preprocess_image(self, image: "Image.Image") -> Any: |
55 | 50 | import numpy as np |
@@ -86,40 +81,40 @@ async def extract_text_from_image(self, image: "Image.Image") -> str: |
86 | 81 | "PYTEST_DONUT=yes is set, running actual OCR in test environment" |
87 | 82 | ) |
88 | 83 |
|
89 | | - # Only import torch and transformers when actually needed and not in test environment |
90 | 84 | try: |
91 | | - # Check if torch is available before trying to import it |
92 | | - try: |
93 | | - # Try to find the module without importing it |
94 | | - spec = importlib.util.find_spec("torch") |
95 | | - if spec is None: |
96 | | - # If we're in a test that somehow bypassed the IN_TEST_ENV check, |
97 | | - # still return a mock result instead of failing |
98 | | - logging.warning("torch module not found, returning mock result") |
99 | | - return json.dumps({"text": "Mock OCR text (torch not available)"}) |
100 | | - |
101 | | - # Ensure dependencies are installed |
102 | | - self.ensure_installed("torch") |
103 | | - self.ensure_installed("transformers") |
104 | | - except ImportError: |
105 | | - # If importlib.util is not available, fall back to direct try/except |
106 | | - pass |
107 | | - |
108 | | - # Import dependencies only when needed |
109 | 85 | try: |
110 | 86 | import torch |
| 87 | + except ImportError as exc: |
| 88 | + raise ImportError(self._missing_dependency_message("torch")) from exc |
| 89 | + |
| 90 | + try: |
111 | 91 | from transformers import DonutProcessor as TransformersDonutProcessor |
112 | 92 | from transformers import VisionEncoderDecoderModel |
113 | 93 | except ImportError as e: |
114 | | - logging.warning(f"Import error: {e}, returning mock result") |
115 | | - return json.dumps({"text": f"Mock OCR text (import error: {e})"}) |
| 94 | + raise ImportError( |
| 95 | + self._missing_dependency_message("transformers") |
| 96 | + ) from e |
116 | 97 |
|
117 | 98 | # Preprocess the image |
118 | 99 | image_np = self.preprocess_image(image) |
119 | 100 |
|
120 | 101 | # Initialize model components |
121 | | - processor = TransformersDonutProcessor.from_pretrained(self.model_path) |
122 | | - model = VisionEncoderDecoderModel.from_pretrained(self.model_path) |
| 102 | + try: |
| 103 | + processor = TransformersDonutProcessor.from_pretrained( |
| 104 | + self.model_path, |
| 105 | + local_files_only=True, |
| 106 | + ) |
| 107 | + model = VisionEncoderDecoderModel.from_pretrained( |
| 108 | + self.model_path, |
| 109 | + local_files_only=True, |
| 110 | + ) |
| 111 | + except OSError as exc: |
| 112 | + raise RuntimeError( |
| 113 | + f"Donut model {self.model_path!r} is not available locally. " |
| 114 | + "Download it explicitly before using Donut OCR, or pass a local " |
| 115 | + "model path." |
| 116 | + ) from exc |
| 117 | + |
123 | 118 | device = "cuda" if torch.cuda.is_available() else "cpu" |
124 | 119 | model.to(device) |
125 | 120 | model.eval() |
@@ -153,6 +148,8 @@ async def extract_text_from_image(self, image: "Image.Image") -> str: |
153 | 148 | result = processor.token2json(sequence) |
154 | 149 | return json.dumps(result) |
155 | 150 |
|
| 151 | + except (ImportError, RuntimeError): |
| 152 | + raise |
156 | 153 | except Exception as e: |
157 | 154 | logging.error(f"Error in extract_text_from_image: {e}") |
158 | 155 | # Return a placeholder in case of error |
|
0 commit comments