690530:1329 ADR-030-231-ocr-sandbox-two-step-flow #04.6 [skip ci]
This commit is contained in:
@@ -1,24 +1,26 @@
|
|||||||
# File: specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/Dockerfile
|
# File: specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/Dockerfile
|
||||||
# PaddleOCR Sidecar — HTTP API server สำหรับสกัดข้อความจาก PDF/Image
|
# Tesseract OCR Sidecar — HTTP API server สำหรับสกัดข้อความจาก PDF/Image
|
||||||
# รันบน Desk-5439 (GPU RTX 2060 Super 8GB) ตาม ADR-023A
|
# รันบน Desk-5439 ตาม ADR-023A
|
||||||
# Change Log:
|
# Change Log:
|
||||||
# - 2026-05-25: Initial Dockerfile สำหรับ PaddleOCR sidecar (port 8765)
|
# - 2026-05-25: Initial Dockerfile สำหรับ PaddleOCR sidecar (port 8765)
|
||||||
|
# - 2026-05-30: เปลี่ยนจาก PaddleOCR เป็น Tesseract OCR เพื่อความเข้ากันได้กับ CPU เก่า
|
||||||
|
|
||||||
FROM python:3.10-slim
|
FROM python:3.10-slim
|
||||||
|
|
||||||
# ติดตั้ง system dependencies สำหรับ PDF processing และ image library
|
# ติดตั้ง system dependencies สำหรับ PDF processing, Tesseract OCR และภาษาไทย
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
libglib2.0-0 \
|
libglib2.0-0 \
|
||||||
libgl1 \
|
libgl1 \
|
||||||
libgomp1 \
|
libgomp1 \
|
||||||
poppler-utils \
|
poppler-utils \
|
||||||
|
tesseract-ocr \
|
||||||
|
tesseract-ocr-tha \
|
||||||
|
tesseract-ocr-eng \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# ติดตั้ง Python dependencies
|
# ติดตั้ง Python dependencies
|
||||||
# ใช้ paddlepaddle-gpu สำหรับ GPU acceleration (RTX 2060 Super — CUDA 11.x)
|
|
||||||
# เปลี่ยนเป็น paddlepaddle (CPU only) ถ้าต้องการ fallback
|
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
# File: specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/app.py
|
# File: specs/04-Infrastructure-OPS/04-00-docker-compose/Desk-5439/ocr-sidecar/app.py
|
||||||
# PaddleOCR HTTP Sidecar API — รับ POST /ocr แล้วคืนข้อความที่สกัดจาก PDF/Image
|
# Tesseract OCR HTTP Sidecar API — รับ POST /ocr แล้วคืนข้อความที่สกัดจาก PDF/Image
|
||||||
# ตาม ADR-023A: OCR auto-detect (PyMuPDF chars > 100 → Fast path, else PaddleOCR)
|
# ตาม ADR-023A: OCR auto-detect (PyMuPDF chars > 100 → Fast path, else Tesseract)
|
||||||
# Change Log:
|
# Change Log:
|
||||||
# - 2026-05-25: Initial FastAPI server สำหรับ PaddleOCR sidecar
|
# - 2026-05-25: Initial FastAPI server สำหรับ PaddleOCR sidecar
|
||||||
# - 2026-05-30: เปลี่ยน lang='en' เป็น lang='ch' (CTJK) เพื่อรองรับภาษาไทย
|
# - 2026-05-30: เปลี่ยน lang='en' เป็น lang='ch' (CTJK) เพื่อรองรับภาษาไทย
|
||||||
|
# - 2026-05-30: เปลี่ยนจาก PaddleOCR เป็น Tesseract OCR เพื่อความเข้ากันได้กับ CPU เก่า
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
@@ -11,33 +12,26 @@ import re
|
|||||||
import fitz # PyMuPDF
|
import fitz # PyMuPDF
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from PIL import Image
|
||||||
|
import pytesseract
|
||||||
|
import io
|
||||||
|
|
||||||
from fastapi import FastAPI, HTTPException
|
from fastapi import FastAPI, HTTPException
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from paddleocr import PaddleOCR
|
|
||||||
from pythainlp.tokenize import word_tokenize
|
from pythainlp.tokenize import word_tokenize
|
||||||
from pythainlp.util import normalize as thai_normalize
|
from pythainlp.util import normalize as thai_normalize
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
logger = logging.getLogger("ocr-sidecar")
|
logger = logging.getLogger("ocr-sidecar")
|
||||||
|
|
||||||
app = FastAPI(title="PaddleOCR Sidecar", version="1.0.0")
|
app = FastAPI(title="Tesseract OCR Sidecar", version="1.0.0")
|
||||||
|
|
||||||
# อ่านค่า config จาก environment
|
# อ่านค่า config จาก environment
|
||||||
OCR_CHAR_THRESHOLD = int(os.getenv("OCR_CHAR_THRESHOLD", "100"))
|
OCR_CHAR_THRESHOLD = int(os.getenv("OCR_CHAR_THRESHOLD", "100"))
|
||||||
USE_GPU = os.getenv("USE_GPU", "false").lower() == "true"
|
|
||||||
MAX_PAGES = int(os.getenv("OCR_MAX_PAGES", "0")) # 0 = ทุกหน้า
|
MAX_PAGES = int(os.getenv("OCR_MAX_PAGES", "0")) # 0 = ทุกหน้า
|
||||||
OCR_LANG = os.getenv("OCR_LANG", "ch") # ch = CTJK (รองรับภาษาไทย), en = English
|
OCR_LANG = os.getenv("OCR_LANG", "tha+eng") # Tesseract language code (tha+eng = Thai + English)
|
||||||
|
|
||||||
# โหลด PaddleOCR model ครั้งเดียวตอน startup (ลด latency ต่อ request)
|
logger.info(f"Tesseract OCR Sidecar initialized (lang={OCR_LANG})")
|
||||||
logger.info(f"Loading PaddleOCR model (use_gpu={USE_GPU}, lang={OCR_LANG})...")
|
|
||||||
ocr_engine = PaddleOCR(
|
|
||||||
use_angle_cls=True,
|
|
||||||
lang=OCR_LANG,
|
|
||||||
use_gpu=USE_GPU,
|
|
||||||
show_log=False,
|
|
||||||
)
|
|
||||||
logger.info("PaddleOCR model loaded.")
|
|
||||||
|
|
||||||
|
|
||||||
class OcrRequest(BaseModel):
|
class OcrRequest(BaseModel):
|
||||||
@@ -54,7 +48,7 @@ class OcrResponse(BaseModel):
|
|||||||
|
|
||||||
@app.get("/health")
|
@app.get("/health")
|
||||||
def health():
|
def health():
|
||||||
return {"status": "ok", "engine": "paddleocr"}
|
return {"status": "ok", "engine": "tesseract"}
|
||||||
|
|
||||||
|
|
||||||
@app.post("/ocr", response_model=OcrResponse)
|
@app.post("/ocr", response_model=OcrResponse)
|
||||||
@@ -90,25 +84,19 @@ def ocr_extract(req: OcrRequest):
|
|||||||
charCount=total_chars,
|
charCount=total_chars,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Slow path: ใช้ PaddleOCR กับทุกหน้า
|
# Slow path: ใช้ Tesseract OCR กับทุกหน้า
|
||||||
logger.info(f"Slow path (PaddleOCR): {total_chars} chars too few for {pdf_path.name}")
|
logger.info(f"Slow path (Tesseract): {total_chars} chars too few for {pdf_path.name}")
|
||||||
ocr_text_parts = []
|
ocr_text_parts = []
|
||||||
for i in pages_to_process:
|
for i in pages_to_process:
|
||||||
page = doc[i]
|
page = doc[i]
|
||||||
pix = page.get_pixmap(dpi=200)
|
pix = page.get_pixmap(dpi=200)
|
||||||
img_bytes = pix.tobytes("png")
|
img_bytes = pix.tobytes("png")
|
||||||
result = ocr_engine.ocr(img_bytes, cls=True)
|
img = Image.open(io.BytesIO(img_bytes))
|
||||||
if result:
|
text = pytesseract.image_to_string(img, lang=OCR_LANG)
|
||||||
for line in result:
|
ocr_text_parts.append(text.strip())
|
||||||
if line:
|
|
||||||
for word_info in line:
|
|
||||||
if word_info and len(word_info) >= 2:
|
|
||||||
text_part = word_info[1]
|
|
||||||
if isinstance(text_part, (list, tuple)) and len(text_part) >= 1:
|
|
||||||
ocr_text_parts.append(str(text_part[0]))
|
|
||||||
|
|
||||||
ocr_text = "\n".join(ocr_text_parts).strip()
|
ocr_text = "\n".join(ocr_text_parts).strip()
|
||||||
logger.info(f"PaddleOCR extracted {len(ocr_text)} chars from {pdf_path.name}")
|
logger.info(f"Tesseract extracted {len(ocr_text)} chars from {pdf_path.name}")
|
||||||
|
|
||||||
return OcrResponse(
|
return OcrResponse(
|
||||||
text=ocr_text,
|
text=ocr_text,
|
||||||
|
|||||||
+1
-5
@@ -26,12 +26,10 @@ services:
|
|||||||
OCR_CHAR_THRESHOLD: "100"
|
OCR_CHAR_THRESHOLD: "100"
|
||||||
OCR_PORT: "8765"
|
OCR_PORT: "8765"
|
||||||
OCR_MAX_PAGES: "0"
|
OCR_MAX_PAGES: "0"
|
||||||
OCR_LANG: "ch" # ch = CTJK (รองรับภาษาไทย), en = English
|
OCR_LANG: "tha+eng" # Tesseract language code (Thai + English)
|
||||||
# ตั้ง USE_GPU=true เพื่อใช้ RTX 2060 Super (ต้องติดตั้ง nvidia-container-toolkit)
|
# ตั้ง USE_GPU=true เพื่อใช้ RTX 2060 Super (ต้องติดตั้ง nvidia-container-toolkit)
|
||||||
USE_GPU: "false"
|
USE_GPU: "false"
|
||||||
volumes:
|
volumes:
|
||||||
# Model cache — Docker named volume เพื่อไม่ต้อง download ใหม่ทุกครั้ง
|
|
||||||
- paddleocr_models:/root/.paddleocr
|
|
||||||
# Uploads จาก QNAP NAS ผ่าน CIFS (SMB) volume — Docker mount โดยตรง
|
# Uploads จาก QNAP NAS ผ่าน CIFS (SMB) volume — Docker mount โดยตรง
|
||||||
- qnap_uploads:/mnt/uploads:ro
|
- qnap_uploads:/mnt/uploads:ro
|
||||||
logging:
|
logging:
|
||||||
@@ -47,8 +45,6 @@ services:
|
|||||||
start_period: 60s
|
start_period: 60s
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
paddleocr_models:
|
|
||||||
name: paddleocr_models
|
|
||||||
qnap_uploads:
|
qnap_uploads:
|
||||||
driver: local
|
driver: local
|
||||||
driver_opts:
|
driver_opts:
|
||||||
|
|||||||
Reference in New Issue
Block a user