Spaces:
Running
Running
| """ | |
| Technical metrics for image quality assessment without using AI models. | |
| These metrics evaluate basic technical aspects of images like sharpness, noise, etc. | |
| """ | |
| import numpy as np | |
| import cv2 | |
| from skimage.metrics import structural_similarity as ssim | |
| from skimage.measure import shannon_entropy | |
| from PIL import Image, ImageStat | |
| class TechnicalMetrics: | |
| """Class for computing technical image quality metrics.""" | |
| def calculate_sharpness(image_array): | |
| """ | |
| Calculate image sharpness using Laplacian variance. | |
| Higher values indicate sharper images. | |
| Args: | |
| image_array: numpy array of the image | |
| Returns: | |
| float: sharpness score | |
| """ | |
| if len(image_array.shape) == 3: | |
| gray = cv2.cvtColor(image_array, cv2.COLOR_RGB2GRAY) | |
| else: | |
| gray = image_array | |
| # Calculate variance of Laplacian | |
| return cv2.Laplacian(gray, cv2.CV_64F).var() | |
| def calculate_noise(image_array): | |
| """ | |
| Estimate image noise level. | |
| Lower values indicate less noisy images. | |
| Args: | |
| image_array: numpy array of the image | |
| Returns: | |
| float: noise level | |
| """ | |
| if len(image_array.shape) == 3: | |
| gray = cv2.cvtColor(image_array, cv2.COLOR_RGB2GRAY) | |
| else: | |
| gray = image_array | |
| # Estimate noise using median filter difference | |
| denoised = cv2.medianBlur(gray, 5) | |
| diff = cv2.absdiff(gray, denoised) | |
| return np.mean(diff) | |
| def calculate_contrast(image_array): | |
| """ | |
| Calculate image contrast. | |
| Higher values indicate higher contrast. | |
| Args: | |
| image_array: numpy array of the image | |
| Returns: | |
| float: contrast score | |
| """ | |
| if len(image_array.shape) == 3: | |
| gray = cv2.cvtColor(image_array, cv2.COLOR_RGB2GRAY) | |
| else: | |
| gray = image_array | |
| # Calculate standard deviation as a measure of contrast | |
| return np.std(gray) | |
| def calculate_saturation(image_array): | |
| """ | |
| Calculate color saturation. | |
| Higher values indicate more saturated colors. | |
| Args: | |
| image_array: numpy array of the image | |
| Returns: | |
| float: saturation score | |
| """ | |
| if len(image_array.shape) != 3: | |
| return 0.0 # Grayscale images have no saturation | |
| # Convert to HSV and calculate mean saturation | |
| hsv = cv2.cvtColor(image_array, cv2.COLOR_RGB2HSV) | |
| return np.mean(hsv[:, :, 1]) | |
| def calculate_entropy(image_array): | |
| """ | |
| Calculate image entropy as a measure of detail/complexity. | |
| Higher values indicate more complex images. | |
| Args: | |
| image_array: numpy array of the image | |
| Returns: | |
| float: entropy score | |
| """ | |
| if len(image_array.shape) == 3: | |
| gray = cv2.cvtColor(image_array, cv2.COLOR_RGB2GRAY) | |
| else: | |
| gray = image_array | |
| return shannon_entropy(gray) | |
| def detect_compression_artifacts(image_array): | |
| """ | |
| Detect JPEG compression artifacts. | |
| Higher values indicate more artifacts. | |
| Args: | |
| image_array: numpy array of the image | |
| Returns: | |
| float: artifact score | |
| """ | |
| if len(image_array.shape) == 3: | |
| gray = cv2.cvtColor(image_array, cv2.COLOR_RGB2GRAY) | |
| else: | |
| gray = image_array | |
| # Apply edge detection to find blocky artifacts | |
| edges = cv2.Canny(gray, 100, 200) | |
| return np.mean(edges) / 255.0 | |
| def calculate_dynamic_range(image_array): | |
| """ | |
| Calculate dynamic range of the image. | |
| Higher values indicate better use of available intensity range. | |
| Args: | |
| image_array: numpy array of the image | |
| Returns: | |
| float: dynamic range score | |
| """ | |
| if len(image_array.shape) == 3: | |
| gray = cv2.cvtColor(image_array, cv2.COLOR_RGB2GRAY) | |
| else: | |
| gray = image_array | |
| p1 = np.percentile(gray, 1) | |
| p99 = np.percentile(gray, 99) | |
| return (p99 - p1) / 255.0 | |
| def calculate_all_metrics(image_path): | |
| """ | |
| Calculate all technical metrics for an image. | |
| Args: | |
| image_path: path to the image file | |
| Returns: | |
| dict: dictionary with all metric scores | |
| """ | |
| # Load image with PIL for metadata | |
| pil_image = Image.open(image_path) | |
| # Convert to numpy array for OpenCV processing | |
| image_array = np.array(pil_image) | |
| # Calculate all metrics | |
| metrics = { | |
| 'sharpness': TechnicalMetrics.calculate_sharpness(image_array), | |
| 'noise': TechnicalMetrics.calculate_noise(image_array), | |
| 'contrast': TechnicalMetrics.calculate_contrast(image_array), | |
| 'saturation': TechnicalMetrics.calculate_saturation(image_array), | |
| 'entropy': TechnicalMetrics.calculate_entropy(image_array), | |
| 'compression_artifacts': TechnicalMetrics.detect_compression_artifacts(image_array), | |
| 'dynamic_range': TechnicalMetrics.calculate_dynamic_range(image_array), | |
| 'resolution': f"{pil_image.width}x{pil_image.height}", | |
| 'aspect_ratio': pil_image.width / pil_image.height if pil_image.height > 0 else 0, | |
| 'file_size_kb': pil_image.fp.tell() / 1024 if hasattr(pil_image.fp, 'tell') else 0, | |
| } | |
| return metrics | |