Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 27 additions & 8 deletions src/ocrbridge/engines/easyocr/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ class EasyOCREngine(OCREngine):
Uses deep learning models for multilingual OCR with automatic GPU acceleration.
GPU is automatically detected and used when available, with graceful fallback to CPU.
Supports 80+ languages with superior accuracy for Asian scripts.

Thread Safety:
This engine is NOT thread-safe. The internal EasyOCR Reader instance is shared
and reused across calls for performance. Concurrent calls to process() from
multiple threads may cause race conditions or undefined behavior.

For thread-safe usage, either:
- Use a separate EasyOCREngine instance per thread
- Serialize access to process() using external locking
- Use the ocr-service which handles concurrency via async/await with to_thread()
"""

def __init__(self):
Expand Down Expand Up @@ -149,6 +159,19 @@ def process(self, file_path: Path, params: OCREngineParams | None = None) -> str

# Create or recreate reader if languages changed
if self.reader is None or self._current_languages != easyocr_params.languages:
# Release old reader to free GPU memory before creating new one
if self.reader is not None:
del self.reader
self.reader = None
# Clear GPU cache if available
try:
import torch

if torch.cuda.is_available():
torch.cuda.empty_cache()
except ImportError:
pass

self.reader = self._create_reader(easyocr_params.languages)
self._current_languages = easyocr_params.languages

Expand Down Expand Up @@ -223,17 +246,13 @@ def _process_pdf(self, pdf_path: Path, params: EasyOCRParams) -> str:
)

# Convert results to HOCR for this page
# Save image temporarily to get dimensions
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_file:
temp_path = Path(tmp_file.name)
# Save image temporarily to get dimensions using secure TemporaryDirectory
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir) / "page.png"
image.save(temp_path, format="PNG")

try:
page_hocr = self._to_hocr(results, temp_path)
page_hocr_list.append(page_hocr)
finally:
# Clean up temp file
temp_path.unlink(missing_ok=True)
# Cleanup happens automatically when context manager exits

# Merge all pages into single HOCR document
if len(page_hocr_list) == 1:
Expand Down
19 changes: 10 additions & 9 deletions src/ocrbridge/engines/easyocr/hocr.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"""

from typing import Sequence, TypedDict
from xml.sax.saxutils import escape as xml_escape

Point2D = tuple[float, float]
BBox = tuple[int, int, int, int]
Expand Down Expand Up @@ -80,8 +81,13 @@ def _group_words_into_lines(easyocr_results: Sequence[EasyOCRResult]) -> list[Li
heights.sort()
median_height = heights[len(heights) // 2]

# Guard against zero height (edge case with certain image types)
if median_height == 0:
median_height = 1.0 # Use minimum default

# Threshold: words are on same line if y_centers within 50% of median height
line_threshold = median_height * 0.5
# Use max to ensure a minimum threshold for very small text
line_threshold = max(1.0, median_height * 0.5)

# Sort words by vertical position (top to bottom)
words.sort(key=lambda w: w["y_center"])
Expand Down Expand Up @@ -172,14 +178,9 @@ def to_hocr(easyocr_results: Sequence[EasyOCRResult], image_width: int, image_he
# Convert confidence (0.0-1.0) to percentage (0-100)
conf_percent = int(word_data["confidence"] * 100)

# Escape text for XML
escaped_text = (
word_data["text"]
.replace("&", "&")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace('"', "&quot;")
.replace("'", "&apos;")
# Escape text for XML using stdlib (more robust than manual replacement)
escaped_text = xml_escape(
word_data["text"], {'"': "&quot;", "'": "&apos;"}
)

# Create HOCR word element
Expand Down