import os import sys import tempfile from io import BytesIO import streamlit as st import pandas as pd from datetime import datetime # Ensure project root is in python path sys.path.append(os.path.dirname(os.path.abspath(__file__))) from src.extractor import PassportExtractor from src.formats import format_fly_baghdad, format_fly_dubai, format_iraqi # Page Config st.set_page_config(page_title="Passport OCR Extractor", page_icon="🛂", layout="wide") # Custom CSS for UI polish st.markdown(""" """, unsafe_allow_html=True) # Initialize Extractor @st.cache_resource def get_extractor(): return PassportExtractor(use_gpu=True) EXTRACTOR = get_extractor() MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024 ALLOWED_FILE_TYPES = ["png", "jpg", "jpeg", "pdf", "avif", "webp", "bmp", "tiff"] def _safe_reason(problem): return ( problem.get("reason") or problem.get("issue") or problem.get("message") or problem.get("error") or "" ) def _format_data(good_results, airline): if airline == "Fly Dubai": return format_fly_dubai(good_results) if airline == "Iraqi": return format_iraqi(good_results) if airline == "Fly Baghdad": return format_fly_baghdad(good_results) return pd.DataFrame(good_results) def _placeholder_rows(airline, problematic_files, good_results): rows = [] for problem in problematic_files: if airline == "Fly Dubai": rows.append({ "TYPE": "Adult", "TITLE": "MR", "FIRST NAME": "•••", "LAST NAME": "•••", "DOB (DDMMMYY)": "•••", "GENDER": "Male" }) elif airline == "Iraqi": rows.append({ "Last Name": "•••", "First Name and Middle Name": "•••", "Title": "MR", "PTC": "ADT", "Gender": "M", "Date of Birth": "•••", "Passport Last Name": "•••", "Passport First Name": "•••", "Passport Middle Name": "", "Passport Number": "•••", "Passport Nationality": "•••", "Passport Issue Country": "•••", "Passport Expiry Date": "•••", "Visa Number": "", "Visa Type": "", "Visa Issue Date": "", "Place of Birth": "", "Visa Place of Issue": "", "Visa Country of Application": "", "Address Type": "", "Address Country": "", "Address Details": "", "Address City": "", "Address State": "", "Address Zip Code": "" }) elif airline == "Fly Baghdad": rows.append({ "Sequence": len(good_results) + len(rows) + 1, "Traveling With": "", "Pax Type": "ADT", "Title": "MR", "First Name": "•••", "Last Name": "•••", "Gender": "MALE", "DOB (dd/mm/yyyy)": "•••", "Nationality": "•••", "Passport Number": "•••", "Passport Expiry (dd/mm/yyyy)": "•••", "Passport Issued Country": "•••" }) else: rows.append({ "source_file": problem["file_name"], "surname": "•••", "given_names": "•••", "passport_number": "•••", "nationality": "•••", "date_of_birth": "•••", "sex": "•••", "expiration_date": "•••", "personal_number": "•••", "mrz_found": False }) return rows def main(): st.title("🛂 Passport OCR Extractor") st.markdown("Extract data from passport MRZ codes into structured tables.") with st.sidebar: st.header("Settings") st.info("💡 Optimized for performance - GPU enabled") airline = st.selectbox( "Choose Airline Format", options=["Default", "Fly Dubai", "Iraqi", "Fly Baghdad"], index=0 ) export_all_files = st.checkbox( "Include all files in export", value=False, help="When checked, includes files with missing/invalid data (shows ••• for missing fields)" ) uploaded_files = st.file_uploader( "Upload Passport Files", type=ALLOWED_FILE_TYPES, accept_multiple_files=True ) if st.button("Extract Data") and uploaded_files: all_results = [] problematic_files = [] # UI placeholder for results - cleared on each run status_container = st.empty() table_container = st.empty() download_container = st.empty() failed_container = st.expander("📋 Failed passport details", expanded=False) with st.status("Processing files...", expanded=True) as status: total_files = len(uploaded_files) for i, uploaded_file in enumerate(uploaded_files): file_name = uploaded_file.name ext = os.path.splitext(file_name)[1].lower() status.update(label=f"Processing {i+1}/{total_files}: {file_name}") if uploaded_file.size > MAX_FILE_SIZE_BYTES: problematic_files.append({ "file_name": file_name, "reason": f"File too large. Max 10 MB allowed." }) continue # Save temp file for processing with tempfile.NamedTemporaryFile(delete=False, suffix=ext) as tmp_file: tmp_file.write(uploaded_file.getvalue()) tmp_path = tmp_file.name file_processed = False results = [] try: if ext == ".pdf": results = EXTRACTOR.process_pdf(tmp_path, airline=airline.lower()) or [] file_processed = True else: result = EXTRACTOR.get_data(tmp_path, airline=airline.lower()) if result: results = [result] file_processed = True except Exception as e: problematic_files.append({"file_name": file_name, "reason": f"Processing error: {e}"}) finally: if os.path.exists(tmp_path): os.remove(tmp_path) mrz_found_in_file = False for res in results: res["source_file"] = file_name if res.get("mrz_found", False): mrz_found_in_file = True all_results.extend(results) if not file_processed: problematic_files.append({"file_name": file_name, "reason": "Unsupported format or corrupted file"}) elif not results: problematic_files.append({"file_name": file_name, "reason": "No passport data detected"}) elif not mrz_found_in_file: problematic_files.append({"file_name": file_name, "reason": "MRZ data not found"}) status.update(label="Extraction Complete!", state="complete", expanded=False) # Filtering logic good_results = [] for result in all_results: is_problematic = any(p["file_name"] == result.get("source_file") for p in problematic_files) if not is_problematic and result.get("mrz_found", False): good_results.append(result) successful_extractions = len(good_results) failed_count = total_files - len(set(r.get("source_file", "") for r in good_results)) if failed_count > 0: status_container.warning(f"⚠️ {successful_extractions} passport(s) were successfully extracted from {total_files} file(s).") else: status_container.success(f"✅ All {successful_extractions} passport(s) were successfully imported.") if problematic_files: with failed_container: for problem in problematic_files: st.write(f"- **{problem['file_name']}**: {_safe_reason(problem)}") if not good_results and not (export_all_files and problematic_files): st.error("⚠️ No data could be extracted. Please check the files.") return # Prepare final DataFrame df = _format_data(good_results, airline) if export_all_files and problematic_files: placeholders = _placeholder_rows(airline, problematic_files, good_results) if placeholders: placeholder_df = pd.DataFrame(placeholders) df = placeholder_df if df.empty else pd.concat([df, placeholder_df], ignore_index=True) # Display results with pagination table_container.subheader(f"📊 Extracted Data ({len(df)} records)") # Streamlit's st.dataframe handles pagination automatically in recent versions table_container.dataframe(df, use_container_width=True, hide_index=True) # Download buttons col1, col2 = download_container.columns(2) csv = df.to_csv(index=False).encode('utf-8') col1.download_button( label="Download data as CSV", data=csv, file_name=f"passport_data_{airline.lower().replace(' ', '_')}.csv", mime='text/csv', ) output = BytesIO() with pd.ExcelWriter(output, engine='openpyxl') as writer: df.to_excel(writer, index=False, sheet_name='PassportData') col2.download_button( label="Download data as Excel", data=output.getvalue(), file_name=f"passport_data_{airline.lower().replace(' ', '_')}.xlsx", mime='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ) if __name__ == "__main__": main()