ab2207 commited on
Commit
28f65c2
·
verified ·
1 Parent(s): c642d5f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +167 -87
app.py CHANGED
@@ -4,23 +4,45 @@ import atexit
4
  import tempfile
5
  import os
6
  import hashlib
 
7
  from dataclasses import dataclass
8
  from typing import Any, Dict, List, Tuple, Optional
9
  from pathlib import Path
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  # --- Computer Vision & UI Libraries ---
12
  import cv2
13
  import numpy as np
14
  import gradio as gr
15
  from ultralytics import YOLO
16
 
17
- # --- Face Recognition Libraries (Optional / Fallback) ---
18
  try:
19
  from deepface import DeepFace
20
  DEEPFACE_AVAILABLE = True
21
  except ImportError:
22
  DEEPFACE_AVAILABLE = False
23
- logging.warning("⚠️ DeepFace not installed - Recognition will be disabled (Fallback Mode).")
24
 
25
  try:
26
  import chromadb
@@ -38,18 +60,11 @@ logger = logging.getLogger(__name__)
38
  # ====================================================
39
 
40
  # --- TUNED PARAMETERS ---
41
- # 1. Detection Sensitivity
42
- DETECTION_CONFIDENCE = 0.4
43
-
44
- # 2. Recognition Strictness (LOWER = STRICTER)
45
- # 0.30 is the sweet spot for Facenet512.
46
- # Anything above 0.40 causes the "Lupita/Spader" identity confusion.
47
- RECOGNITION_THRESHOLD = 0.30
48
-
49
- # 3. Visual Settings
50
- TARGET_MOSAIC_GRID = 10 # Resolution of the blur
51
- MIN_PIXEL_SIZE = 12 # Minimum pixel block size
52
- COVERAGE_SCALE = 1.2 # 120% Coverage (Padding around face to catch hair/ears)
53
 
54
  TEMP_FILES = []
55
 
@@ -72,12 +87,9 @@ def get_padded_coords(image, x, y, w, h, scale=COVERAGE_SCALE):
72
  Calculates the padded coordinates once so Blur and Box match perfectly.
73
  """
74
  h_img, w_img = image.shape[:2]
75
-
76
- # Calculate padding amount
77
  pad_w = int(w * (scale - 1.0) / 2)
78
  pad_h = int(h * (scale - 1.0) / 2)
79
 
80
- # Apply padding with boundary checks
81
  new_x = max(0, x - pad_w)
82
  new_y = max(0, y - pad_h)
83
  new_w = min(w_img - new_x, w + (2 * pad_w))
@@ -101,8 +113,12 @@ class FaceDatabase:
101
  self.client = chromadb.PersistentClient(path=db_path)
102
  self.collection = self.client.get_or_create_collection(name="face_embeddings", metadata={"hnsw:space": "cosine"})
103
  self.is_active = True
104
- if self.faces_dir.exists(): self._scan_and_index()
105
- else: self.faces_dir.mkdir(parents=True, exist_ok=True)
 
 
 
 
106
  except Exception as e:
107
  logger.error(f"❌ DB Init Error: {e}")
108
 
@@ -123,9 +139,14 @@ class FaceDatabase:
123
  if img_path.suffix.lower() not in ['.jpg', '.png', '.webp', '.jpeg']: continue
124
  try:
125
  img_hash = self._get_hash(img_path)
 
126
  if self.collection.get(ids=[img_hash])['ids']: continue
127
 
128
- embedding_objs = DeepFace.represent(img_path=str(img_path), model_name="Facenet512", enforce_detection=False)
 
 
 
 
129
  if embedding_objs:
130
  self.collection.add(
131
  ids=[img_hash],
@@ -141,8 +162,6 @@ class FaceDatabase:
141
  if not self.is_active or self.collection.count() == 0: return default
142
 
143
  try:
144
- # DeepFace expects BGR or Path. Convert RGB->BGR just in case.
145
- # Using a temp file ensures DeepFace preprocessing runs consistently.
146
  temp_path = "temp_query.jpg"
147
  cv2.imwrite(temp_path, cv2.cvtColor(face_img, cv2.COLOR_RGB2BGR))
148
 
@@ -157,7 +176,7 @@ class FaceDatabase:
157
  distance = results['distances'][0][0]
158
  metadata = results['metadatas'][0][0]
159
 
160
- # --- SECURITY FIX: STRICT THRESHOLD ---
161
  if distance < RECOGNITION_THRESHOLD:
162
  return {
163
  "match": True,
@@ -166,7 +185,6 @@ class FaceDatabase:
166
  "color": (0, 255, 0) # Green
167
  }
168
  return default
169
-
170
  except Exception as e:
171
  return default
172
 
@@ -176,10 +194,11 @@ class FaceDatabase:
176
  FACE_DB = FaceDatabase()
177
 
178
  # ====================================================
179
- # 3. DETECTOR & DRAWING LOGIC
180
  # ====================================================
181
  class Detector:
182
  def __init__(self):
 
183
  self.model = YOLO("yolov8n-face.pt")
184
 
185
  def detect(self, image: np.ndarray):
@@ -189,7 +208,7 @@ class Detector:
189
  if r.boxes is None: continue
190
  for box in r.boxes:
191
  x1, y1, x2, y2 = map(int, box.xyxy[0])
192
- faces.append((x1, y1, x2-x1, y2-y1)) # Return as X, Y, W, H
193
  return faces
194
 
195
  GLOBAL_DETECTOR = Detector()
@@ -198,11 +217,9 @@ def apply_blur(image, x, y, w, h):
198
  roi = image[y:y+h, x:x+w]
199
  if roi.size == 0: return image
200
 
201
- # Adaptive Grid Logic
202
  grid_pixel_limit = max(1, w // MIN_PIXEL_SIZE)
203
  final_grid_size = max(2, min(TARGET_MOSAIC_GRID, grid_pixel_limit))
204
 
205
- # Blur
206
  small = cv2.resize(roi, (final_grid_size, final_grid_size), interpolation=cv2.INTER_LINEAR)
207
  pixelated = cv2.resize(small, (w, h), interpolation=cv2.INTER_NEAREST)
208
  image[y:y+h, x:x+w] = pixelated
@@ -210,146 +227,209 @@ def apply_blur(image, x, y, w, h):
210
 
211
  def draw_smart_label(image, x, y, w, h, text, color):
212
  """
213
- UX FIX: Draws the label OUTSIDE the face box.
214
  """
215
- # 1. Draw the Bounding Box
216
- thickness = 2
217
  cv2.rectangle(image, (x, y), (x+w, y+h), color, thickness)
218
 
219
- # 2. Prepare Text
 
 
220
  font = cv2.FONT_HERSHEY_SIMPLEX
221
- font_scale = 0.6 # Slightly larger for readability
222
  font_thick = 2
223
- (tw, th), baseline = cv2.getTextSize(text, font, font_scale, font_thick)
224
 
225
  # 3. Smart Positioning (Top vs Bottom)
226
- # Default to TOP. If face is at y=0, flip to BOTTOM.
227
  text_y = y - 10
 
228
  if y - th - 15 < 0:
229
  text_y = y + h + th + 10
230
 
231
- # 4. Draw Background Box (Header Style)
232
- # Center the text horizontally relative to the face box
233
  center_x = x + (w // 2)
234
  text_x = center_x - (tw // 2)
235
 
236
- # Background rectangle for text
237
- pad = 5
238
  cv2.rectangle(image,
239
  (text_x - pad, text_y - th - pad),
240
  (text_x + tw + pad, text_y + pad),
241
- color, -1) # Filled
242
 
243
- # 5. Draw Text
244
  cv2.putText(image, text, (text_x, text_y), font, font_scale, (255, 255, 255), font_thick, cv2.LINE_AA)
245
 
246
  def process_frame(image, mode):
 
 
 
247
  if image is None: return None, "No Image"
248
 
 
249
  faces = GLOBAL_DETECTOR.detect(image)
250
  processed_img = image.copy()
251
  log_entries = []
252
 
253
- # Queue for drawing so labels appear ON TOP of blur
254
  draw_queue = []
255
 
256
  for i, (raw_x, raw_y, raw_w, raw_h) in enumerate(faces):
257
 
258
- # --- STEP 1: CALCULATE UNIFIED COORDINATES ---
259
- # We use these padded coordinates for EVERYTHING (Crop, Blur, Box)
260
- # This prevents the "Bleeding" visual glitch.
261
  px, py, pw, ph = get_padded_coords(processed_img, raw_x, raw_y, raw_w, raw_h)
262
 
263
- # Defaults
264
- label_text = "Unknown"
265
- box_color = (200, 0, 0) # Dark Red default
266
  log_text = "Unknown"
267
 
268
- # --- STEP 2: RECOGNITION (Data/Smart Mode) ---
269
  if mode in ["data", "smart"]:
270
- # Crop using the PADDED area to give the model more context (hair, chin)
271
  face_crop = processed_img[py:py+ph, px:px+pw]
272
 
273
  if face_crop.size > 0:
274
  res = FACE_DB.recognize(face_crop)
275
  if res['match']:
276
- label_text = f"{res['name']} ({res['id']})"
277
  box_color = (0, 200, 0) # Green
278
  log_text = f"MATCH: {res['name']}"
279
  else:
 
280
  log_text = "Unknown Person"
281
-
282
  draw_queue.append((px, py, pw, ph, label_text, box_color))
283
  log_entries.append(f"Face #{i+1}: {log_text}")
284
 
285
- # --- STEP 3: PRIVACY (Privacy/Smart Mode) ---
286
  if mode in ["privacy", "smart"]:
287
  processed_img = apply_blur(processed_img, px, py, pw, ph)
288
  if mode == "privacy":
289
  log_entries.append(f"Face #{i+1}: Redacted")
290
 
291
- # --- STEP 4: DRAW UI (Last Layer) ---
292
  for (dx, dy, dw, dh, txt, col) in draw_queue:
293
  draw_smart_label(processed_img, dx, dy, dw, dh, txt, col)
294
 
295
- final_log = "--- Report ---\n" + "\n".join(log_entries) if log_entries else "No faces."
296
  return processed_img, final_log
297
 
298
  # ====================================================
299
- # 4. VIDEO & GRADIO SETUP
300
  # ====================================================
301
- def process_video(video_path, mode, progress=gr.Progress()):
302
  if not video_path: return None
 
303
  cap = cv2.VideoCapture(video_path)
 
 
304
  fps = cap.get(cv2.CAP_PROP_FPS)
305
- width, height = int(cap.get(3)), int(cap.get(4))
 
 
306
 
307
  out_path = create_temp_file()
 
308
  out = cv2.VideoWriter(out_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (width, height))
309
 
310
- total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
311
  cnt = 0
312
  while cap.isOpened():
313
  ret, frame = cap.read()
314
  if not ret: break
 
315
  res_frame, _ = process_frame(frame, mode)
316
  out.write(res_frame)
 
317
  cnt += 1
318
- if cnt % 10 == 0: progress(cnt/total)
 
319
 
320
  cap.release()
321
  out.release()
322
  return out_path
323
 
324
- with gr.Blocks(theme=gr.themes.Soft(), title="Secure Redaction V2") as demo:
325
- gr.Markdown("# 🛡️ Smart Redaction System (Patched V2)")
326
- gr.Markdown(f"**Engine Status:** {FACE_DB.get_stats()} | **Security Threshold:** {RECOGNITION_THRESHOLD}")
 
 
 
 
327
 
328
  with gr.Tabs():
329
- with gr.Tab("1️⃣ Raw Privacy"):
330
- with gr.Row():
331
- p_in = gr.Image(label="Input", type="numpy")
332
- p_out = gr.Image(label="Redacted Output")
333
- p_btn = gr.Button("Anonymize", variant="primary")
334
- p_btn.click(lambda x: process_frame(x, "privacy")[0], p_in, p_out)
335
-
336
- with gr.Tab("2️⃣ Security Data"):
337
- with gr.Row():
338
- d_in = gr.Image(label="Input", type="numpy")
339
- with gr.Column():
340
- d_out = gr.Image(label="Analyst View (Clear Face)")
341
- d_log = gr.Textbox(label="Logs")
342
- d_btn = gr.Button("Analyze", variant="primary")
343
- d_btn.click(lambda x: process_frame(x, "data"), d_in, [d_out, d_log])
344
-
345
- with gr.Tab("3️⃣ Smart Mode"):
346
- with gr.Row():
347
- s_in = gr.Image(label="Input", type="numpy")
348
- with gr.Column():
349
- s_out = gr.Image(label="Smart Output (Blurred + ID)")
350
- s_log = gr.Textbox(label="Logs")
351
- s_btn = gr.Button("Execute Smart Redaction", variant="primary")
352
- s_btn.click(lambda x: process_frame(x, "smart"), s_in, [s_out, s_log])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
353
 
354
  if __name__ == "__main__":
355
  demo.launch()
 
4
  import tempfile
5
  import os
6
  import hashlib
7
+ import shutil
8
  from dataclasses import dataclass
9
  from typing import Any, Dict, List, Tuple, Optional
10
  from pathlib import Path
11
 
12
+ # ==========================================
13
+ # 🚀 FAST STARTUP: LOCAL WEIGHTS SETUP
14
+ # ==========================================
15
+ def setup_local_weights():
16
+ os.environ["DEEPFACE_HOME"] = os.getcwd()
17
+ target_dir = os.path.join(os.getcwd(), ".deepface", "weights")
18
+ os.makedirs(target_dir, exist_ok=True)
19
+ weight_file = "facenet512_weights.h5"
20
+ target_path = os.path.join(target_dir, weight_file)
21
+
22
+ if os.path.exists(weight_file) and not os.path.exists(target_path):
23
+ print(f"📦 Found local weights: {weight_file}. Installing...")
24
+ shutil.copy(weight_file, target_path) # Use copy so original stays visible in files
25
+ elif os.path.exists(target_path):
26
+ print("✅ Weights already installed.")
27
+ else:
28
+ print("⚠️ Local weights not found. DeepFace might download them.")
29
+
30
+ # RUN THIS IMMEDIATELY BEFORE IMPORTS
31
+ setup_local_weights()
32
+
33
  # --- Computer Vision & UI Libraries ---
34
  import cv2
35
  import numpy as np
36
  import gradio as gr
37
  from ultralytics import YOLO
38
 
39
+ # --- Face Recognition Libraries ---
40
  try:
41
  from deepface import DeepFace
42
  DEEPFACE_AVAILABLE = True
43
  except ImportError:
44
  DEEPFACE_AVAILABLE = False
45
+ logging.warning("⚠️ DeepFace not installed - Recognition disabled.")
46
 
47
  try:
48
  import chromadb
 
60
  # ====================================================
61
 
62
  # --- TUNED PARAMETERS ---
63
+ DETECTION_CONFIDENCE = 0.4 # Moderate strictness to reduce false boxes
64
+ RECOGNITION_THRESHOLD = 0.30 # STRICT: Prevents Identity Confusion
65
+ TARGET_MOSAIC_GRID = 10 # Pixelation Grid Size
66
+ MIN_PIXEL_SIZE = 12 # Min block size (prevents weak blur on small faces)
67
+ COVERAGE_SCALE = 1.2 # 120% padding (Captures hair/chin)
 
 
 
 
 
 
 
68
 
69
  TEMP_FILES = []
70
 
 
87
  Calculates the padded coordinates once so Blur and Box match perfectly.
88
  """
89
  h_img, w_img = image.shape[:2]
 
 
90
  pad_w = int(w * (scale - 1.0) / 2)
91
  pad_h = int(h * (scale - 1.0) / 2)
92
 
 
93
  new_x = max(0, x - pad_w)
94
  new_y = max(0, y - pad_h)
95
  new_w = min(w_img - new_x, w + (2 * pad_w))
 
113
  self.client = chromadb.PersistentClient(path=db_path)
114
  self.collection = self.client.get_or_create_collection(name="face_embeddings", metadata={"hnsw:space": "cosine"})
115
  self.is_active = True
116
+
117
+ if self.faces_dir.exists():
118
+ self._scan_and_index()
119
+ else:
120
+ self.faces_dir.mkdir(parents=True, exist_ok=True)
121
+
122
  except Exception as e:
123
  logger.error(f"❌ DB Init Error: {e}")
124
 
 
139
  if img_path.suffix.lower() not in ['.jpg', '.png', '.webp', '.jpeg']: continue
140
  try:
141
  img_hash = self._get_hash(img_path)
142
+ # Skip if already indexed
143
  if self.collection.get(ids=[img_hash])['ids']: continue
144
 
145
+ embedding_objs = DeepFace.represent(
146
+ img_path=str(img_path),
147
+ model_name="Facenet512",
148
+ enforce_detection=False
149
+ )
150
  if embedding_objs:
151
  self.collection.add(
152
  ids=[img_hash],
 
162
  if not self.is_active or self.collection.count() == 0: return default
163
 
164
  try:
 
 
165
  temp_path = "temp_query.jpg"
166
  cv2.imwrite(temp_path, cv2.cvtColor(face_img, cv2.COLOR_RGB2BGR))
167
 
 
176
  distance = results['distances'][0][0]
177
  metadata = results['metadatas'][0][0]
178
 
179
+ # STRICT THRESHOLD APPLIED HERE
180
  if distance < RECOGNITION_THRESHOLD:
181
  return {
182
  "match": True,
 
185
  "color": (0, 255, 0) # Green
186
  }
187
  return default
 
188
  except Exception as e:
189
  return default
190
 
 
194
  FACE_DB = FaceDatabase()
195
 
196
  # ====================================================
197
+ # 3. DETECTOR & PROCESSING LOGIC
198
  # ====================================================
199
  class Detector:
200
  def __init__(self):
201
+ # Uses the local pt file if available
202
  self.model = YOLO("yolov8n-face.pt")
203
 
204
  def detect(self, image: np.ndarray):
 
208
  if r.boxes is None: continue
209
  for box in r.boxes:
210
  x1, y1, x2, y2 = map(int, box.xyxy[0])
211
+ faces.append((x1, y1, x2-x1, y2-y1))
212
  return faces
213
 
214
  GLOBAL_DETECTOR = Detector()
 
217
  roi = image[y:y+h, x:x+w]
218
  if roi.size == 0: return image
219
 
 
220
  grid_pixel_limit = max(1, w // MIN_PIXEL_SIZE)
221
  final_grid_size = max(2, min(TARGET_MOSAIC_GRID, grid_pixel_limit))
222
 
 
223
  small = cv2.resize(roi, (final_grid_size, final_grid_size), interpolation=cv2.INTER_LINEAR)
224
  pixelated = cv2.resize(small, (w, h), interpolation=cv2.INTER_NEAREST)
225
  image[y:y+h, x:x+w] = pixelated
 
227
 
228
  def draw_smart_label(image, x, y, w, h, text, color):
229
  """
230
+ UX FIX: Draws label OUTSIDE the box (Header/Footer style)
231
  """
232
+ # 1. Draw Box
233
+ thickness = 2 if w > 40 else 1
234
  cv2.rectangle(image, (x, y), (x+w, y+h), color, thickness)
235
 
236
+ if not text: return
237
+
238
+ # 2. Measure Text
239
  font = cv2.FONT_HERSHEY_SIMPLEX
240
+ font_scale = 0.6
241
  font_thick = 2
242
+ (tw, th), _ = cv2.getTextSize(text, font, font_scale, font_thick)
243
 
244
  # 3. Smart Positioning (Top vs Bottom)
 
245
  text_y = y - 10
246
+ # If close to top edge, flip to bottom
247
  if y - th - 15 < 0:
248
  text_y = y + h + th + 10
249
 
250
+ # Center horizontally
 
251
  center_x = x + (w // 2)
252
  text_x = center_x - (tw // 2)
253
 
254
+ # Background Box
255
+ pad = 4
256
  cv2.rectangle(image,
257
  (text_x - pad, text_y - th - pad),
258
  (text_x + tw + pad, text_y + pad),
259
+ color, -1)
260
 
 
261
  cv2.putText(image, text, (text_x, text_y), font, font_scale, (255, 255, 255), font_thick, cv2.LINE_AA)
262
 
263
  def process_frame(image, mode):
264
+ """
265
+ MASTER FUNCTION
266
+ """
267
  if image is None: return None, "No Image"
268
 
269
+ # Detect
270
  faces = GLOBAL_DETECTOR.detect(image)
271
  processed_img = image.copy()
272
  log_entries = []
273
 
274
+ # Queue drawing to ensure labels are ON TOP of blur
275
  draw_queue = []
276
 
277
  for i, (raw_x, raw_y, raw_w, raw_h) in enumerate(faces):
278
 
279
+ # 1. UNIFIED COORDINATES (Fixes "Bleeding" Blur)
 
 
280
  px, py, pw, ph = get_padded_coords(processed_img, raw_x, raw_y, raw_w, raw_h)
281
 
282
+ label_text = ""
283
+ box_color = (200, 0, 0) # Default Red
 
284
  log_text = "Unknown"
285
 
286
+ # 2. ANALYSIS (Data/Smart Mode)
287
  if mode in ["data", "smart"]:
288
+ # Crop using padded coords for better context
289
  face_crop = processed_img[py:py+ph, px:px+pw]
290
 
291
  if face_crop.size > 0:
292
  res = FACE_DB.recognize(face_crop)
293
  if res['match']:
294
+ label_text = f"ID: {res['id']}"
295
  box_color = (0, 200, 0) # Green
296
  log_text = f"MATCH: {res['name']}"
297
  else:
298
+ label_text = "Unknown"
299
  log_text = "Unknown Person"
300
+
301
  draw_queue.append((px, py, pw, ph, label_text, box_color))
302
  log_entries.append(f"Face #{i+1}: {log_text}")
303
 
304
+ # 3. MODIFICATION (Privacy/Smart Mode)
305
  if mode in ["privacy", "smart"]:
306
  processed_img = apply_blur(processed_img, px, py, pw, ph)
307
  if mode == "privacy":
308
  log_entries.append(f"Face #{i+1}: Redacted")
309
 
310
+ # 4. DRAW UI (Top Layer)
311
  for (dx, dy, dw, dh, txt, col) in draw_queue:
312
  draw_smart_label(processed_img, dx, dy, dw, dh, txt, col)
313
 
314
+ final_log = "--- Detection Report ---\n" + "\n".join(log_entries) if log_entries else "No faces detected."
315
  return processed_img, final_log
316
 
317
  # ====================================================
318
+ # 4. VIDEO PROCESSING
319
  # ====================================================
320
+ def process_video_general(video_path, mode, progress=gr.Progress()):
321
  if not video_path: return None
322
+
323
  cap = cv2.VideoCapture(video_path)
324
+ if not cap.isOpened(): return None
325
+
326
  fps = cap.get(cv2.CAP_PROP_FPS)
327
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
328
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
329
+ total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
330
 
331
  out_path = create_temp_file()
332
+ # Try mp4v for better compatibility
333
  out = cv2.VideoWriter(out_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (width, height))
334
 
 
335
  cnt = 0
336
  while cap.isOpened():
337
  ret, frame = cap.read()
338
  if not ret: break
339
+
340
  res_frame, _ = process_frame(frame, mode)
341
  out.write(res_frame)
342
+
343
  cnt += 1
344
+ if total > 0 and cnt % 5 == 0:
345
+ progress(cnt/total, desc=f"Processing Frame {cnt}/{total}")
346
 
347
  cap.release()
348
  out.release()
349
  return out_path
350
 
351
+ # ====================================================
352
+ # 5. GRADIO INTERFACE (FULL)
353
+ # ====================================================
354
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue"), title="Smart Redaction Pro") as demo:
355
+
356
+ gr.Markdown("# 🛡️ Smart Redaction System (Enterprise Patch)")
357
+ gr.Markdown(f"**Status:** {FACE_DB.get_stats()} | **Strictness:** {RECOGNITION_THRESHOLD}")
358
 
359
  with gr.Tabs():
360
+
361
+ # --- TAB 1: RAW PRIVACY ---
362
+ with gr.TabItem("1️⃣ Raw Privacy"):
363
+ gr.Markdown("### 🔒 Total Anonymization")
364
+ with gr.Tabs():
365
+ with gr.TabItem("Image"):
366
+ p_img_in = gr.Image(label="Input", type="numpy", height=400)
367
+ p_img_out = gr.Image(label="Output", height=400)
368
+ p_btn = gr.Button("Apply Privacy", variant="primary")
369
+ with gr.TabItem("Video"):
370
+ p_vid_in = gr.Video(label="Input Video")
371
+ p_vid_out = gr.Video(label="Output Video")
372
+ p_vid_btn = gr.Button("Process Video", variant="primary")
373
+ with gr.TabItem("Webcam"):
374
+ p_web_in = gr.Image(sources=["webcam"], streaming=True, type="numpy")
375
+ p_web_out = gr.Image(label="Live Stream")
376
+
377
+ # --- TAB 2: DATA LAYER ---
378
+ with gr.TabItem("2️⃣ Security Data"):
379
+ gr.Markdown("### 🔍 Recognition (No Blur)")
380
+ with gr.Tabs():
381
+ with gr.TabItem("Image"):
382
+ with gr.Row():
383
+ d_img_in = gr.Image(label="Input", type="numpy", height=400)
384
+ with gr.Column():
385
+ d_img_out = gr.Image(label="Output", height=400)
386
+ d_log_out = gr.Textbox(label="Logs", lines=4)
387
+ d_btn = gr.Button("Analyze", variant="primary")
388
+ with gr.TabItem("Video"):
389
+ d_vid_in = gr.Video(label="Input Video")
390
+ d_vid_out = gr.Video(label="Output Video")
391
+ d_vid_btn = gr.Button("Analyze Video", variant="primary")
392
+ with gr.TabItem("Webcam"):
393
+ d_web_in = gr.Image(sources=["webcam"], streaming=True, type="numpy")
394
+ d_web_out = gr.Image(label="Live Data Stream")
395
+
396
+ # --- TAB 3: SMART REDACTION ---
397
+ with gr.TabItem("3️⃣ Smart Redaction"):
398
+ gr.Markdown("### 🛡️ Identity + Privacy")
399
+ with gr.Tabs():
400
+ with gr.TabItem("Image"):
401
+ with gr.Row():
402
+ s_img_in = gr.Image(label="Input", type="numpy", height=400)
403
+ with gr.Column():
404
+ s_img_out = gr.Image(label="Output", height=400)
405
+ s_log_out = gr.Textbox(label="Logs", lines=4)
406
+ s_btn = gr.Button("Apply Smart Redaction", variant="primary")
407
+ with gr.TabItem("Video"):
408
+ s_vid_in = gr.Video(label="Input Video")
409
+ s_vid_out = gr.Video(label="Output Video")
410
+ s_vid_btn = gr.Button("Process Smart Video", variant="primary")
411
+ with gr.TabItem("Webcam"):
412
+ s_web_in = gr.Image(sources=["webcam"], streaming=True, type="numpy")
413
+ s_web_out = gr.Image(label="Live Smart Stream")
414
+
415
+ # =========================================
416
+ # WIRING
417
+ # =========================================
418
+
419
+ # Privacy
420
+ p_btn.click(lambda img: process_frame(img, "privacy")[0], inputs=[p_img_in], outputs=p_img_out)
421
+ p_vid_btn.click(lambda vid: process_video_general(vid, "privacy"), inputs=[p_vid_in], outputs=p_vid_out)
422
+ p_web_in.stream(lambda img: process_frame(img, "privacy")[0], inputs=[p_web_in], outputs=p_web_out)
423
+
424
+ # Data
425
+ d_btn.click(lambda img: process_frame(img, "data"), inputs=[d_img_in], outputs=[d_img_out, d_log_out])
426
+ d_vid_btn.click(lambda vid: process_video_general(vid, "data"), inputs=[d_vid_in], outputs=d_vid_out)
427
+ d_web_in.stream(lambda img: process_frame(img, "data")[0], inputs=[d_web_in], outputs=d_web_out)
428
+
429
+ # Smart
430
+ s_btn.click(lambda img: process_frame(img, "smart"), inputs=[s_img_in], outputs=[s_img_out, s_log_out])
431
+ s_vid_btn.click(lambda vid: process_video_general(vid, "smart"), inputs=[s_vid_in], outputs=s_vid_out)
432
+ s_web_in.stream(lambda img: process_frame(img, "smart")[0], inputs=[s_web_in], outputs=s_web_out)
433
 
434
  if __name__ == "__main__":
435
  demo.launch()