Spaces:
Sleeping
Sleeping
Update main.py
Browse files
main.py
CHANGED
|
@@ -1,48 +1,152 @@
|
|
| 1 |
-
# app.py
|
| 2 |
-
from fastapi import FastAPI, UploadFile, File, Form, HTTPException
|
| 3 |
-
from fastapi.responses import JSONResponse
|
| 4 |
-
from predictor import predict_file
|
| 5 |
-
import shutil
|
| 6 |
import os
|
| 7 |
-
import
|
| 8 |
-
import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import os
|
| 2 |
+
import json
|
| 3 |
+
import numpy as np
|
| 4 |
+
import tensorflow as tf
|
| 5 |
+
from tensorflow.keras.preprocessing import image
|
| 6 |
+
from pdf2image import convert_from_path
|
| 7 |
+
from fastapi import FastAPI, UploadFile, File, HTTPException, Query
|
| 8 |
+
from fastapi.responses import JSONResponse
|
| 9 |
+
import uvicorn
|
| 10 |
|
| 11 |
+
# -----------------------------
|
| 12 |
+
# CONFIG
|
| 13 |
+
# -----------------------------
|
| 14 |
+
IMG_SIZE = (224, 224)
|
| 15 |
+
MODEL_PATH = "final_model (2).keras" # Updated to relative path for Docker
|
| 16 |
+
class_names = ['Other', 'Aadhar Card', 'Pan Card', 'Voter Id']
|
| 17 |
|
| 18 |
+
# Mapping for expected_type input (normalize to match class_names)
|
| 19 |
+
EXPECTED_TYPE_MAPPING = {
|
| 20 |
+
"aadhar_card": "Aadhar Card",
|
| 21 |
+
"pan_card": "Pan Card",
|
| 22 |
+
"voter_id": "Voter Id"
|
| 23 |
+
}
|
| 24 |
|
| 25 |
+
# -----------------------------
|
| 26 |
+
# LOAD MODEL
|
| 27 |
+
# -----------------------------
|
| 28 |
+
model = tf.keras.models.load_model(MODEL_PATH)
|
| 29 |
|
| 30 |
+
# -----------------------------
|
| 31 |
+
# Predict a single image array (H,W,C)
|
| 32 |
+
# -----------------------------
|
| 33 |
+
def predict_array(img_array):
|
| 34 |
+
img_array = tf.cast(img_array, tf.float32)
|
| 35 |
+
img_array = tf.image.resize(img_array, IMG_SIZE)
|
| 36 |
+
# DO NOT divide by 255.0 (training did not use Rescaling)
|
| 37 |
+
img_array = tf.expand_dims(img_array, axis=0)
|
| 38 |
+
pred = model.predict(img_array, verbose=0)[0]
|
| 39 |
+
class_id = int(np.argmax(pred))
|
| 40 |
+
conf = float(np.max(pred))
|
| 41 |
+
return class_names[class_id], conf, pred
|
| 42 |
|
| 43 |
+
# -----------------------------
|
| 44 |
+
# Predict from image path
|
| 45 |
+
# -----------------------------
|
| 46 |
+
def predict_image(img_path):
|
| 47 |
+
img = image.load_img(img_path, target_size=IMG_SIZE, color_mode="rgb")
|
| 48 |
+
img_array = image.img_to_array(img)
|
| 49 |
+
label, conf, _ = predict_array(img_array)
|
| 50 |
+
return {
|
| 51 |
+
"type": label,
|
| 52 |
+
"confidence": round(conf, 6)
|
| 53 |
+
}
|
| 54 |
|
| 55 |
+
# -----------------------------
|
| 56 |
+
# Predict from PDF (max 2 pages only)
|
| 57 |
+
# -----------------------------
|
| 58 |
+
def predict_pdf(pdf_path, dpi=200, max_pages=2):
|
| 59 |
+
pages = convert_from_path(pdf_path, dpi=dpi)
|
| 60 |
+
if len(pages) > max_pages:
|
| 61 |
+
return {
|
| 62 |
+
"error": "Maximum page limit reached",
|
| 63 |
+
"max_pages": max_pages,
|
| 64 |
+
"found_pages": len(pages)
|
| 65 |
+
}
|
| 66 |
+
results = []
|
| 67 |
+
labels = []
|
| 68 |
+
for page in pages:
|
| 69 |
+
page_np = np.array(page)
|
| 70 |
+
# RGBA -> RGB
|
| 71 |
+
if page_np.shape[-1] == 4:
|
| 72 |
+
page_np = page_np[..., :3]
|
| 73 |
+
label, conf, _ = predict_array(page_np)
|
| 74 |
+
results.append((label, conf))
|
| 75 |
+
labels.append(label)
|
| 76 |
+
# 2 pages validation
|
| 77 |
+
if len(labels) == 2:
|
| 78 |
+
if labels[0] != labels[1]:
|
| 79 |
+
return {
|
| 80 |
+
"error": "Invalid input",
|
| 81 |
+
"reason": "Pages belong to different document types",
|
| 82 |
+
"page1_type": labels[0],
|
| 83 |
+
"page2_type": labels[1]
|
| 84 |
+
}
|
| 85 |
+
# both same -> return single output
|
| 86 |
+
final_label = labels[0]
|
| 87 |
+
final_conf = float(max(results[0][1], results[1][1]))
|
| 88 |
+
return {
|
| 89 |
+
"type": final_label,
|
| 90 |
+
"confidence": round(final_conf, 6)
|
| 91 |
+
}
|
| 92 |
+
# 1-page pdf normal output
|
| 93 |
+
return {
|
| 94 |
+
"type": labels[0],
|
| 95 |
+
"confidence": round(float(results[0][1]), 6)
|
| 96 |
+
}
|
| 97 |
|
| 98 |
+
# -----------------------------
|
| 99 |
+
# Main function
|
| 100 |
+
# -----------------------------
|
| 101 |
+
def predict_file(path):
|
| 102 |
+
if not os.path.exists(path):
|
| 103 |
+
return {"error": "File not found", "path": path}
|
| 104 |
+
ext = os.path.splitext(path)[1].lower()
|
| 105 |
+
if ext in [".png", ".jpg", ".jpeg", ".bmp", ".webp"]:
|
| 106 |
+
return predict_image(path)
|
| 107 |
+
if ext == ".pdf":
|
| 108 |
+
return predict_pdf(path)
|
| 109 |
+
return {"error": "Unsupported file type", "ext": ext}
|
| 110 |
|
| 111 |
+
# -----------------------------
|
| 112 |
+
# FastAPI App
|
| 113 |
+
# -----------------------------
|
| 114 |
+
app = FastAPI(title="ID Document Validator", description="Predict document type and optionally validate against expected type.")
|
| 115 |
|
| 116 |
+
@app.post("/predict")
|
| 117 |
+
async def predict(
|
| 118 |
+
file: UploadFile = File(...),
|
| 119 |
+
expected_type: str = Query(None, description="Optional: Expected document type ('aadhar_card', 'pan_card', 'voter_id'). If provided, adds 'Authentication' to response.")
|
| 120 |
+
):
|
| 121 |
+
# Save uploaded file temporarily
|
| 122 |
+
temp_path = f"/tmp/{file.filename}"
|
| 123 |
+
with open(temp_path, "wb") as f:
|
| 124 |
+
f.write(await file.read())
|
| 125 |
+
|
| 126 |
+
try:
|
| 127 |
+
# Get prediction result
|
| 128 |
+
result = predict_file(temp_path)
|
| 129 |
+
|
| 130 |
+
# If there's an error in result, return it as-is
|
| 131 |
+
if "error" in result:
|
| 132 |
+
return JSONResponse(status_code=400, content=result)
|
| 133 |
+
|
| 134 |
+
# If expected_type is provided and valid, add Authentication
|
| 135 |
+
if expected_type and expected_type in EXPECTED_TYPE_MAPPING:
|
| 136 |
+
expected_label = EXPECTED_TYPE_MAPPING[expected_type]
|
| 137 |
+
predicted_label = result["type"]
|
| 138 |
+
authentication = "Valid" if predicted_label == expected_label else "Not Valid"
|
| 139 |
+
result["Authentication"] = authentication
|
| 140 |
+
elif expected_type and expected_type not in EXPECTED_TYPE_MAPPING:
|
| 141 |
+
# Invalid expected_type, but still return prediction without Authentication
|
| 142 |
+
pass # Just proceed without adding Authentication
|
| 143 |
+
|
| 144 |
+
return result
|
| 145 |
+
finally:
|
| 146 |
+
# Clean up temp file
|
| 147 |
+
if os.path.exists(temp_path):
|
| 148 |
+
os.remove(temp_path)
|
| 149 |
|
| 150 |
+
# For local testing
|
| 151 |
+
if __name__ == "__main__":
|
| 152 |
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|