File size: 5,856 Bytes
1066715
b9bce82
86131c1
b9bce82
 
 
 
5a7d9c6
b9bce82
1066715
b9bce82
 
 
 
5a7d9c6
2e49730
1066715
86131c1
2e49730
b9bce82
 
 
1066715
5a7d9c6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b9bce82
 
 
 
1066715
b9bce82
 
 
 
 
 
 
5a7d9c6
b9bce82
 
 
5a7d9c6
b9bce82
8e5f0ef
b9bce82
 
 
 
 
 
 
 
 
 
 
8e5f0ef
b9bce82
 
 
 
 
5a7d9c6
b9bce82
 
 
 
 
 
5a7d9c6
b9bce82
 
5a7d9c6
b9bce82
 
5a7d9c6
b9bce82
 
 
5a7d9c6
b9bce82
 
 
5a7d9c6
b9bce82
 
 
 
 
 
 
 
 
5a7d9c6
b9bce82
 
5a7d9c6
b9bce82
 
 
 
5a7d9c6
 
b9bce82
 
 
 
8e5f0ef
b9bce82
 
 
 
 
 
5a7d9c6
b9bce82
5a7d9c6
b9bce82
 
5a7d9c6
b9bce82
 
5a7d9c6
b9bce82
8e5f0ef
b9bce82
 
 
5a7d9c6
 
 
 
8e5f0ef
b9bce82
 
86131c1
5a7d9c6
 
b9bce82
86131c1
 
5a7d9c6
86131c1
 
 
5a7d9c6
b9bce82
 
5a7d9c6
 
86131c1
 
 
 
 
5a7d9c6
86131c1
5a7d9c6
b9bce82
 
86131c1
8e5f0ef
86131c1
 
 
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
import os
import json
import tempfile
import numpy as np
import tensorflow as tf
from tensorflow.keras.preprocessing import image
from pdf2image import convert_from_path
from fastapi import FastAPI, UploadFile, Form, HTTPException, Header, Depends
from fastapi.responses import JSONResponse

# -----------------------------
# CONFIG
# -----------------------------
IMG_SIZE = (224, 224)
MODEL_PATH = "./final_model.keras"
class_names = ['Other', 'Aadhaar Card', 'Pan Card', 'Voter Id']

type_mapping = {
    "aadhaar_card": "Aadhaar Card",
    "pan_card": "Pan Card",
    "voter_id": "Voter Id"
}

# -----------------------------
# AUTH CONFIG (HF SECRETS)
# -----------------------------
MASTER_SECRET_KEY = os.getenv("MASTER_SECRET_KEY")

PROJECT_KEYS_JSON = os.getenv("PROJECT_KEYS_JSON", "{}")
try:
    PROJECT_KEYS = json.loads(PROJECT_KEYS_JSON)
except Exception:
    PROJECT_KEYS = {}

# -----------------------------
# AUTH VALIDATION
# -----------------------------
def verify_keys(
    x_secret_key: str = Header(None),
    x_project_id: str = Header(None),
    x_project_key: str = Header(None),
):
    # Server configuration check
    if not MASTER_SECRET_KEY:
        raise HTTPException(status_code=500, detail="MASTER_SECRET_KEY not configured in Space secrets")

    # 1) Validate master key
    if not x_secret_key or x_secret_key != MASTER_SECRET_KEY:
        raise HTTPException(status_code=401, detail="Invalid Secret Key")

    # 2) Validate project key
    if not x_project_id or not x_project_key:
        raise HTTPException(status_code=401, detail="Project Id and Project Key are required")

    if x_project_id not in PROJECT_KEYS:
        raise HTTPException(status_code=401, detail=f"Unknown project: {x_project_id}")

    if PROJECT_KEYS.get(x_project_id) != x_project_key:
        raise HTTPException(status_code=401, detail="Invalid Project Key")

    return True

# -----------------------------
# LOAD MODEL
# -----------------------------
model = tf.keras.models.load_model(MODEL_PATH)

# -----------------------------
# Predict a single image array (H,W,C)
# -----------------------------
def predict_array(img_array):
    img_array = tf.cast(img_array, tf.float32)
    img_array = tf.image.resize(img_array, IMG_SIZE)
    img_array = tf.expand_dims(img_array, axis=0)

    pred = model.predict(img_array, verbose=0)[0]
    class_id = int(np.argmax(pred))
    conf = float(np.max(pred))

    return class_names[class_id], conf, pred

# -----------------------------
# Predict from image path
# -----------------------------
def predict_image(img_path):
    img = image.load_img(img_path, target_size=IMG_SIZE, color_mode="rgb")
    img_array = image.img_to_array(img)
    label, conf, _ = predict_array(img_array)
    return {
        "type": label,
        "confidence": round(conf, 6)
    }

# -----------------------------
# Predict from PDF (max 2 pages only)
# -----------------------------
def predict_pdf(pdf_path, dpi=200, max_pages=2):
    pages = convert_from_path(pdf_path, dpi=dpi)

    if len(pages) > max_pages:
        return {
            "error": "Maximum page limit reached",
            "max_pages": max_pages,
            "found_pages": len(pages)
        }

    results = []
    labels = []

    for page in pages:
        page_np = np.array(page)

        # RGBA -> RGB
        if page_np.shape[-1] == 4:
            page_np = page_np[..., :3]

        label, conf, _ = predict_array(page_np)
        results.append((label, conf))
        labels.append(label)

    # 2 pages validation
    if len(labels) == 2:
        if labels[0] != labels[1]:
            return {
                "error": "Invalid input",
                "reason": "Pages belong to different document types",
                "page1_type": labels[0],
                "page2_type": labels[1]
            }

        final_label = labels[0]
        final_conf = float(max(results[0][1], results[1][1]))

        return {
            "type": final_label,
            "confidence": round(final_conf, 6)
        }

    # 1 page output
    return {
        "type": labels[0],
        "confidence": round(float(results[0][1]), 6)
    }

# -----------------------------
# Main function
# -----------------------------
def predict_file(path):
    if not os.path.exists(path):
        return {"error": "File not found", "path": path}

    ext = os.path.splitext(path)[1].lower()

    if ext in [".png", ".jpg", ".jpeg", ".bmp", ".webp"]:
        return predict_image(path)

    if ext == ".pdf":
        return predict_pdf(path)

    return {"error": "Unsupported file type", "ext": ext}

# -----------------------------
# FastAPI App
# -----------------------------
app = FastAPI(
    title="ID Document Validator",
    description="Predict document type and authenticate based on expected type."
)

@app.post("/predict")
async def predict(
    file: UploadFile,
    expected_type: str = Form(None),
    auth: bool = Depends(verify_keys)   # ✅ Authentication required
):
    if not file.filename:
        raise HTTPException(status_code=400, detail="No file provided")

    with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(file.filename)[1]) as temp_file:
        temp_path = temp_file.name
        temp_file.write(await file.read())

    try:
        result = predict_file(temp_path)

        # authentication based on expected_type
        if expected_type and expected_type in type_mapping and "type" in result:
            if result["type"] == type_mapping[expected_type]:
                result["Authentication"] = "Valid"
            else:
                result["Authentication"] = "Not valid"

        return JSONResponse(content=result)

    finally:
        if os.path.exists(temp_path):
            os.unlink(temp_path)

@app.get("/")
def read_root():
    return {"message": "ID Validator API is running"}