Spaces:
Sleeping
Sleeping
File size: 8,280 Bytes
89672fd 890c0e1 89672fd 0439c5e 89672fd 225b038 89672fd 8b9e068 0439c5e 8b9e068 0439c5e 8b9e068 0439c5e d06531d 5ecfc76 0439c5e 89672fd 0439c5e 89672fd 0439c5e 89672fd 0439c5e 89672fd 8b9e068 831ae97 aa67b69 0439c5e aa67b69 6e8ba07 0439c5e 831ae97 0439c5e 812ef70 0439c5e d06531d 0439c5e 8b9e068 0439c5e c3420fb 90ad37b 0439c5e b29659d 0439c5e 89672fd 0439c5e 812ef70 0439c5e ce3d819 812ef70 89672fd 812ef70 8b9e068 812ef70 ce3d819 812ef70 ce3d819 812ef70 86dba64 812ef70 ce3d819 812ef70 ce3d819 812ef70 ce3d819 0439c5e ce3d819 812ef70 ce3d819 812ef70 86dba64 812ef70 b29659d ce3d819 89672fd 0439c5e 55fdc0b 89672fd 0439c5e 4229e0c 89672fd 55fdc0b 89672fd 55fdc0b ef2edf0 b29659d a2270ec ce3d819 a2270ec 0439c5e 8b9e068 812ef70 0439c5e a2270ec 8b9e068 d06531d 4229e0c 225b038 0439c5e 90ad37b 812ef70 90ad37b 812ef70 | 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 202 203 204 205 206 207 208 | import os
import cv2
import numpy as np
import easyocr
import warnings
import ssl
import re
from passporteye import read_mrz
from pdf2image import convert_from_path
from PIL import Image
import string as st
from src.utils import (
clean_string,
clean_mrz_line,
parse_date,
get_country_name,
get_sex,
setup_logger,
clean_name_field
)
from src.fallback_mrz import FallbackMRZ
from config.settings import USE_GPU, OCR_LANGUAGES, TEMP_DIR
warnings.filterwarnings("ignore")
logger = setup_logger(__name__)
# Fix SSL issue (Mac EasyOCR model download fix)
try:
_create_unverified_https_context = ssl._create_unverified_context
except AttributeError:
pass
else:
ssl._create_default_https_context = _create_unverified_https_context
class PassportExtractor:
def __init__(self, use_gpu=USE_GPU, languages=None):
self.languages = languages if languages else OCR_LANGUAGES
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
model_dir = os.path.join(base_dir, "data", "models")
os.makedirs(model_dir, exist_ok=True)
logger.debug(f"Initializing EasyOCR Reader (GPU={use_gpu})...")
# Create custom user network directory to avoid permission issues
user_network_dir = os.path.join(base_dir, "data", "easyocr_user_network")
os.makedirs(user_network_dir, exist_ok=True)
self.reader = easyocr.Reader(
self.languages,
gpu=use_gpu,
model_storage_directory=model_dir,
user_network_directory=user_network_dir,
verbose=False
)
logger.debug("EasyOCR initialized.")
# ---------------------------------------------------
# VISUAL GIVEN NAME EXTRACTION
# ---------------------------------------------------
def extract_given_names_from_visual(self, img_path):
try:
results = self.reader.readtext(img_path, detail=0)
lines = [r.strip() for r in results if r.strip()]
for i, line in enumerate(lines):
upper_line = line.upper()
if "GIVEN" in upper_line and "NAME" in upper_line:
if ":" in line:
candidate = line.split(":")[1].strip()
else:
if i + 1 < len(lines):
candidate = lines[i + 1].strip()
else:
return ""
candidate = re.sub(r'[^A-Za-z\s]', '', candidate)
candidate = re.sub(r'\s+', ' ', candidate).strip()
candidate = re.sub(r'([A-Z]{2,})[K]$', r'\1', candidate)
return candidate.strip()
return ""
except Exception as e:
logger.error(f"Given Names extraction failed: {e}")
return ""
# ---------------------------------------------------
# MRZ EXTRACTION (SMART FALLBACK)
# ---------------------------------------------------
def extract_mrz_easyocr(self, img_path):
"""Find MRZ lines using EasyOCR when Tesseract is missing."""
try:
# Use EasyOCR to read ALL text
allow = st.ascii_uppercase + st.digits + "<"
results = self.reader.readtext(img_path, detail=0, allowlist=allow)
# 1. Clean and filter candidates
candidates = []
for res in results:
# Remove spaces and keep uppercase/digits/<
clean = "".join([c for c in res.upper().replace(" ", "") if c in allow])
if len(clean) >= 30:
candidates.append(clean)
# 2. Look for Line 1 (starts with P<)
line1, line2 = None, None
for i, c in enumerate(candidates):
if c.startswith("P<"):
line1 = c
# Line 2 is almost always the next one or one after
if i + 1 < len(candidates):
line2 = candidates[i+1]
break
# 3. If no P< found, look for any two 44-char lines
if not line1 and len(candidates) >= 2:
for i in range(len(candidates)-1):
if len(candidates[i]) >= 40 and len(candidates[i+1]) >= 40:
line1, line2 = candidates[i], candidates[i+1]
break
return line1, line2
except Exception as e:
logger.error(f"EasyOCR fallback error: {e}")
return None, None
def extract_mrz_from_roi(self, img_path):
try:
# First attempt: PassportEye (If Tesseract works)
try:
mrz = read_mrz(img_path, save_roi=True)
if mrz and getattr(mrz, 'valid_score', 0) > 20:
l1 = clean_mrz_line(getattr(mrz, 'line1', ''))
l2 = clean_mrz_line(getattr(mrz, 'line2', ''))
if l1 and l2:
return l1, l2, mrz
except:
pass
# Second attempt: EasyOCR Manual Search
l1, l2 = self.extract_mrz_easyocr(img_path)
if l1 and l2:
# Standardize to 44 chars for FallbackMRZ
l1_clean = clean_mrz_line(l1)
l2_clean = clean_mrz_line(l2)
return l1_clean, l2_clean, FallbackMRZ(l1_clean, l2_clean)
return None, None, None
except Exception as e:
logger.error(f"MRZ extraction failed: {e}")
return None, None, None
# ---------------------------------------------------
# MAIN DATA FUNCTION
# ---------------------------------------------------
def get_data(self, img_path, airline="iraqi"):
if not os.path.exists(img_path):
return None
line1, line2, mrz = self.extract_mrz_from_roi(img_path)
if mrz is None:
return {
"surname": "•••", "name": "•••", "country": "•••", "nationality": "•••",
"passport_number": "•••", "sex": "•••", "date_of_birth": "•••",
"expiration_date": "•••", "personal_number": "•••",
"mrz_full_string": "", "valid_score": 0, "mrz_found": False
}
data = {
"surname": clean_name_field(getattr(mrz, "surname", "")),
"name": self.extract_given_names_from_visual(img_path) or clean_name_field(getattr(mrz, "names", getattr(mrz, "name", ""))),
"country": get_country_name(getattr(mrz, "country", "")),
"nationality": get_country_name(getattr(mrz, "nationality", "")),
"passport_number": clean_string(getattr(mrz, "number", "")),
"sex": get_sex(getattr(mrz, "sex", "")),
"date_of_birth": parse_date(getattr(mrz, "date_of_birth", ""), airline=airline),
"expiration_date": parse_date(getattr(mrz, "expiration_date", ""), airline=airline),
"mrz_full_string": (line1 or "") + (line2 or ""),
"valid_score": getattr(mrz, "valid_score", 0),
"mrz_found": True,
}
return data
def process_pdf(self, pdf_path, progress_callback=None, airline="iraqi"):
os.makedirs(TEMP_DIR, exist_ok=True)
try:
from pdf2image import pdfinfo_from_path, convert_from_path
info = pdfinfo_from_path(pdf_path)
results = []
for page in range(1, info["Pages"] + 1):
if progress_callback: progress_callback(page / info["Pages"])
imgs = convert_from_path(pdf_path, dpi=150, first_page=page, last_page=page)
if not imgs: continue
tmp = os.path.join(TEMP_DIR, f"tmp_{page}.jpg")
imgs[0].save(tmp, "JPEG", quality=70)
res = self.get_data(tmp, airline=airline)
if res:
res["page_number"] = page
results.append(res)
if os.path.exists(tmp): os.remove(tmp)
return results
except Exception as e:
logger.error(f"PDF failed: {e}"); return []
|