Spaces:
Sleeping
Sleeping
Commit
·
29d11d5
1
Parent(s):
9231da6
Corregido cálculo de brillo y contraste para evitar valores cero
Browse files- 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 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
result["
|
| 288 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 303 |
-
|
| 304 |
-
|
| 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
|