Johnnyyyyy56 commited on
Commit
b27b7b1
·
verified ·
1 Parent(s): 82db053

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +559 -100
app.py CHANGED
@@ -1,4 +1,12 @@
1
  import os
 
 
 
 
 
 
 
 
2
  import cv2
3
  import gradio as gr
4
  from deepface import DeepFace
@@ -7,7 +15,6 @@ from PIL import Image
7
  import time
8
  from pathlib import Path
9
  import pandas as pd
10
- import re
11
 
12
  # Configuration
13
  EMOTION_MAP = {
@@ -41,21 +48,27 @@ def log_emotion(batch_no, emotion, confidence, face_path, annotated_path):
41
  writer.writerow([timestamp, batch_no, emotion, confidence, str(face_path), str(annotated_path)])
42
 
43
  def validate_batch_no(batch_no):
 
44
  if not batch_no.strip():
45
  return False, "Batch number cannot be empty"
46
  if not re.match(r'^\d+$', batch_no):
47
  return False, "Batch number must contain only numbers"
48
  return True, ""
49
 
50
- def process_frame(batch_no, frame):
51
- if not batch_no.strip() or frame is None:
52
- return None, None, "Waiting for input...", False, False
 
 
 
53
 
54
  try:
55
- frame = np.array(frame)
 
56
  if frame.ndim == 3:
57
  frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
58
 
 
59
  results = None
60
  for backend in BACKENDS:
61
  try:
@@ -63,158 +76,604 @@ def process_frame(batch_no, frame):
63
  frame,
64
  actions=['emotion'],
65
  detector_backend=backend,
66
- enforce_detection=False,
67
  silent=True
68
  )
69
- if results:
70
- break
71
  except Exception:
72
  continue
73
 
74
  if not results:
75
- return None, None, "Position your face in frame", False, False
76
 
77
- result = results[0]
 
78
  emotion = result['dominant_emotion']
79
  confidence = result['emotion'][emotion]
80
  region = result['region']
81
 
 
82
  x, y, w, h = region['x'], region['y'], region['w'], region['h']
83
 
84
- # Save files and log data
 
85
  timestamp = int(time.time())
86
  face_dir = SAVE_DIR / "faces" / emotion
87
  face_path = face_dir / f"{batch_no}_{timestamp}.jpg"
88
- cv2.imwrite(str(face_path), frame[y:y+h, x:x+w])
89
 
90
- annotated = frame.copy()
91
- cv2.rectangle(annotated, (x,y), (x+w,y+h), (0,255,0), 2)
92
- cv2.putText(annotated, f"{emotion} {confidence:.1f}%",
93
- (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,255,0), 2)
 
94
 
95
  annotated_dir = SAVE_DIR / "annotated" / emotion
96
  annotated_path = annotated_dir / f"{batch_no}_{timestamp}.jpg"
97
- cv2.imwrite(str(annotated_path), annotated)
98
 
 
99
  log_emotion(batch_no, emotion, confidence, face_path, annotated_path)
100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  return (
102
- Image.fromarray(cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB)),
103
- f"Batch {batch_no}: {emotion.title()} ({confidence:.1f}%)",
104
- "",
105
- True,
106
- True
 
107
  )
108
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  except Exception as e:
110
- return None, None, f"Error: {str(e)}", False, False
 
 
 
111
 
112
- # Main Interface
113
- with gr.Blocks(title="Auto Emotion Detection") as interface:
114
- gr.Markdown("# Automatic Emotion Detection")
115
- gr.Markdown("1. Enter your batch number\n2. Webcam will activate automatically\n3. System will detect your face\n4. Results will appear")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
  with gr.Row():
118
- batch_input = gr.Textbox(
119
  label="Batch Number",
120
- placeholder="Enter numbers only",
121
  interactive=True
122
  )
123
- status = gr.Textbox(
124
- label="Status",
125
- interactive=False,
126
- visible=False
127
- )
 
 
128
 
129
  with gr.Row():
130
  webcam = gr.Image(
131
  sources=["webcam"],
132
- streaming=True,
 
 
133
  mirror_webcam=True,
134
- visible=False,
135
- interactive=False
136
  )
 
 
137
  result_img = gr.Image(
138
- label="Result",
 
139
  visible=False
140
  )
141
 
142
  with gr.Row():
143
  result_text = gr.Textbox(
144
- label="Analysis",
 
145
  visible=False
146
  )
 
 
147
  done_btn = gr.Button(
148
- "Done",
149
  visible=False
150
  )
151
 
152
- # Hidden trigger for continuous processing
153
- trigger = gr.Textbox(visible=False)
154
-
155
- def handle_batch_input(batch_no):
156
- is_valid, msg = validate_batch_no(batch_no)
157
- if not is_valid:
158
- return (
159
- gr.Textbox(interactive=True),
160
- gr.Textbox(value=msg, visible=bool(msg)),
161
- gr.Image(visible=False),
162
- gr.Image(visible=False),
163
- gr.Textbox(visible=False),
164
- gr.Button(visible=False),
165
- gr.Textbox(visible=False)
166
- )
167
-
168
- return (
169
- gr.Textbox(interactive=False),
170
- gr.Textbox(value="Webcam activated - position your face", visible=True),
171
- gr.Image(visible=True),
172
- gr.Image(visible=False),
173
- gr.Textbox(visible=False),
174
- gr.Button(visible=False),
175
- gr.Textbox(value=str(time.time()), visible=False)
176
- )
177
-
178
- def process_and_continue(batch_no, frame, trigger_val):
179
- result_img, result_text, msg, show_img, show_text = process_frame(batch_no, frame)
180
- return (
181
- result_img,
182
- result_text,
183
- msg,
184
- show_img,
185
- show_text,
186
- gr.Textbox.update(value=str(time.time()))
187
- )
188
-
189
- # Setup event handlers
190
- batch_input.change(
191
- handle_batch_input,
192
- inputs=batch_input,
193
- outputs=[batch_input, status, webcam, result_img, result_text, done_btn, trigger]
194
  )
195
 
 
196
  webcam.change(
197
- process_and_continue,
198
- inputs=[batch_input, webcam, trigger],
199
- outputs=[result_img, result_text, status, result_img, result_text, trigger],
200
- queue=False
201
  )
202
 
203
- def reset_all():
204
- return (
205
- gr.Textbox(value="", interactive=True),
206
- gr.Textbox(value="", visible=False),
207
- gr.Image(visible=False),
208
- gr.Image(visible=False),
209
- gr.Textbox(visible=False),
210
- gr.Button(visible=False),
211
- gr.Textbox(visible=False)
212
- )
213
-
214
  done_btn.click(
215
- reset_all,
216
- outputs=[batch_input, status, webcam, result_img, result_text, done_btn, trigger]
217
  )
218
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  if __name__ == "__main__":
220
- interface.launch()
 
1
  import os
2
+ import csv
3
+ import zipfile
4
+ import shutil
5
+ import re
6
+ from datetime import datetime
7
+ os.environ['TF_ENABLE_ONEDNN_OPTS'] = '0'
8
+ os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
9
+
10
  import cv2
11
  import gradio as gr
12
  from deepface import DeepFace
 
15
  import time
16
  from pathlib import Path
17
  import pandas as pd
 
18
 
19
  # Configuration
20
  EMOTION_MAP = {
 
48
  writer.writerow([timestamp, batch_no, emotion, confidence, str(face_path), str(annotated_path)])
49
 
50
  def validate_batch_no(batch_no):
51
+ """Validate that batch number contains only digits"""
52
  if not batch_no.strip():
53
  return False, "Batch number cannot be empty"
54
  if not re.match(r'^\d+$', batch_no):
55
  return False, "Batch number must contain only numbers"
56
  return True, ""
57
 
58
+ def predict_emotion(batch_no: str, image):
59
+ if not batch_no.strip():
60
+ return None, None, "Please enter a batch number first", gr.Image(visible=False), gr.Textbox(visible=False), gr.Button(visible=False)
61
+
62
+ if image is None:
63
+ return None, None, "Please capture your face first", gr.Image(visible=False), gr.Textbox(visible=False), gr.Button(visible=False)
64
 
65
  try:
66
+ # Convert PIL Image to OpenCV format
67
+ frame = np.array(image)
68
  if frame.ndim == 3:
69
  frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
70
 
71
+ # Try different backends for face detection
72
  results = None
73
  for backend in BACKENDS:
74
  try:
 
76
  frame,
77
  actions=['emotion'],
78
  detector_backend=backend,
79
+ enforce_detection=True,
80
  silent=True
81
  )
82
+ break
 
83
  except Exception:
84
  continue
85
 
86
  if not results:
87
+ return None, None, "No face detected. Please try again.", gr.Image(visible=False), gr.Textbox(visible=False), gr.Button(visible=False)
88
 
89
+ # Process the first face found
90
+ result = results[0] if isinstance(results, list) else results
91
  emotion = result['dominant_emotion']
92
  confidence = result['emotion'][emotion]
93
  region = result['region']
94
 
95
+ # Extract face coordinates
96
  x, y, w, h = region['x'], region['y'], region['w'], region['h']
97
 
98
+ # 1. Save raw face crop
99
+ face_crop = frame[y:y+h, x:x+w]
100
  timestamp = int(time.time())
101
  face_dir = SAVE_DIR / "faces" / emotion
102
  face_path = face_dir / f"{batch_no}_{timestamp}.jpg"
103
+ cv2.imwrite(str(face_path), face_crop)
104
 
105
+ # 2. Create and save annotated image
106
+ annotated_frame = frame.copy()
107
+ cv2.rectangle(annotated_frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
108
+ cv2.putText(annotated_frame, f"{emotion} {EMOTION_MAP[emotion]} {confidence:.1f}%",
109
+ (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
110
 
111
  annotated_dir = SAVE_DIR / "annotated" / emotion
112
  annotated_path = annotated_dir / f"{batch_no}_{timestamp}.jpg"
113
+ cv2.imwrite(str(annotated_path), annotated_frame)
114
 
115
+ # Log both paths
116
  log_emotion(batch_no, emotion, confidence, face_path, annotated_path)
117
 
118
+ # Convert back to PIL format for display
119
+ output_img = Image.fromarray(cv2.cvtColor(annotated_frame, cv2.COLOR_BGR2RGB))
120
+ return output_img, f"Batch {batch_no}: {emotion.title()} ({confidence:.1f}%)", "", gr.Image(visible=True), gr.Textbox(visible=True), gr.Button(visible=True)
121
+
122
+ except Exception as e:
123
+ return None, None, f"Error processing image: {str(e)}", gr.Image(visible=False), gr.Textbox(visible=False), gr.Button(visible=False)
124
+
125
+ def check_batch_no(batch_no):
126
+ """Check if batch number is entered and valid"""
127
+ is_valid, validation_msg = validate_batch_no(batch_no)
128
+ if not is_valid:
129
+ return (
130
+ gr.Textbox(interactive=True), # Keep batch_no interactive
131
+ gr.Textbox(value=validation_msg, visible=bool(validation_msg)), # Show validation message
132
+ gr.Image(visible=False), # Hide webcam
133
+ gr.Image(visible=False), # Hide result image
134
+ gr.Textbox(visible=False), # Hide result text
135
+ gr.Button(visible=False) # Hide done button
136
+ )
137
+
138
+ # After validation, disable input and show countdown
139
+ return (
140
+ gr.Textbox(interactive=False), # Disable batch_no
141
+ gr.Textbox(value="Processing will start in 5 seconds...", visible=True), # Show countdown
142
+ gr.Image(visible=False), # Keep webcam hidden initially
143
+ gr.Image(visible=False), # Hide result image
144
+ gr.Textbox(visible=False), # Hide result text
145
+ gr.Button(visible=False) # Hide done button
146
+ )
147
+
148
+ def activate_webcam(batch_no):
149
+ """Actually activate the webcam after the delay"""
150
+ is_valid, _ = validate_batch_no(batch_no)
151
+ if not is_valid:
152
  return (
153
+ gr.Textbox(interactive=True), # Re-enable batch_no if invalid
154
+ gr.Textbox(visible=False), # Hide message
155
+ gr.Image(visible=False), # Hide webcam
156
+ gr.Image(visible=False), # Hide result image
157
+ gr.Textbox(visible=False), # Hide result text
158
+ gr.Button(visible=False) # Hide done button
159
  )
160
 
161
+ return (
162
+ gr.Textbox(interactive=False), # Keep batch_no disabled
163
+ gr.Textbox(value="Please capture your face now", visible=True), # Show instruction
164
+ gr.Image(visible=True), # Show webcam
165
+ gr.Image(visible=False), # Hide result image
166
+ gr.Textbox(visible=False), # Hide result text
167
+ gr.Button(visible=False) # Hide done button
168
+ )
169
+
170
+ def reset_interface():
171
+ """Reset the interface to initial state"""
172
+ return (
173
+ gr.Textbox(value="", interactive=True), # Enable batch_no
174
+ gr.Textbox(value="", visible=False), # Hide message
175
+ gr.Image(value=None, visible=False), # Hide webcam
176
+ gr.Image(visible=False), # Hide result image
177
+ gr.Textbox(visible=False), # Hide result text
178
+ gr.Button(visible=False) # Hide done button
179
+ )
180
+
181
+ def get_image_gallery(emotion, image_type):
182
+ """Get image gallery for selected emotion and type"""
183
+ if emotion == "All Emotions":
184
+ image_dict = {}
185
+ for emot in EMOTION_MAP.keys():
186
+ folder = SAVE_DIR / image_type / emot
187
+ image_dict[emot] = [str(f) for f in folder.glob("*.jpg") if f.exists()]
188
+ else:
189
+ folder = SAVE_DIR / image_type / emotion
190
+ image_dict = {emotion: [str(f) for f in folder.glob("*.jpg") if f.exists()]}
191
+ return image_dict
192
+
193
+ def create_custom_zip(file_paths):
194
+ """Create zip from selected images and return the file path"""
195
+ if not file_paths:
196
+ return None
197
+
198
+ temp_dir = SAVE_DIR / "temp_downloads"
199
+ temp_dir.mkdir(exist_ok=True)
200
+ zip_path = temp_dir / f"emotion_images_{int(time.time())}.zip"
201
+
202
+ if zip_path.exists():
203
+ try:
204
+ zip_path.unlink()
205
+ except Exception as e:
206
+ print(f"Error deleting old zip: {e}")
207
+
208
+ try:
209
+ with zipfile.ZipFile(zip_path, 'w') as zipf:
210
+ for file_path in file_paths:
211
+ file_path = Path(file_path)
212
+ if file_path.exists():
213
+ zipf.write(file_path, arcname=file_path.name)
214
+ return str(zip_path) if zip_path.exists() else None
215
+ except Exception as e:
216
+ print(f"Error creating zip file: {e}")
217
+ return None
218
+
219
+ def download_all_emotions_structured():
220
+ """Download all emotions in a structured ZIP with folders for each emotion"""
221
+ temp_dir = SAVE_DIR / "temp_downloads"
222
+ temp_dir.mkdir(exist_ok=True)
223
+ zip_path = temp_dir / f"all_emotions_structured_{int(time.time())}.zip"
224
+
225
+ if zip_path.exists():
226
+ try:
227
+ zip_path.unlink()
228
+ except Exception as e:
229
+ print(f"Error deleting old zip: {e}")
230
+
231
+ try:
232
+ with zipfile.ZipFile(zip_path, 'w') as zipf:
233
+ for emotion in EMOTION_MAP.keys():
234
+ # Add faces
235
+ face_dir = SAVE_DIR / "faces" / emotion
236
+ for face_file in face_dir.glob("*.jpg"):
237
+ if face_file.exists():
238
+ arcname = f"faces/{emotion}/{face_file.name}"
239
+ zipf.write(face_file, arcname=arcname)
240
+
241
+ # Add annotated images
242
+ annotated_dir = SAVE_DIR / "annotated" / emotion
243
+ for annotated_file in annotated_dir.glob("*.jpg"):
244
+ if annotated_file.exists():
245
+ arcname = f"annotated/{emotion}/{annotated_file.name}"
246
+ zipf.write(annotated_file, arcname=arcname)
247
+ return str(zip_path) if zip_path.exists() else None
248
+ except Exception as e:
249
+ print(f"Error creating structured zip file: {e}")
250
+ return None
251
+
252
+ def delete_selected_images(selected_images):
253
+ """Delete selected images with proper validation"""
254
+ if not selected_images:
255
+ return "No images selected for deletion"
256
+
257
+ deleted_count = 0
258
+ failed_deletions = []
259
+
260
+ for img_path in selected_images:
261
+ img_path = Path(img_path)
262
+ try:
263
+ if img_path.exists():
264
+ img_path.unlink()
265
+ deleted_count += 1
266
+ else:
267
+ failed_deletions.append(str(img_path))
268
+ except Exception as e:
269
+ print(f"Error deleting {img_path}: {e}")
270
+ failed_deletions.append(str(img_path))
271
+
272
+ if deleted_count > 0 and LOG_FILE.exists():
273
+ try:
274
+ df = pd.read_csv(LOG_FILE)
275
+ for img_path in selected_images:
276
+ img_path = str(img_path)
277
+ if "faces" in img_path:
278
+ df = df[df.face_path != img_path]
279
+ else:
280
+ df = df[df.annotated_path != img_path]
281
+ df.to_csv(LOG_FILE, index=False)
282
+ except Exception as e:
283
+ print(f"Error updating logs: {e}")
284
+
285
+ status_msg = f"Deleted {deleted_count} images"
286
+ if failed_deletions:
287
+ status_msg += f"\nFailed to delete {len(failed_deletions)} images"
288
+ return status_msg
289
+
290
+ def delete_images_in_category(emotion, image_type, confirm=False):
291
+ """Delete all images in a specific category with confirmation"""
292
+ if not confirm:
293
+ return "Please check the confirmation box to delete all images in this category"
294
+
295
+ if emotion == "All Emotions":
296
+ deleted_count = 0
297
+ for emot in EMOTION_MAP.keys():
298
+ deleted_count += delete_images_in_category(emot, image_type, confirm=True)
299
+ return f"Deleted {deleted_count} images across all emotion categories"
300
+
301
+ folder = SAVE_DIR / image_type / emotion
302
+ deleted_count = 0
303
+ failed_deletions = []
304
+
305
+ for file in folder.glob("*"):
306
+ if file.is_file():
307
+ try:
308
+ file.unlink()
309
+ deleted_count += 1
310
+ except Exception as e:
311
+ print(f"Error deleting {file}: {e}")
312
+ failed_deletions.append(str(file))
313
+
314
+ if deleted_count > 0 and LOG_FILE.exists():
315
+ try:
316
+ df = pd.read_csv(LOG_FILE)
317
+ if image_type == "faces":
318
+ df = df[df.emotion != emotion]
319
+ else:
320
+ df = df[~((df.emotion == emotion) & (df.annotated_path.str.contains(str(folder))))]
321
+ df.to_csv(LOG_FILE, index=False)
322
+ except Exception as e:
323
+ print(f"Error updating logs: {e}")
324
+
325
+ status_msg = f"Deleted {deleted_count} images from {emotion}/{image_type}"
326
+ if failed_deletions:
327
+ status_msg += f"\nFailed to delete {len(failed_deletions)} images"
328
+ return status_msg
329
+
330
+ def get_logs():
331
+ if LOG_FILE.exists():
332
+ return pd.read_csv(LOG_FILE)
333
+ return pd.DataFrame()
334
+
335
+ def view_logs():
336
+ df = get_logs()
337
+ if not df.empty:
338
+ try:
339
+ return df.to_markdown()
340
+ except ImportError:
341
+ return df.to_string()
342
+ return "No logs available yet"
343
+
344
+ def download_logs():
345
+ if LOG_FILE.exists():
346
+ temp_dir = SAVE_DIR / "temp_downloads"
347
+ temp_dir.mkdir(exist_ok=True)
348
+ download_path = temp_dir / "emotion_logs.csv"
349
+ shutil.copy2(LOG_FILE, download_path)
350
+ return str(download_path)
351
+ return None
352
+
353
+ def clear_all_data():
354
+ """Clear all images and logs"""
355
+ deleted_count = 0
356
+
357
+ for emotion in EMOTION_MAP.keys():
358
+ for img_type in ["faces", "annotated"]:
359
+ folder = SAVE_DIR / img_type / emotion
360
+ for file in folder.glob("*"):
361
+ if file.is_file():
362
+ try:
363
+ file.unlink()
364
+ deleted_count += 1
365
+ except Exception as e:
366
+ print(f"Error deleting {file}: {e}")
367
+
368
+ temp_dir = SAVE_DIR / "temp_downloads"
369
+ if temp_dir.exists():
370
+ try:
371
+ shutil.rmtree(temp_dir)
372
+ except Exception as e:
373
+ print(f"Error deleting temp directory: {e}")
374
+
375
+ if LOG_FILE.exists():
376
+ try:
377
+ LOG_FILE.unlink()
378
+ except Exception as e:
379
+ print(f"Error deleting log file: {e}")
380
+
381
+ try:
382
+ with open(LOG_FILE, 'w', newline='') as f:
383
+ writer = csv.writer(f)
384
+ writer.writerow(["timestamp", "batch_no", "emotion", "confidence", "face_path", "annotated_path"])
385
  except Exception as e:
386
+ print(f"Error recreating log file: {e}")
387
+
388
+ empty_df = pd.DataFrame(columns=["timestamp", "batch_no", "emotion", "confidence", "face_path", "annotated_path"])
389
+ return f"Deleted {deleted_count} items. All data has been cleared.", empty_df, None
390
 
391
+ # Capture Interface
392
+ with gr.Blocks(title="Emotion Capture", css="""
393
+ .gradio-container { max-width: 800px !important }
394
+ .message { color: red; font-weight: bold; }
395
+ .gallery { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
396
+ .disabled-input { background-color: #f0f0f0; }
397
+ .processing { color: orange; font-weight: bold; }
398
+ .success { color: green; font-weight: bold; }
399
+ """) as capture_interface:
400
+
401
+ gr.Markdown("""
402
+ # Emotion Capture Interface
403
+ 1. Enter/scan your batch number (numbers only)
404
+ 2. System will automatically proceed after 5 seconds of inactivity
405
+ 3. Webcam will activate for face capture
406
+ 4. View your emotion analysis results
407
+ 5. Click "Done" to reset the interface
408
+ """)
409
 
410
  with gr.Row():
411
+ batch_no = gr.Textbox(
412
  label="Batch Number",
413
+ placeholder="Enter or scan numbers only",
414
  interactive=True
415
  )
416
+
417
+ message = gr.Textbox(
418
+ label="Status",
419
+ interactive=False,
420
+ elem_classes="message",
421
+ visible=False
422
+ )
423
 
424
  with gr.Row():
425
  webcam = gr.Image(
426
  sources=["webcam"],
427
+ type="pil",
428
+ label="Face Capture",
429
+ interactive=True,
430
  mirror_webcam=True,
431
+ visible=False
 
432
  )
433
+
434
+ with gr.Row():
435
  result_img = gr.Image(
436
+ label="Analysis Result",
437
+ interactive=False,
438
  visible=False
439
  )
440
 
441
  with gr.Row():
442
  result_text = gr.Textbox(
443
+ label="Emotion Result",
444
+ interactive=False,
445
  visible=False
446
  )
447
+
448
+ with gr.Row():
449
  done_btn = gr.Button(
450
+ "Done",
451
  visible=False
452
  )
453
 
454
+ # Detect when user stops typing (with 5 second delay)
455
+ batch_no.change(
456
+ check_batch_no,
457
+ inputs=batch_no,
458
+ outputs=[batch_no, message, webcam, result_img, result_text, done_btn],
459
+ queue=False
460
+ ).then(
461
+ lambda: time.sleep(5), # Wait for 5 seconds of inactivity
462
+ None,
463
+ None,
464
+ queue=False
465
+ ).then(
466
+ activate_webcam,
467
+ inputs=batch_no,
468
+ outputs=[batch_no, message, webcam, result_img, result_text, done_btn],
469
+ queue=False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
  )
471
 
472
+ # Process when webcam captures an image
473
  webcam.change(
474
+ predict_emotion,
475
+ inputs=[batch_no, webcam],
476
+ outputs=[result_img, result_text, message, result_img, result_text, done_btn]
 
477
  )
478
 
479
+ # Reset interface when Done is clicked
 
 
 
 
 
 
 
 
 
 
480
  done_btn.click(
481
+ reset_interface,
482
+ outputs=[batch_no, message, webcam, result_img, result_text, done_btn]
483
  )
484
 
485
+ # Data Management Interface
486
+ with gr.Blocks(title="Data Management") as data_interface:
487
+
488
+ gr.Markdown("# Data Management Interface")
489
+
490
+ with gr.Tab("Image Management"):
491
+ with gr.Column():
492
+ gr.Markdown("## Select and Manage Images")
493
+ with gr.Row():
494
+ emotion_selector = gr.Dropdown(
495
+ choices=["All Emotions"] + list(EMOTION_MAP.keys()),
496
+ label="Emotion Category",
497
+ value="All Emotions"
498
+ )
499
+ image_type_selector = gr.Dropdown(
500
+ choices=["faces", "annotated"],
501
+ label="Image Type",
502
+ value="faces"
503
+ )
504
+ refresh_btn = gr.Button("Refresh Gallery")
505
+
506
+ current_image_paths = gr.State([])
507
+
508
+ gallery = gr.Gallery(
509
+ label="Image Gallery",
510
+ columns=4
511
+ )
512
+ selected_images = gr.CheckboxGroup(
513
+ label="Selected Images",
514
+ interactive=True,
515
+ value=[]
516
+ )
517
+
518
+ with gr.Row(variant="panel"):
519
+ with gr.Column():
520
+ gr.Markdown("### Download Options")
521
+ download_btn = gr.Button("Download Selected", variant="primary")
522
+ download_all_btn = gr.Button("Download All in Category")
523
+ download_structured_btn = gr.Button("Download All (Structured)", variant="primary")
524
+ download_output = gr.File(label="Download Result", visible=False)
525
+
526
+ with gr.Column():
527
+ gr.Markdown("### Delete Options")
528
+ delete_btn = gr.Button("Delete Selected", variant="stop")
529
+ with gr.Row():
530
+ delete_confirm = gr.Checkbox(label="I confirm I want to delete ALL images in this category", value=False)
531
+ delete_all_btn = gr.Button("Delete All in Category", variant="stop", interactive=False)
532
+ delete_output = gr.Textbox(label="Delete Status")
533
+
534
+ def update_gallery_components(emotion, image_type):
535
+ image_dict = get_image_gallery(emotion, image_type)
536
+ gallery_items = []
537
+ image_paths = []
538
+ for emotion, images in image_dict.items():
539
+ for img_path in images:
540
+ gallery_items.append((img_path, f"{emotion}: {Path(img_path).name}"))
541
+ image_paths.append(img_path)
542
+ return gallery_items, image_paths
543
+
544
+ initial_gallery, initial_paths = update_gallery_components("All Emotions", "faces")
545
+ gallery.value = initial_gallery
546
+ current_image_paths.value = initial_paths
547
+ selected_images.choices = initial_paths
548
+
549
+ def update_components(emotion, image_type):
550
+ gallery_items, image_paths = update_gallery_components(emotion, image_type)
551
+ return {
552
+ gallery: gallery_items,
553
+ current_image_paths: image_paths,
554
+ selected_images: gr.CheckboxGroup(choices=image_paths, value=[])
555
+ }
556
+
557
+ emotion_selector.change(
558
+ update_components,
559
+ inputs=[emotion_selector, image_type_selector],
560
+ outputs=[gallery, current_image_paths, selected_images]
561
+ )
562
+
563
+ image_type_selector.change(
564
+ update_components,
565
+ inputs=[emotion_selector, image_type_selector],
566
+ outputs=[gallery, current_image_paths, selected_images]
567
+ )
568
+
569
+ refresh_btn.click(
570
+ update_components,
571
+ inputs=[emotion_selector, image_type_selector],
572
+ outputs=[gallery, current_image_paths, selected_images]
573
+ )
574
+
575
+ download_btn.click(
576
+ lambda selected: create_custom_zip(selected),
577
+ inputs=selected_images,
578
+ outputs=download_output,
579
+ api_name="download_selected"
580
+ ).then(
581
+ lambda x: gr.File(visible=x is not None),
582
+ inputs=download_output,
583
+ outputs=download_output
584
+ )
585
+
586
+ download_all_btn.click(
587
+ lambda emotion, img_type: create_custom_zip(
588
+ [str(f) for f in (SAVE_DIR / img_type / (emotion if emotion != "All Emotions" else "*")).glob("*.jpg") if f.exists()]
589
+ ),
590
+ inputs=[emotion_selector, image_type_selector],
591
+ outputs=download_output,
592
+ api_name="download_all"
593
+ ).then(
594
+ lambda x: gr.File(visible=x is not None),
595
+ inputs=download_output,
596
+ outputs=download_output
597
+ )
598
+
599
+ download_structured_btn.click(
600
+ download_all_emotions_structured,
601
+ outputs=download_output,
602
+ api_name="download_all_structured"
603
+ ).then(
604
+ lambda x: gr.File(visible=x is not None),
605
+ inputs=download_output,
606
+ outputs=download_output
607
+ )
608
+
609
+ delete_btn.click(
610
+ lambda selected: {
611
+ "delete_output": delete_selected_images(selected),
612
+ **update_components(emotion_selector.value, image_type_selector.value)
613
+ },
614
+ inputs=selected_images,
615
+ outputs=[delete_output, gallery, current_image_paths, selected_images]
616
+ )
617
+
618
+ delete_confirm.change(
619
+ lambda x: gr.Button(interactive=x),
620
+ inputs=delete_confirm,
621
+ outputs=delete_all_btn
622
+ )
623
+
624
+ delete_all_btn.click(
625
+ lambda emotion, img_type, confirm: {
626
+ "delete_output": delete_images_in_category(emotion, img_type, confirm),
627
+ **update_components(emotion, img_type)
628
+ },
629
+ inputs=[emotion_selector, image_type_selector, delete_confirm],
630
+ outputs=[delete_output, gallery, current_image_paths, selected_images]
631
+ )
632
+
633
+ with gr.Tab("Emotion Logs"):
634
+ with gr.Column():
635
+ gr.Markdown("## Emotion Analysis Logs")
636
+ with gr.Row():
637
+ refresh_logs_btn = gr.Button("Refresh Logs")
638
+ download_logs_btn = gr.Button("Download Logs as CSV")
639
+ clear_all_btn = gr.Button("Clear All Data", variant="stop")
640
+
641
+ logs_display = gr.Markdown()
642
+ logs_csv = gr.File(label="Logs Download", visible=False)
643
+ clear_message = gr.Textbox(label="Status", interactive=False)
644
+
645
+ refresh_logs_btn.click(
646
+ view_logs,
647
+ outputs=logs_display
648
+ )
649
+
650
+ download_logs_btn.click(
651
+ download_logs,
652
+ outputs=logs_csv,
653
+ api_name="download_logs"
654
+ ).then(
655
+ lambda x: gr.File(visible=x is not None),
656
+ inputs=logs_csv,
657
+ outputs=logs_csv
658
+ )
659
+
660
+ clear_all_btn.click(
661
+ clear_all_data,
662
+ outputs=[clear_message, logs_display, logs_csv]
663
+ ).then(
664
+ lambda: update_components("All Emotions", "faces"),
665
+ outputs=[gallery, current_image_paths]
666
+ ).then(
667
+ lambda: gr.CheckboxGroup(choices=[], value=[]),
668
+ outputs=selected_images
669
+ )
670
+
671
+ # Combine interfaces
672
+ demo = gr.TabbedInterface(
673
+ [capture_interface, data_interface],
674
+ ["Emotion Capture", "Data Management"],
675
+ css=".gradio-container { max-width: 1200px !important }"
676
+ )
677
+
678
  if __name__ == "__main__":
679
+ demo.launch()