| from __future__ import annotations |
| import numpy as np |
| from PIL import Image |
| from typing import Dict, Tuple |
| from .utils import pil_to_np |
| from skimage.metrics import structural_similarity as ssim |
|
|
|
|
| def calculate_mse(original: Image.Image, reconstructed: Image.Image) -> float: |
| """ |
| Calculate Mean Squared Error between original and reconstructed images. |
| |
| Args: |
| original: Original PIL Image |
| reconstructed: Reconstructed PIL Image |
| |
| Returns: |
| MSE value |
| """ |
| orig_array = pil_to_np(original) |
| recon_array = pil_to_np(reconstructed) |
| |
| |
| if orig_array.shape != recon_array.shape: |
| |
| recon_pil = reconstructed.resize(original.size, Image.LANCZOS) |
| recon_array = pil_to_np(recon_pil) |
| |
| |
| mse = np.mean((orig_array - recon_array) ** 2) |
| return float(mse) |
|
|
|
|
| def calculate_psnr(original: Image.Image, reconstructed: Image.Image) -> float: |
| """ |
| Calculate Peak Signal-to-Noise Ratio. |
| |
| Args: |
| original: Original PIL Image |
| reconstructed: Reconstructed PIL Image |
| |
| Returns: |
| PSNR value in dB |
| """ |
| mse = calculate_mse(original, reconstructed) |
| if mse == 0: |
| return float('inf') |
| |
| psnr = 20 * np.log10(1.0 / np.sqrt(mse)) |
| return float(psnr) |
|
|
|
|
| def calculate_ssim(original: Image.Image, reconstructed: Image.Image) -> float: |
| """ |
| Calculate Structural Similarity Index. |
| |
| Args: |
| original: Original PIL Image |
| reconstructed: Reconstructed PIL Image |
| |
| Returns: |
| SSIM value between 0 and 1 |
| """ |
| orig_array = pil_to_np(original) |
| recon_array = pil_to_np(reconstructed) |
| |
| |
| if orig_array.shape != recon_array.shape: |
| |
| recon_pil = reconstructed.resize(original.size, Image.LANCZOS) |
| recon_array = pil_to_np(recon_pil) |
| |
| |
| if len(orig_array.shape) == 3: |
| orig_gray = np.mean(orig_array, axis=2) |
| recon_gray = np.mean(recon_array, axis=2) |
| else: |
| orig_gray = orig_array |
| recon_gray = recon_array |
| |
| |
| ssim_value = ssim(orig_gray, recon_gray, data_range=1.0) |
| return float(ssim_value) |
|
|
|
|
| def calculate_color_similarity(original: Image.Image, reconstructed: Image.Image) -> Dict[str, float]: |
| """ |
| Calculate color-based similarity metrics. |
| |
| Args: |
| original: Original PIL Image |
| reconstructed: Reconstructed PIL Image |
| |
| Returns: |
| Dictionary with color similarity metrics |
| """ |
| orig_array = pil_to_np(original) |
| recon_array = pil_to_np(reconstructed) |
| |
| |
| if orig_array.shape != recon_array.shape: |
| recon_pil = reconstructed.resize(original.size, Image.LANCZOS) |
| recon_array = pil_to_np(recon_pil) |
| |
| |
| channel_diffs = [] |
| for channel in range(3): |
| orig_channel = orig_array[:, :, channel] |
| recon_channel = recon_array[:, :, channel] |
| channel_mse = np.mean((orig_channel - recon_channel) ** 2) |
| channel_diffs.append(channel_mse) |
| |
| |
| color_mse = np.mean(channel_diffs) |
| |
| |
| orig_hist = np.histogram(orig_array.flatten(), bins=256, range=(0, 1))[0] |
| recon_hist = np.histogram(recon_array.flatten(), bins=256, range=(0, 1))[0] |
| |
| |
| orig_hist = orig_hist / np.sum(orig_hist) |
| recon_hist = recon_hist / np.sum(recon_hist) |
| |
| |
| hist_correlation = np.corrcoef(orig_hist, recon_hist)[0, 1] |
| |
| return { |
| 'color_mse': float(color_mse), |
| 'red_channel_mse': float(channel_diffs[0]), |
| 'green_channel_mse': float(channel_diffs[1]), |
| 'blue_channel_mse': float(channel_diffs[2]), |
| 'histogram_correlation': float(hist_correlation) if not np.isnan(hist_correlation) else 0.0 |
| } |
|
|
|
|
| def calculate_comprehensive_metrics(original: Image.Image, reconstructed: Image.Image) -> Dict[str, float]: |
| """ |
| Calculate comprehensive similarity metrics. |
| |
| Args: |
| original: Original PIL Image |
| reconstructed: Reconstructed PIL Image |
| |
| Returns: |
| Dictionary with all similarity metrics |
| """ |
| metrics = {} |
| |
| |
| metrics['mse'] = calculate_mse(original, reconstructed) |
| metrics['psnr'] = calculate_psnr(original, reconstructed) |
| metrics['ssim'] = calculate_ssim(original, reconstructed) |
| |
| |
| color_metrics = calculate_color_similarity(original, reconstructed) |
| metrics.update(color_metrics) |
| |
| |
| metrics['rmse'] = np.sqrt(metrics['mse']) |
| metrics['mae'] = calculate_mae(original, reconstructed) |
| |
| return metrics |
|
|
|
|
| def calculate_mae(original: Image.Image, reconstructed: Image.Image) -> float: |
| """ |
| Calculate Mean Absolute Error. |
| |
| Args: |
| original: Original PIL Image |
| reconstructed: Reconstructed PIL Image |
| |
| Returns: |
| MAE value |
| """ |
| orig_array = pil_to_np(original) |
| recon_array = pil_to_np(reconstructed) |
| |
| |
| if orig_array.shape != recon_array.shape: |
| recon_pil = reconstructed.resize(original.size, Image.LANCZOS) |
| recon_array = pil_to_np(recon_pil) |
| |
| |
| mae = np.mean(np.abs(orig_array - recon_array)) |
| return float(mae) |
|
|
|
|
| def interpret_metrics(metrics: Dict[str, float]) -> Dict[str, str]: |
| """ |
| Provide human-readable interpretations of metrics. |
| |
| Args: |
| metrics: Dictionary of metric values |
| |
| Returns: |
| Dictionary with interpretations |
| """ |
| interpretations = {} |
| |
| |
| mse = metrics.get('mse', 0) |
| if mse < 0.01: |
| interpretations['mse'] = "Excellent similarity" |
| elif mse < 0.05: |
| interpretations['mse'] = "Good similarity" |
| elif mse < 0.1: |
| interpretations['mse'] = "Moderate similarity" |
| else: |
| interpretations['mse'] = "Poor similarity" |
| |
| |
| psnr = metrics.get('psnr', 0) |
| if psnr > 40: |
| interpretations['psnr'] = "Excellent quality" |
| elif psnr > 30: |
| interpretations['psnr'] = "Good quality" |
| elif psnr > 20: |
| interpretations['psnr'] = "Acceptable quality" |
| else: |
| interpretations['psnr'] = "Poor quality" |
| |
| |
| ssim_val = metrics.get('ssim', 0) |
| if ssim_val > 0.9: |
| interpretations['ssim'] = "Very similar structure" |
| elif ssim_val > 0.7: |
| interpretations['ssim'] = "Similar structure" |
| elif ssim_val > 0.5: |
| interpretations['ssim'] = "Moderately similar structure" |
| else: |
| interpretations['ssim'] = "Different structure" |
| |
| return interpretations |
|
|