paddle ocr suport

This commit is contained in:
m
2026-07-12 12:19:19 +02:00
parent f303ac8b5c
commit 571a571db6
8 changed files with 197 additions and 4 deletions
+1
View File
@@ -24,6 +24,7 @@ require (
github.com/google/uuid v1.6.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/ledongthuc/pdf v0.0.0-20250511090121-5959a4027728 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+2
View File
@@ -45,6 +45,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/ledongthuc/pdf v0.0.0-20250511090121-5959a4027728 h1:QwWKgMY28TAXaDl+ExRDqGQltzXqN/xypdKP86niVn8=
github.com/ledongthuc/pdf v0.0.0-20250511090121-5959a4027728/go.mod h1:1fEHWurg7pvf5SG6XNE5Q8UZmOwex51Mkx3SLhrW5B4=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+67
View File
@@ -8,6 +8,8 @@ import (
"io"
"net/http"
"time"
"github.com/ledongthuc/pdf"
)
type Client struct {
@@ -25,6 +27,18 @@ func NewClient(endpoint string) *Client {
}
func (c *Client) Recognize(imageData []byte) ([]TextBlock, error) {
docType := DetectDocumentType(imageData)
switch docType {
case PDFScanned:
fmt.Println("PDFScanned -> PaddleOCR")
case Image:
fmt.Println("Image -> PaddleOCR")
default:
return []TextBlock{}, fmt.Errorf("DOCTYPE not supported => %s", docType)
}
b64 := base64.StdEncoding.EncodeToString(imageData)
reqBody, err := json.Marshal(OCRRequest{Image: b64})
@@ -63,6 +77,59 @@ func (c *Client) Recognize(imageData []byte) ([]TextBlock, error) {
return flattenResults(ocrResp.Result), nil
}
type DocType string
const (
Image DocType = "image"
PDFText DocType = "pdf_text"
PDFScanned DocType = "pdf_scanned"
Unknown DocType = "unknown"
)
func isPDFText(data []byte) bool {
reader := bytes.NewReader(data)
r, err := pdf.NewReader(reader, int64(len(data)))
if err != nil {
return false
}
for i := 1; i <= r.NumPage(); i++ {
page := r.Page(i)
if page.V.IsNull() {
continue
}
text, _ := page.GetPlainText(nil)
if len(text) > 20 {
return true
}
}
return false
}
func DetectDocumentType(data []byte) DocType {
mime := http.DetectContentType(data)
switch {
case mime == "application/pdf":
if isPDFText(data) {
return PDFText
}
return PDFScanned
case bytes.HasPrefix(data, []byte{0xFF, 0xD8}): // JPEG
return Image
case bytes.HasPrefix(data, []byte{0x89, 0x50, 0x4E, 0x47}): // PNG
return Image
default:
return Unknown
}
}
func (c *Client) HealthCheck() error {
resp, err := c.httpClient.Get(c.endpoint + "/health")
if err != nil {
+1 -1
View File
@@ -22,7 +22,7 @@ func GenerateFileDownloadUrl(fileID string) string {
sig := sign(fileID, expires, secret)
url := fmt.Sprintf(
"http://192.168.1.142:8080/api/v1/files/%s?expires=%d&sig=%s",
"http://192.168.1.17:8080/api/v1/files/%s?expires=%d&sig=%s",
fileID,
expires,
sig,
+23
View File
@@ -0,0 +1,23 @@
FROM python:3.10-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
libglib2.0-0 libgl1 libgomp1 curl \
&& rm -rf /var/lib/apt/lists/*
RUN pip install --no-cache-dir \
paddlepaddle==3.0.0 \
paddleocr==3.3.3 \
paddlex==3.3.13 \
fastapi \
uvicorn \
python-multipart \
Pillow \
numpy
COPY server.py /workspace/server.py
WORKDIR /workspace
EXPOSE 8080
CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "8080"]
+96
View File
@@ -0,0 +1,96 @@
import os
import base64
import logging
from io import BytesIO
import numpy as np
from fastapi import FastAPI, UploadFile, File
from fastapi.responses import JSONResponse
from paddleocr import PaddleOCR
from pydantic import BaseModel
from PIL import Image
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("paddleocr-server")
OCR_LANG = os.getenv("OCR_LANG", "fr")
print(f"[INIT] Initializing PaddleOCR (lang={OCR_LANG})...", flush=True)
ocr_engine = PaddleOCR(
use_doc_orientation_classify=False,
use_doc_unwarping=False,
use_textline_orientation=False,
lang=OCR_LANG,
)
print("[INIT] PaddleOCR ready.", flush=True)
app = FastAPI()
class OCRRequest(BaseModel):
image: str
@app.get("/health")
def health():
return {"status": "healthy", "service": "PaddleOCR Server"}
@app.post("/ocr")
def ocr_json(req: OCRRequest):
print(f"[OCR] Request received, image field length: {len(req.image)}", flush=True)
try:
img_bytes = base64.b64decode(req.image)
except Exception as e:
print(f"[OCR] Base64 decode failed: {e}", flush=True)
return JSONResponse(
status_code=400,
content={"errorCode": 1, "message": "invalid base64 image"},
)
print(f"[OCR] Decoded {len(img_bytes)} bytes, header: {img_bytes[:32].hex()}", flush=True)
return run_ocr(img_bytes)
@app.post("/ocr/upload")
def ocr_upload(file: UploadFile = File(...)):
img_bytes = file.file.read()
print(f"[OCR] Upload received, {len(img_bytes)} bytes, header: {img_bytes[:32].hex()}", flush=True)
return run_ocr(img_bytes)
def run_ocr(img_bytes: bytes):
try:
image = Image.open(BytesIO(img_bytes))
if image.mode != "RGB":
image = image.convert("RGB")
img_array = np.array(image)
print(f"[OCR] Image decoded: {img_array.shape}", flush=True)
except Exception as e:
print(f"[OCR] Image decode FAILED: {e}", flush=True)
return JSONResponse(
status_code=400,
content={"errorCode": 1, "message": f"failed to decode image: {e}"},
)
try:
result = list(ocr_engine.predict(img_array))
pages = []
for r in result:
raw = r._to_json()
data = raw.get("res", raw)
pages.append({
"rec_texts": data.get("rec_texts", []),
"rec_scores": [float(s) for s in data.get("rec_scores", [])],
"rec_boxes": data.get("rec_boxes", []),
"rec_polys": data.get("rec_polys", []),
})
return {"errorCode": 0, "result": {"ocrResults": pages}}
except Exception as e:
logger.exception("OCR failed")
return JSONResponse(
status_code=500,
content={"errorCode": 2, "message": str(e)},
)