jarondon82 commited on
Commit
d23e909
1 Parent(s): b9be998

Implemented simplified face detection and labeling interface with cleaner UI and minimal complexity

Browse files
Files changed (2) hide show
  1. app.py +3 -4
  2. utils/simple_face_labeling.py +297 -0
app.py CHANGED
@@ -12,15 +12,14 @@ from datetime import datetime
12
  from PIL import Image
13
  import numpy as np
14
  import cv2
15
- import json
16
- import pandas as pd
17
 
18
  # Import app modules
19
  from config import settings
20
  from agent_framework.agent_manager import AgentManager
21
  from utils.file_utils import allowed_file, save_uploaded_file
22
  from utils.export_utils import get_download_link
23
- from utils.preprocessing_ui import display_preprocessing_comparison, setup_preprocessing_controls, display_processing_status, get_processing_image, show_preprocessing_ui
 
24
  from utils.face_validation import validate_image_faces, display_face_validation_result, should_continue_processing
25
  from services.database_service import DatabaseService
26
  from services.image_service import ImageService
@@ -493,7 +492,7 @@ elif page == "Visual Analysis":
493
  # Solo proceder si tenemos una imagen para procesar
494
  if img_to_use is not None:
495
  # Usar nuestro nuevo m贸dulo para la detecci贸n y etiquetado facial
496
- face_detection_result = show_face_detection_and_labeling_ui(img_to_use, face_service)
497
 
498
  # Guardar el resultado para la secci贸n de an谩lisis emocional
499
  if face_detection_result.get("proceed_to_analysis", False):
 
12
  from PIL import Image
13
  import numpy as np
14
  import cv2
 
 
15
 
16
  # Import app modules
17
  from config import settings
18
  from agent_framework.agent_manager import AgentManager
19
  from utils.file_utils import allowed_file, save_uploaded_file
20
  from utils.export_utils import get_download_link
21
+ from utils.preprocessing import show_preprocessing_ui
22
+ from utils.simple_face_labeling import simple_face_detection_and_labeling_ui
23
  from utils.face_validation import validate_image_faces, display_face_validation_result, should_continue_processing
24
  from services.database_service import DatabaseService
25
  from services.image_service import ImageService
 
492
  # Solo proceder si tenemos una imagen para procesar
493
  if img_to_use is not None:
494
  # Usar nuestro nuevo m贸dulo para la detecci贸n y etiquetado facial
495
+ face_detection_result = simple_face_detection_and_labeling_ui(img_to_use, face_service)
496
 
497
  # Guardar el resultado para la secci贸n de an谩lisis emocional
498
  if face_detection_result.get("proceed_to_analysis", False):
utils/simple_face_labeling.py ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Simplified face labeling module.
3
+
4
+ Provides a streamlined UI for face detection and labeling with minimal complexity.
5
+ """
6
+ import streamlit as st
7
+ import numpy as np
8
+ import cv2
9
+ from typing import List, Dict, Tuple, Any, Set
10
+
11
+ def draw_numbered_faces(image: np.ndarray, faces: List[Tuple[int, int, int, int]],
12
+ max_faces: int = 5) -> np.ndarray:
13
+ """
14
+ Draw numbered rectangles on detected faces.
15
+
16
+ Args:
17
+ image: Image in numpy array format (RGB)
18
+ faces: List of tuples (x, y, w, h) with face coordinates
19
+ max_faces: Maximum number of faces to display
20
+
21
+ Returns:
22
+ Image with labeled faces
23
+ """
24
+ # Work with a copy to avoid modifying the original
25
+ labeled_img = image.copy()
26
+
27
+ # Limit to max_faces faces
28
+ faces_to_draw = faces[:max_faces] if len(faces) > max_faces else faces
29
+
30
+ # Get removed faces set (if exists)
31
+ removed_faces = st.session_state.get("removed_faces", set())
32
+
33
+ # Draw each face
34
+ for i, (x, y, w, h) in enumerate(faces_to_draw):
35
+ face_key = f"face_{i}"
36
+
37
+ # Skip if this face was marked as removed
38
+ if face_key in removed_faces:
39
+ continue
40
+
41
+ # Draw green rectangle
42
+ cv2.rectangle(labeled_img, (x, y), (x+w, y+h), (0, 255, 0), 2)
43
+
44
+ # Add numbered label
45
+ label = f"Face {i+1}"
46
+ cv2.putText(labeled_img, label, (x, y-10),
47
+ cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)
48
+
49
+ return labeled_img
50
+
51
+ def extract_face_thumbnails(image: np.ndarray, faces: List[Tuple[int, int, int, int]],
52
+ max_faces: int = 5) -> Dict[int, np.ndarray]:
53
+ """
54
+ Extracts thumbnails of detected faces.
55
+
56
+ Args:
57
+ image: Image in numpy array format (RGB)
58
+ faces: List of tuples (x, y, w, h) with face coordinates
59
+ max_faces: Maximum number of faces to process
60
+
61
+ Returns:
62
+ Dictionary with face index and its cropped image
63
+ """
64
+ thumbnails = {}
65
+
66
+ # Limit to max_faces faces
67
+ faces_to_extract = faces[:max_faces] if len(faces) > max_faces else faces
68
+
69
+ # Extract each thumbnail
70
+ for i, (x, y, w, h) in enumerate(faces_to_extract):
71
+ # Apply a small margin around the face if possible
72
+ margin = int(min(w, h) * 0.1) # 10% margin
73
+
74
+ # Ensure we don't go out of the image bounds
75
+ img_h, img_w = image.shape[:2]
76
+ x_start = max(0, x - margin)
77
+ y_start = max(0, y - margin)
78
+ x_end = min(img_w, x + w + margin)
79
+ y_end = min(img_h, y + h + margin)
80
+
81
+ # Extract the thumbnail with margin
82
+ face_thumbnail = image[y_start:y_end, x_start:x_end]
83
+ thumbnails[i] = face_thumbnail
84
+
85
+ return thumbnails
86
+
87
+ def simple_face_labeling_ui(image: np.ndarray, faces: List[Tuple[int, int, int, int]],
88
+ max_faces: int = 5) -> Dict[str, Any]:
89
+ """
90
+ Displays a simplified interface for labeling faces.
91
+
92
+ Args:
93
+ image: Image in numpy array format (RGB)
94
+ faces: List of tuples (x, y, w, h) with face coordinates
95
+ max_faces: Maximum number of faces to process
96
+
97
+ Returns:
98
+ Dictionary with information about labeled faces
99
+ """
100
+ # Initialize session state for face labels and removed faces
101
+ if "face_labels" not in st.session_state:
102
+ st.session_state.face_labels = {}
103
+
104
+ if "removed_faces" not in st.session_state:
105
+ st.session_state.removed_faces = set()
106
+
107
+ # Limit to max_faces faces
108
+ faces_to_show = faces[:max_faces] if len(faces) > max_faces else faces
109
+ num_faces = len(faces_to_show)
110
+
111
+ # Display labeled image
112
+ labeled_image = draw_numbered_faces(image, faces_to_show)
113
+ st.image(labeled_image, caption="Detected Faces", use_column_width=True)
114
+
115
+ # Only proceed if faces were detected
116
+ if num_faces > 0:
117
+ st.success(f"{num_faces} face(s) detected in the image")
118
+
119
+ # Extract thumbnails
120
+ thumbnails = extract_face_thumbnails(image, faces_to_show)
121
+
122
+ # Create a form for labeling
123
+ st.subheader("Enter names for detected faces")
124
+
125
+ # Create a simple list of faces with names and remove buttons
126
+ for i, (x, y, w, h) in enumerate(faces_to_show):
127
+ face_key = f"face_{i}"
128
+
129
+ # Skip if this face was marked as removed
130
+ if face_key in st.session_state.removed_faces:
131
+ continue
132
+
133
+ # Create a row with thumbnail, name field and remove button
134
+ cols = st.columns([1, 3, 1])
135
+
136
+ with cols[0]:
137
+ # Display thumbnail
138
+ if i in thumbnails:
139
+ st.image(thumbnails[i], caption=f"Face {i+1}", width=80)
140
+
141
+ with cols[1]:
142
+ # Input field for name
143
+ label = st.text_input(
144
+ f"Name for Face {i+1}:",
145
+ key=f"label_{face_key}",
146
+ value=st.session_state.face_labels.get(face_key, "")
147
+ )
148
+ # Save to session state
149
+ st.session_state.face_labels[face_key] = label
150
+
151
+ with cols[2]:
152
+ # Remove button
153
+ if st.button("Remove", key=f"remove_{face_key}"):
154
+ st.session_state.removed_faces.add(face_key)
155
+ # Delete any existing label
156
+ if face_key in st.session_state.face_labels:
157
+ del st.session_state.face_labels[face_key]
158
+
159
+ # Prepare result data
160
+ result = {
161
+ "success": True,
162
+ "num_faces": num_faces,
163
+ "labeled_image": labeled_image
164
+ }
165
+
166
+ # Add face data
167
+ labeled_faces = {}
168
+ for i, (x, y, w, h) in enumerate(faces_to_show):
169
+ face_key = f"face_{i}"
170
+
171
+ # Skip removed faces
172
+ if face_key in st.session_state.removed_faces:
173
+ continue
174
+
175
+ # Check if this face has a label
176
+ label = st.session_state.face_labels.get(face_key, "")
177
+ if label:
178
+ labeled_faces[face_key] = {
179
+ "index": i,
180
+ "label": label,
181
+ "coordinates": (x, y, w, h)
182
+ }
183
+
184
+ # Add to result
185
+ result["labeled_faces"] = labeled_faces
186
+ result["can_proceed"] = len(labeled_faces) > 0
187
+
188
+ # Show proceed button if at least one face is labeled
189
+ if result["can_proceed"]:
190
+ if st.button("Continue to Analysis", key="continue_to_analysis"):
191
+ result["proceed_to_analysis"] = True
192
+ else:
193
+ result["proceed_to_analysis"] = False
194
+ else:
195
+ st.warning("Please provide at least one name to continue to analysis.")
196
+ result["proceed_to_analysis"] = False
197
+
198
+ return result
199
+ else:
200
+ st.warning("No faces detected in the image.")
201
+ return {
202
+ "success": False,
203
+ "num_faces": 0,
204
+ "message": "No faces detected in the image."
205
+ }
206
+
207
+ def simple_face_detection_and_labeling_ui(image: np.ndarray, face_service: Any) -> Dict[str, Any]:
208
+ """
209
+ Main function for simplified face detection and labeling.
210
+
211
+ Args:
212
+ image: Image in numpy array format (RGB)
213
+ face_service: Face detection service
214
+
215
+ Returns:
216
+ Dictionary with processed results
217
+ """
218
+ # Ensure we have an image
219
+ if image is None:
220
+ st.warning("No image available for processing.")
221
+ return {
222
+ "success": False,
223
+ "message": "No image available for processing."
224
+ }
225
+
226
+ # Set maximum faces
227
+ max_faces = 5
228
+
229
+ # Convert to BGR for detection if needed
230
+ img_bgr = None
231
+ if len(image.shape) == 3 and image.shape[2] == 3:
232
+ img_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
233
+ else:
234
+ img_bgr = image.copy()
235
+
236
+ # Perform face detection
237
+ with st.spinner("Detecting faces..."):
238
+ faces = face_service.detect_faces(img_bgr)
239
+
240
+ # Check if any faces were detected
241
+ if faces is None or len(faces) == 0:
242
+ st.warning("No faces detected in the image.")
243
+ st.image(image, caption="Uploaded image (no faces detected)", use_column_width=True)
244
+ return {
245
+ "success": False,
246
+ "message": "No faces detected in the image."
247
+ }
248
+
249
+ # Save detected faces in session state
250
+ st.session_state["detected_faces"] = faces
251
+
252
+ # Show the simple labeling UI
253
+ labeling_result = simple_face_labeling_ui(image, faces, max_faces)
254
+
255
+ # Handle result
256
+ if labeling_result.get("proceed_to_analysis", False):
257
+ # Prepare data for analysis
258
+ faces_to_analyze = []
259
+ labeled_faces = labeling_result.get("labeled_faces", {})
260
+
261
+ # Process each labeled face
262
+ for face_key, face_info in labeled_faces.items():
263
+ index = face_info.get("index", 0)
264
+ label = face_info.get("label", "")
265
+ coords = face_info.get("coordinates", (0, 0, 0, 0))
266
+
267
+ # Extract thumbnail
268
+ x, y, w, h = coords
269
+ margin = int(min(w, h) * 0.1)
270
+ img_h, img_w = image.shape[:2]
271
+ x_start = max(0, x - margin)
272
+ y_start = max(0, y - margin)
273
+ x_end = min(img_w, x + w + margin)
274
+ y_end = min(img_h, y + h + margin)
275
+ thumbnail = image[y_start:y_end, x_start:x_end]
276
+
277
+ # Add to faces to analyze
278
+ faces_to_analyze.append({
279
+ "key": face_key,
280
+ "label": label,
281
+ "coordinates": coords,
282
+ "thumbnail": thumbnail
283
+ })
284
+
285
+ # Return analysis data
286
+ return {
287
+ "success": True,
288
+ "proceed_to_analysis": True,
289
+ "faces_to_analyze": faces_to_analyze
290
+ }
291
+
292
+ # Return result without proceeding
293
+ return {
294
+ "success": labeling_result.get("success", False),
295
+ "proceed_to_analysis": False,
296
+ "message": labeling_result.get("message", "")
297
+ }