| from __future__ import annotations | |
| import torch | |
| CSI_THRESHOLDS = torch.tensor([16, 74, 133, 160, 181, 219], dtype=torch.float32) / 255.0 | |
| def mse(prediction: torch.Tensor, target: torch.Tensor) -> torch.Tensor: | |
| return torch.mean((prediction - target) ** 2) | |
| def _ssim_per_sample(prediction: torch.Tensor, target: torch.Tensor) -> torch.Tensor: | |
| prediction, target = prediction.clamp(0.0, 1.0), target.clamp(0.0, 1.0) | |
| mu_x, mu_y = prediction.mean((2, 3)), target.mean((2, 3)) | |
| centered_x = prediction - mu_x[:, :, None, None, :] | |
| centered_y = target - mu_y[:, :, None, None, :] | |
| var_x = centered_x.square().mean((2, 3)) | |
| var_y = centered_y.square().mean((2, 3)) | |
| covariance = (centered_x * centered_y).mean((2, 3)) | |
| c1, c2 = 0.01**2, 0.03**2 | |
| score = ((2 * mu_x * mu_y + c1) * (2 * covariance + c2)) / ( | |
| (mu_x.square() + mu_y.square() + c1) * (var_x + var_y + c2) | |
| ) | |
| return score.mean((1, 2)) | |
| def metric_sums(prediction: torch.Tensor, target: torch.Tensor) -> torch.Tensor: | |
| """Return additive sample-level MSE/MAE/SSIM sums and CSI event counts.""" | |
| prediction, target = prediction.float(), target.float() | |
| reduce_dims = tuple(range(1, prediction.ndim)) | |
| values = [ | |
| (prediction - target).square().mean(reduce_dims).sum(), | |
| (prediction - target).abs().mean(reduce_dims).sum(), | |
| _ssim_per_sample(prediction, target).sum(), | |
| prediction.new_tensor(prediction.shape[0]), | |
| ] | |
| for threshold in CSI_THRESHOLDS.to(prediction.device): | |
| predicted, observed = prediction >= threshold, target >= threshold | |
| values.extend([(predicted & observed).sum(), (predicted & ~observed).sum(), (~predicted & observed).sum()]) | |
| return torch.stack(values).to(torch.float64) | |
| def metrics_from_sums(sums: torch.Tensor) -> dict[str, float]: | |
| count = sums[3].clamp_min(1) | |
| csi = [] | |
| for index in range(len(CSI_THRESHOLDS)): | |
| hits, false_alarms, misses = sums[4 + index * 3 : 7 + index * 3] | |
| csi.append((hits / (hits + false_alarms + misses).clamp_min(1)).item()) | |
| return { | |
| "mse": (sums[0] / count).item(), | |
| "mae": (sums[1] / count).item(), | |
| "ssim": (sums[2] / count).item(), | |
| "mean_csi": sum(csi) / len(csi), | |
| } | |
| def metric_sums_light(prediction: torch.Tensor, target: torch.Tensor) -> torch.Tensor: | |
| """Additive MSE/MAE sums for cheap per-epoch validation: [mse_sum, mae_sum, count].""" | |
| prediction, target = prediction.float(), target.float() | |
| reduce_dims = tuple(range(1, prediction.ndim)) | |
| return torch.stack( | |
| [ | |
| (prediction - target).square().mean(reduce_dims).sum(), | |
| (prediction - target).abs().mean(reduce_dims).sum(), | |
| prediction.new_tensor(prediction.shape[0]), | |
| ] | |
| ).to(torch.float64) | |
| def metrics_from_light_sums(sums: torch.Tensor) -> dict[str, float]: | |
| count = sums[2].clamp_min(1) | |
| return {"mse": (sums[0] / count).item(), "mae": (sums[1] / count).item()} | |
| def compute_metrics(prediction: torch.Tensor, target: torch.Tensor) -> dict[str, float]: | |
| return metrics_from_sums(metric_sums(prediction.detach(), target.detach())) | |