Preload OCR model at startup
Browse files
server.py
CHANGED
|
@@ -9,16 +9,18 @@ from fastapi import FastAPI, UploadFile, File, Form, HTTPException, BackgroundTa
|
|
| 9 |
from fastapi.responses import FileResponse, JSONResponse
|
| 10 |
from fastapi.staticfiles import StaticFiles
|
| 11 |
from fastapi.middleware.cors import CORSMiddleware
|
| 12 |
-
|
| 13 |
-
# Import existing functions
|
| 14 |
from barcode_scanner import scan_barcode, scan_all_barcodes
|
| 15 |
-
from ocr import read_chassis, postprocess_with_hint, ocr_image
|
| 16 |
from preprocess import preprocess_chassis
|
| 17 |
from evaluate import evaluate, get_pairs
|
| 18 |
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
-
|
| 22 |
app.add_middleware(
|
| 23 |
CORSMiddleware,
|
| 24 |
allow_origins=["*"],
|
|
@@ -33,7 +35,6 @@ CONFIG_PATH = "config.json"
|
|
| 33 |
os.makedirs(TEMP_DIR, exist_ok=True)
|
| 34 |
os.makedirs(RESULTS_DIR, exist_ok=True)
|
| 35 |
|
| 36 |
-
# Helper to convert cv2 image to base64 jpeg
|
| 37 |
def cv2_to_base64(img):
|
| 38 |
_, buffer = cv2.imencode('.jpg', img)
|
| 39 |
return base64.b64encode(buffer).decode('utf-8')
|
|
@@ -51,7 +52,6 @@ def get_test_pairs():
|
|
| 51 |
chassis_dir = "images/chassis"
|
| 52 |
if not os.path.exists(barcode_dir) or not os.path.exists(chassis_dir):
|
| 53 |
return []
|
| 54 |
-
|
| 55 |
barcodes = {os.path.splitext(f)[0] for f in os.listdir(barcode_dir) if f.lower().endswith(('.jpg', '.jpeg', '.png'))}
|
| 56 |
chassis = {os.path.splitext(f)[0] for f in os.listdir(chassis_dir) if f.lower().endswith(('.jpg', '.jpeg', '.png'))}
|
| 57 |
common = sorted(list(barcodes & chassis))
|
|
@@ -59,7 +59,6 @@ def get_test_pairs():
|
|
| 59 |
|
| 60 |
@app.get("/api/scan-barcode/{key}")
|
| 61 |
def scan_barcode_by_key(key: str):
|
| 62 |
-
"""Scan a barcode image from the dataset by its key (filename without extension)."""
|
| 63 |
for ext in ['.jpg', '.jpeg', '.png', '.JPG', '.PNG']:
|
| 64 |
path = os.path.join("images/barcode", f"{key}{ext}")
|
| 65 |
if os.path.exists(path):
|
|
@@ -67,7 +66,6 @@ def scan_barcode_by_key(key: str):
|
|
| 67 |
return {"success": result is not None, "barcode": result}
|
| 68 |
raise HTTPException(status_code=404, detail=f"Barcode image for key '{key}' not found")
|
| 69 |
|
| 70 |
-
|
| 71 |
@app.post("/api/scan-barcode")
|
| 72 |
async def api_scan_barcode(file: UploadFile = File(...)):
|
| 73 |
temp_filename = f"{uuid.uuid4()}_{file.filename}"
|
|
@@ -75,7 +73,6 @@ async def api_scan_barcode(file: UploadFile = File(...)):
|
|
| 75 |
try:
|
| 76 |
with open(temp_path, "wb") as buffer:
|
| 77 |
shutil.copyfileobj(file.file, buffer)
|
| 78 |
-
|
| 79 |
result = scan_barcode(temp_path)
|
| 80 |
return {"success": result is not None, "barcode": result}
|
| 81 |
except Exception as e:
|
|
@@ -100,7 +97,6 @@ async def save_config(config_data: dict):
|
|
| 100 |
except Exception as e:
|
| 101 |
raise HTTPException(status_code=500, detail=str(e))
|
| 102 |
|
| 103 |
-
# Global state to keep track of batch evaluation runs
|
| 104 |
eval_status = {"running": False, "progress": 0, "total": 0, "results": []}
|
| 105 |
|
| 106 |
def run_evaluation_task():
|
|
@@ -108,10 +104,7 @@ def run_evaluation_task():
|
|
| 108 |
try:
|
| 109 |
eval_status["running"] = True
|
| 110 |
eval_status["progress"] = 0
|
| 111 |
-
|
| 112 |
-
# We can call the evaluate function but let's read the report afterwards
|
| 113 |
evaluate()
|
| 114 |
-
|
| 115 |
report_path = os.path.join(RESULTS_DIR, "report.json")
|
| 116 |
if os.path.exists(report_path):
|
| 117 |
with open(report_path) as f:
|
|
@@ -128,7 +121,6 @@ def trigger_evaluation(background_tasks: BackgroundTasks):
|
|
| 128 |
global eval_status
|
| 129 |
if eval_status["running"]:
|
| 130 |
return {"status": "already_running", "message": "Evaluation task is currently running"}
|
| 131 |
-
|
| 132 |
eval_status = {"running": True, "progress": 0, "total": 50, "results": []}
|
| 133 |
background_tasks.add_task(run_evaluation_task)
|
| 134 |
return {"status": "started", "message": "Batch evaluation started in the background"}
|
|
@@ -143,7 +135,6 @@ def get_evaluation_status():
|
|
| 143 |
results = json.load(f)
|
| 144 |
except Exception:
|
| 145 |
pass
|
| 146 |
-
|
| 147 |
return {
|
| 148 |
"running": eval_status["running"],
|
| 149 |
"progress": eval_status["progress"],
|
|
@@ -165,8 +156,6 @@ async def match_chassis(
|
|
| 165 |
temp_path = None
|
| 166 |
|
| 167 |
if chassis_key:
|
| 168 |
-
# Load from test set
|
| 169 |
-
# Check standard extensions (.jpg, .png, etc.)
|
| 170 |
for ext in ['.jpg', '.jpeg', '.png', '.JPG', '.PNG']:
|
| 171 |
p = os.path.join("images/chassis", f"{chassis_key}{ext}")
|
| 172 |
if os.path.exists(p):
|
|
@@ -175,7 +164,6 @@ async def match_chassis(
|
|
| 175 |
if not chassis_path:
|
| 176 |
raise HTTPException(status_code=404, detail=f"Chassis image for key '{chassis_key}' not found in images/chassis")
|
| 177 |
else:
|
| 178 |
-
# Save uploaded file
|
| 179 |
temp_filename = f"{uuid.uuid4()}_{chassis_file.filename}"
|
| 180 |
temp_path = os.path.join(TEMP_DIR, temp_filename)
|
| 181 |
with open(temp_path, "wb") as buffer:
|
|
@@ -183,18 +171,12 @@ async def match_chassis(
|
|
| 183 |
chassis_path = temp_path
|
| 184 |
|
| 185 |
try:
|
| 186 |
-
# 1. Run Preprocessing to get variations
|
| 187 |
-
# Use save_comparison=True so we also write the side-by-side view to results/ (useful for viewing static file later)
|
| 188 |
-
# Note: if it's a temp file, let's create a friendly name for results comparison
|
| 189 |
save_comp = True
|
| 190 |
comp_filename = chassis_key if chassis_key else os.path.splitext(chassis_file.filename)[0]
|
| 191 |
-
|
| 192 |
variations = preprocess_chassis(chassis_path, save_comparison=save_comp)
|
| 193 |
-
|
| 194 |
-
# 2. Get base64 representation of original and each variation
|
| 195 |
original_img = cv2.imread(chassis_path)
|
| 196 |
base64_original = cv2_to_base64(original_img)
|
| 197 |
-
|
| 198 |
base64_variations = []
|
| 199 |
labels = ["CLAHE", "Bilateral", "Otsu", "Adaptive"]
|
| 200 |
for idx, var in enumerate(variations):
|
|
@@ -203,11 +185,10 @@ async def match_chassis(
|
|
| 203 |
"base64": cv2_to_base64(var)
|
| 204 |
})
|
| 205 |
|
| 206 |
-
# 3. Run OCR on each variation and compute scores, finding the best
|
| 207 |
best_text, best_conf, best_score = "", 0.0, -1
|
| 208 |
winning_label = ""
|
| 209 |
variation_details = []
|
| 210 |
-
|
| 211 |
for idx, var in enumerate(variations):
|
| 212 |
text, conf = ocr_image(var)
|
| 213 |
score = conf * max(len(text), 1)
|
|
@@ -221,16 +202,14 @@ async def match_chassis(
|
|
| 221 |
best_text, best_conf, best_score = text, conf, score
|
| 222 |
winning_label = labels[idx]
|
| 223 |
|
| 224 |
-
# 4. Perform error correction and match check
|
| 225 |
corrected_text, is_match = postprocess_with_hint(best_text, barcode_val)
|
| 226 |
-
|
| 227 |
status = "FAILED"
|
| 228 |
if best_text == barcode_val:
|
| 229 |
status = "EXACT"
|
| 230 |
elif is_match:
|
| 231 |
status = "CORRECTED"
|
| 232 |
|
| 233 |
-
|
| 234 |
"success": is_match,
|
| 235 |
"status": status,
|
| 236 |
"barcode_val": barcode_val,
|
|
@@ -243,7 +222,6 @@ async def match_chassis(
|
|
| 243 |
"variation_details": variation_details,
|
| 244 |
"comparison_url": f"/results/{comp_filename}_comparison.jpg" if save_comp else None
|
| 245 |
}
|
| 246 |
-
return response_data
|
| 247 |
|
| 248 |
except Exception as e:
|
| 249 |
raise HTTPException(status_code=500, detail=str(e))
|
|
@@ -251,14 +229,9 @@ async def match_chassis(
|
|
| 251 |
if temp_path and os.path.exists(temp_path):
|
| 252 |
os.remove(temp_path)
|
| 253 |
|
| 254 |
-
# Serve results images directly
|
| 255 |
app.mount("/results", StaticFiles(directory="results"), name="results")
|
| 256 |
-
|
| 257 |
-
# Serve frontend application static files
|
| 258 |
-
# We will mount at "/" with html=True so index.html is served automatically
|
| 259 |
-
# Make sure to run this *after* route declarations
|
| 260 |
app.mount("/", StaticFiles(directory="web", html=True), name="static")
|
| 261 |
|
| 262 |
if __name__ == "__main__":
|
| 263 |
import uvicorn
|
| 264 |
-
uvicorn.run("server:app", host="0.0.0.0", port=8000, reload=True)
|
|
|
|
| 9 |
from fastapi.responses import FileResponse, JSONResponse
|
| 10 |
from fastapi.staticfiles import StaticFiles
|
| 11 |
from fastapi.middleware.cors import CORSMiddleware
|
| 12 |
+
from contextlib import asynccontextmanager
|
|
|
|
| 13 |
from barcode_scanner import scan_barcode, scan_all_barcodes
|
| 14 |
+
from ocr import read_chassis, postprocess_with_hint, ocr_image, get_ocr
|
| 15 |
from preprocess import preprocess_chassis
|
| 16 |
from evaluate import evaluate, get_pairs
|
| 17 |
|
| 18 |
+
@asynccontextmanager
|
| 19 |
+
async def lifespan(app: FastAPI):
|
| 20 |
+
get_ocr()
|
| 21 |
+
yield
|
| 22 |
|
| 23 |
+
app = FastAPI(title="Chassis OCR API", description="API backend for Chassis OCR PWA", lifespan=lifespan)
|
| 24 |
app.add_middleware(
|
| 25 |
CORSMiddleware,
|
| 26 |
allow_origins=["*"],
|
|
|
|
| 35 |
os.makedirs(TEMP_DIR, exist_ok=True)
|
| 36 |
os.makedirs(RESULTS_DIR, exist_ok=True)
|
| 37 |
|
|
|
|
| 38 |
def cv2_to_base64(img):
|
| 39 |
_, buffer = cv2.imencode('.jpg', img)
|
| 40 |
return base64.b64encode(buffer).decode('utf-8')
|
|
|
|
| 52 |
chassis_dir = "images/chassis"
|
| 53 |
if not os.path.exists(barcode_dir) or not os.path.exists(chassis_dir):
|
| 54 |
return []
|
|
|
|
| 55 |
barcodes = {os.path.splitext(f)[0] for f in os.listdir(barcode_dir) if f.lower().endswith(('.jpg', '.jpeg', '.png'))}
|
| 56 |
chassis = {os.path.splitext(f)[0] for f in os.listdir(chassis_dir) if f.lower().endswith(('.jpg', '.jpeg', '.png'))}
|
| 57 |
common = sorted(list(barcodes & chassis))
|
|
|
|
| 59 |
|
| 60 |
@app.get("/api/scan-barcode/{key}")
|
| 61 |
def scan_barcode_by_key(key: str):
|
|
|
|
| 62 |
for ext in ['.jpg', '.jpeg', '.png', '.JPG', '.PNG']:
|
| 63 |
path = os.path.join("images/barcode", f"{key}{ext}")
|
| 64 |
if os.path.exists(path):
|
|
|
|
| 66 |
return {"success": result is not None, "barcode": result}
|
| 67 |
raise HTTPException(status_code=404, detail=f"Barcode image for key '{key}' not found")
|
| 68 |
|
|
|
|
| 69 |
@app.post("/api/scan-barcode")
|
| 70 |
async def api_scan_barcode(file: UploadFile = File(...)):
|
| 71 |
temp_filename = f"{uuid.uuid4()}_{file.filename}"
|
|
|
|
| 73 |
try:
|
| 74 |
with open(temp_path, "wb") as buffer:
|
| 75 |
shutil.copyfileobj(file.file, buffer)
|
|
|
|
| 76 |
result = scan_barcode(temp_path)
|
| 77 |
return {"success": result is not None, "barcode": result}
|
| 78 |
except Exception as e:
|
|
|
|
| 97 |
except Exception as e:
|
| 98 |
raise HTTPException(status_code=500, detail=str(e))
|
| 99 |
|
|
|
|
| 100 |
eval_status = {"running": False, "progress": 0, "total": 0, "results": []}
|
| 101 |
|
| 102 |
def run_evaluation_task():
|
|
|
|
| 104 |
try:
|
| 105 |
eval_status["running"] = True
|
| 106 |
eval_status["progress"] = 0
|
|
|
|
|
|
|
| 107 |
evaluate()
|
|
|
|
| 108 |
report_path = os.path.join(RESULTS_DIR, "report.json")
|
| 109 |
if os.path.exists(report_path):
|
| 110 |
with open(report_path) as f:
|
|
|
|
| 121 |
global eval_status
|
| 122 |
if eval_status["running"]:
|
| 123 |
return {"status": "already_running", "message": "Evaluation task is currently running"}
|
|
|
|
| 124 |
eval_status = {"running": True, "progress": 0, "total": 50, "results": []}
|
| 125 |
background_tasks.add_task(run_evaluation_task)
|
| 126 |
return {"status": "started", "message": "Batch evaluation started in the background"}
|
|
|
|
| 135 |
results = json.load(f)
|
| 136 |
except Exception:
|
| 137 |
pass
|
|
|
|
| 138 |
return {
|
| 139 |
"running": eval_status["running"],
|
| 140 |
"progress": eval_status["progress"],
|
|
|
|
| 156 |
temp_path = None
|
| 157 |
|
| 158 |
if chassis_key:
|
|
|
|
|
|
|
| 159 |
for ext in ['.jpg', '.jpeg', '.png', '.JPG', '.PNG']:
|
| 160 |
p = os.path.join("images/chassis", f"{chassis_key}{ext}")
|
| 161 |
if os.path.exists(p):
|
|
|
|
| 164 |
if not chassis_path:
|
| 165 |
raise HTTPException(status_code=404, detail=f"Chassis image for key '{chassis_key}' not found in images/chassis")
|
| 166 |
else:
|
|
|
|
| 167 |
temp_filename = f"{uuid.uuid4()}_{chassis_file.filename}"
|
| 168 |
temp_path = os.path.join(TEMP_DIR, temp_filename)
|
| 169 |
with open(temp_path, "wb") as buffer:
|
|
|
|
| 171 |
chassis_path = temp_path
|
| 172 |
|
| 173 |
try:
|
|
|
|
|
|
|
|
|
|
| 174 |
save_comp = True
|
| 175 |
comp_filename = chassis_key if chassis_key else os.path.splitext(chassis_file.filename)[0]
|
|
|
|
| 176 |
variations = preprocess_chassis(chassis_path, save_comparison=save_comp)
|
|
|
|
|
|
|
| 177 |
original_img = cv2.imread(chassis_path)
|
| 178 |
base64_original = cv2_to_base64(original_img)
|
| 179 |
+
|
| 180 |
base64_variations = []
|
| 181 |
labels = ["CLAHE", "Bilateral", "Otsu", "Adaptive"]
|
| 182 |
for idx, var in enumerate(variations):
|
|
|
|
| 185 |
"base64": cv2_to_base64(var)
|
| 186 |
})
|
| 187 |
|
|
|
|
| 188 |
best_text, best_conf, best_score = "", 0.0, -1
|
| 189 |
winning_label = ""
|
| 190 |
variation_details = []
|
| 191 |
+
|
| 192 |
for idx, var in enumerate(variations):
|
| 193 |
text, conf = ocr_image(var)
|
| 194 |
score = conf * max(len(text), 1)
|
|
|
|
| 202 |
best_text, best_conf, best_score = text, conf, score
|
| 203 |
winning_label = labels[idx]
|
| 204 |
|
|
|
|
| 205 |
corrected_text, is_match = postprocess_with_hint(best_text, barcode_val)
|
|
|
|
| 206 |
status = "FAILED"
|
| 207 |
if best_text == barcode_val:
|
| 208 |
status = "EXACT"
|
| 209 |
elif is_match:
|
| 210 |
status = "CORRECTED"
|
| 211 |
|
| 212 |
+
return {
|
| 213 |
"success": is_match,
|
| 214 |
"status": status,
|
| 215 |
"barcode_val": barcode_val,
|
|
|
|
| 222 |
"variation_details": variation_details,
|
| 223 |
"comparison_url": f"/results/{comp_filename}_comparison.jpg" if save_comp else None
|
| 224 |
}
|
|
|
|
| 225 |
|
| 226 |
except Exception as e:
|
| 227 |
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
| 229 |
if temp_path and os.path.exists(temp_path):
|
| 230 |
os.remove(temp_path)
|
| 231 |
|
|
|
|
| 232 |
app.mount("/results", StaticFiles(directory="results"), name="results")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
app.mount("/", StaticFiles(directory="web", html=True), name="static")
|
| 234 |
|
| 235 |
if __name__ == "__main__":
|
| 236 |
import uvicorn
|
| 237 |
+
uvicorn.run("server:app", host="0.0.0.0", port=8000, reload=True)
|