from flask import Flask, request, jsonify import zxingcpp import cv2 from PIL import Image import numpy as np import re import os import subprocess import tempfile from datetime import datetime import logging # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # --- AAMVA Field Mapping --- AAMVA_FIELD_MAP = { "DCA": "jurisdiction_vehicle_class", "DCB": "jurisdiction_restriction_codes", "DCD": "jurisdiction_endorsement_codes", "DBA": "date_of_expiry", "DCS": "customer_last_name", "DCT": "customer_first_name", "DAC": "customer_first_name", "DAD": "customer_middle_name", "DBD": "date_of_issue", "DBB": "date_of_birth", "DBC": "sex", "DAY": "eye_color", "DAU": "height", "DAG": "address_street", "DAI": "address_city", "DAJ": "address_state", "DAK": "address_postal_code", "DAQ": "customer_id_number", "DCF": "document_discriminator", "DCG": "country_identification", "DDE": "last_name_truncation", "DDF": "first_name_truncation", "DDG": "middle_name_truncation", } # --- Flask App Initialization --- app = Flask(__name__) # --- Helper Functions --- def parse_aamva_data(raw_text): parsed_data = {} normalized_text = raw_text.replace('', '').replace('', '') entries = normalized_text.split('') for entry in entries: entry = entry.strip() if not entry or len(entry) < 3: continue code = entry[:3] value = entry[3:].strip() if not code.isalpha() or len(code) != 3 or code == 'ANS': continue if code in AAMVA_FIELD_MAP: parsed_data[AAMVA_FIELD_MAP[code]] = value else: parsed_data[f"UNKNOWN_{code}"] = value if 'sex' in parsed_data: sex_val = parsed_data['sex'] if sex_val == '1': parsed_data['sex'] = 'Male' elif sex_val == '2': parsed_data['sex'] = 'Female' return {k: v for k, v in parsed_data.items() if v} def preprocess_image_for_barcode(cv_image): if cv_image is None: return None gray = cv2.cvtColor(cv_image, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (5, 5), 0) thresh = cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2) return thresh def read_barcode_from_image(cv_image): if cv_image is None: return None # 1. Try raw image results = zxingcpp.read_barcodes(cv_image) pdf417_results = [r for r in results if r.format == zxingcpp.BarcodeFormat.PDF417] if pdf417_results: return pdf417_results # 2. Try with preprocessing preprocessed = preprocess_image_for_barcode(cv_image) results = zxingcpp.read_barcodes(preprocessed) pdf417_results = [r for r in results if r.format == zxingcpp.BarcodeFormat.PDF417] if pdf417_results: return pdf417_results # 3. Try rotations for angle in [90, 180, 270]: h, w = cv_image.shape[:2] center = (w // 2, h // 2) M = cv2.getRotationMatrix2D(center, angle, 1.0) rotated = cv2.warpAffine(cv_image, M, (w, h)) results = zxingcpp.read_barcodes(rotated) pdf417_results = [r for r in results if r.format == zxingcpp.BarcodeFormat.PDF417] if pdf417_results: return pdf417_results return None def ocr_with_tesseract(image_bytes): """Use Tesseract for OCR - much lighter than EasyOCR""" try: # Save image to temp file with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp: tmp.write(image_bytes) tmp_path = tmp.name # Preprocess with ImageMagick processed_path = tempfile.mktemp(suffix='.png') subprocess.run([ 'convert', tmp_path, '-density', '300', '-normalize', '-sharpen', '0x1', processed_path ], check=True, capture_output=True) # Run Tesseract output_base = tempfile.mktemp() subprocess.run([ 'tesseract', processed_path, output_base, '-l', 'eng', '--oem', '1', '--psm', '6' ], check=True, capture_output=True) # Read results with open(output_base + '.txt', 'r') as f: text = f.read() # Cleanup os.unlink(tmp_path) os.unlink(processed_path) os.unlink(output_base + '.txt') return text except Exception as e: logger.error(f"Tesseract OCR failed: {e}") return None def extract_date_from_text(text): pattern = r'(\d{2})/(\d{2})/(\d{4})' return re.findall(pattern, text) def correct_ocr_errors(text): corrections = {'ROEERT': 'ROBERT', 'EONTE': 'PONTE', '8eaC4': 'BEACH', 'BErNStEIN': 'BERNSTEIN'} for wrong, right in corrections.items(): text = text.replace(wrong, right) return text def parse_dl_front_with_ocr(ocr_text): extracted_data = {} dates = extract_date_from_text(ocr_text) if len(dates) >= 2: try: date1 = datetime.strptime(f"{dates[0][0]}/{dates[0][1]}/{dates[0][2]}", "%m/%d/%Y") date2 = datetime.strptime(f"{dates[1][0]}/{dates[1][1]}/{dates[1][2]}", "%m/%d/%Y") extracted_data['date_of_expiry'] = f"{dates[1][0]}/{dates[1][1]}/{dates[1][2]}" if date2 > date1 else f"{dates[0][0]}/{dates[0][1]}/{dates[0][2]}" except: extracted_data['date_of_expiry'] = f"{dates[0][0]}/{dates[0][1]}/{dates[0][2]}" elif dates: extracted_data['date_of_expiry'] = f"{dates[0][0]}/{dates[0][1]}/{dates[0][2]}" # Find DL number if re.search(r'[A-Z]\d{3}-\d{3}\.\d{2}-\d{3}-\d', ocr_text): match = re.search(r'[A-Z]\d{3}-\d{3}\.\d{2}-\d{3}-\d', ocr_text) extracted_data['dl_number'] = match.group(0) # Find address components lines = ocr_text.split('\n') for line in lines: line = line.strip() if re.match(r'^\d+\s+[A-Z]', line) and 'address_street' not in extracted_data: extracted_data['address_street'] = correct_ocr_errors(line) if re.search(r'[A-Z]{2}\s+\d{5}', line) and 'address_city_state_zip' not in extracted_data: extracted_data['address_city_state_zip'] = correct_ocr_errors(line) return extracted_data # --- Flask Endpoint --- @app.route('/parse-dl', methods=['POST']) def parse_dl_endpoint(): try: if 'id_front_image' not in request.files or 'id_back_image' not in request.files: return jsonify({'error': 'Front and back ID images are required.'}), 400 front_file = request.files['id_front_image'] back_file = request.files['id_back_image'] # Read images into memory front_image_bytes = front_file.read() back_image_bytes = back_file.read() back_img_cv = cv2.imdecode(np.frombuffer(back_image_bytes, np.uint8), cv2.IMREAD_COLOR) logger.info("Attempting barcode scan...") barcode_results = read_barcode_from_image(back_img_cv) if barcode_results: barcode_data = parse_aamva_data(barcode_results[0].text) if barcode_data: logger.info("Successfully extracted barcode data") return jsonify({ 'source': 'barcode', 'data': barcode_data }) # Barcode failed, try OCR with Tesseract logger.info("Barcode failed, attempting OCR with Tesseract...") ocr_text = ocr_with_tesseract(front_image_bytes) if ocr_text: ocr_data = parse_dl_front_with_ocr(ocr_text) ocr_data['raw_text'] = ocr_text logger.info("Successfully extracted OCR data") return jsonify({ 'source': 'ocr', 'data': ocr_data }) logger.warning("Could not extract data from either barcode or OCR") return jsonify({'error': 'Could not extract data from either barcode or OCR.'}), 500 except Exception as e: logger.error(f"Error processing DL: {str(e)}", exc_info=True) return jsonify({'error': str(e)}), 500 if __name__ == '__main__': app.run(host='0.0.0.0', port=5003)
Fatal error: Uncaught Error: Class "App\Services\DlParseService" not found in /var/www/peabodysecure.com/app/routes/KycRoutes.php:16 Stack trace: #0 /var/www/peabodysecure.com/public/index.php(58): App\Routes\KycRoutes::register() #1 {main} thrown in /var/www/peabodysecure.com/app/routes/KycRoutes.php on line 16