CoolFace
Modelpublic

ParallelLLC/Segmentation

sourceHugging Faceapache-2.0updated 1y agoView on Hugging Face
0likes
metrics.py336 linesDownload Raw Back to utils
1"""2Segmentation Metrics3 4This module provides comprehensive metrics for evaluating segmentation performance5in few-shot and zero-shot learning scenarios.6"""7 8import torch9import torch.nn as nn10import numpy as np11from typing import Dict, List, Tuple, Optional12from sklearn.metrics import precision_recall_curve, average_precision_score13import cv214 15 16class SegmentationMetrics:17    """Comprehensive segmentation metrics calculator."""18    19    def __init__(self, threshold: float = 0.5):20        self.threshold = threshold21    22    def compute_metrics(23        self, 24        pred_mask: torch.Tensor, 25        gt_mask: torch.Tensor26    ) -> Dict[str, float]:27        """28        Compute comprehensive segmentation metrics.29        30        Args:31            pred_mask: Predicted mask tensor [H, W] or [1, H, W]32            gt_mask: Ground truth mask tensor [H, W] or [1, H, W]33            34        Returns:35            Dictionary containing various metrics36        """37        # Ensure masks are 2D38        if pred_mask.dim() == 3:39            pred_mask = pred_mask.squeeze(0)40        if gt_mask.dim() == 3:41            gt_mask = gt_mask.squeeze(0)42        43        # Convert to binary masks44        pred_binary = (pred_mask > self.threshold).float()45        gt_binary = (gt_mask > self.threshold).float()46        47        # Compute basic metrics48        metrics = {}49        50        # IoU (Intersection over Union)51        metrics['iou'] = self.compute_iou(pred_binary, gt_binary)52        53        # Dice coefficient54        metrics['dice'] = self.compute_dice(pred_binary, gt_binary)55        56        # Precision and Recall57        metrics['precision'] = self.compute_precision(pred_binary, gt_binary)58        metrics['recall'] = self.compute_recall(pred_binary, gt_binary)59        60        # F1 Score61        metrics['f1'] = self.compute_f1_score(pred_binary, gt_binary)62        63        # Accuracy64        metrics['accuracy'] = self.compute_accuracy(pred_binary, gt_binary)65        66        # Boundary metrics67        metrics['boundary_iou'] = self.compute_boundary_iou(pred_binary, gt_binary)68        metrics['hausdorff_distance'] = self.compute_hausdorff_distance(pred_binary, gt_binary)69        70        # Area metrics71        metrics['area_ratio'] = self.compute_area_ratio(pred_binary, gt_binary)72        73        return metrics74    75    def compute_iou(self, pred: torch.Tensor, gt: torch.Tensor) -> float:76        """Compute Intersection over Union."""77        intersection = (pred & gt).sum()78        union = (pred | gt).sum()79        return (intersection / union).item() if union > 0 else 0.080    81    def compute_dice(self, pred: torch.Tensor, gt: torch.Tensor) -> float:82        """Compute Dice coefficient."""83        intersection = (pred & gt).sum()84        total = pred.sum() + gt.sum()85        return (2 * intersection / total).item() if total > 0 else 0.086    87    def compute_precision(self, pred: torch.Tensor, gt: torch.Tensor) -> float:88        """Compute precision."""89        intersection = (pred & gt).sum()90        return (intersection / pred.sum()).item() if pred.sum() > 0 else 0.091    92    def compute_recall(self, pred: torch.Tensor, gt: torch.Tensor) -> float:93        """Compute recall."""94        intersection = (pred & gt).sum()95        return (intersection / gt.sum()).item() if gt.sum() > 0 else 0.096    97    def compute_f1_score(self, pred: torch.Tensor, gt: torch.Tensor) -> float:98        """Compute F1 score."""99        precision = self.compute_precision(pred, gt)100        recall = self.compute_recall(pred, gt)101        return (2 * precision * recall / (precision + recall)).item() if (precision + recall) > 0 else 0.0102    103    def compute_accuracy(self, pred: torch.Tensor, gt: torch.Tensor) -> float:104        """Compute pixel accuracy."""105        correct = (pred == gt).sum()106        total = pred.numel()107        return (correct / total).item()108    109    def compute_boundary_iou(self, pred: torch.Tensor, gt: torch.Tensor) -> float:110        """Compute boundary IoU."""111        # Extract boundaries112        pred_boundary = self.extract_boundary(pred)113        gt_boundary = self.extract_boundary(gt)114        115        # Compute IoU on boundaries116        return self.compute_iou(pred_boundary, gt_boundary)117    118    def extract_boundary(self, mask: torch.Tensor) -> torch.Tensor:119        """Extract boundary from binary mask."""120        mask_np = mask.cpu().numpy().astype(np.uint8)121        122        # Use morphological operations to extract boundary123        kernel = np.ones((3, 3), np.uint8)124        dilated = cv2.dilate(mask_np, kernel, iterations=1)125        eroded = cv2.erode(mask_np, kernel, iterations=1)126        boundary = dilated - eroded127        128        return torch.from_numpy(boundary).float()129    130    def compute_hausdorff_distance(self, pred: torch.Tensor, gt: torch.Tensor) -> float:131        """Compute Hausdorff distance between boundaries."""132        pred_boundary = self.extract_boundary(pred)133        gt_boundary = self.extract_boundary(gt)134        135        # Convert to numpy for distance computation136        pred_np = pred_boundary.cpu().numpy()137        gt_np = gt_boundary.cpu().numpy()138        139        # Find boundary points140        pred_points = np.column_stack(np.where(pred_np > 0))141        gt_points = np.column_stack(np.where(gt_np > 0))142        143        if len(pred_points) == 0 or len(gt_points) == 0:144            return float('inf')145        146        # Compute Hausdorff distance147        hausdorff_dist = self._hausdorff_distance(pred_points, gt_points)148        return hausdorff_dist149    150    def _hausdorff_distance(self, set1: np.ndarray, set2: np.ndarray) -> float:151        """Compute Hausdorff distance between two point sets."""152        def directed_hausdorff(set_a, set_b):153            min_distances = []154            for point_a in set_a:155                distances = np.linalg.norm(set_b - point_a, axis=1)156                min_distances.append(np.min(distances))157            return np.max(min_distances)158        159        d1 = directed_hausdorff(set1, set2)160        d2 = directed_hausdorff(set2, set1)161        return max(d1, d2)162    163    def compute_area_ratio(self, pred: torch.Tensor, gt: torch.Tensor) -> float:164        """Compute ratio of predicted area to ground truth area."""165        pred_area = pred.sum()166        gt_area = gt.sum()167        return (pred_area / gt_area).item() if gt_area > 0 else 0.0168    169    def compute_class_metrics(170        self, 171        predictions: Dict[str, torch.Tensor], 172        ground_truth: Dict[str, torch.Tensor]173    ) -> Dict[str, Dict[str, float]]:174        """Compute metrics for multiple classes."""175        class_metrics = {}176        177        for class_name in ground_truth.keys():178            if class_name in predictions:179                metrics = self.compute_metrics(predictions[class_name], ground_truth[class_name])180                class_metrics[class_name] = metrics181            else:182                # No prediction for this class183                class_metrics[class_name] = {184                    'iou': 0.0,185                    'dice': 0.0,186                    'precision': 0.0,187                    'recall': 0.0,188                    'f1': 0.0,189                    'accuracy': 0.0,190                    'boundary_iou': 0.0,191                    'hausdorff_distance': float('inf'),192                    'area_ratio': 0.0193                }194        195        return class_metrics196    197    def compute_average_metrics(198        self, 199        class_metrics: Dict[str, Dict[str, float]]200    ) -> Dict[str, float]:201        """Compute average metrics across all classes."""202        if not class_metrics:203            return {}204        205        # Collect all metric names206        metric_names = list(class_metrics[list(class_metrics.keys())[0]].keys())207        208        # Compute averages209        averages = {}210        for metric_name in metric_names:211            values = [class_metrics[cls][metric_name] for cls in class_metrics.keys()]212            213            # Handle infinite values in Hausdorff distance214            if metric_name == 'hausdorff_distance':215                finite_values = [v for v in values if v != float('inf')]216                if finite_values:217                    averages[metric_name] = np.mean(finite_values)218                else:219                    averages[metric_name] = float('inf')220            else:221                averages[metric_name] = np.mean(values)222        223        return averages224 225 226class FewShotMetrics:227    """Specialized metrics for few-shot learning evaluation."""228    229    def __init__(self):230        self.segmentation_metrics = SegmentationMetrics()231    232    def compute_episode_metrics(233        self, 234        episode_results: List[Dict]235    ) -> Dict[str, float]:236        """Compute metrics across multiple episodes."""237        all_metrics = []238        239        for episode in episode_results:240            if 'metrics' in episode:241                all_metrics.append(episode['metrics'])242        243        if not all_metrics:244            return {}245        246        # Compute episode-level statistics247        episode_stats = {}248        metric_names = all_metrics[0].keys()249        250        for metric_name in metric_names:251            values = [ep[metric_name] for ep in all_metrics if metric_name in ep]252            if values:253                episode_stats[f'mean_{metric_name}'] = np.mean(values)254                episode_stats[f'std_{metric_name}'] = np.std(values)255                episode_stats[f'min_{metric_name}'] = np.min(values)256                episode_stats[f'max_{metric_name}'] = np.max(values)257        258        return episode_stats259    260    def compute_shot_analysis(261        self, 262        results_by_shots: Dict[int, List[Dict]]263    ) -> Dict[str, Dict[str, float]]:264        """Analyze performance across different numbers of shots."""265        shot_analysis = {}266        267        for num_shots, results in results_by_shots.items():268            episode_metrics = self.compute_episode_metrics(results)269            shot_analysis[f'{num_shots}_shots'] = episode_metrics270        271        return shot_analysis272 273 274class ZeroShotMetrics:275    """Specialized metrics for zero-shot learning evaluation."""276    277    def __init__(self):278        self.segmentation_metrics = SegmentationMetrics()279    280    def compute_prompt_strategy_comparison(281        self, 282        strategy_results: Dict[str, List[Dict]]283    ) -> Dict[str, Dict[str, float]]:284        """Compare different prompt strategies."""285        strategy_comparison = {}286        287        for strategy_name, results in strategy_results.items():288            # Compute average metrics for this strategy289            avg_metrics = {}290            if results:291                metric_names = results[0].keys()292                for metric_name in metric_names:293                    values = [r[metric_name] for r in results if metric_name in r]294                    if values:295                        avg_metrics[f'mean_{metric_name}'] = np.mean(values)296                        avg_metrics[f'std_{metric_name}'] = np.std(values)297            298            strategy_comparison[strategy_name] = avg_metrics299        300        return strategy_comparison301    302    def compute_attention_analysis(303        self, 304        with_attention: List[Dict], 305        without_attention: List[Dict]306    ) -> Dict[str, float]:307        """Analyze the impact of attention mechanisms."""308        if not with_attention or not without_attention:309            return {}310        311        # Compute average metrics312        with_attention_avg = {}313        without_attention_avg = {}314        315        metric_names = with_attention[0].keys()316        for metric_name in metric_names:317            with_values = [r[metric_name] for r in with_attention if metric_name in r]318            without_values = [r[metric_name] for r in without_attention if metric_name in r]319            320            if with_values:321                with_attention_avg[metric_name] = np.mean(with_values)322            if without_values:323                without_attention_avg[metric_name] = np.mean(without_values)324        325        # Compute improvements326        improvements = {}327        for metric_name in with_attention_avg.keys():328            if metric_name in without_attention_avg:329                improvement = with_attention_avg[metric_name] - without_attention_avg[metric_name]330                improvements[f'{metric_name}_improvement'] = improvement331        332        return {333            'with_attention': with_attention_avg,334            'without_attention': without_attention_avg,335            'improvements': improvements336        }