zombee11's picture
Upload 6 files
868cfa1 verified
Raw
History Blame Contribute Delete
6.28 kB
import json
import os
import re
import secrets
import cv2
import easyocr
import numpy as np
import uvicorn
from fastapi import FastAPI, File, Header, HTTPException, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import Response
APP_ENV = os.getenv("APP_ENV", "dev").lower()
APP_PORT = int(os.getenv("PORT", "7860"))
MAX_UPLOAD_MB = int(os.getenv("MAX_UPLOAD_MB", "10"))
MAX_UPLOAD_BYTES = MAX_UPLOAD_MB * 1024 * 1024
API_KEY = os.getenv("MASKING_API_KEY", "")
EXPOSE_DOCS = os.getenv("EXPOSE_DOCS", "true" if APP_ENV != "prod" else "false").lower() == "true"
ALLOW_ORIGINS = [origin.strip() for origin in os.getenv("ALLOWED_ORIGINS", "").split(",") if origin.strip()]
app = FastAPI(
title="Aadhaar Strict Masking API",
docs_url="/docs" if EXPOSE_DOCS else None,
redoc_url="/redoc" if EXPOSE_DOCS else None,
openapi_url="/openapi.json" if EXPOSE_DOCS else None,
)
app.add_middleware(
CORSMiddleware,
allow_origins=ALLOW_ORIGINS or ["*"],
allow_credentials=False,
allow_methods=["POST", "GET"],
allow_headers=["*"],
)
reader = easyocr.Reader(["en"], gpu=False)
def enhance_mobile_photo(img):
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
return cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)).apply(gray)
def get_clean_digits(text):
text = text.upper().replace("O", "0").replace("I", "1").replace("L", "1").replace("B", "8")
return re.sub(r"[^0-9]", "", text)
def mask_numeric_value(value, visible_last=4, mask_char="x"):
if not value or len(value) <= visible_last:
return value
return f'{mask_char * (len(value) - visible_last)}{value[-visible_last:]}'
def apply_mask(img, bbox, ratio):
p1 = tuple(map(int, bbox[0]))
p3 = tuple(map(int, bbox[2]))
width = max(1, p3[0] - p1[0])
mask_width = int(width * ratio)
cv2.rectangle(img, p1, (p1[0] + mask_width, p3[1]), (0, 0, 0), -1)
return img
def validate_api_key(x_api_key):
if not API_KEY:
return
if not x_api_key or not secrets.compare_digest(x_api_key, API_KEY):
raise HTTPException(status_code=401, detail="Unauthorized")
@app.get("/health")
async def health_check():
return {"status": "ok", "env": APP_ENV}
@app.post("/v1/aadhaar/process")
async def process_document(
file: UploadFile = File(...),
x_api_key: str | None = Header(default=None, alias="x-api-key"),
):
try:
validate_api_key(x_api_key)
contents = await file.read()
if not contents:
raise HTTPException(status_code=400, detail="Empty upload")
if len(contents) > MAX_UPLOAD_BYTES:
raise HTTPException(status_code=413, detail=f"File too large. Max allowed is {MAX_UPLOAD_MB} MB")
img = cv2.imdecode(np.frombuffer(contents, np.uint8), cv2.IMREAD_COLOR)
if img is None:
raise HTTPException(status_code=400, detail="Invalid image format")
h, w = img.shape[:2]
img = cv2.resize(img, (1200, int(h * (1200 / w))))
processed_img = enhance_mobile_photo(img)
results = reader.readtext(processed_img, detail=1)
aadhaar_values = []
vid_values = []
masked_aadhaar_keys = set()
masked_vid_keys = set()
for i, (bbox, text, conf) in enumerate(results):
if conf < 0.35:
continue
clean = get_clean_digits(text)
text_upper = text.upper()
if len(clean) == 12 and not clean.startswith(("0", "1")):
key = (clean, tuple(map(int, bbox[0])), tuple(map(int, bbox[2])))
if key not in masked_aadhaar_keys:
aadhaar_values.append(clean)
img = apply_mask(img, bbox, 0.68)
masked_aadhaar_keys.add(key)
continue
if "VID" in text_upper and len(clean) >= 12:
key = (clean[:16], tuple(map(int, bbox[0])), tuple(map(int, bbox[2])))
if key not in masked_vid_keys:
vid_values.append(clean[:16])
img = apply_mask(img, bbox, 0.79)
masked_vid_keys.add(key)
elif "VID" in text_upper and i + 1 < len(results):
next_bbox, next_text, next_conf = results[i + 1]
if next_conf < 0.35:
continue
next_clean = get_clean_digits(next_text)
if len(next_clean) >= 12:
key = (next_clean[:16], tuple(map(int, next_bbox[0])), tuple(map(int, next_bbox[2])))
if key not in masked_vid_keys:
vid_values.append(next_clean[:16])
img = apply_mask(img, next_bbox, 0.79)
masked_vid_keys.add(key)
elif len(clean) == 16:
key = (clean, tuple(map(int, bbox[0])), tuple(map(int, bbox[2])))
if key not in masked_vid_keys:
vid_values.append(clean)
img = apply_mask(img, bbox, 0.79)
masked_vid_keys.add(key)
extracted = {
"aadhaar": [mask_numeric_value(value, visible_last=4) for value in aadhaar_values] or None,
"vid": [mask_numeric_value(value, visible_last=4) for value in vid_values] or None,
}
if isinstance(extracted["aadhaar"], list) and len(extracted["aadhaar"]) == 1:
extracted["aadhaar"] = extracted["aadhaar"][0]
if isinstance(extracted["vid"], list) and len(extracted["vid"]) == 1:
extracted["vid"] = extracted["vid"][0]
_, buffer = cv2.imencode(".jpg", img)
return Response(
content=buffer.tobytes(),
media_type="image/jpeg",
headers={
"x-data": json.dumps(extracted),
"Access-Control-Expose-Headers": "x-data",
"Cache-Control": "no-store",
"Pragma": "no-cache",
},
)
except Exception as e:
if isinstance(e, HTTPException):
raise e
print("Error while processing document")
raise HTTPException(status_code=500, detail="Internal server error")
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=APP_PORT)