Yao211 commited on
Commit
3c2d0be
·
verified ·
1 Parent(s): c1d2e01

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +741 -0
app.py ADDED
@@ -0,0 +1,741 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import requests
4
+ from PIL import Image
5
+ import json
6
+
7
+ # Configuration
8
+ API_BASE_URL = os.getenv("API_BASE_URL")
9
+
10
+ def face_compare(frame1, frame2, request: gr.Request = None):
11
+ """Face comparison with enhanced result display"""
12
+ try:
13
+ url = f"{API_BASE_URL}"
14
+
15
+ # Prepare files
16
+ files = {}
17
+ if frame1:
18
+ files['image1'] = open(frame1, 'rb')
19
+ if frame2:
20
+ files['image2'] = open(frame2, 'rb')
21
+
22
+ if not files:
23
+ return "<div class='error-message'>Please upload both images</div>"
24
+
25
+ # Make API request
26
+ response = requests.post(url=url, files=files)
27
+ result = response.json()
28
+
29
+ # Close files
30
+ for file in files.values():
31
+ file.close()
32
+
33
+ # Enhanced result processing
34
+ return format_face_comparison_result(result, frame1, frame2)
35
+
36
+ except Exception as e:
37
+ return f"<div class='error-message'>Error processing request: {str(e)}</div>"
38
+
39
+ def format_face_comparison_result(result, img1_path, img2_path):
40
+ """Format face comparison results with professional styling"""
41
+
42
+ detections = result.get("detections", [])
43
+ matches = result.get("match", [])
44
+
45
+ # Create result HTML
46
+ html = "<div class='result-content'>"
47
+
48
+ # Detection results - show all detected faces
49
+ if detections:
50
+ for i, detection in enumerate(detections):
51
+ face_image = detection.get("face", "")
52
+ first_face_index = detection.get("firstFaceIndex")
53
+ second_face_index = detection.get("secondFaceIndex")
54
+
55
+ # Matching results in the new table format
56
+ if matches:
57
+ html += """
58
+ <div class="matching-section">
59
+ <div class="matches-table">
60
+ <table>
61
+ <thead>
62
+ <tr>
63
+ <th>First Image Face</th>
64
+ <th>Second Image Face</th>
65
+ <th>Similarity Score</th>
66
+ <th>Result</th>
67
+ </tr>
68
+ </thead>
69
+ <tbody>
70
+ """
71
+
72
+ # Group matches by first image face index for better organization
73
+ match_groups = {}
74
+ for match in matches:
75
+ first_face_index = match.get("firstFaceIndex", "N/A")
76
+ if first_face_index not in match_groups:
77
+ match_groups[first_face_index] = []
78
+ match_groups[first_face_index].append(match)
79
+
80
+ row_number = 1
81
+ for first_face_index in sorted(match_groups.keys()):
82
+ for match in match_groups[first_face_index]:
83
+ first_face_index = match.get("firstFaceIndex", "N/A")
84
+ second_face_index = match.get("secondFaceIndex", "N/A")
85
+ similarity = match.get("similarity", 0)
86
+
87
+ # Get face images for display
88
+ first_face_img = ""
89
+ second_face_img = ""
90
+
91
+ for detection in detections:
92
+ if detection.get("firstFaceIndex") == first_face_index:
93
+ first_face_img = detection.get("face", "")
94
+ if detection.get("secondFaceIndex") == second_face_index:
95
+ second_face_img = detection.get("face", "")
96
+
97
+ # Determine result and color
98
+ if similarity >= 0.6: # Threshold for same person
99
+ result_text = "same person"
100
+ result_class = "result-same"
101
+ else:
102
+ result_text = "different person"
103
+ result_class = "result-different"
104
+
105
+ first_face_display = f"<img src='data:image/png;base64,{first_face_img}' class='table-face-thumbnail' />" if first_face_img else f"Face {first_face_index}"
106
+ second_face_display = f"<img src='data:image/png;base64,{second_face_img}' class='table-face-thumbnail' />" if second_face_img else f"Face {second_face_index}"
107
+
108
+ html += f"""
109
+ <tr>
110
+ <td class="face-cell">
111
+ <div class="face-display">
112
+ {first_face_display}
113
+ <div class="face-label">Face {first_face_index}</div>
114
+ </div>
115
+ </td>
116
+ <td class="face-cell">
117
+ <div class="face-display">
118
+ {second_face_display}
119
+ <div class="face-label">Face {second_face_index}</div>
120
+ </div>
121
+ </td>
122
+ <td class="similarity-score">{similarity:.4f}</td>
123
+ <td><span class="result-text {result_class}">{result_text}</span></td>
124
+ </tr>
125
+ """
126
+ row_number += 1
127
+
128
+ html += """
129
+ </tbody>
130
+ </table>
131
+ </div>
132
+ </div>
133
+ """
134
+ else:
135
+ html += "<div class='no-results'>No face matches found.</div>"
136
+
137
+ html += "</div>"
138
+ return html
139
+
140
+ def create_footer():
141
+ """Create simple footer"""
142
+ return """
143
+ <div class="footer">
144
+ <p>© 2024 MiniAiLive. All rights reserved.</p>
145
+ <p>
146
+ <a href="https://www.miniai.live" target="_blank">Website</a> |
147
+ <a href="mailto:info@miniai.live">Contact Us</a> |
148
+ <a href="https://www.miniai.live/privacy-policy" target="_blank">Privacy Policy</a>
149
+ </p>
150
+ </div>
151
+ """
152
+
153
+ def get_custom_css():
154
+ """Return simplified CSS styling that works for both light and dark themes"""
155
+ return """
156
+ footer {visibility: hidden}
157
+
158
+ /* Full width container */
159
+ .gradio-container {
160
+ max-width: 95% !important;
161
+ width: 95% !important;
162
+ margin: 0 auto !important;
163
+ }
164
+
165
+ /* Center everything */
166
+ .container {
167
+ display: flex;
168
+ flex-direction: column;
169
+ align-items: center;
170
+ justify-content: center;
171
+ width: 100%;
172
+ }
173
+
174
+ /* Header styling - logo and text in same line */
175
+ .company-header {
176
+ background: var(--background-fill-primary);
177
+ padding: 20px;
178
+ margin-bottom: 20px;
179
+ text-align: center;
180
+ width: 100%;
181
+ display: flex;
182
+ align-items: center;
183
+ justify-content: center;
184
+ gap: 20px;
185
+ flex-wrap: wrap;
186
+ }
187
+
188
+ .header-logo {
189
+ flex-shrink: 0;
190
+ }
191
+
192
+ .header-logo img {
193
+ width: 120px;
194
+ height: auto;
195
+ }
196
+
197
+ .header-text {
198
+ text-align: center;
199
+ }
200
+
201
+ .header-text h1 {
202
+ font-size: 2em;
203
+ margin-bottom: 8px;
204
+ font-weight: 600;
205
+ margin-top: 0;
206
+ color: var(--body-text-color);
207
+ }
208
+
209
+ .header-text p {
210
+ font-size: 1.1em;
211
+ margin-bottom: 0;
212
+ color: var(--body-text-color);
213
+ opacity: 0.8;
214
+ }
215
+
216
+ /* About section layout */
217
+ .about-section {
218
+ width: 100%;
219
+ margin-bottom: 20px;
220
+ }
221
+
222
+ .about-content {
223
+ display: flex;
224
+ gap: 20px;
225
+ width: 100%;
226
+ }
227
+
228
+ .features-section {
229
+ flex: 0.6;
230
+ padding: 20px;
231
+ background: var(--background-fill-secondary);
232
+ border-radius: 8px;
233
+ }
234
+
235
+ .demo-items-section {
236
+ flex: 0.4;
237
+ padding: 20px;
238
+ background: var(--background-fill-secondary);
239
+ border-radius: 8px;
240
+ }
241
+
242
+ .demo-items-grid {
243
+ display: grid;
244
+ grid-template-columns: repeat(2, 1fr);
245
+ gap: 10px;
246
+ }
247
+
248
+ .demo-item {
249
+ background: var(--background-fill-primary);
250
+ padding: 15px;
251
+ border-radius: 6px;
252
+ text-align: center;
253
+ transition: background-color 0.2s ease;
254
+ display: flex;
255
+ flex-direction: column;
256
+ align-items: center;
257
+ justify-content: center;
258
+ text-decoration: none;
259
+ color: var(--body-text-color);
260
+ }
261
+
262
+ .demo-item:hover {
263
+ background: var(--button-secondary-background-fill-hover);
264
+ }
265
+
266
+ .demo-item img {
267
+ height: 25px;
268
+ margin-bottom: 8px;
269
+ }
270
+
271
+ .demo-item span {
272
+ font-size: 0.85em;
273
+ font-weight: 500;
274
+ }
275
+
276
+ /* Main content layout */
277
+ .main-content-row {
278
+ display: flex;
279
+ gap: 20px;
280
+ width: 100%;
281
+ margin-bottom: 20px;
282
+ }
283
+
284
+ .upload-section {
285
+ flex: 2;
286
+ display: flex;
287
+ flex-direction: column;
288
+ gap: 15px;
289
+ }
290
+
291
+ .result-section {
292
+ flex: 1.2;
293
+ }
294
+
295
+ .upload-images-row {
296
+ display: flex;
297
+ gap: 15px;
298
+ width: 100%;
299
+ }
300
+
301
+ .upload-image-col {
302
+ flex: 1;
303
+ }
304
+
305
+ /* Button styling */
306
+ .button-primary {
307
+ background: var(--button-primary-background-fill) !important;
308
+ border: none !important;
309
+ padding: 12px 24px !important;
310
+ font-size: 16px !important;
311
+ font-weight: 600 !important;
312
+ color: var(--button-primary-text-color) !important;
313
+ border-radius: 6px !important;
314
+ cursor: pointer !important;
315
+ transition: background-color 0.2s ease !important;
316
+ margin: 15px 0 !important;
317
+ width: 100% !important;
318
+ }
319
+
320
+ .button-primary:hover {
321
+ background: var(--button-primary-background-fill-hover) !important;
322
+ }
323
+
324
+ /* Result container styling */
325
+ .result-container {
326
+ background: var(--background-fill-primary);
327
+ padding: 4px;
328
+ border-radius: 8px;
329
+ margin-top: 0;
330
+ width: 100%;
331
+ text-align: center;
332
+ height: fit-content;
333
+ }
334
+
335
+ .result-content {
336
+ width: 100%;
337
+ }
338
+
339
+ /* Detection cards */
340
+ .detections-grid {
341
+ display: grid;
342
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
343
+ gap: 12px;
344
+ margin-top: 15px;
345
+ justify-content: center;
346
+ }
347
+
348
+ .detection-card {
349
+ background: var(--background-fill-secondary);
350
+ padding: 12px;
351
+ border-radius: 6px;
352
+ text-align: center;
353
+ display: flex;
354
+ flex-direction: column;
355
+ align-items: center;
356
+ }
357
+
358
+ .face-thumbnail {
359
+ width: 50px;
360
+ height: 50px;
361
+ border-radius: 50%;
362
+ object-fit: cover;
363
+ margin-bottom: 8px;
364
+ }
365
+
366
+ .no-face {
367
+ width: 50px;
368
+ height: 50px;
369
+ background: var(--background-fill-primary);
370
+ border-radius: 50%;
371
+ display: flex;
372
+ align-items: center;
373
+ justify-content: center;
374
+ margin: 0 auto 8px;
375
+ color: var(--body-text-color);
376
+ font-size: 0.7em;
377
+ opacity: 0.7;
378
+ }
379
+
380
+ .face-source {
381
+ font-size: 0.8em;
382
+ color: var(--body-text-color);
383
+ opacity: 0.8;
384
+ margin-top: 5px;
385
+ }
386
+
387
+ /* Matching table - NEW STYLING */
388
+ .matches-table {
389
+ display: flex;
390
+ justify-content: center;
391
+ width: 100%;
392
+ overflow-x: auto;
393
+ }
394
+
395
+ .matches-table table {
396
+ width: 100%;
397
+ border-collapse: collapse;
398
+ margin-top: 4px;
399
+ font-size: 0.9em;
400
+ min-width: 500px;
401
+ }
402
+
403
+ .matches-table th {
404
+ background: var(--background-fill-secondary);
405
+ color: var(--body-text-color);
406
+ padding: 4px 2px;
407
+ text-align: center;
408
+ font-size: 0.85em;
409
+ font-weight: 600;
410
+ border-bottom: 2px solid var(--border-color-primary);
411
+ }
412
+
413
+ .matches-table td {
414
+ padding: 4px 2px;
415
+ border-bottom: 1px solid var(--border-color-primary);
416
+ text-align: center;
417
+ font-size: 0.8em;
418
+ color: var(--body-text-color);
419
+ }
420
+
421
+ .row-number {
422
+ font-weight: 600;
423
+ color: var(--body-text-color);
424
+ }
425
+
426
+ .face-cell {
427
+ vertical-align: middle;
428
+ }
429
+
430
+ .face-display {
431
+ display: flex;
432
+ flex-direction: column;
433
+ align-items: center;
434
+ gap: 1px;
435
+ }
436
+
437
+ .table-face-thumbnail {
438
+ width: 60px;
439
+ height: 60px;
440
+ border-radius: 50%;
441
+ object-fit: cover;
442
+ border: 2px solid var(--border-color-primary);
443
+ }
444
+
445
+ .face-label {
446
+ font-size: 0.75em;
447
+ color: var(--body-text-color);
448
+ opacity: 1;
449
+ }
450
+
451
+ .similarity-score {
452
+ font-weight: 600;
453
+ color: var(--body-text-color);
454
+ }
455
+
456
+ .result-text {
457
+ padding: 4px 2px;
458
+ border-radius: 10px;
459
+ font-size: 1.25em;
460
+ font-weight: 600;
461
+ text-transform: capitalize;
462
+ }
463
+
464
+ .result-same {
465
+ background: #d4edda;
466
+ color: #155724;
467
+ }
468
+
469
+ .result-different {
470
+ background: #f8d7da;
471
+ color: #721c24;
472
+ }
473
+
474
+ .no-results {
475
+ text-align: center;
476
+ padding: 30px;
477
+ color: var(--body-text-color);
478
+ opacity: 0.7;
479
+ font-style: italic;
480
+ }
481
+
482
+ /* Error messages */
483
+ .error-message {
484
+ background: var(--background-fill-secondary);
485
+ color: var(--body-text-color);
486
+ padding: 15px;
487
+ border-radius: 6px;
488
+ text-align: center;
489
+ width: 100%;
490
+ opacity: 0.9;
491
+ }
492
+
493
+ /* Input images styling */
494
+ .gradio-image {
495
+ display: flex !important;
496
+ justify-content: center !important;
497
+ align-items: center !important;
498
+ height: 350px !important;
499
+ }
500
+
501
+ .gradio-image .wrap {
502
+ display: flex !important;
503
+ justify-content: center !important;
504
+ align-items: center !important;
505
+ width: 100% !important;
506
+ height: 100% !important;
507
+ }
508
+
509
+ /* Examples styling */
510
+ .examples-container {
511
+ background: var(--background-fill-secondary);
512
+ padding: 12px;
513
+ border-radius: 6px;
514
+ margin-top: 12px;
515
+ width: 100%;
516
+ text-align: center;
517
+ }
518
+
519
+ /* Footer */
520
+ .footer {
521
+ text-align: center;
522
+ margin-top: 20px;
523
+ padding: 15px;
524
+ border-radius: 6px;
525
+ background: var(--background-fill-secondary);
526
+ }
527
+
528
+ .footer p {
529
+ margin: 5px 0;
530
+ color: var(--body-text-color);
531
+ }
532
+
533
+ .footer a {
534
+ color: var(--body-text-color);
535
+ text-decoration: none;
536
+ opacity: 0.8;
537
+ }
538
+
539
+ .footer a:hover {
540
+ opacity: 1;
541
+ text-decoration: underline;
542
+ }
543
+
544
+ /* Section titles */
545
+ .section-title {
546
+ margin-bottom: 12px !important;
547
+ text-align: center;
548
+ color: var(--body-text-color);
549
+ font-weight: 600;
550
+ }
551
+
552
+ /* Ensure proper text visibility in dark mode */
553
+ .gradio-markdown {
554
+ color: var(--body-text-color) !important;
555
+ }
556
+
557
+ .gradio-markdown h3 {
558
+ color: var(--body-text-color) !important;
559
+ }
560
+
561
+ .gradio-markdown p {
562
+ color: var(--body-text-color) !important;
563
+ }
564
+
565
+ .gradio-markdown li {
566
+ color: var(--body-text-color) !important;
567
+ }
568
+ """
569
+
570
+ # Create Gradio interface
571
+ with gr.Blocks(
572
+ title="MiniAiLive - Face Recognition WebAPI Playground",
573
+ theme=gr.themes.Glass(),
574
+ css=get_custom_css()
575
+ ) as demo:
576
+
577
+ with gr.Column(elem_classes="container"):
578
+ # Header Section - Logo and text in same line, centered
579
+ gr.HTML("""
580
+ <div class="company-header">
581
+ <div class="header-logo">
582
+ <img src="https://miniai.live/wp-content/uploads/2024/02/logo_name-1-768x426-1.png" alt="MiniAiLive Logo">
583
+ </div>
584
+ <div class="header-text">
585
+ <h1>MiniAiLive Face Recognition WebAPI Playground</h1>
586
+ <p>Experience our NIST FRVT Top Ranked 1:1 & 1:N Face Matching Technology</p>
587
+ </div>
588
+ </div>
589
+ """)
590
+
591
+ # About Section with Features and Demo Items
592
+ with gr.Column(elem_classes="about-section"):
593
+ with gr.Row(elem_classes="about-content"):
594
+ # Features Section (60%)
595
+ with gr.Column(scale=0.6, elem_classes="features-section"):
596
+ gr.Markdown("""
597
+ **MiniAiLive** is a leading provider of cutting-edge face recognition and liveness detection solutions.
598
+ Our technology is **NIST FRVT Top Ranked** for Face Recognition.
599
+
600
+ ### 🏆 Key Features:
601
+ - **NIST FRVT Top Ranked** Top Accuracy in both 1:1 & 1:N Face Matching Technology
602
+ - **Real-time** Face Matching & Verification
603
+ - **On-Premise** Offers complete data control and privacy
604
+ - **On-Device, Offline** Runs on machine. No internet connection is required.
605
+ - **Cross-Platform Support** Supports Windows, Linux, Android & iOS
606
+ - **Free Service** Completely free to use, Free technical support & update
607
+
608
+ [Visit our website](https://miniai.live) to explore our complete suite of AI solutions!
609
+ """)
610
+
611
+ # Demo Items Section (40%)
612
+ with gr.Column(scale=0.4, elem_classes="demo-items-section"):
613
+ with gr.Column(elem_classes="demo-items-grid"):
614
+ gr.HTML("""
615
+ <a href="https://github.com/MiniAiLive" target="_blank" class="demo-item">
616
+ <img src="https://miniai.live/wp-content/uploads/2024/10/new_git-1-300x67.png" alt="GitHub">
617
+ <span>GitHub</span>
618
+ </a>
619
+ """)
620
+
621
+ gr.HTML("""
622
+ <a href="https://huggingface.co/MiniAiLive" target="_blank" class="demo-item">
623
+ <img src="https://miniai.live/wp-content/uploads/2024/10/new_hugging-1-300x67.png" alt="HuggingFace">
624
+ <span>HuggingFace</span>
625
+ </a>
626
+ """)
627
+
628
+ gr.HTML("""
629
+ <a href="https://demo.miniai.live" target="_blank" class="demo-item">
630
+ <img src="https://miniai.live/wp-content/uploads/2024/10/new_gradio-300x67.png" alt="Gradio">
631
+ <span>Live Demo</span>
632
+ </a>
633
+ """)
634
+
635
+ gr.HTML("""
636
+ <a href="https://docs.miniai.live/" target="_blank" class="demo-item">
637
+ <img src="https://miniai.live/wp-content/uploads/2024/10/a-300x70.png" alt="Documentation">
638
+ <span>Documentation</span>
639
+ </a>
640
+ """)
641
+
642
+ gr.HTML("""
643
+ <a href="https://www.youtube.com/@miniailive" target="_blank" class="demo-item">
644
+ <img src="https://miniai.live/wp-content/uploads/2024/10/Untitled-1-300x70.png" alt="YouTube">
645
+ <span>YouTube</span>
646
+ </a>
647
+ """)
648
+
649
+ gr.HTML("""
650
+ <a href="https://play.google.com/store/apps/dev?id=5831076207730531667" target="_blank" class="demo-item">
651
+ <img src="https://miniai.live/wp-content/uploads/2024/10/googleplay-300x62.png" alt="Google Play">
652
+ <span>Google Play</span>
653
+ </a>
654
+ """)
655
+
656
+ # Main Content - Upload and Results
657
+ with gr.Row(elem_classes="main-content-row"):
658
+ # Upload Section
659
+ with gr.Column(scale=0.6, elem_classes="upload-section"):
660
+ gr.Markdown("### 1. Upload Images for Comparison", elem_classes="section-title")
661
+
662
+ with gr.Row(elem_classes="upload-images-row"):
663
+ # First Image
664
+ with gr.Column(scale=1, elem_classes="upload-image-col"):
665
+ im_match_in1 = gr.Image(
666
+ type='filepath',
667
+ height=350,
668
+ label="First Image",
669
+ show_download_button=False
670
+ )
671
+
672
+ # Second Image
673
+ with gr.Column(scale=1, elem_classes="upload-image-col"):
674
+ im_match_in2 = gr.Image(
675
+ type='filepath',
676
+ height=350,
677
+ label="Second Image",
678
+ show_download_button=False
679
+ )
680
+
681
+ # Examples and Button
682
+ with gr.Accordion("Try Example Images", open=True):
683
+ with gr.Row():
684
+ gr.Examples(
685
+ examples=[
686
+ "assets/1.jpg",
687
+ "assets/2.jpg",
688
+ "assets/3.jpg",
689
+ "assets/4.jpg",
690
+ ],
691
+ inputs=im_match_in1,
692
+ label="First Image Examples"
693
+ )
694
+ gr.Examples(
695
+ examples=[
696
+ "assets/1-1.jpg",
697
+ "assets/2-1.jpg",
698
+ "assets/3-1.jpg",
699
+ "assets/4-1.jpg",
700
+ ],
701
+ inputs=im_match_in2,
702
+ label="Second Image Examples"
703
+ )
704
+
705
+ btn_f_match = gr.Button(
706
+ "Compare Faces 🚀",
707
+ variant='primary',
708
+ elem_classes="button-primary"
709
+ )
710
+
711
+ # Results Section
712
+ with gr.Column(scale=0.4, elem_classes="result-section"):
713
+ gr.Markdown("### 2. Comparison Results", elem_classes="section-title")
714
+ txt_compare_out = gr.HTML(
715
+ value="<div style='text-align: center; padding: 40px;'>Results will appear here after comparison</div>"
716
+ )
717
+
718
+ # Connect the function
719
+ btn_f_match.click(
720
+ face_compare,
721
+ inputs=[im_match_in1, im_match_in2],
722
+ outputs=txt_compare_out
723
+ )
724
+
725
+ # Visitor Counter
726
+ gr.HTML("""
727
+ <a href="https://visitorbadge.io/status?path=https%3A%2F%2Fhuggingface.co%2Fspaces%2FMiniAiLive%2FFaceRecognition-LivenessDetection-Demo">
728
+ <img src="https://api.visitorbadge.io/api/combined?path=https%3A%2F%2Fhuggingface.co%2Fspaces%2FMiniAiLive%2FFaceRecognition-LivenessDetection-Demo&label=VISITORS&labelColor=%2337d67a&countColor=%23ff8a65&style=plastic&labelStyle=none" />
729
+ </a>
730
+ """)
731
+
732
+ # Footer
733
+ gr.HTML(create_footer())
734
+
735
+ if __name__ == "__main__":
736
+ demo.launch(
737
+ share=False,
738
+ show_error=True,
739
+ server_name="0.0.0.0",
740
+ server_port=7860
741
+ )