""" 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.""" @staticmethod 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() @staticmethod 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) @staticmethod 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) @staticmethod 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]) @staticmethod 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) @staticmethod 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 @staticmethod 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 @staticmethod 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