# 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()