import numpy as np class Evaluator(object): def __init__(self, num_class): self.num_class = num_class self.confusion_matrix = np.zeros((self.num_class,) * 2, dtype=np.longlong) self._epsilon = 1e-7 def Pixel_Accuracy(self): Acc = np.diag(self.confusion_matrix).sum() / self.confusion_matrix.sum() return Acc def Pixel_Accuracy_Class(self): Acc = np.diag(self.confusion_matrix) / (self.confusion_matrix.sum(axis=1) + self._epsilon) mAcc = np.nanmean(Acc) return mAcc, Acc def Pixel_Precision_Rate(self): assert self.confusion_matrix.shape[0] == 2 Pre = self.confusion_matrix[1, 1] / (self.confusion_matrix[0, 1] + self.confusion_matrix[1, 1] + self._epsilon) return Pre def Pixel_Recall_Rate(self): assert self.confusion_matrix.shape[0] == 2 Rec = self.confusion_matrix[1, 1] / (self.confusion_matrix[1, 0] + self.confusion_matrix[1, 1] + self._epsilon) return Rec def Pixel_F1_score(self): assert self.confusion_matrix.shape[0] == 2 Rec = self.Pixel_Recall_Rate() Pre = self.Pixel_Precision_Rate() F1 = 2 * Rec * Pre / (Rec + Pre) return F1 def calculate_per_class_metrics(self): # Adjustments to exclude class 0 in calculations TPs = np.diag(self.confusion_matrix)[1:] # Start from index 1 to exclude class 0 FNs = np.sum(self.confusion_matrix, axis=1)[1:] - TPs FPs = np.sum(self.confusion_matrix, axis=0)[1:] - TPs return TPs, FNs, FPs def Damage_F1_socore(self): TPs, FNs, FPs = self.calculate_per_class_metrics() precisions = TPs / (TPs + FPs + 1e-7) recalls = TPs / (TPs + FNs + 1e-7) f1_scores = 2 * (precisions * recalls) / (precisions + recalls + 1e-7) return f1_scores def Mean_Intersection_over_Union(self): MIoU = np.nanmean(self.Intersection_over_Union()) return MIoU def Intersection_over_Union(self): IoU = np.diag(self.confusion_matrix) / ( np.sum(self.confusion_matrix, axis=1) + np.sum(self.confusion_matrix, axis=0) - np.diag(self.confusion_matrix) + 1e-7) return IoU def Kappa_coefficient(self): # Number of observations (total number of classifications) # num_total = np.array(0, dtype=np.long) # row_sums = np.array([0, 0], dtype=np.long) # col_sums = np.array([0, 0], dtype=np.long) # total += np.sum(self.confusion_matrix) # # Observed agreement (i.e., sum of diagonal elements) # observed_agreement = np.sum(np.diag(self.confusion_matrix)) # # Compute expected agreement # row_sums += np.sum(self.confusion_matrix, axis=0) # col_sums += np.sum(self.confusion_matrix, axis=1) # expected_agreement = np.sum((row_sums * col_sums) / total) num_total = np.sum(self.confusion_matrix) observed_accuracy = np.trace(self.confusion_matrix) / num_total expected_accuracy = np.sum( np.sum(self.confusion_matrix, axis=0) / num_total * np.sum(self.confusion_matrix, axis=1) / num_total) # Calculate Cohen's kappa kappa = (observed_accuracy - expected_accuracy) / (1 - expected_accuracy) return kappa def Frequency_Weighted_Intersection_over_Union(self): freq = np.sum(self.confusion_matrix, axis=1) / np.sum(self.confusion_matrix) iu = np.diag(self.confusion_matrix) / ( np.sum(self.confusion_matrix, axis=1) + np.sum(self.confusion_matrix, axis=0) - np.diag(self.confusion_matrix)) FWIoU = (freq[freq > 0] * iu[freq > 0]).sum() return FWIoU def _generate_matrix(self, gt_image, pre_image): mask = (gt_image >= 0) & (gt_image < self.num_class) label = self.num_class * gt_image[mask].astype('int64') + pre_image[mask] count = np.bincount(label, minlength=self.num_class ** 2) confusion_matrix = count.reshape(self.num_class, self.num_class) return confusion_matrix def add_batch(self, gt_image, pre_image): assert gt_image.shape == pre_image.shape self.confusion_matrix += self._generate_matrix(gt_image, pre_image) def reset(self): self.confusion_matrix = np.zeros((self.num_class,) * 2)