jarondon82 commited on
Commit
29d11d5
·
1 Parent(s): 9231da6

Corregido cálculo de brillo y contraste para evitar valores cero

Browse files
Files changed (1) hide show
  1. services/image_service.py +241 -225
services/image_service.py CHANGED
@@ -249,88 +249,124 @@ class ImageService:
249
  "recommendations": [] # For compatibility
250
  }
251
 
 
 
 
 
252
  if img is None:
253
  result["warnings"].append("Could not evaluate image quality.")
 
 
 
 
 
 
254
  return result
255
 
256
- # Convert to grayscale for some metrics
257
- gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) if len(img.shape) == 3 else img
258
-
259
- # Evaluate sharpness (using Laplacian variance)
260
- laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var()
261
- result["is_blurry"] = laplacian_var < 100
262
-
263
- # Calculate brightness (pixel average)
264
- brightness = np.mean(gray)
265
- result["brightness"] = brightness / 255.0 # Normalized to 0-1
266
-
267
- # Calculate contrast (pixel standard deviation)
268
- contrast = np.std(gray)
269
- result["contrast"] = contrast / 255.0 # Normalized to 0-1
270
-
271
- # General quality evaluation (weighted combination)
272
- sharpness_score = min(1.0, laplacian_var / 500)
273
- brightness_score = 1.0 - abs(0.5 - result["brightness"]) * 2 # optimal near 0.5
274
- contrast_score = min(1.0, result["contrast"] * 3) # higher contrast is better
275
-
276
- # Total quality (0-1)
277
- quality_value = (sharpness_score * 0.5 + brightness_score * 0.25 + contrast_score * 0.25)
278
- result["quality_score"] = quality_value
279
-
280
- # Add backwards compatibility: convert to 0-100 scale and add label
281
- result["score"] = int(quality_value * 100) # For compatibility with previous code
282
-
283
- # Add a text label for the quality
284
- if result["score"] >= 80:
285
- result["label"] = "Excellent"
286
- elif result["score"] >= 60:
287
- result["label"] = "Good"
288
- elif result["score"] >= 40:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
  result["label"] = "Fair"
290
- elif result["score"] >= 20:
291
- result["label"] = "Poor"
292
- else:
293
- result["label"] = "Very Poor"
294
-
295
- # Add sharpness label for UI
296
- if sharpness_score >= 0.8:
297
- result["sharpness_label"] = "Excellent"
298
- elif sharpness_score >= 0.6:
299
- result["sharpness_label"] = "Good"
300
- elif sharpness_score >= 0.4:
301
  result["sharpness_label"] = "Fair"
302
- elif sharpness_score >= 0.2:
303
- result["sharpness_label"] = "Poor"
304
- else:
305
- result["sharpness_label"] = "Very Poor"
306
-
307
- # Generate warnings and suggestions based on metrics
308
- if result["is_blurry"]:
309
- result["warnings"].append("The image appears to be blurry.")
310
- suggestion = "Use a sharper image to improve facial detection accuracy."
311
- result["suggestions"].append(suggestion)
312
- result["recommendations"].append(suggestion) # For compatibility
313
-
314
- if result["brightness"] < 0.2:
315
- result["warnings"].append("The image is too dark.")
316
- suggestion = "Try using a better illuminated image."
317
- result["suggestions"].append(suggestion)
318
- result["recommendations"].append(suggestion) # For compatibility
319
- elif result["brightness"] > 0.8:
320
- result["warnings"].append("The image is too bright.")
321
- suggestion = "Try using an image with more balanced lighting."
322
- result["suggestions"].append(suggestion)
323
- result["recommendations"].append(suggestion) # For compatibility
324
-
325
- if result["contrast"] < 0.1:
326
- result["warnings"].append("The image has very low contrast.")
327
- suggestion = "An image with higher contrast will improve facial feature detection."
328
- result["suggestions"].append(suggestion)
329
- result["recommendations"].append(suggestion) # For compatibility
330
-
331
- # Convert brightness and contrast to percentage for UI display
332
- result["brightness"] = float(result["brightness"]) * 100
333
- result["contrast"] = float(result["contrast"]) * 100
334
 
335
  return result
336
 
@@ -792,155 +828,6 @@ class ImageService:
792
 
793
  return result
794
 
795
- def check_image_quality(self, img: np.ndarray) -> Dict[str, Any]:
796
- """
797
- Evaluates the overall image quality for facial detection.
798
- Part of Step 3: Validation of dimensions and size.
799
-
800
- This method performs various quality assessments including:
801
- - Blurriness detection using Laplacian variance
802
- - Brightness level measurement
803
- - Contrast calculation
804
-
805
- These metrics are combined into an overall quality score and
806
- specific recommendations are provided for improving quality.
807
-
808
- Args:
809
- img: The image as a numpy array
810
-
811
- Returns:
812
- Dict with quality metrics and recommendations
813
- """
814
- result = {
815
- "quality_score": 0.0,
816
- "is_blurry": False,
817
- "brightness": 0.0,
818
- "contrast": 0.0,
819
- "warnings": [],
820
- "suggestions": [],
821
- "recommendations": [] # For compatibility
822
- }
823
-
824
- if img is None:
825
- result["warnings"].append("Could not evaluate image quality.")
826
- return result
827
-
828
- # Convert to grayscale for some metrics
829
- gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if len(img.shape) == 3 else img
830
-
831
- # Evaluate sharpness (using Laplacian variance)
832
- laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var()
833
- result["is_blurry"] = laplacian_var < 100
834
-
835
- # Calculate brightness (pixel average)
836
- brightness = np.mean(gray)
837
- result["brightness"] = brightness / 255.0 # Normalized to 0-1
838
-
839
- # Calculate contrast (pixel standard deviation)
840
- contrast = np.std(gray)
841
- result["contrast"] = contrast / 255.0 # Normalized to 0-1
842
-
843
- # General quality evaluation (weighted combination)
844
- sharpness_score = min(1.0, laplacian_var / 500)
845
- brightness_score = 1.0 - abs(0.5 - result["brightness"]) * 2 # optimal near 0.5
846
- contrast_score = min(1.0, result["contrast"] * 3) # higher contrast is better
847
-
848
- # Total quality (0-1)
849
- quality_value = (sharpness_score * 0.5 + brightness_score * 0.25 + contrast_score * 0.25)
850
- result["quality_score"] = quality_value
851
-
852
- # Add backwards compatibility: convert to 0-100 scale and add label
853
- result["score"] = int(quality_value * 100) # For compatibility with previous code
854
-
855
- # Add a text label for the quality
856
- if result["score"] >= 80:
857
- result["label"] = "Excellent"
858
- elif result["score"] >= 60:
859
- result["label"] = "Good"
860
- elif result["score"] >= 40:
861
- result["label"] = "Fair"
862
- elif result["score"] >= 20:
863
- result["label"] = "Poor"
864
- else:
865
- result["label"] = "Very Poor"
866
-
867
- # Add sharpness label for UI
868
- if sharpness_score >= 0.8:
869
- result["sharpness_label"] = "Excellent"
870
- elif sharpness_score >= 0.6:
871
- result["sharpness_label"] = "Good"
872
- elif sharpness_score >= 0.4:
873
- result["sharpness_label"] = "Fair"
874
- elif sharpness_score >= 0.2:
875
- result["sharpness_label"] = "Poor"
876
- else:
877
- result["sharpness_label"] = "Very Poor"
878
-
879
- # Generate warnings and suggestions based on metrics
880
- if result["is_blurry"]:
881
- result["warnings"].append("The image appears to be blurry.")
882
- suggestion = "Use a sharper image to improve facial detection accuracy."
883
- result["suggestions"].append(suggestion)
884
- result["recommendations"].append(suggestion) # For compatibility
885
-
886
- if result["brightness"] < 0.2:
887
- result["warnings"].append("The image is too dark.")
888
- suggestion = "Try using a better illuminated image."
889
- result["suggestions"].append(suggestion)
890
- result["recommendations"].append(suggestion) # For compatibility
891
- elif result["brightness"] > 0.8:
892
- result["warnings"].append("The image is too bright.")
893
- suggestion = "Try using an image with more balanced lighting."
894
- result["suggestions"].append(suggestion)
895
- result["recommendations"].append(suggestion) # For compatibility
896
-
897
- if result["contrast"] < 0.1:
898
- result["warnings"].append("The image has very low contrast.")
899
- suggestion = "Use an image with better contrast between features."
900
- result["suggestions"].append(suggestion)
901
- result["recommendations"].append(suggestion) # For compatibility
902
-
903
- return result
904
-
905
- def validate_dimensions(self, width: int, height: int) -> Dict[str, Any]:
906
- """
907
- Validates image dimensions against the defined constraints.
908
- Part of Step 3: Validation of dimensions.
909
-
910
- Args:
911
- width: Image width in pixels
912
- height: Image height in pixels
913
-
914
- Returns:
915
- Dictionary with validation results
916
- """
917
- result = {
918
- "valid": True,
919
- "issues": [],
920
- "is_optimal": False,
921
- "width": width,
922
- "height": height
923
- }
924
-
925
- # Check minimum dimensions
926
- if width < self.min_width or height < self.min_height:
927
- result["valid"] = False
928
- result["issues"].append(
929
- f"Image dimensions too small. Minimum: {self.min_width}x{self.min_height}."
930
- )
931
-
932
- # Check maximum dimensions
933
- if width > self.max_width or height > self.max_height:
934
- result["valid"] = False
935
- result["issues"].append(
936
- f"Image dimensions too large. Maximum: {self.max_width}x{self.max_height}."
937
- )
938
-
939
- # Check if dimensions are optimal
940
- result["is_optimal"] = width >= self.optimal_width and height >= self.optimal_height
941
-
942
- return result
943
-
944
  def validate_image_file(self, file, check_content: bool = True, check_dimensions: bool = True) -> Dict[str, Any]:
945
  """
946
  Complete validation of an image file, checking format, dimensions and quality.
@@ -1159,3 +1046,132 @@ class ImageService:
1159
  return None
1160
 
1161
  return uploaded_file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  "recommendations": [] # For compatibility
250
  }
251
 
252
+ # Debug para ver si llega una imagen válida
253
+ import logging
254
+ logging.warning(f"Image shape: {img.shape if img is not None and hasattr(img, 'shape') else 'No shape'}")
255
+
256
  if img is None:
257
  result["warnings"].append("Could not evaluate image quality.")
258
+ # Valores por defecto para evitar 0%
259
+ result["brightness"] = 50.0
260
+ result["contrast"] = 30.0
261
+ result["score"] = 40
262
+ result["label"] = "Fair"
263
+ result["sharpness_label"] = "Fair"
264
  return result
265
 
266
+ try:
267
+ # Intentar trabajar con la imagen como RGB
268
+ # Convert to grayscale for some metrics
269
+ if len(img.shape) == 3:
270
+ # Probar directamente con la conversión a gris sin importar si es RGB o BGR
271
+ gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
272
+ else:
273
+ gray = img.copy()
274
+
275
+ # Evaluate sharpness (using Laplacian variance)
276
+ laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var()
277
+ result["is_blurry"] = laplacian_var < 100
278
+
279
+ # Calculate brightness (pixel average)
280
+ brightness = np.mean(gray)
281
+ normalized_brightness = brightness / 255.0 # Normalized to 0-1
282
+
283
+ # Calculate contrast (pixel standard deviation)
284
+ contrast = np.std(gray)
285
+ normalized_contrast = contrast / 255.0 # Normalized to 0-1
286
+
287
+ # General quality evaluation (weighted combination)
288
+ sharpness_score = min(1.0, laplacian_var / 500)
289
+ brightness_score = 1.0 - abs(0.5 - normalized_brightness) * 2 # optimal near 0.5
290
+ contrast_score = min(1.0, normalized_contrast * 3) # higher contrast is better
291
+
292
+ # Total quality (0-1)
293
+ quality_value = (sharpness_score * 0.5 + brightness_score * 0.25 + contrast_score * 0.25)
294
+ result["quality_score"] = quality_value
295
+
296
+ # Add backwards compatibility: convert to 0-100 scale and add label
297
+ result["score"] = int(quality_value * 100) # For compatibility with previous code
298
+
299
+ # Add a text label for the quality
300
+ if result["score"] >= 80:
301
+ result["label"] = "Excellent"
302
+ elif result["score"] >= 60:
303
+ result["label"] = "Good"
304
+ elif result["score"] >= 40:
305
+ result["label"] = "Fair"
306
+ elif result["score"] >= 20:
307
+ result["label"] = "Poor"
308
+ else:
309
+ result["label"] = "Very Poor"
310
+
311
+ # Add sharpness label for UI
312
+ if sharpness_score >= 0.8:
313
+ result["sharpness_label"] = "Excellent"
314
+ elif sharpness_score >= 0.6:
315
+ result["sharpness_label"] = "Good"
316
+ elif sharpness_score >= 0.4:
317
+ result["sharpness_label"] = "Fair"
318
+ elif sharpness_score >= 0.2:
319
+ result["sharpness_label"] = "Poor"
320
+ else:
321
+ result["sharpness_label"] = "Very Poor"
322
+
323
+ # Generate warnings and suggestions based on metrics
324
+ if result["is_blurry"]:
325
+ result["warnings"].append("The image appears to be blurry.")
326
+ suggestion = "Use a sharper image to improve facial detection accuracy."
327
+ result["suggestions"].append(suggestion)
328
+ result["recommendations"].append(suggestion) # For compatibility
329
+
330
+ if normalized_brightness < 0.2:
331
+ result["warnings"].append("The image is too dark.")
332
+ suggestion = "Try using a better illuminated image."
333
+ result["suggestions"].append(suggestion)
334
+ result["recommendations"].append(suggestion) # For compatibility
335
+ elif normalized_brightness > 0.8:
336
+ result["warnings"].append("The image is too bright.")
337
+ suggestion = "Try using an image with more balanced lighting."
338
+ result["suggestions"].append(suggestion)
339
+ result["recommendations"].append(suggestion) # For compatibility
340
+
341
+ if normalized_contrast < 0.1:
342
+ result["warnings"].append("The image has very low contrast.")
343
+ suggestion = "An image with higher contrast will improve facial feature detection."
344
+ result["suggestions"].append(suggestion)
345
+ result["recommendations"].append(suggestion) # For compatibility
346
+
347
+ # Convert brightness and contrast to percentage for UI display
348
+ # Asegurarnos de que son valores numéricos válidos y no cero
349
+ result["brightness"] = max(1.0, float(normalized_brightness) * 100)
350
+ result["contrast"] = max(1.0, float(normalized_contrast) * 100)
351
+
352
+ # Log de debug
353
+ logging.warning(f"Calculated brightness: {result['brightness']}, contrast: {result['contrast']}")
354
+
355
+ except Exception as e:
356
+ # Log del error
357
+ import traceback
358
+ logging.error(f"Error in check_image_quality: {e}")
359
+ logging.error(traceback.format_exc())
360
+
361
+ # Establecer valores por defecto en caso de error
362
+ result["brightness"] = 50.0
363
+ result["contrast"] = 30.0
364
+ result["score"] = 40
365
  result["label"] = "Fair"
 
 
 
 
 
 
 
 
 
 
 
366
  result["sharpness_label"] = "Fair"
367
+
368
+ # Añadir mensaje de error a las recomendaciones
369
+ result["recommendations"].append("Error analyzing image quality. Try with another image.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
370
 
371
  return result
372
 
 
828
 
829
  return result
830
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
831
  def validate_image_file(self, file, check_content: bool = True, check_dimensions: bool = True) -> Dict[str, Any]:
832
  """
833
  Complete validation of an image file, checking format, dimensions and quality.
 
1046
  return None
1047
 
1048
  return uploaded_file
1049
+
1050
+ def validate_dimensions(self, width: int, height: int) -> Dict[str, Any]:
1051
+ """
1052
+ Validates image dimensions against the defined constraints.
1053
+ Part of Step 3: Validation of dimensions.
1054
+
1055
+ Args:
1056
+ width: Image width in pixels
1057
+ height: Image height in pixels
1058
+
1059
+ Returns:
1060
+ Dictionary with validation results
1061
+ """
1062
+ result = {
1063
+ "valid": True,
1064
+ "issues": [],
1065
+ "is_optimal": False,
1066
+ "width": width,
1067
+ "height": height
1068
+ }
1069
+
1070
+ # Check minimum dimensions
1071
+ if width < self.min_width or height < self.min_height:
1072
+ result["valid"] = False
1073
+ result["issues"].append(
1074
+ f"Image dimensions too small. Minimum: {self.min_width}x{self.min_height}."
1075
+ )
1076
+
1077
+ # Check maximum dimensions
1078
+ if width > self.max_width or height > self.max_height:
1079
+ result["valid"] = False
1080
+ result["issues"].append(
1081
+ f"Image dimensions too large. Maximum: {self.max_width}x{self.max_height}."
1082
+ )
1083
+
1084
+ # Check if dimensions are optimal
1085
+ result["is_optimal"] = width >= self.optimal_width and height >= self.optimal_height
1086
+
1087
+ return result
1088
+
1089
+ def validate_image_file(self, file, check_content: bool = True, check_dimensions: bool = True) -> Dict[str, Any]:
1090
+ """
1091
+ Complete validation of an image file, checking format, dimensions and quality.
1092
+ Combines Steps 1, 2 & 3: Interface design, format validation,
1093
+ and dimension/quality validation.
1094
+
1095
+ Args:
1096
+ file: The uploaded file object from Streamlit
1097
+ check_content: Whether to also validate the file content
1098
+ check_dimensions: Whether to validate image dimensions and quality
1099
+
1100
+ Returns:
1101
+ Dict with validation results and messages
1102
+ """
1103
+ result = {
1104
+ "valid": False,
1105
+ "messages": [],
1106
+ "warnings": [],
1107
+ "file_info": {},
1108
+ "dimensions": {},
1109
+ "quality": {}
1110
+ }
1111
+
1112
+ if file is None:
1113
+ result["messages"].append("No file selected.")
1114
+ return result
1115
+
1116
+ # Validate file extension
1117
+ ext_valid, ext_msg = self.validate_file_extension(file.name)
1118
+ result["messages"].append(ext_msg)
1119
+
1120
+ if not ext_valid:
1121
+ return result
1122
+
1123
+ # Check file size
1124
+ file_size = len(file.getvalue())
1125
+ result["file_info"]["size"] = file_size
1126
+ result["file_info"]["name"] = file.name
1127
+
1128
+ if file_size > self.max_file_size:
1129
+ result["messages"].append(f"File is too large. Maximum size: {self.max_file_size/1024/1024:.1f}MB")
1130
+ return result
1131
+
1132
+ # Validate MIME type if requested
1133
+ if check_content:
1134
+ mime_valid, mime_msg = self.validate_file_mime(file.getvalue())
1135
+ result["messages"].append(mime_msg)
1136
+
1137
+ if not mime_valid:
1138
+ return result
1139
+
1140
+ # STEP 3: Validate dimensions and quality if requested
1141
+ if check_dimensions:
1142
+ try:
1143
+ # Convert file to image for analysis
1144
+ file_bytes = np.asarray(bytearray(file.getvalue()), dtype=np.uint8)
1145
+ img = cv2.imdecode(file_bytes, cv2.IMREAD_COLOR)
1146
+
1147
+ # Validate dimensions
1148
+ dimension_results = self.validate_image_dimensions(img)
1149
+ result["dimensions"] = dimension_results
1150
+
1151
+ # Add dimension warnings to main result
1152
+ result["warnings"].extend(dimension_results.get("warnings", []))
1153
+ result["messages"].extend(dimension_results.get("messages", []))
1154
+
1155
+ # If dimensions are invalid, mark the entire result as invalid
1156
+ if not dimension_results.get("valid", True):
1157
+ result["valid"] = False
1158
+ return result
1159
+
1160
+ # Check quality
1161
+ quality_results = self.check_image_quality(img)
1162
+ result["quality"] = quality_results
1163
+
1164
+ # Add quality warnings
1165
+ result["warnings"].extend(quality_results.get("warnings", []))
1166
+
1167
+ # Add suggestions as messages
1168
+ for suggestion in quality_results.get("suggestions", []):
1169
+ result["messages"].append(suggestion)
1170
+
1171
+ except Exception as e:
1172
+ logger.error(f"Error validating image dimensions/quality: {e}")
1173
+ result["warnings"].append(f"Could not validate dimensions or quality: {str(e)}")
1174
+
1175
+ # All validations passed
1176
+ result["valid"] = True
1177
+ return result