File size: 5,853 Bytes
9398e88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# ocr_server.py - Clean natural language messages (no Reference number text)
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import base64
import binascii
from PIL import Image
import io
import torch
from transformers import Qwen2VLForConditionalGeneration, AutoProcessor
import re

app = FastAPI()

# Enable CORS for mobile app
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Request model for Base64
class OCRRequest(BaseModel):
    image_base64: str
    filename: str = "image.jpg"

# Load model
print("Loading OCR model...")
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model_id = "prithivMLmods/coreOCR-7B-050325-preview"

processor = AutoProcessor.from_pretrained(model_id, trust_remote_code=True)
model = Qwen2VLForConditionalGeneration.from_pretrained(
    model_id,
    trust_remote_code=True,
    torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32
).to(device).eval()

print(f"Model loaded on {device}")

def extract_numbers(text):
    numbers = re.findall(r'\d+', text)
    return ''.join(numbers) if numbers else ""

def base64_to_image(base64_string):
    if ',' in base64_string and base64_string.startswith('data:'):
        base64_string = base64_string.split(',', 1)[1]
    image_bytes = base64.b64decode(base64_string)
    image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
    return image

@app.post("/ocr")
async def ocr_image(request: OCRRequest):
    try:
        # Convert base64 to image
        image = base64_to_image(request.image_base64)
        
        # Run OCR
        messages = [{
            "role": "user",
            "content": [
                {"type": "image"},
                {"type": "text", "text": "Extract all numbers from this meter reading. Return only the numbers."},
            ]
        }]
        
        prompt_full = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
        inputs = processor(
            text=[prompt_full],
            images=[image],
            return_tensors="pt",
        ).to(device)
        
        with torch.no_grad():
            outputs = model.generate(
                **inputs, 
                max_new_tokens=200,
                temperature=0.1,
                do_sample=False
            )
        
        result = processor.decode(outputs[0], skip_special_tokens=True)
        numbers_only = extract_numbers(result)
        
        # Clean natural language messages
        if numbers_only:
            message = "Meter reading successfully extracted"
            ref_no = numbers_only[-14:] if len(numbers_only) >= 14 else numbers_only
        else:
            message = "No numbers found in the image. Please provide a clear meter reading photo"
            ref_no = ""
        
        return JSONResponse({
            "success": True,
            "message": message,
            "ref_no": ref_no,
            "numbers": numbers_only
        })
        
    except binascii.Error:
        return JSONResponse({
            "success": False,
            "message": "Invalid image format. Please send a valid Base64 encoded image"
        }, status_code=400)
        
    except Exception as e:
        error_message = str(e)
        if "image" in error_message.lower():
            message = "Could not process the image. Please ensure it's a valid photo of a meter reading"
        elif "timeout" in error_message.lower():
            message = "OCR processing timed out. Please try with a smaller or clearer image"
        else:
            message = "Failed to process image. Please try again"
        
        return JSONResponse({
            "success": False,
            "message": message
        }, status_code=500)

@app.get("/health")
async def health_check():
    return {
        "status": "ok",
        "model": "coreOCR-7B",
        "message": "OCR server is running normally"
    }

@app.post("/ocr-file")
async def ocr_image_file(file: bytes = None):
    """Legacy file upload endpoint for testing"""
    try:
        image = Image.open(io.BytesIO(file)).convert("RGB")
        
        messages = [{
            "role": "user",
            "content": [
                {"type": "image"},
                {"type": "text", "text": "Extract all numbers from this meter reading. Return only the numbers."},
            ]
        }]
        
        prompt_full = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
        inputs = processor(
            text=[prompt_full],
            images=[image],
            return_tensors="pt",
        ).to(device)
        
        with torch.no_grad():
            outputs = model.generate(**inputs, max_new_tokens=200, temperature=0.1, do_sample=False)
        
        result = processor.decode(outputs[0], skip_special_tokens=True)
        numbers_only = extract_numbers(result)
        
        if numbers_only:
            message = "Meter reading successfully extracted"
            ref_no = numbers_only[-14:] if len(numbers_only) >= 14 else numbers_only
        else:
            message = "No numbers found in the image. Please provide a clear meter reading photo"
            ref_no = ""
        
        return JSONResponse({
            "success": True,
            "message": message,
            "ref_no": ref_no,
            "numbers": numbers_only
        })
    except Exception as e:
        return JSONResponse({
            "success": False,
            "message": "Failed to process image. Please try again"
        }, status_code=500)

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)