ers/heman/Desktop/Notes/metrics/computervision/Instancesegmentation.py
Traceback (most recent call last):
  File "c:\Users\heman\Desktop\Notes\metrics\computervision\Instancesegmentation.py", line 239, in <module>
    example_usage()
  File "c:\Users\heman\Desktop\Notes\metrics\computervision\Instancesegmentation.py", line 233, in example_usage
    metrics = metrics_calculator.compute_metrics(predictions, ground_truths)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Users\heman\Desktop\Notes\metrics\computervision\Instancesegmentation.py", line 112, in compute_metrics
    mAP = self.mean_average_precision(iou_scores)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "c:\Users\heman\Desktop\Notes\metrics\computervision\Instancesegmentation.py", line 153, in mean_average_precision
    aps.append(torch.tensor(iou_scores) >= thresh).float().mean().item()
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'float'

import torch
from typing import List, Tuple, Dict

class InstanceSegmentationMetrics:
    """
    A class to compute various instance segmentation metrics.

    Attributes:
        iou_thresholds (List[float]): List of IoU thresholds for mAP and Average Recall.
    """

    def __init__(self, iou_thresholds: List[float] = [0.5, 0.75, 0.9]):
        """
        Initializes the InstanceSegmentationMetrics with specified IoU thresholds.

        Args:
            iou_thresholds (List[float], optional): IoU thresholds. Defaults to [0.5, 0.75, 0.9].

        Raises:
            ValueError: If any IoU threshold is not between 0 and 1.
        """
        if not all(0.0 < thresh <= 1.0 for thresh in iou_thresholds):
            raise ValueError("All IoU thresholds must be in the range (0, 1].")
        self.iou_thresholds: List[float] = iou_thresholds

    @staticmethod
    def calculate_iou(pred_mask: torch.Tensor, gt_mask: torch.Tensor) -> float:
        """
        Calculates Intersection over Union (IoU) between two binary masks.

        Args:
            pred_mask (torch.Tensor): Predicted binary mask.
            gt_mask (torch.Tensor): Ground truth binary mask.

        Returns:
            float: IoU score.
        """
        intersection = torch.logical_and(pred_mask, gt_mask).sum().item()
        union = torch.logical_or(pred_mask, gt_mask).sum().item()
        if union == 0:
            return 1.0  # If both masks are empty
        return intersection / union

    def compute_metrics(
        self,
        predictions: List[torch.Tensor],
        ground_truths: List[torch.Tensor]
    ) -> Dict[str, float]:
        """
        Computes all defined metrics given predictions and ground truths.

        Args:
            predictions (List[torch.Tensor]): List of prediction masks.
            ground_truths (List[torch.Tensor]): List of ground truth masks.

        Returns:
            Dict[str, float]: Dictionary containing all computed metrics.

        Raises:
            ValueError: If the lengths of predictions and ground_truths do not match.
        """
        if len(predictions) != len(ground_truths):
            raise ValueError("The number of predictions must match the number of ground truths.")

        total_tp = 0
        total_fn = 0
        total_fp = 0
        iou_scores = []
        pixel_accuracy_accum = 0.0
        total_pixels = 0

        for pred, gt in zip(predictions, ground_truths):
            if pred.shape != gt.shape:
                raise ValueError("Prediction and ground truth masks must have the same shape.")

            # Compute per-instance IoUs
            unique_preds = torch.unique(pred)
            unique_gts = torch.unique(gt)
            unique_preds = unique_preds[unique_preds != 0]
            unique_gts = unique_gts[unique_gts != 0]

            matched_preds = set()
            matched_gts = set()
            for pred_id in unique_preds:
                pred_mask = (pred == pred_id)
                best_iou = 0.0
                best_gt_id = None
                for gt_id in unique_gts:
                    if gt_id in matched_gts:
                        continue
                    gt_mask = (gt == gt_id)
                    iou = self.calculate_iou(pred_mask, gt_mask)
                    if iou > best_iou:
                        best_iou = iou
                        best_gt_id = gt_id
                if best_iou >= 0.5 and best_gt_id is not None:
                    total_tp += 1
                    iou_scores.append(best_iou)
                    matched_preds.add(pred_id.item())
                    matched_gts.add(best_gt_id.item())
                else:
                    total_fp += 1

            total_fn += len(unique_gts) - len(matched_gts)

            # Pixel Accuracy
            correct_pixels = (pred == gt).sum().item()
            pixel_accuracy_accum += correct_pixels
            total_pixels += pred.numel()

        # Compute metrics
        mAP = self.mean_average_precision(iou_scores)
        instance_iou = sum(iou_scores) / len(iou_scores) if iou_scores else 0.0
        pixel_accuracy = pixel_accuracy_accum / total_pixels if total_pixels > 0 else 0.0
        precision = total_tp / (total_tp + total_fp) if (total_tp + total_fp) > 0 else 0.0
        recall = total_tp / (total_tp + total_fn) if (total_tp + total_fn) > 0 else 0.0
        f1_score = (
            2 * precision * recall / (precision + recall)
            if (precision + recall) > 0
            else 0.0
        )

        average_recall = self.compute_average_recall(predictions, ground_truths)

        return {
            "mAP": mAP,
            "Instancewise_IoU": instance_iou,
            "Pixel_Accuracy": pixel_accuracy,
            "Recall": recall,
            "Precision": precision,
            "Average_Recall": average_recall,
            "F1_Score": f1_score,
            "True_Positives": total_tp,
            "False_Negatives": total_fn,
            "False_Positives": total_fp,
        }

    def mean_average_precision(self, iou_scores: List[float]) -> float:
        """
        Computes Mean Average Precision (mAP) over IoU thresholds.

        Args:
            iou_scores (List[float]): List of IoU scores for matched instances.

        Returns:
            float: mAP value.
        """
        if not iou_scores:
            return 0.0
        # Compute average precision for each IoU threshold
        aps = []
        for thresh in self.iou_thresholds:
            aps.append(torch.tensor(iou_scores) >= thresh).float().mean().item()
        return sum(aps) / len(aps)

    def compute_average_recall(
        self,
        predictions: List[torch.Tensor],
        ground_truths: List[torch.Tensor]
    ) -> float:
        """
        Computes Average Recall over multiple IoU thresholds.

        Args:
            predictions (List[torch.Tensor]): List of prediction masks.
            ground_truths (List[torch.Tensor]): List of ground truth masks.

        Returns:
            float: Average Recall value.
        """
        total_recall = 0.0
        count = 0
        for thresh in self.iou_thresholds:
            recall = 0.0
            total = 0
            for pred, gt in zip(predictions, ground_truths):
                unique_gts = torch.unique(gt)
                unique_gts = unique_gts[unique_gts != 0]
                matched = 0
                for gt_id in unique_gts:
                    gt_mask = (gt == gt_id)
                    best_iou = 0.0
                    for pred_id in torch.unique(pred):
                        if pred_id == 0:
                            continue
                        pred_mask = (pred == pred_id)
                        iou = self.calculate_iou(pred_mask, gt_mask)
                        if iou > best_iou:
                            best_iou = iou
                    if best_iou >= thresh:
                        matched += 1
                total += len(unique_gts)
                recall += matched
            average_recall = recall / total if total > 0 else 0.0
            total_recall += average_recall
            count += 1
        return total_recall / count if count > 0 else 0.0


def example_usage():
    import torch

    # Example prediction and ground truth masks
    # 0 represents the background
    pred1 = torch.tensor([
        [0, 1, 1],
        [0, 1, 0],
        [2, 2, 0]
    ])

    gt1 = torch.tensor([
        [0, 1, 1],
        [0, 1, 0],
        [2, 0, 0]
    ])

    pred2 = torch.tensor([
        [0, 1, 1],
        [0, 0, 0],
        [2, 2, 2]
    ])

    gt2 = torch.tensor([
        [0, 1, 1],
        [0, 0, 0],
        [2, 2, 0]
    ])

    predictions = [pred1, pred2]
    ground_truths = [gt1, gt2]

    metrics_calculator = InstanceSegmentationMetrics(iou_thresholds=[0.5, 0.75])
    metrics = metrics_calculator.compute_metrics(predictions, ground_truths)

    for metric, value in metrics.items():
        print(f"{metric}: {value:.4f}")

if __name__ == "__main__":
    example_usage()