Z User commited on
Commit
6ca2131
·
1 Parent(s): 7559ec2

fix(P0): make torch optional, add OCR deps

Browse files

- Wrap 'import torch' in try/except so app starts without PyTorch
- Safe device detection with string fallback when torch missing
- Guard all device.type references with _HAS_TORCH check
- Add PaddleOCR, paddlepaddle, torch (CPU), transformers to requirements
- Update Dockerfile to install CPU-only torch before other deps
- TrOCR/Surya remain available but load lazily only when selected

Files changed (3) hide show
  1. Dockerfile +10 -3
  2. app.py +29 -13
  3. requirements.txt +21 -1
Dockerfile CHANGED
@@ -5,17 +5,24 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
5
  tesseract-ocr \
6
  tesseract-ocr-ara \
7
  tesseract-ocr-deu \
 
8
  libgl1 \
9
  libglib2.0-0 \
 
10
  && rm -rf /var/lib/apt/lists/*
11
 
12
  WORKDIR /app
13
 
14
- # Install Python deps
 
 
 
 
15
  COPY requirements.txt .
16
- RUN pip install --no-cache-dir -r requirements.txt
 
17
 
18
- # Copy app (standalone — includes RTL fixer, field extractor, preprocessing)
19
  COPY app.py .
20
 
21
  # Data directory (SQLite DB + JSONL exports)
 
5
  tesseract-ocr \
6
  tesseract-ocr-ara \
7
  tesseract-ocr-deu \
8
+ tesseract-ocr-eng \
9
  libgl1 \
10
  libglib2.0-0 \
11
+ libgomp1 \
12
  && rm -rf /var/lib/apt/lists/*
13
 
14
  WORKDIR /app
15
 
16
+ # Install CPU-only PyTorch FIRST (smaller footprint)
17
+ RUN pip install --no-cache-dir --extra-index-url https://download.pytorch.org/whl/cpu \
18
+ torch
19
+
20
+ # Install Python deps (OCR engines + framework)
21
  COPY requirements.txt .
22
+ RUN pip install --no-cache-dir --extra-index-url https://download.pytorch.org/whl/cpu \
23
+ -r requirements.txt
24
 
25
+ # Copy app (standalone — includes preprocessing, RTL fixer, field extractor)
26
  COPY app.py .
27
 
28
  # Data directory (SQLite DB + JSONL exports)
app.py CHANGED
@@ -20,10 +20,28 @@ from datetime import datetime
20
  from pathlib import Path
21
  from typing import Optional, List, Tuple
22
 
23
- import torch
24
  import numpy as np
25
  from PIL import Image, ImageFilter, ImageEnhance
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  # ============================================
28
  # Configuration
29
  # ============================================
@@ -37,12 +55,6 @@ CACHE_TTL = 1800 # 30 minutes
37
  MAX_IMAGE_DIMENSION = 2000 # Max width/height for OCR processing
38
  DPI_TARGET = 200 # Target DPI for scanned documents
39
 
40
- # ============================================
41
- # Device Detection
42
- # ============================================
43
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
44
- print(f"Device: {device}")
45
-
46
  # ============================================
47
  # Lazy Model Loading
48
  # ============================================
@@ -80,7 +92,7 @@ def _load_paddleocr():
80
  use_angle_cls=True,
81
  lang='en', # Supports Arabic through multi-language model
82
  show_log=False,
83
- use_gpu=device.type == 'cuda',
84
  max_text_length=1000,
85
  )
86
  print("PaddleOCR loaded")
@@ -91,13 +103,15 @@ def _load_easyocr():
91
  if _easyocr_reader is None:
92
  import easyocr
93
  print("Loading EasyOCR...")
94
- _easyocr_reader = easyocr.Reader(['en', 'ar'], gpu=device.type == 'cuda')
95
  print("EasyOCR loaded")
96
  return _easyocr_reader
97
 
98
  def _load_trocr():
99
  global _trocr_processor, _trocr_model
100
  if _trocr_processor is None:
 
 
101
  from transformers import TrOCRProcessor, VisionEncoderDecoderModel
102
  print("Loading TrOCR...")
103
  _trocr_processor = TrOCRProcessor.from_pretrained("microsoft/trocr-base-handwritten")
@@ -110,7 +124,8 @@ def _load_trocr():
110
  def _load_surya():
111
  global _surya_det_model, _surya_rec_model, _surya_languages, SURYA_AVAILABLE
112
  if not SURYA_AVAILABLE:
113
- if device.type == 'cuda':
 
114
  try:
115
  from surya.ocr import run_ocr
116
  from surya.model.detection import segformer
@@ -371,7 +386,8 @@ def ocr_ensemble(image: Image.Image, engines: list = None) -> dict:
371
  available.append("paddleocr")
372
  if EASY_AVAILABLE:
373
  available.append("easyocr")
374
- if device.type == 'cuda' and SURYA_AVAILABLE:
 
375
  available.append("surya")
376
 
377
  # Filter to requested engines
@@ -1039,7 +1055,7 @@ async def health():
1039
  if EASY_AVAILABLE:
1040
  models.append("easyocr")
1041
  models.append("trocr")
1042
- if device.type == "cuda":
1043
  models.append("surya")
1044
  return {
1045
  "status": "healthy",
@@ -1057,7 +1073,7 @@ async def get_models():
1057
  {"id": "paddleocr", "name": "PaddleOCR", "type": "full-page", "status": "available" if PADDLE_AVAILABLE else "not installed", "size_mb": 300, "best_for": "Complete documents, Arabic+English"},
1058
  {"id": "easyocr", "name": "EasyOCR", "type": "multi-language", "status": "available" if EASY_AVAILABLE else "not installed", "size_mb": 500, "best_for": "Multi-language text"},
1059
  {"id": "trocr", "name": "TrOCR", "type": "handwriting", "status": "available", "size_mb": 1500, "best_for": "Handwriting recognition"},
1060
- {"id": "surya", "name": "Surya OCR", "type": "layout-aware", "status": "available" if device.type == "cuda" else "disabled (CPU)", "size_mb": 800, "best_for": "Layout-aware, GPU only"},
1061
  {"id": "auto", "name": "Ensemble (Auto)", "type": "ensemble", "status": "available", "size_mb": 2000, "best_for": "Best result from all engines"},
1062
  ]
1063
  }
 
20
  from pathlib import Path
21
  from typing import Optional, List, Tuple
22
 
 
23
  import numpy as np
24
  from PIL import Image, ImageFilter, ImageEnhance
25
 
26
+ # ============================================
27
+ # Optional PyTorch (needed for TrOCR / Surya)
28
+ # ============================================
29
+ try:
30
+ import torch
31
+ _HAS_TORCH = True
32
+ except ImportError:
33
+ torch = None # type: ignore[assignment]
34
+ _HAS_TORCH = False
35
+ print("[INFO] PyTorch not installed — TrOCR and Surya will be unavailable")
36
+
37
+ # Device detection (safe fallback when torch is missing)
38
+ if _HAS_TORCH:
39
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
40
+ print(f"Device: {device}")
41
+ else:
42
+ device = "cpu"
43
+ print("Device: cpu (no PyTorch)")
44
+
45
  # ============================================
46
  # Configuration
47
  # ============================================
 
55
  MAX_IMAGE_DIMENSION = 2000 # Max width/height for OCR processing
56
  DPI_TARGET = 200 # Target DPI for scanned documents
57
 
 
 
 
 
 
 
58
  # ============================================
59
  # Lazy Model Loading
60
  # ============================================
 
92
  use_angle_cls=True,
93
  lang='en', # Supports Arabic through multi-language model
94
  show_log=False,
95
+ use_gpu=_HAS_TORCH and hasattr(device, 'type') and device.type == 'cuda',
96
  max_text_length=1000,
97
  )
98
  print("PaddleOCR loaded")
 
103
  if _easyocr_reader is None:
104
  import easyocr
105
  print("Loading EasyOCR...")
106
+ _easyocr_reader = easyocr.Reader(['en', 'ar'], gpu=_HAS_TORCH and hasattr(device, 'type') and device.type == 'cuda')
107
  print("EasyOCR loaded")
108
  return _easyocr_reader
109
 
110
  def _load_trocr():
111
  global _trocr_processor, _trocr_model
112
  if _trocr_processor is None:
113
+ if not _HAS_TORCH:
114
+ raise ImportError("PyTorch is required for TrOCR")
115
  from transformers import TrOCRProcessor, VisionEncoderDecoderModel
116
  print("Loading TrOCR...")
117
  _trocr_processor = TrOCRProcessor.from_pretrained("microsoft/trocr-base-handwritten")
 
124
  def _load_surya():
125
  global _surya_det_model, _surya_rec_model, _surya_languages, SURYA_AVAILABLE
126
  if not SURYA_AVAILABLE:
127
+ _device_type = device.type if _HAS_TORCH and hasattr(device, 'type') else 'cpu'
128
+ if _device_type == 'cuda':
129
  try:
130
  from surya.ocr import run_ocr
131
  from surya.model.detection import segformer
 
386
  available.append("paddleocr")
387
  if EASY_AVAILABLE:
388
  available.append("easyocr")
389
+ _device_type = device.type if _HAS_TORCH and hasattr(device, 'type') else 'cpu'
390
+ if _device_type == 'cuda' and SURYA_AVAILABLE:
391
  available.append("surya")
392
 
393
  # Filter to requested engines
 
1055
  if EASY_AVAILABLE:
1056
  models.append("easyocr")
1057
  models.append("trocr")
1058
+ if _HAS_TORCH and hasattr(device, 'type') and device.type == "cuda":
1059
  models.append("surya")
1060
  return {
1061
  "status": "healthy",
 
1073
  {"id": "paddleocr", "name": "PaddleOCR", "type": "full-page", "status": "available" if PADDLE_AVAILABLE else "not installed", "size_mb": 300, "best_for": "Complete documents, Arabic+English"},
1074
  {"id": "easyocr", "name": "EasyOCR", "type": "multi-language", "status": "available" if EASY_AVAILABLE else "not installed", "size_mb": 500, "best_for": "Multi-language text"},
1075
  {"id": "trocr", "name": "TrOCR", "type": "handwriting", "status": "available", "size_mb": 1500, "best_for": "Handwriting recognition"},
1076
+ {"id": "surya", "name": "Surya OCR", "type": "layout-aware", "status": "available" if (_HAS_TORCH and hasattr(device, 'type') and device.type == "cuda") else "disabled (CPU)", "size_mb": 800, "best_for": "Layout-aware, GPU only"},
1077
  {"id": "auto", "name": "Ensemble (Auto)", "type": "ensemble", "status": "available", "size_mb": 2000, "best_for": "Best result from all engines"},
1078
  ]
1079
  }
requirements.txt CHANGED
@@ -1,6 +1,26 @@
 
 
 
 
1
  gradio>=5.0.0,<7.0.0
 
 
 
2
  Pillow>=10.0.0
3
  opencv-python-headless>=4.8.0
 
4
  pytesseract>=0.3.10
5
  PyMuPDF>=1.24.0
6
- numpy>=1.26.0
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ============================================
2
+ # HF Space — Medical OCR Demo (CPU Free Tier)
3
+ # ============================================
4
+ # Core framework
5
  gradio>=5.0.0,<7.0.0
6
+ fastapi>=0.110.0
7
+ uvicorn[standard]>=0.29.0
8
+ python-multipart>=0.0.9
9
  Pillow>=10.0.0
10
  opencv-python-headless>=4.8.0
11
+ numpy>=1.26.0,<2.0.0
12
  pytesseract>=0.3.10
13
  PyMuPDF>=1.24.0
14
+
15
+ # OCR Engines — lightweight for CPU free tier
16
+ paddleocr>=2.7.0
17
+ paddlepaddle>=2.5.0
18
+
19
+ # Optional: torch for TrOCR (CPU-only build)
20
+ # TrOCR is available but loads lazily only when selected
21
+ torch --extra-index-url https://download.pytorch.org/whl/cpu
22
+ transformers>=4.36.0
23
+ sentencepiece>=0.1.99
24
+
25
+ # Utilities
26
+ rapidfuzz>=3.0.0