LEGENDFTW/image-filtering-explorer
0
1"""metrics.py — Image quality and noise assessment."""2 3import numpy as np4import cv25from skimage.metrics import structural_similarity as ssim_fn6from skimage.metrics import peak_signal_noise_ratio as psnr_fn7 8 9def compute_metrics(original: np.ndarray, filtered: np.ndarray) -> dict:10 """11 Compute image quality metrics between the original (noisy) input12 and the filtered output.13 """14 orig = original.astype(np.float32)15 filt = filtered.astype(np.float32)16 17 # PSNR — higher is better (more similar images)18 # We clamp to avoid log(0); identical images → inf, we cap at 60 dB19 mse = np.mean((orig - filt) ** 2)20 if mse == 0:21 psnr = 60.022 else:23 psnr = float(psnr_fn(original, filtered, data_range=255))24 psnr = min(psnr, 60.0)25 26 # SSIM — structural similarity, 0–127 ssim_val = float(28 ssim_fn(original, filtered, data_range=255, channel_axis=-1)29 )30 31 # Mean absolute difference32 mean_diff = float(np.mean(np.abs(orig - filt)))33 34 # Noise reduction estimate: compare std-dev of high-frequency residual35 # (Laplacian response) before and after36 lp_in = _laplacian_std(original)37 lp_out = _laplacian_std(filtered)38 if lp_in > 0:39 noise_reduction = max(0.0, (lp_in - lp_out) / lp_in * 100)40 else:41 noise_reduction = 0.042 43 return {44 "psnr": psnr,45 "ssim": ssim_val,46 "mean_diff": mean_diff,47 "noise_reduction": noise_reduction,48 "mse": mse,49 }50 51 52def compute_noise_profile(image: np.ndarray) -> np.ndarray:53 """54 Return a 2-D map of estimated local noise magnitude via55 Laplacian high-frequency response (grayscale, float32).56 """57 gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)58 lap = cv2.Laplacian(gray.astype(np.float32), cv2.CV_32F)59 # Smooth the absolute response for a "heatmap" look60 profile = cv2.GaussianBlur(np.abs(lap), (15, 15), 5)61 return profile62 63 64def _laplacian_std(image: np.ndarray) -> float:65 """Standard deviation of the Laplacian — proxy for noise level."""66 gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)67 lap = cv2.Laplacian(gray.astype(np.float64), cv2.CV_64F)68 return float(lap.std())69 