Woolv7007's picture
Create app.py
25b208e verified
# main.py
import cv2
import numpy as np
import easyocr
import logging
import re
import datetime
import mediapipe as mp
# ----------------- Logging -----------------
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# ----------------- Settings -----------------
class Settings:
threshold_distance = 0.58
max_image_size = (1600, 1600)
min_depth_variation = 0.001
min_texture_score = 0.5
action_threshold = {
"smile": 0.045,
"blink": 0.20,
"head_turn": 15.0
}
target_size = (160, 160)
settings = Settings()
# ----------------- Utils -----------------
def process_image(image_bytes: bytes) -> np.ndarray:
nparr = np.frombuffer(image_bytes, np.uint8)
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
return img
def cosine_similarity(a, b):
a = np.array(a)
b = np.array(b)
a = a / np.linalg.norm(a)
b = b / np.linalg.norm(b)
return float(np.dot(a, b))
easyocr_reader = easyocr.Reader(['ar', 'en'], gpu=False)
def ocr_text(image: np.ndarray, region: tuple = None) -> str:
"""Run OCR on full image or region. Converts Arabic numerals to English."""
if region:
h, w = image.shape[:2]
y1, y2, x1, x2 = region
image = image[int(h*y1):int(h*y2), int(w*x1):int(w*x2)]
result = easyocr_reader.readtext(image)
arabic_to_western = str.maketrans('٠١٢٣٤٥٦٧٨٩', '0123456789')
return " ".join([item[1].translate(arabic_to_western) for item in result])
# ----------------- Factory Number Extraction -----------------
def extract_factory_number(image: np.ndarray) -> str | None:
"""Extract the factory number robustly from the full image using OCR."""
try:
text = ocr_text(image)
logger.info(f"[OCR] Full image text for factory number: {text}")
# OCR confusion correction
confusion_map = str.maketrans({
'$': 'S', '§': 'S', '5': 'S', 's': 'S',
'1': 'I', 'l': 'I', '|': 'I', '!': 'I',
'0': 'O', 'O': '0', 'Q': '0', 'D': '0',
'8': 'B', 'B': '8', '2': 'Z', 'Z': '2',
'6': 'G', 'G': '6', '9': 'G', 'g': 'G',
'4': 'A', 'A': '4', '7': 'T', 'T': '7',
})
norm_text = text.translate(confusion_map).upper()
# Pattern 1: starts with 1–2 letters + 5–9 digits
pattern_letters = r'([A-Z]{1,2}[0-9]{5,9})'
candidates_letters = re.findall(pattern_letters, norm_text)
# Pattern 2: fallback 7–11 alphanumerics with at least 5 digits
pattern_any = r'([A-Z0-9]{7,11})'
candidates_any = [
c for c in re.findall(pattern_any, norm_text)
if sum(x.isdigit() for x in c) >= 5 and sum(x.isalpha() for x in c) >= 1
]
# Prefer pattern with letters
candidate = None
if candidates_letters:
candidate = candidates_letters[-1]
elif candidates_any:
candidate = candidates_any[-1]
# Extra fallback: first char suspicious correction
suspicious_map = {
'|': 'I', '$': 'S', '§': 'S', '5': 'S', '1': 'I',
'l': 'I', '!': 'I', '0': 'O', '8': 'B', '2': 'Z',
'6': 'G', '9': 'G', '4': 'A', '7': 'T'
}
if candidate:
if candidate[0] in suspicious_map:
candidate = suspicious_map[candidate[0]] + candidate[1:]
return candidate.upper()
# Extra fallback: search full text again
fallback_pattern = r'([\|\$§5sl!0182g46947][A-Z0-9]{6,10})'
fallback_candidates = re.findall(fallback_pattern, text)
for fc in fallback_candidates:
fc_up = fc.upper()
if sum(x.isdigit() for x in fc_up) >= 5:
if fc_up[0] in suspicious_map:
fc_up = suspicious_map[fc_up[0]] + fc_up[1:]
return fc_up
return None
except Exception as e:
logger.error(f"Factory number extraction error: {str(e)}")
return None
# ----------------- Gradio Interface -----------------
import gradio as gr
def verify_id_card_gradio(id_img: np.ndarray) -> dict:
"""Gradio-friendly function to extract factory number from ID card image."""
try:
h, w = id_img.shape[:2]
if w > settings.max_image_size[0] or h > settings.max_image_size[1]:
return {
"verified": False,
"factory_number": None,
"message": f"Image too large. Max allowed: {settings.max_image_size[0]}x{settings.max_image_size[1]} pixels."
}
factory_number = extract_factory_number(id_img)
if factory_number:
return {
"verified": True,
"factory_number": factory_number,
"message": "Factory number extracted successfully."
}
else:
return {
"verified": False,
"factory_number": None,
"message": "Factory number not found in the image."
}
except Exception as e:
logger.error(f"Gradio ID card error: {str(e)}")
return {
"verified": False,
"factory_number": None,
"message": "An error occurred while processing the image."
}
iface = gr.Interface(
fn=verify_id_card_gradio,
inputs=gr.Image(type="numpy", label="Upload Egyptian ID Card"),
outputs="json",
title="Egyptian ID Factory Number Extractor",
description="Upload an Egyptian ID card image to extract the factory number. Only returns: match status and extracted factory number.",
allow_flagging="never",
examples=None
)
if __name__ == "__main__":
iface.launch()