"""Advanced comparison metrics for change detection evaluation.""" import numpy as np from typing import Dict, Optional from .evaluation import calculate_metrics def calculate_change_statistics( change_mask: np.ndarray, change_confidence: np.ndarray ) -> Dict: """ Calculate statistics from a 2D change mask and confidence map. Args: change_mask: Binary 2D array (H, W), 1 = changed change_confidence: Float 2D array (H, W) in [0, 1] Returns: Dict with total_pixels, changed_pixels, unchanged_pixels, change_percentage, mean_confidence, min_confidence, max_confidence, change_confidence_mean (mean conf among changed pixels only) """ # Ensure 2D arrays mask = change_mask.astype(np.uint8) conf = change_confidence.astype(np.float32) total_pixels = int(mask.size) changed_pixels = int(np.sum(mask == 1)) unchanged_pixels = total_pixels - changed_pixels change_percentage = 100.0 * changed_pixels / total_pixels if total_pixels > 0 else 0.0 mean_confidence = float(conf.mean()) min_confidence = float(conf.min()) max_confidence = float(conf.max()) # Mean confidence among changed pixels only if changed_pixels > 0: change_confidence_mean = float(conf[mask == 1].mean()) else: change_confidence_mean = 0.0 return { "total_pixels": total_pixels, "changed_pixels": changed_pixels, "unchanged_pixels": unchanged_pixels, "change_percentage": change_percentage, "mean_confidence": mean_confidence, "min_confidence": min_confidence, "max_confidence": max_confidence, "change_confidence_mean": change_confidence_mean, } def compare_with_without_masking( pred_with_mask: np.ndarray, pred_without_mask: np.ndarray, gt_mask: Optional[np.ndarray] = None ) -> Dict: """ Compare detection results with and without cloud masking. Args: pred_with_mask: Change mask produced WITH cloud masking (H, W) pred_without_mask: Change mask produced WITHOUT cloud masking (H, W) gt_mask: Optional ground truth mask for metric computation (H, W) Returns: Dict with pixel-level comparison and optional metric differences """ agreement = int(np.sum(pred_with_mask == pred_without_mask)) total = int(pred_with_mask.size) agreement_pct = 100.0 * agreement / total if total > 0 else 0.0 result = { "agreement_pixels": agreement, "total_pixels": total, "agreement_percentage": agreement_pct, "changed_with_mask": int(np.sum(pred_with_mask)), "changed_without_mask": int(np.sum(pred_without_mask)), } if gt_mask is not None: metrics_with = calculate_metrics(pred_with_mask, gt_mask) metrics_without = calculate_metrics(pred_without_mask, gt_mask) result["iou_with_mask"] = metrics_with["iou"] result["iou_without_mask"] = metrics_without["iou"] result["iou_improvement"] = metrics_with["iou"] - metrics_without["iou"] result["f1_with_mask"] = metrics_with["f1"] result["f1_without_mask"] = metrics_without["f1"] result["f1_improvement"] = metrics_with["f1"] - metrics_without["f1"] return result