kawaiipeace commited on
Commit
e29dad8
·
1 Parent(s): b0a9c41

initialization

Browse files
Files changed (4) hide show
  1. .env +3 -0
  2. app.py +122 -0
  3. dockerfile +19 -0
  4. requirements.txt +8 -0
.env ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ API_KEY=123456
2
+ TYPHOON_OCR_API_KEY=sk-D1sMp4fivWSKNhnydFOvqFwzdxqK7OIsVoLHn6rN3komSG3L
3
+ TYPHOON_BASE_URL=https://api.opentyphoon.ai/v1
app.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from fastapi import FastAPI, HTTPException, Header, UploadFile, File
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ import gradio as gr
5
+ from typhoon_ocr import ocr_document
6
+ from pdf2image import convert_from_bytes
7
+ from PIL import Image
8
+ import re
9
+ from dotenv import load_dotenv
10
+
11
+ # --- Load environment variables from .env ---
12
+ load_dotenv()
13
+
14
+ # --- Config ---
15
+ API_KEY = os.getenv("API_KEY")
16
+ TYPHOON_API_KEY = os.getenv("TYPHOON_OCR_API_KEY")
17
+ TYPHOON_BASE_URL = os.getenv("TYPHOON_BASE_URL", "https://api.opentyphoon.ai/v1")
18
+
19
+ # --- FastAPI App ---
20
+ app = FastAPI()
21
+
22
+ # CORS (optional for public usage)
23
+ app.add_middleware(
24
+ CORSMiddleware,
25
+ allow_origins=["*"],
26
+ allow_methods=["*"],
27
+ allow_headers=["*"],
28
+ )
29
+
30
+ def extract_fields_regex(text: str) -> dict:
31
+ # Preprocess text
32
+ text = re.sub(r"<.*?>", "", text) # Strip tags
33
+ text = re.sub(r"\n+", "\n", text) # Collapse newlines
34
+ text = re.sub(r"\s{2,}", " ", text) # Collapse multiple spaces
35
+ text = re.sub(r"\t+", " ", text)
36
+
37
+ patterns = {
38
+ "เลขที่ผู้เสียภาษี": r"(?:TAX\s*ID|เลขที่ผู้เสียภาษี)[\s:\-\.]*([\d]{10,13})",
39
+
40
+ # Updated pattern for correct tax invoice number
41
+ "เลขที่ใบกำกับภาษี": r"(?:TAX\s*INV\.?|เลขที่ใบกำกับภาษี|ใบกำกับ)[\s:\-\.]*([\d]{8,20})",
42
+ "จำนวนเงิน": r"(?:AMOUNT\s*THB|จำนวนเงิน|รวมเงิน)[\s:\-\.]*([\d,]+\.\d{2})",
43
+ "ราคาต่อลิตร": r"(?:Baht\/Litr\.?|Bath\/Ltr\.?|ราคาต่อลิตร|ราคา\/ลิตร|ราคาน้ำมัน)[\s:\-\.]*([\d,]+\.\d{2})",
44
+ "ลิตร": r"(?:Ltr\.?|Ltrs?\.?|ลิตร)[\s:\-\.]*([\d,]+\.\d{3})",
45
+ "ภาษีมูลค่าเพิ่ม": r"(?:VAT|ภาษีมูลค่าเพิ่ม)[\s:\-\.]*([\d,]+\.\d{2})",
46
+ "ยอดรวม": r"(?:TOTAL\s*THB|ยอดรวม|รวมทั้งสิ้น|รวมเงินทั้งสิ้น)[\s:\-\.]*([\d,]+\.\d{2})",
47
+ "วันที่": r"(?:DATE|วันที่|ออกใบกำกับวันที่)[\s:\-\.]*([\d]{2}/[\d]{2}/[\d]{2,4})",
48
+ }
49
+
50
+ results = {}
51
+ for field, pattern in patterns.items():
52
+ match = re.search(pattern, text, re.IGNORECASE)
53
+ results[field] = match.group(1).strip() if match else None
54
+
55
+ # Optional fallback if regex fails
56
+ # if not results["เลขที่ใบกำกับภาษี"]:
57
+ # match = re.search(r"TAX\s*INV\.?\s*</td>\s*<td>\s*([\d\-]+)", text, re.IGNORECASE)
58
+ # if match:
59
+ # results["เลขที่ใบกำกับภาษี"] = match.group(1).strip()
60
+
61
+ return results
62
+
63
+
64
+
65
+ def pdf_to_image(file_bytes: bytes) -> Image.Image:
66
+ images = convert_from_bytes(file_bytes)
67
+ return images[0] # First page only
68
+
69
+ # --- API Endpoint ---
70
+ @app.post("/api/ocr_receipt")
71
+ async def ocr_receipt(
72
+ file: UploadFile = File(...),
73
+ x_api_key: str | None = Header(None),
74
+ ):
75
+ if API_KEY and x_api_key != API_KEY:
76
+ raise HTTPException(status_code=401, detail="Invalid API key")
77
+
78
+ content = await file.read()
79
+
80
+ try:
81
+ # Handle PDF and image
82
+ if file.filename.lower().endswith(".pdf"):
83
+ image = pdf_to_image(content)
84
+ raw_output = ocr_document(image, task_type="structure")
85
+ else:
86
+ raw_output = ocr_document(content, task_type="structure")
87
+
88
+ text = raw_output if isinstance(raw_output, str) else raw_output.get("text", "")
89
+ extracted = extract_fields_regex(text)
90
+
91
+ return {
92
+ "raw_ocr": text,
93
+ "extracted_fields": extracted,
94
+ }
95
+
96
+ except Exception as e:
97
+ raise HTTPException(status_code=500, detail=str(e))
98
+
99
+ # --- Gradio UI ---
100
+ def gradio_interface(image_path: str | Image.Image):
101
+ if isinstance(image_path, str) and image_path.lower().endswith(".pdf"):
102
+ with open(image_path, "rb") as f:
103
+ image = pdf_to_image(f.read())
104
+ else:
105
+ image = image_path
106
+
107
+ raw = ocr_document(image, task_type="structure")
108
+ text = raw if isinstance(raw, str) else raw.get("text", "")
109
+ extracted = extract_fields_regex(text)
110
+ return text, extracted
111
+
112
+ with gr.Blocks() as demo:
113
+ gr.Markdown("# 🧾 Thai Receipt OCR")
114
+ with gr.Row():
115
+ img = gr.Image(type="filepath", label="Upload receipt image or PDF")
116
+ out_text = gr.Textbox(label="OCR Text", lines=10)
117
+ out_fields = gr.JSON(label="Extracted Fields")
118
+ btn = gr.Button("Run OCR")
119
+ btn.click(fn=gradio_interface, inputs=img, outputs=[out_text, out_fields])
120
+
121
+ # --- Mount Gradio on FastAPI ---
122
+ app = gr.mount_gradio_app(app, demo, path="/ui")
dockerfile ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12-slim
2
+
3
+ ENV PYTHONDONTWRITEBYTECODE=1
4
+ ENV PYTHONUNBUFFERED=1
5
+
6
+ WORKDIR /app
7
+
8
+ # System dependencies
9
+ RUN apt-get update && apt-get install -y \
10
+ poppler-utils \
11
+ && rm -rf /var/lib/apt/lists/*
12
+
13
+ COPY requirements.txt .
14
+ RUN pip install --upgrade pip && pip install -r requirements.txt
15
+
16
+ COPY . .
17
+
18
+ EXPOSE 7860
19
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ typhoon-ocr
2
+ fastapi
3
+ gradio
4
+ uvicorn
5
+ python-multipart
6
+ pdf2image
7
+ Pillow
8
+ python-dotenv