import gradio as gr import os import requests from PIL import Image import json # Configuration API_BASE_URL = os.getenv("API_BASE_URL") def face_compare(frame1, frame2, request: gr.Request = None): """Face comparison with enhanced result display""" try: url = f"{API_BASE_URL}/api/face_match" # Prepare files files = {} if frame1: files['image1'] = open(frame1, 'rb') if frame2: files['image2'] = open(frame2, 'rb') if not files: return "
Please upload both images
" # Make API request response = requests.post(url=url, files=files) result = response.json() # Close files for file in files.values(): file.close() # Enhanced result processing return format_face_comparison_result(result, frame1, frame2) except Exception as e: return f"
Error processing request: {str(e)}
" def format_face_comparison_result(result, img1_path, img2_path): """Format face comparison results with professional styling""" detections = result.get("detections", []) matches = result.get("match", []) # Create result HTML html = "
" # Detection results - show all detected faces if detections: for i, detection in enumerate(detections): face_image = detection.get("face", "") first_face_index = detection.get("firstFaceIndex") second_face_index = detection.get("secondFaceIndex") # Matching results in the new table format if matches: html += """
""" # Group matches by first image face index for better organization match_groups = {} for match in matches: first_face_index = match.get("firstFaceIndex", "N/A") if first_face_index not in match_groups: match_groups[first_face_index] = [] match_groups[first_face_index].append(match) row_number = 1 for first_face_index in sorted(match_groups.keys()): for match in match_groups[first_face_index]: first_face_index = match.get("firstFaceIndex", "N/A") second_face_index = match.get("secondFaceIndex", "N/A") similarity = match.get("similarity", 0) # Get face images for display first_face_img = "" second_face_img = "" for detection in detections: if detection.get("firstFaceIndex") == first_face_index: first_face_img = detection.get("face", "") if detection.get("secondFaceIndex") == second_face_index: second_face_img = detection.get("face", "") # Determine result and color if similarity >= 0.6: # Threshold for same person result_text = "same person" result_class = "result-same" else: result_text = "different person" result_class = "result-different" first_face_display = f"" if first_face_img else f"Face {first_face_index}" second_face_display = f"" if second_face_img else f"Face {second_face_index}" html += f""" """ row_number += 1 html += """
First Image Face Second Image Face Similarity Score Result
{first_face_display}
Face {first_face_index}
{second_face_display}
Face {second_face_index}
{similarity:.4f} {result_text}
""" else: html += "
No face matches found.
" html += "
" return html def create_footer(): """Create simple footer""" return """ """ def get_custom_css(): """Return simplified CSS styling that works for both light and dark themes""" return """ footer {visibility: hidden} /* Full width container */ .gradio-container { max-width: 95% !important; width: 95% !important; margin: 0 auto !important; } /* Center everything */ .container { display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%; } /* Header styling - logo and text in same line */ .company-header { background: var(--background-fill-primary); padding: 20px; margin-bottom: 20px; text-align: center; width: 100%; display: flex; align-items: center; justify-content: center; gap: 20px; flex-wrap: wrap; } .header-logo { flex-shrink: 0; } .header-logo img { width: 120px; height: auto; } .header-text { text-align: center; } .header-text h1 { font-size: 2em; margin-bottom: 8px; font-weight: 600; margin-top: 0; color: var(--body-text-color); } .header-text p { font-size: 1.1em; margin-bottom: 0; color: var(--body-text-color); opacity: 0.8; } /* About section layout */ .about-section { width: 100%; margin-bottom: 20px; } .about-content { display: flex; gap: 20px; width: 100%; } .features-section { flex: 0.6; padding: 20px; background: var(--background-fill-secondary); border-radius: 8px; } .demo-items-section { flex: 0.4; padding: 20px; background: var(--background-fill-secondary); border-radius: 8px; } .demo-items-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; } .demo-item { background: var(--background-fill-primary); padding: 15px; border-radius: 6px; text-align: center; transition: background-color 0.2s ease; display: flex; flex-direction: column; align-items: center; justify-content: center; text-decoration: none; color: var(--body-text-color); } .demo-item:hover { background: var(--button-secondary-background-fill-hover); } .demo-item img { height: 25px; margin-bottom: 8px; } .demo-item span { font-size: 0.85em; font-weight: 500; } /* Main content layout */ .main-content-row { display: flex; gap: 20px; width: 100%; margin-bottom: 20px; } .upload-section { flex: 2; display: flex; flex-direction: column; gap: 15px; } .result-section { flex: 1.2; } .upload-images-row { display: flex; gap: 15px; width: 100%; } .upload-image-col { flex: 1; } /* Button styling */ .button-primary { background: var(--button-primary-background-fill) !important; border: none !important; padding: 12px 24px !important; font-size: 16px !important; font-weight: 600 !important; color: var(--button-primary-text-color) !important; border-radius: 6px !important; cursor: pointer !important; transition: background-color 0.2s ease !important; margin: 15px 0 !important; width: 100% !important; } .button-primary:hover { background: var(--button-primary-background-fill-hover) !important; } /* Result container styling */ .result-container { background: var(--background-fill-primary); padding: 4px; border-radius: 8px; margin-top: 0; width: 100%; text-align: center; height: fit-content; } .result-content { width: 100%; } /* Detection cards */ .detections-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-top: 15px; justify-content: center; } .detection-card { background: var(--background-fill-secondary); padding: 12px; border-radius: 6px; text-align: center; display: flex; flex-direction: column; align-items: center; } .face-thumbnail { width: 50px; height: 50px; border-radius: 50%; object-fit: cover; margin-bottom: 8px; } .no-face { width: 50px; height: 50px; background: var(--background-fill-primary); border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 8px; color: var(--body-text-color); font-size: 0.7em; opacity: 0.7; } .face-source { font-size: 0.8em; color: var(--body-text-color); opacity: 0.8; margin-top: 5px; } /* Matching table - NEW STYLING */ .matches-table { display: flex; justify-content: center; width: 100%; overflow-x: auto; } .matches-table table { width: 100%; border-collapse: collapse; margin-top: 4px; font-size: 0.9em; min-width: 500px; } .matches-table th { background: var(--background-fill-secondary); color: var(--body-text-color); padding: 4px 2px; text-align: center; font-size: 0.85em; font-weight: 600; border-bottom: 2px solid var(--border-color-primary); } .matches-table td { padding: 4px 2px; border-bottom: 1px solid var(--border-color-primary); text-align: center; font-size: 0.8em; color: var(--body-text-color); } .row-number { font-weight: 600; color: var(--body-text-color); } .face-cell { vertical-align: middle; } .face-display { display: flex; flex-direction: column; align-items: center; gap: 1px; } .table-face-thumbnail { width: 60px; height: 60px; border-radius: 50%; object-fit: cover; border: 2px solid var(--border-color-primary); } .face-label { font-size: 0.75em; color: var(--body-text-color); opacity: 1; } .similarity-score { font-weight: 600; color: var(--body-text-color); } .result-text { padding: 4px 2px; border-radius: 10px; font-size: 1.25em; font-weight: 600; text-transform: capitalize; } .result-same { background: #d4edda; color: #155724; } .result-different { background: #f8d7da; color: #721c24; } .no-results { text-align: center; padding: 30px; color: var(--body-text-color); opacity: 0.7; font-style: italic; } /* Error messages */ .error-message { background: var(--background-fill-secondary); color: var(--body-text-color); padding: 15px; border-radius: 6px; text-align: center; width: 100%; opacity: 0.9; } /* Input images styling */ .gradio-image { display: flex !important; justify-content: center !important; align-items: center !important; height: 350px !important; } .gradio-image .wrap { display: flex !important; justify-content: center !important; align-items: center !important; width: 100% !important; height: 100% !important; } /* Examples styling */ .examples-container { background: var(--background-fill-secondary); padding: 12px; border-radius: 6px; margin-top: 12px; width: 100%; text-align: center; } /* Footer */ .footer { text-align: center; margin-top: 20px; padding: 15px; border-radius: 6px; background: var(--background-fill-secondary); } .footer p { margin: 5px 0; color: var(--body-text-color); } .footer a { color: var(--body-text-color); text-decoration: none; opacity: 0.8; } .footer a:hover { opacity: 1; text-decoration: underline; } /* Section titles */ .section-title { margin-bottom: 12px !important; text-align: center; color: var(--body-text-color); font-weight: 600; } /* Ensure proper text visibility in dark mode */ .gradio-markdown { color: var(--body-text-color) !important; } .gradio-markdown h3 { color: var(--body-text-color) !important; } .gradio-markdown p { color: var(--body-text-color) !important; } .gradio-markdown li { color: var(--body-text-color) !important; } """ # Create Gradio interface with gr.Blocks( title="MiniAiLive - Face Recognition WebAPI Playground", theme=gr.themes.Soft(), css=get_custom_css() ) as demo: with gr.Column(elem_classes="container"): # Header Section - Logo and text in same line, centered gr.HTML("""

MiniAiLive Face Recognition WebAPI Playground

Experience our NIST FRVT Top Ranked 1:1 & 1:N Face Matching Technology

""") # About Section with Features and Demo Items with gr.Column(elem_classes="about-section"): with gr.Row(elem_classes="about-content"): # Features Section (60%) with gr.Column(scale=0.6, elem_classes="features-section"): gr.Markdown(""" **MiniAiLive** is a leading provider of cutting-edge face recognition and liveness detection solutions. Our technology is **NIST FRVT Top Ranked** for Face Recognition. ### 🏆 Key Features: - **NIST FRVT Top Ranked** Top Accuracy in both 1:1 & 1:N Face Matching Technology - **Real-time** Face Matching & Verification - **On-Premise** Offers complete data control and privacy - **On-Device, Offline** Runs on machine. No internet connection is required. - **Cross-Platform Support** Supports Windows, Linux, Android & iOS - **Free Service** Completely free to use, Free technical support & update [Visit our website](https://miniai.live) to explore our complete suite of AI solutions! """) # Demo Items Section (40%) with gr.Column(scale=0.4, elem_classes="demo-items-section"): with gr.Column(elem_classes="demo-items-grid"): gr.HTML(""" GitHub GitHub """) gr.HTML(""" HuggingFace HuggingFace """) gr.HTML(""" Gradio Live Demo """) gr.HTML(""" Documentation Documentation """) gr.HTML(""" YouTube YouTube """) gr.HTML(""" Google Play Google Play """) # Main Content - Upload and Results with gr.Row(elem_classes="main-content-row"): # Upload Section with gr.Column(scale=0.6, elem_classes="upload-section"): gr.Markdown("### Upload Images for Comparison", elem_classes="section-title") with gr.Row(elem_classes="upload-images-row"): # First Image with gr.Column(scale=1, elem_classes="upload-image-col"): im_match_in1 = gr.Image( type='filepath', height=350, label="First Image", show_download_button=False ) # Second Image with gr.Column(scale=1, elem_classes="upload-image-col"): im_match_in2 = gr.Image( type='filepath', height=350, label="Second Image", show_download_button=False ) # Examples and Button with gr.Accordion("Try Example Images", open=True): with gr.Row(): gr.Examples( examples=[ "assets/1.jpg", "assets/2.jpg", "assets/3.jpg", "assets/4.jpg", ], inputs=im_match_in1, label="First Image Examples" ) gr.Examples( examples=[ "assets/1-1.jpg", "assets/2-1.jpg", "assets/3-1.jpg", "assets/4-1.jpg", ], inputs=im_match_in2, label="Second Image Examples" ) btn_f_match = gr.Button( "Compare Faces 🚀", variant='primary', elem_classes="button-primary" ) # Results Section with gr.Column(scale=0.4, elem_classes="result-section"): gr.Markdown("### Comparison Results", elem_classes="section-title") txt_compare_out = gr.HTML( value="
Results will appear here after comparison
" ) # Connect the function btn_f_match.click( face_compare, inputs=[im_match_in1, im_match_in2], outputs=txt_compare_out ) # Visitor Counter gr.HTML("""
""") # Footer gr.HTML(create_footer()) if __name__ == "__main__": demo.launch( share=False, show_error=True, server_name="0.0.0.0", server_port=7860 )