Spaces:
Runtime error
Runtime error
# 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() | |