wuhp commited on
Commit
5a539d7
·
verified ·
1 Parent(s): 63b35da

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +46 -96
app.py CHANGED
@@ -1,10 +1,3 @@
1
- # app.py
2
- # --------------------------------------------------------------------
3
- # A Gradio-based face recognition system that mimics most features of
4
- # your Streamlit app: real-time webcam, image tests, configuration,
5
- # database enrollment, searching, user removal, etc.
6
- # --------------------------------------------------------------------
7
-
8
  import os
9
  import sys
10
  import math
@@ -19,7 +12,7 @@ from typing import Optional, Dict, List, Tuple
19
  from dataclasses import dataclass, field
20
  from collections import Counter
21
 
22
- # 3rd-party modules
23
  import gradio as gr
24
  from ultralytics import YOLO
25
  from facenet_pytorch import InceptionResnetV1
@@ -27,7 +20,7 @@ from torchvision import transforms
27
  from deep_sort_realtime.deepsort_tracker import DeepSort
28
 
29
  # --------------------------------------------------------------------
30
- # GLOBALS & CONSTANTS
31
  # --------------------------------------------------------------------
32
  logging.basicConfig(
33
  level=logging.INFO,
@@ -36,20 +29,24 @@ logging.basicConfig(
36
  )
37
  logger = logging.getLogger(__name__)
38
 
 
39
  logging.getLogger('torch').setLevel(logging.ERROR)
40
  logging.getLogger('deep_sort_realtime').setLevel(logging.ERROR)
41
 
 
 
 
42
  DEFAULT_MODEL_URL = "https://github.com/wuhplaptop/face-11-n/blob/main/face2.pt?raw=true"
43
  DEFAULT_DB_PATH = os.path.expanduser("~/.face_pipeline/known_faces.pkl")
44
  MODEL_DIR = os.path.expanduser("~/.face_pipeline/models")
45
  CONFIG_PATH = os.path.expanduser("~/.face_pipeline/config.pkl")
46
 
47
- # If you need blink detection or face mesh, keep or define your landmarks:
48
  LEFT_EYE_IDX = [33, 160, 158, 133, 153, 144]
49
  RIGHT_EYE_IDX = [263, 387, 385, 362, 380, 373]
50
 
51
  # --------------------------------------------------------------------
52
- # PIPELINE CONFIG DATACLASS
53
  # --------------------------------------------------------------------
54
  @dataclass
55
  class PipelineConfig:
@@ -191,11 +188,11 @@ class FaceDatabase:
191
 
192
  def search_by_image(self, query_embedding: np.ndarray, threshold: float = 0.7) -> List[Tuple[str, float]]:
193
  results = []
194
- for label, embeddings in self.embeddings.items():
195
- for db_emb in embeddings:
196
  similarity = FacePipeline.cosine_similarity(query_embedding, db_emb)
197
  if similarity >= threshold:
198
- results.append((label, similarity))
199
  return sorted(results, key=lambda x: x[1], reverse=True)
200
 
201
  # --------------------------------------------------------------------
@@ -241,7 +238,7 @@ class YOLOFaceDetector:
241
  return []
242
 
243
  # --------------------------------------------------------------------
244
- # FACE TRACKER
245
  # --------------------------------------------------------------------
246
  class FaceTracker:
247
  def __init__(self, max_age: int = 30):
@@ -287,7 +284,7 @@ class FaceNetEmbedder:
287
  return None
288
 
289
  # --------------------------------------------------------------------
290
- # MAIN PIPELINE
291
  # --------------------------------------------------------------------
292
  class FacePipeline:
293
  def __init__(self, config: PipelineConfig):
@@ -351,7 +348,6 @@ class FacePipeline:
351
  name = "Spoofed"
352
  similarity = 0.0
353
  else:
354
- # Face recognition
355
  embedding = self.facenet.get_embedding(face_roi)
356
  if embedding is not None and self.config.recognition['enable']:
357
  name, similarity = self.recognize_face(
@@ -371,7 +367,7 @@ class FacePipeline:
371
  label_text = f"{name}"
372
  cv2.rectangle(annotated_frame, (x1, y1), (x2, y2), box_color_bgr, 2)
373
  cv2.putText(
374
- annotated_frame, label_text, (x1, y1 - 10),
375
  cv2.FONT_HERSHEY_SIMPLEX, 0.5, box_color_bgr, 2
376
  )
377
 
@@ -422,10 +418,9 @@ class FacePipeline:
422
  return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-6))
423
 
424
  # --------------------------------------------------------------------
425
- # GLOBAL pipeline instance (we can store it in a lazy loader)
426
  # --------------------------------------------------------------------
427
  pipeline = None
428
-
429
  def load_pipeline() -> FacePipeline:
430
  global pipeline
431
  if pipeline is None:
@@ -436,31 +431,25 @@ def load_pipeline() -> FacePipeline:
436
  return pipeline
437
 
438
  # --------------------------------------------------------------------
439
- # GRADIO HELPER FUNCTIONS
440
  # --------------------------------------------------------------------
441
  def hex_to_bgr(hex_str: str) -> Tuple[int,int,int]:
442
- """
443
- Convert a hex string (#RRGGBB) into a BGR tuple as used in OpenCV.
444
- """
445
  if not hex_str.startswith('#'):
446
  hex_str = f"#{hex_str}"
447
  hex_str = hex_str.lstrip('#')
448
  if len(hex_str) != 6:
449
- return (255, 0, 0) # fallback to something
450
  r = int(hex_str[0:2], 16)
451
  g = int(hex_str[2:4], 16)
452
  b = int(hex_str[4:6], 16)
453
  return (b,g,r)
454
 
455
  def bgr_to_hex(bgr: Tuple[int,int,int]) -> str:
456
- """
457
- Convert a BGR tuple (as stored in pipeline config) to a #RRGGBB hex string.
458
- """
459
  b,g,r = bgr
460
  return f"#{r:02x}{g:02x}{b:02x}"
461
 
462
  # --------------------------------------------------------------------
463
- # TAB: Configuration
464
  # --------------------------------------------------------------------
465
  def update_config(
466
  enable_recognition, enable_antispoof, enable_blink, enable_hand, enable_eyecolor, enable_facemesh,
@@ -473,11 +462,10 @@ def update_config(
473
  mesh_hex, contour_hex, iris_hex,
474
  eye_color_text_hex
475
  ):
476
- # Load pipeline
477
  pl = load_pipeline()
478
  cfg = pl.config
479
 
480
- # Update toggles
481
  cfg.recognition['enable'] = enable_recognition
482
  cfg.anti_spoof['enable'] = enable_antispoof
483
  cfg.blink['enable'] = enable_blink
@@ -489,7 +477,7 @@ def update_config(
489
  cfg.face_mesh_options['contours'] = show_contours
490
  cfg.face_mesh_options['irises'] = show_irises
491
 
492
- # Update thresholds
493
  cfg.detection_conf_thres = detection_conf
494
  cfg.recognition_conf_thres = recognition_thresh
495
  cfg.anti_spoof['lap_thresh'] = antispoof_thresh
@@ -497,8 +485,8 @@ def update_config(
497
  cfg.hand['min_detection_confidence'] = hand_det_conf
498
  cfg.hand['min_tracking_confidence'] = hand_track_conf
499
 
500
- # Update color fields
501
- cfg.bbox_color = hex_to_bgr(bbox_hex)[::-1] # store in (R,G,B)
502
  cfg.spoofed_bbox_color = hex_to_bgr(spoofed_hex)[::-1]
503
  cfg.unknown_bbox_color = hex_to_bgr(unknown_hex)[::-1]
504
  cfg.eye_outline_color = hex_to_bgr(eye_hex)[::-1]
@@ -511,19 +499,13 @@ def update_config(
511
  cfg.iris_color = hex_to_bgr(iris_hex)[::-1]
512
  cfg.eye_color_text_color = hex_to_bgr(eye_color_text_hex)[::-1]
513
 
514
- # Save config
515
  cfg.save(CONFIG_PATH)
516
-
517
  return "Configuration saved successfully!"
518
 
519
  # --------------------------------------------------------------------
520
- # TAB: Database Management
521
  # --------------------------------------------------------------------
522
  def enroll_user(name: str, images: List[np.ndarray]) -> str:
523
- """
524
- Enroll user by name using one or more images. images is a list of
525
- NxMx3 numpy arrays in BGR or RGB depending on Gradio type.
526
- """
527
  pl = load_pipeline()
528
  if not name:
529
  return "Please provide a user name."
@@ -535,13 +517,8 @@ def enroll_user(name: str, images: List[np.ndarray]) -> str:
535
  for img in images:
536
  if img is None:
537
  continue
538
- # Gradio provides images in RGB by default, let's ensure BGR for pipeline
539
- if img.shape[-1] == 3: # RGB
540
- img_bgr = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
541
- else:
542
- img_bgr = img
543
-
544
- # Run YOLO detection on each image
545
  detections = pl.detector.detect(img_bgr, pl.config.detection_conf_thres)
546
  for x1, y1, x2, y2, conf, cls in detections:
547
  face_roi = img_bgr[y1:y2, x1:x2]
@@ -569,9 +546,6 @@ def search_by_name(name: str) -> str:
569
  return f"No embeddings found for user '{name}'."
570
 
571
  def search_by_image(image: np.ndarray) -> str:
572
- """
573
- Search database by face in the uploaded image.
574
- """
575
  pl = load_pipeline()
576
  if image is None:
577
  return "No image uploaded."
@@ -611,27 +585,11 @@ def list_users() -> str:
611
  pl = load_pipeline()
612
  labels = pl.db.list_labels()
613
  if labels:
614
- return f"Enrolled users:\n{', '.join(labels)}"
615
  return "No users enrolled."
616
 
617
  # --------------------------------------------------------------------
618
- # TAB: Real-Time Recognition
619
- # --------------------------------------------------------------------
620
- def process_webcam_frame(frame: np.ndarray) -> Tuple[np.ndarray, str]:
621
- """
622
- Called for every incoming webcam frame. Return annotated frame + textual info.
623
- Gradio delivers frames in RGB.
624
- """
625
- if frame is None:
626
- return None, "No frame."
627
- pl = load_pipeline()
628
- frame_bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
629
- annotated_bgr, detections = pl.process_frame(frame_bgr)
630
- annotated_rgb = cv2.cvtColor(annotated_bgr, cv2.COLOR_BGR2RGB)
631
- return annotated_rgb, str(detections)
632
-
633
- # --------------------------------------------------------------------
634
- # TAB: Image Test
635
  # --------------------------------------------------------------------
636
  def process_test_image(img: np.ndarray) -> Tuple[np.ndarray, str]:
637
  if img is None:
@@ -647,21 +605,10 @@ def process_test_image(img: np.ndarray) -> Tuple[np.ndarray, str]:
647
  # --------------------------------------------------------------------
648
  def build_app():
649
  with gr.Blocks() as demo:
650
- gr.Markdown("# Face Recognition System (Gradio)")
651
-
652
- with gr.Tab("Real-Time Recognition"):
653
- gr.Markdown("Live face recognition from your webcam (roughly 'real-time').")
654
- webcam_input = gr.Video(source="webcam", mirror=True, streaming=True)
655
- webcam_output = gr.Image()
656
- webcam_info = gr.Textbox(label="Detections", interactive=False)
657
- webcam_input.change(
658
- fn=process_webcam_frame,
659
- inputs=webcam_input,
660
- outputs=[webcam_output, webcam_info],
661
- )
662
 
663
  with gr.Tab("Image Test"):
664
- gr.Markdown("Upload a single image for face detection and recognition.")
665
  image_input = gr.Image(type="numpy", label="Upload Image")
666
  image_out = gr.Image()
667
  image_info = gr.Textbox(label="Detections", interactive=False)
@@ -674,8 +621,8 @@ def build_app():
674
  )
675
 
676
  with gr.Tab("Configuration"):
677
- gr.Markdown("Modify the pipeline settings and thresholds here.")
678
-
679
  with gr.Row():
680
  enable_recognition = gr.Checkbox(label="Enable Face Recognition", value=True)
681
  enable_antispoof = gr.Checkbox(label="Enable Anti-Spoof", value=True)
@@ -684,7 +631,7 @@ def build_app():
684
  enable_eyecolor = gr.Checkbox(label="Enable Eye Color Detection", value=False)
685
  enable_facemesh = gr.Checkbox(label="Enable Face Mesh", value=False)
686
 
687
- gr.Markdown("**Face Mesh Options** (only if Face Mesh is enabled):")
688
  with gr.Row():
689
  show_tesselation = gr.Checkbox(label="Show Tesselation", value=False)
690
  show_contours = gr.Checkbox(label="Show Contours", value=False)
@@ -713,7 +660,6 @@ def build_app():
713
  mesh_hex = gr.Textbox(label="Mesh Color", value="#64ff64")
714
  contour_hex = gr.Textbox(label="Contour Color", value="#c8c800")
715
  iris_hex = gr.Textbox(label="Iris Color", value="#ff00ff")
716
-
717
  eye_color_text_hex = gr.Textbox(label="Eye Color Text Color", value="#ffffff")
718
 
719
  save_btn = gr.Button("Save Configuration")
@@ -722,8 +668,8 @@ def build_app():
722
  save_btn.click(
723
  fn=update_config,
724
  inputs=[
725
- enable_recognition, enable_antispoof, enable_blink,
726
- enable_hand, enable_eyecolor, enable_facemesh,
727
  show_tesselation, show_contours, show_irises,
728
  detection_conf, recognition_thresh, antispoof_thresh, blink_thresh,
729
  hand_det_conf, hand_track_conf,
@@ -753,14 +699,17 @@ def build_app():
753
  search_result = gr.Textbox(label="", interactive=False)
754
 
755
  def update_search_visibility(mode):
 
756
  if mode == "Name":
757
  return gr.update(visible=True), gr.update(visible=False)
758
  else:
759
  return gr.update(visible=False), gr.update(visible=True)
760
 
761
- search_mode.change(fn=update_search_visibility,
762
- inputs=[search_mode],
763
- outputs=[search_name_input, search_image_input])
 
 
764
 
765
  def search_user(mode, name, img):
766
  if mode == "Name":
@@ -768,21 +717,21 @@ def build_app():
768
  else:
769
  return search_by_image(img)
770
 
771
- search_btn.click(fn=search_user,
772
- inputs=[search_mode, search_name_input, search_image_input],
773
- outputs=[search_result])
 
 
774
 
775
  with gr.Accordion("User Management Tools", open=False):
776
  list_btn = gr.Button("List Enrolled Users")
777
  list_output = gr.Textbox(label="", interactive=False)
778
  list_btn.click(fn=lambda: list_users(), inputs=[], outputs=[list_output])
779
 
780
- # Reload user list dropdown
781
  def get_user_list():
782
  pl = load_pipeline()
783
  return gr.update(choices=pl.db.list_labels())
784
 
785
- # A dedicated button to refresh the dropdown
786
  refresh_users_btn = gr.Button("Refresh User List")
787
  refresh_users_btn.click(fn=get_user_list, inputs=[], outputs=[search_name_input])
788
 
@@ -800,4 +749,5 @@ def build_app():
800
  # --------------------------------------------------------------------
801
  if __name__ == "__main__":
802
  app = build_app()
 
803
  app.queue().launch(server_name="0.0.0.0", server_port=7860)
 
 
 
 
 
 
 
 
1
  import os
2
  import sys
3
  import math
 
12
  from dataclasses import dataclass, field
13
  from collections import Counter
14
 
15
+ # 3rd-party
16
  import gradio as gr
17
  from ultralytics import YOLO
18
  from facenet_pytorch import InceptionResnetV1
 
20
  from deep_sort_realtime.deepsort_tracker import DeepSort
21
 
22
  # --------------------------------------------------------------------
23
+ # LOGGING
24
  # --------------------------------------------------------------------
25
  logging.basicConfig(
26
  level=logging.INFO,
 
29
  )
30
  logger = logging.getLogger(__name__)
31
 
32
+ # Mute some debug logs from third-party libraries
33
  logging.getLogger('torch').setLevel(logging.ERROR)
34
  logging.getLogger('deep_sort_realtime').setLevel(logging.ERROR)
35
 
36
+ # --------------------------------------------------------------------
37
+ # CONSTANTS
38
+ # --------------------------------------------------------------------
39
  DEFAULT_MODEL_URL = "https://github.com/wuhplaptop/face-11-n/blob/main/face2.pt?raw=true"
40
  DEFAULT_DB_PATH = os.path.expanduser("~/.face_pipeline/known_faces.pkl")
41
  MODEL_DIR = os.path.expanduser("~/.face_pipeline/models")
42
  CONFIG_PATH = os.path.expanduser("~/.face_pipeline/config.pkl")
43
 
44
+ # Example eye indices if you still want blink detection somewhere
45
  LEFT_EYE_IDX = [33, 160, 158, 133, 153, 144]
46
  RIGHT_EYE_IDX = [263, 387, 385, 362, 380, 373]
47
 
48
  # --------------------------------------------------------------------
49
+ # PIPELINE CONFIG
50
  # --------------------------------------------------------------------
51
  @dataclass
52
  class PipelineConfig:
 
188
 
189
  def search_by_image(self, query_embedding: np.ndarray, threshold: float = 0.7) -> List[Tuple[str, float]]:
190
  results = []
191
+ for lbl, embs in self.embeddings.items():
192
+ for db_emb in embs:
193
  similarity = FacePipeline.cosine_similarity(query_embedding, db_emb)
194
  if similarity >= threshold:
195
+ results.append((lbl, similarity))
196
  return sorted(results, key=lambda x: x[1], reverse=True)
197
 
198
  # --------------------------------------------------------------------
 
238
  return []
239
 
240
  # --------------------------------------------------------------------
241
+ # FACE TRACKER (Used if you want tracking across frames - optional)
242
  # --------------------------------------------------------------------
243
  class FaceTracker:
244
  def __init__(self, max_age: int = 30):
 
284
  return None
285
 
286
  # --------------------------------------------------------------------
287
+ # FACE PIPELINE
288
  # --------------------------------------------------------------------
289
  class FacePipeline:
290
  def __init__(self, config: PipelineConfig):
 
348
  name = "Spoofed"
349
  similarity = 0.0
350
  else:
 
351
  embedding = self.facenet.get_embedding(face_roi)
352
  if embedding is not None and self.config.recognition['enable']:
353
  name, similarity = self.recognize_face(
 
367
  label_text = f"{name}"
368
  cv2.rectangle(annotated_frame, (x1, y1), (x2, y2), box_color_bgr, 2)
369
  cv2.putText(
370
+ annotated_frame, label_text, (x1, y1 - 10),
371
  cv2.FONT_HERSHEY_SIMPLEX, 0.5, box_color_bgr, 2
372
  )
373
 
 
418
  return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-6))
419
 
420
  # --------------------------------------------------------------------
421
+ # GLOBAL LOAD PIPELINE
422
  # --------------------------------------------------------------------
423
  pipeline = None
 
424
  def load_pipeline() -> FacePipeline:
425
  global pipeline
426
  if pipeline is None:
 
431
  return pipeline
432
 
433
  # --------------------------------------------------------------------
434
+ # GRADIO UTILS
435
  # --------------------------------------------------------------------
436
  def hex_to_bgr(hex_str: str) -> Tuple[int,int,int]:
 
 
 
437
  if not hex_str.startswith('#'):
438
  hex_str = f"#{hex_str}"
439
  hex_str = hex_str.lstrip('#')
440
  if len(hex_str) != 6:
441
+ return (255, 0, 0)
442
  r = int(hex_str[0:2], 16)
443
  g = int(hex_str[2:4], 16)
444
  b = int(hex_str[4:6], 16)
445
  return (b,g,r)
446
 
447
  def bgr_to_hex(bgr: Tuple[int,int,int]) -> str:
 
 
 
448
  b,g,r = bgr
449
  return f"#{r:02x}{g:02x}{b:02x}"
450
 
451
  # --------------------------------------------------------------------
452
+ # TAB: CONFIGURATION
453
  # --------------------------------------------------------------------
454
  def update_config(
455
  enable_recognition, enable_antispoof, enable_blink, enable_hand, enable_eyecolor, enable_facemesh,
 
462
  mesh_hex, contour_hex, iris_hex,
463
  eye_color_text_hex
464
  ):
 
465
  pl = load_pipeline()
466
  cfg = pl.config
467
 
468
+ # Toggles
469
  cfg.recognition['enable'] = enable_recognition
470
  cfg.anti_spoof['enable'] = enable_antispoof
471
  cfg.blink['enable'] = enable_blink
 
477
  cfg.face_mesh_options['contours'] = show_contours
478
  cfg.face_mesh_options['irises'] = show_irises
479
 
480
+ # Thresholds
481
  cfg.detection_conf_thres = detection_conf
482
  cfg.recognition_conf_thres = recognition_thresh
483
  cfg.anti_spoof['lap_thresh'] = antispoof_thresh
 
485
  cfg.hand['min_detection_confidence'] = hand_det_conf
486
  cfg.hand['min_tracking_confidence'] = hand_track_conf
487
 
488
+ # Colors
489
+ cfg.bbox_color = hex_to_bgr(bbox_hex)[::-1]
490
  cfg.spoofed_bbox_color = hex_to_bgr(spoofed_hex)[::-1]
491
  cfg.unknown_bbox_color = hex_to_bgr(unknown_hex)[::-1]
492
  cfg.eye_outline_color = hex_to_bgr(eye_hex)[::-1]
 
499
  cfg.iris_color = hex_to_bgr(iris_hex)[::-1]
500
  cfg.eye_color_text_color = hex_to_bgr(eye_color_text_hex)[::-1]
501
 
 
502
  cfg.save(CONFIG_PATH)
 
503
  return "Configuration saved successfully!"
504
 
505
  # --------------------------------------------------------------------
506
+ # TAB: DATABASE MANAGEMENT
507
  # --------------------------------------------------------------------
508
  def enroll_user(name: str, images: List[np.ndarray]) -> str:
 
 
 
 
509
  pl = load_pipeline()
510
  if not name:
511
  return "Please provide a user name."
 
517
  for img in images:
518
  if img is None:
519
  continue
520
+ # Gradio typically supplies images in RGB
521
+ img_bgr = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
 
 
 
 
 
522
  detections = pl.detector.detect(img_bgr, pl.config.detection_conf_thres)
523
  for x1, y1, x2, y2, conf, cls in detections:
524
  face_roi = img_bgr[y1:y2, x1:x2]
 
546
  return f"No embeddings found for user '{name}'."
547
 
548
  def search_by_image(image: np.ndarray) -> str:
 
 
 
549
  pl = load_pipeline()
550
  if image is None:
551
  return "No image uploaded."
 
585
  pl = load_pipeline()
586
  labels = pl.db.list_labels()
587
  if labels:
588
+ return "Enrolled users:\n" + ", ".join(labels)
589
  return "No users enrolled."
590
 
591
  # --------------------------------------------------------------------
592
+ # TAB: IMAGE-BASED RECOGNITION
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
593
  # --------------------------------------------------------------------
594
  def process_test_image(img: np.ndarray) -> Tuple[np.ndarray, str]:
595
  if img is None:
 
605
  # --------------------------------------------------------------------
606
  def build_app():
607
  with gr.Blocks() as demo:
608
+ gr.Markdown("# Face Recognition System (Image-Only)")
 
 
 
 
 
 
 
 
 
 
 
609
 
610
  with gr.Tab("Image Test"):
611
+ gr.Markdown("Upload a single image for face detection & recognition:")
612
  image_input = gr.Image(type="numpy", label="Upload Image")
613
  image_out = gr.Image()
614
  image_info = gr.Textbox(label="Detections", interactive=False)
 
621
  )
622
 
623
  with gr.Tab("Configuration"):
624
+ gr.Markdown("Modify pipeline settings & thresholds.")
625
+
626
  with gr.Row():
627
  enable_recognition = gr.Checkbox(label="Enable Face Recognition", value=True)
628
  enable_antispoof = gr.Checkbox(label="Enable Anti-Spoof", value=True)
 
631
  enable_eyecolor = gr.Checkbox(label="Enable Eye Color Detection", value=False)
632
  enable_facemesh = gr.Checkbox(label="Enable Face Mesh", value=False)
633
 
634
+ gr.Markdown("**Face Mesh Options**")
635
  with gr.Row():
636
  show_tesselation = gr.Checkbox(label="Show Tesselation", value=False)
637
  show_contours = gr.Checkbox(label="Show Contours", value=False)
 
660
  mesh_hex = gr.Textbox(label="Mesh Color", value="#64ff64")
661
  contour_hex = gr.Textbox(label="Contour Color", value="#c8c800")
662
  iris_hex = gr.Textbox(label="Iris Color", value="#ff00ff")
 
663
  eye_color_text_hex = gr.Textbox(label="Eye Color Text Color", value="#ffffff")
664
 
665
  save_btn = gr.Button("Save Configuration")
 
668
  save_btn.click(
669
  fn=update_config,
670
  inputs=[
671
+ enable_recognition, enable_antispoof, enable_blink, enable_hand,
672
+ enable_eyecolor, enable_facemesh,
673
  show_tesselation, show_contours, show_irises,
674
  detection_conf, recognition_thresh, antispoof_thresh, blink_thresh,
675
  hand_det_conf, hand_track_conf,
 
699
  search_result = gr.Textbox(label="", interactive=False)
700
 
701
  def update_search_visibility(mode):
702
+ # Show name dropdown if "Name", else show image upload
703
  if mode == "Name":
704
  return gr.update(visible=True), gr.update(visible=False)
705
  else:
706
  return gr.update(visible=False), gr.update(visible=True)
707
 
708
+ search_mode.change(
709
+ fn=update_search_visibility,
710
+ inputs=[search_mode],
711
+ outputs=[search_name_input, search_image_input]
712
+ )
713
 
714
  def search_user(mode, name, img):
715
  if mode == "Name":
 
717
  else:
718
  return search_by_image(img)
719
 
720
+ search_btn.click(
721
+ fn=search_user,
722
+ inputs=[search_mode, search_name_input, search_image_input],
723
+ outputs=[search_result]
724
+ )
725
 
726
  with gr.Accordion("User Management Tools", open=False):
727
  list_btn = gr.Button("List Enrolled Users")
728
  list_output = gr.Textbox(label="", interactive=False)
729
  list_btn.click(fn=lambda: list_users(), inputs=[], outputs=[list_output])
730
 
 
731
  def get_user_list():
732
  pl = load_pipeline()
733
  return gr.update(choices=pl.db.list_labels())
734
 
 
735
  refresh_users_btn = gr.Button("Refresh User List")
736
  refresh_users_btn.click(fn=get_user_list, inputs=[], outputs=[search_name_input])
737
 
 
749
  # --------------------------------------------------------------------
750
  if __name__ == "__main__":
751
  app = build_app()
752
+ # queue() is optional if you expect concurrency
753
  app.queue().launch(server_name="0.0.0.0", server_port=7860)