690530:1329 ADR-030-231-ocr-sandbox-two-step-flow #04.6 [skip ci]

This commit is contained in:
2026-05-30 13:29:08 +07:00
parent ddc9332122
commit c9edd62a0b
3 changed files with 24 additions and 38 deletions
@@ -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,
@@ -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: