from __future__ import annotations import numpy as np def mean_squared_error(reference: np.ndarray, test: np.ndarray) -> float: diff = np.asarray(reference, dtype=np.float32) - np.asarray(test, dtype=np.float32) return float(np.mean(diff**2)) def peak_signal_to_noise_ratio(reference: np.ndarray, test: np.ndarray, data_range: float) -> float: mse = mean_squared_error(reference, test) if mse <= 1e-12: return float("inf") data_range = max(float(data_range), 1e-6) return float(20.0 * np.log10(data_range) - 10.0 * np.log10(mse)) def gray_levels_used(level_indices: np.ndarray) -> int: return int(np.unique(np.asarray(level_indices)).size) def shannon_entropy(level_indices: np.ndarray) -> float: values, counts = np.unique(np.asarray(level_indices), return_counts=True) if values.size == 0: return 0.0 probabilities = counts.astype(np.float64) / counts.sum() return float(-(probabilities * np.log2(probabilities + 1e-12)).sum()) def gray_levels_used_from_image(image: np.ndarray) -> int: return int(np.unique(np.asarray(image)).size) def shannon_entropy_from_image(image: np.ndarray) -> float: values, counts = np.unique(np.asarray(image), return_counts=True) if values.size == 0: return 0.0 probabilities = counts.astype(np.float64) / counts.sum() return float(-(probabilities * np.log2(probabilities + 1e-12)).sum()) def clipped_pixel_ratios(image: np.ndarray, low: float, high: float) -> tuple[float, float]: image = np.asarray(image, dtype=np.float32) total = float(image.size) low_ratio = float(np.count_nonzero(image < low) / total) high_ratio = float(np.count_nonzero(image > high) / total) return low_ratio, high_ratio def format_metric(value: float, digits: int = 3) -> str: if np.isinf(value): return "inf" return f"{value:.{digits}f}"