| import os |
| import time |
| import json |
|
|
| import numpy as np |
| from scipy.io import savemat |
| import torch |
| from torchvision import transforms |
|
|
| from PIL import ImageFile |
| ImageFile.LOAD_TRUNCATED_IMAGES = True |
|
|
|
|
| class Eval_thread(): |
| def __init__(self, loader, method='', dataset='', output_dir='', epoch='', cuda=True): |
| self.loader = loader |
| self.method = method |
| self.dataset = dataset |
| self.cuda = cuda |
| self.output_dir = output_dir |
| self.epoch = epoch.split('ep')[-1] |
| self.logfile = os.path.join(output_dir, 'result.txt') |
| self.dataset2smeasure_bottom_bound = {'CoCA': 0.673, 'CoSOD3k': 0.802, 'CoSal2015': 0.845} |
|
|
| def run(self, AP=False, AUC=False, save_metrics=False, continue_eval=True): |
| Res = {} |
| start_time = time.time() |
|
|
| if continue_eval: |
| s = self.Eval_Smeasure() |
| if s > self.dataset2smeasure_bottom_bound[self.dataset]: |
| mae = self.Eval_mae() |
| Em = self.Eval_Emeasure() |
| max_e = Em.max().item() |
| mean_e = Em.mean().item() |
| Em = Em.cpu().numpy() |
| Fm, prec, recall = self.Eval_fmeasure() |
| max_f = Fm.max().item() |
| mean_f = Fm.mean().item() |
| Fm = Fm.cpu().numpy() |
| else: |
| mae = 1 |
| Em = torch.zeros(255).cpu().numpy() |
| max_e = 0 |
| mean_e = 0 |
| Fm, prec, recall = 0, 0, 0 |
| max_f = 0 |
| mean_f = 0 |
| continue_eval = False |
| else: |
| s = 0 |
| mae = 1 |
| Em = torch.zeros(255).cpu().numpy() |
| max_e = 0 |
| mean_e = 0 |
| Fm, prec, recall = 0, 0, 0 |
| max_f = 0 |
| mean_f = 0 |
| continue_eval = False |
|
|
|
|
| if AP: |
| prec = prec.cpu().numpy() |
| recall = recall.cpu().numpy() |
| avg_p = self.Eval_AP(prec, recall) |
|
|
| if AUC: |
| auc, TPR, FPR = self.Eval_auc() |
| TPR = TPR.cpu().numpy() |
| FPR = FPR.cpu().numpy() |
|
|
| if save_metrics: |
| os.makedirs(os.path.join(self.output_dir, self.method, self.epoch), exist_ok=True) |
| Res['Sm'] = s |
| if s > self.dataset2smeasure_bottom_bound[self.dataset]: |
| Res['MAE'] = mae |
| Res['MaxEm'] = max_e |
| Res['MeanEm'] = mean_e |
| Res['Em'] = Em |
| Res['Fm'] = Fm |
| else: |
| Res['MAE'] = 1 |
| Res['MaxEm'] = 0 |
| Res['MeanEm'] = 0 |
| Res['Em'] = torch.zeros(255).cpu().numpy() |
| Res['Fm'] = 0 |
|
|
| if AP: |
| Res['MaxFm'] = max_f |
| Res['MeanFm'] = mean_f |
| Res['AP'] = avg_p |
| Res['Prec'] = prec |
| Res['Recall'] = recall |
|
|
| if AUC: |
| Res['AUC'] = auc |
| Res['TPR'] = TPR |
| Res['FPR'] = FPR |
|
|
| os.makedirs(os.path.join(self.output_dir, self.method, self.epoch), exist_ok=True) |
| savemat(os.path.join(self.output_dir, self.method, self.epoch, self.dataset + '.mat'), Res) |
|
|
| info = '{} ({}): {:.4f} max-Emeasure || {:.4f} S-measure || {:.4f} max-fm || {:.4f} mae || {:.4f} mean-Emeasure || {:.4f} mean-fm'.format( |
| self.dataset, self.method+'-ep{}'.format(self.epoch), max_e, s, max_f, mae, mean_e, mean_f |
| ) |
| if AP: |
| info += ' || {:.4f} AP'.format(avg_p) |
| if AUC: |
| info += ' || {:.4f} AUC'.format(auc) |
| info += '.' |
| self.LOG(info + '\n') |
|
|
| return '[cost:{:.4f}s] '.format(time.time() - start_time) + info, continue_eval |
|
|
| def Eval_mae(self): |
| if self.epoch: |
| print('Evaluating MAE...') |
| avg_mae, img_num = 0.0, 0.0 |
| with torch.no_grad(): |
| trans = transforms.Compose([transforms.ToTensor()]) |
| for pred, gt in self.loader: |
| if self.cuda: |
| pred = trans(pred).cuda() |
| gt = trans(gt).cuda() |
| else: |
| pred = trans(pred) |
| gt = trans(gt) |
| mea = torch.abs(pred - gt).mean() |
| if mea == mea: |
| avg_mae += mea |
| img_num += 1.0 |
| avg_mae /= img_num |
| return avg_mae.item() |
|
|
| def Eval_fmeasure(self): |
| print('Evaluating FMeasure...') |
| beta2 = 0.3 |
| avg_f, avg_p, avg_r, img_num = 0.0, 0.0, 0.0, 0.0 |
|
|
| with torch.no_grad(): |
| trans = transforms.Compose([transforms.ToTensor()]) |
| for pred, gt in self.loader: |
| if self.cuda: |
| pred = trans(pred).cuda() |
| gt = trans(gt).cuda() |
| pred = (pred - torch.min(pred)) / (torch.max(pred) - |
| torch.min(pred) + 1e-20) |
| else: |
| pred = trans(pred) |
| pred = (pred - torch.min(pred)) / (torch.max(pred) - |
| torch.min(pred) + 1e-20) |
| gt = trans(gt) |
| prec, recall = self._eval_pr(pred, gt, 255) |
| f_score = (1 + beta2) * prec * recall / (beta2 * prec + recall) |
| f_score[f_score != f_score] = 0 |
| avg_f += f_score |
| avg_p += prec |
| avg_r += recall |
| img_num += 1.0 |
| Fm = avg_f / img_num |
| avg_p = avg_p / img_num |
| avg_r = avg_r / img_num |
| return Fm, avg_p, avg_r |
|
|
| def Eval_auc(self): |
| print('Evaluating AUC...') |
|
|
| avg_tpr, avg_fpr, avg_auc, img_num = 0.0, 0.0, 0.0, 0.0 |
|
|
| with torch.no_grad(): |
| trans = transforms.Compose([transforms.ToTensor()]) |
| for pred, gt in self.loader: |
| if self.cuda: |
| pred = trans(pred).cuda() |
| pred = (pred - torch.min(pred)) / (torch.max(pred) - |
| torch.min(pred) + 1e-20) |
| gt = trans(gt).cuda() |
| else: |
| pred = trans(pred) |
| pred = (pred - torch.min(pred)) / (torch.max(pred) - |
| torch.min(pred) + 1e-20) |
| gt = trans(gt) |
| TPR, FPR = self._eval_roc(pred, gt, 255) |
| avg_tpr += TPR |
| avg_fpr += FPR |
| img_num += 1.0 |
| avg_tpr = avg_tpr / img_num |
| avg_fpr = avg_fpr / img_num |
|
|
| sorted_idxes = torch.argsort(avg_fpr) |
| avg_tpr = avg_tpr[sorted_idxes] |
| avg_fpr = avg_fpr[sorted_idxes] |
| avg_auc = torch.trapz(avg_tpr, avg_fpr) |
|
|
| return avg_auc.item(), avg_tpr, avg_fpr |
|
|
| def Eval_Emeasure(self): |
| print('Evaluating EMeasure...') |
| avg_e, img_num = 0.0, 0.0 |
| with torch.no_grad(): |
| trans = transforms.Compose([transforms.ToTensor()]) |
| Em = torch.zeros(255) |
| if self.cuda: |
| Em = Em.cuda() |
| for pred, gt in self.loader: |
| if self.cuda: |
| pred = trans(pred).cuda() |
| pred = (pred - torch.min(pred)) / (torch.max(pred) - |
| torch.min(pred) + 1e-20) |
| gt = trans(gt).cuda() |
| else: |
| pred = trans(pred) |
| pred = (pred - torch.min(pred)) / (torch.max(pred) - |
| torch.min(pred) + 1e-20) |
| gt = trans(gt) |
| Em += self._eval_e(pred, gt, 255) |
| img_num += 1.0 |
|
|
| Em /= img_num |
| return Em |
|
|
| def select_by_Smeasure(self, bar=0.9, loader_comp=None, bar_comp=0.1): |
| print('Evaluating SMeasure...') |
| good_ones = [] |
| good_ones_comp = [] |
| good_ones_gt = [] |
| alpha, avg_q, img_num = 0.5, 0.0, 0.0 |
| with torch.no_grad(): |
| trans = transforms.Compose([transforms.ToTensor()]) |
| for (pred, gt, predpath, gtpath), (pred_comp, gt_comp, predpath_comp) in zip(self.loader, loader_comp): |
| |
| if self.cuda: |
| pred = trans(pred).cuda() |
| pred = (pred - torch.min(pred)) / (torch.max(pred) - |
| torch.min(pred) + 1e-20) |
| gt = trans(gt).cuda() |
| else: |
| pred = trans(pred) |
| pred = (pred - torch.min(pred)) / (torch.max(pred) - |
| torch.min(pred) + 1e-20) |
| gt = trans(gt) |
| y = gt.mean() |
| if y == 0: |
| x = pred.mean() |
| Q = 1.0 - x |
| elif y == 1: |
| x = pred.mean() |
| Q = x |
| else: |
| gt[gt >= 0.5] = 1 |
| gt[gt < 0.5] = 0 |
| Q = alpha * self._S_object( |
| pred, gt) + (1 - alpha) * self._S_region(pred, gt) |
| if Q.item() < 0: |
| Q = torch.FloatTensor([0.0]) |
| img_num += 1.0 |
| avg_q += Q.item() |
| |
| if self.cuda: |
| pred_comp = trans(pred_comp).cuda() |
| pred_comp = (pred_comp - torch.min(pred_comp)) / (torch.max(pred_comp) - |
| torch.min(pred_comp) + 1e-20) |
| gt_comp = trans(gt_comp).cuda() |
| else: |
| pred_comp = trans(pred_comp) |
| pred_comp = (pred_comp - torch.min(pred_comp)) / (torch.max(pred_comp) - |
| torch.min(pred_comp) + 1e-20) |
| gt_comp = trans(gt_comp) |
| y = gt_comp.mean() |
| if y == 0: |
| x = pred_comp.mean() |
| Q_comp = 1.0 - x |
| elif y == 1: |
| x = pred_comp.mean() |
| Q_comp = x |
| else: |
| gt_comp[gt_comp >= 0.5] = 1 |
| gt_comp[gt_comp < 0.5] = 0 |
| Q_comp = alpha * self._S_object( |
| pred_comp, gt_comp) + (1 - alpha) * self._S_region(pred_comp, gt_comp) |
| if Q_comp.item() < 0: |
| Q_comp = torch.FloatTensor([0.0]) |
| if Q.item() > bar and (Q.item() - Q_comp.item()) > bar_comp: |
| good_ones.append(predpath) |
| good_ones_comp.append(predpath_comp) |
| good_ones_gt.append(gtpath) |
| avg_q /= img_num |
| return avg_q, good_ones, good_ones_comp, good_ones_gt |
|
|
| def Eval_Smeasure(self): |
| print('Evaluating SMeasure...') |
| alpha, avg_q, img_num = 0.5, 0.0, 0.0 |
| with torch.no_grad(): |
| trans = transforms.Compose([transforms.ToTensor()]) |
| for pred, gt in self.loader: |
| if self.cuda: |
| pred = trans(pred).cuda() |
| pred = (pred - torch.min(pred)) / (torch.max(pred) - |
| torch.min(pred) + 1e-20) |
| gt = trans(gt).cuda() |
| else: |
| pred = trans(pred) |
| pred = (pred - torch.min(pred)) / (torch.max(pred) - |
| torch.min(pred) + 1e-20) |
| gt = trans(gt) |
| y = gt.mean() |
| if y == 0: |
| x = pred.mean() |
| Q = 1.0 - x |
| elif y == 1: |
| x = pred.mean() |
| Q = x |
| else: |
| gt[gt >= 0.5] = 1 |
| gt[gt < 0.5] = 0 |
| Q = alpha * self._S_object( |
| pred, gt) + (1 - alpha) * self._S_region(pred, gt) |
| if Q.item() < 0: |
| Q = torch.FloatTensor([0.0]) |
| img_num += 1.0 |
| avg_q += Q.item() |
| avg_q /= img_num |
| return avg_q |
|
|
| def LOG(self, output): |
| os.makedirs(self.output_dir, exist_ok=True) |
| with open(self.logfile, 'a') as f: |
| f.write(output) |
|
|
| def _eval_e(self, y_pred, y, num): |
| if self.cuda: |
| score = torch.zeros(num).cuda() |
| thlist = torch.linspace(0, 1 - 1e-10, num).cuda() |
| else: |
| score = torch.zeros(num) |
| thlist = torch.linspace(0, 1 - 1e-10, num) |
| for i in range(num): |
| y_pred_th = (y_pred >= thlist[i]).float() |
| fm = y_pred_th - y_pred_th.mean() |
| gt = y - y.mean() |
| align_matrix = 2 * gt * fm / (gt * gt + fm * fm + 1e-20) |
| enhanced = ((align_matrix + 1) * (align_matrix + 1)) / 4 |
| score[i] = torch.sum(enhanced) / (y.numel() - 1 + 1e-20) |
| return score |
|
|
| def _eval_pr(self, y_pred, y, num): |
| if self.cuda: |
| prec, recall = torch.zeros(num).cuda(), torch.zeros(num).cuda() |
| thlist = torch.linspace(0, 1 - 1e-10, num).cuda() |
| else: |
| prec, recall = torch.zeros(num), torch.zeros(num) |
| thlist = torch.linspace(0, 1 - 1e-10, num) |
| for i in range(num): |
| y_temp = (y_pred >= thlist[i]).float() |
| tp = (y_temp * y).sum() |
| prec[i], recall[i] = tp / (y_temp.sum() + 1e-20), tp / (y.sum() + 1e-20) |
| return prec, recall |
|
|
| def _eval_roc(self, y_pred, y, num): |
| if self.cuda: |
| TPR, FPR = torch.zeros(num).cuda(), torch.zeros(num).cuda() |
| thlist = torch.linspace(0, 1 - 1e-10, num).cuda() |
| else: |
| TPR, FPR = torch.zeros(num), torch.zeros(num) |
| thlist = torch.linspace(0, 1 - 1e-10, num) |
| for i in range(num): |
| y_temp = (y_pred >= thlist[i]).float() |
| tp = (y_temp * y).sum() |
| fp = (y_temp * (1 - y)).sum() |
| tn = ((1 - y_temp) * (1 - y)).sum() |
| fn = ((1 - y_temp) * y).sum() |
|
|
| TPR[i] = tp / (tp + fn + 1e-20) |
| FPR[i] = fp / (fp + tn + 1e-20) |
|
|
| return TPR, FPR |
|
|
| def _S_object(self, pred, gt): |
| fg = torch.where(gt == 0, torch.zeros_like(pred), pred) |
| bg = torch.where(gt == 1, torch.zeros_like(pred), 1 - pred) |
| o_fg = self._object(fg, gt) |
| o_bg = self._object(bg, 1 - gt) |
| u = gt.mean() |
| Q = u * o_fg + (1 - u) * o_bg |
| return Q |
|
|
| def _object(self, pred, gt): |
| temp = pred[gt == 1] |
| x = temp.mean() |
| sigma_x = temp.std() |
| score = 2.0 * x / (x * x + 1.0 + sigma_x + 1e-20) |
|
|
| return score |
|
|
| def _S_region(self, pred, gt): |
| X, Y = self._centroid(gt) |
| gt1, gt2, gt3, gt4, w1, w2, w3, w4 = self._divideGT(gt, X, Y) |
| p1, p2, p3, p4 = self._dividePrediction(pred, X, Y) |
| Q1 = self._ssim(p1, gt1) |
| Q2 = self._ssim(p2, gt2) |
| Q3 = self._ssim(p3, gt3) |
| Q4 = self._ssim(p4, gt4) |
| Q = w1 * Q1 + w2 * Q2 + w3 * Q3 + w4 * Q4 |
| return Q |
|
|
| def _centroid(self, gt): |
| rows, cols = gt.size()[-2:] |
| gt = gt.view(rows, cols) |
| if gt.sum() == 0: |
| if self.cuda: |
| X = torch.eye(1).cuda() * round(cols / 2) |
| Y = torch.eye(1).cuda() * round(rows / 2) |
| else: |
| X = torch.eye(1) * round(cols / 2) |
| Y = torch.eye(1) * round(rows / 2) |
| else: |
| total = gt.sum() |
| if self.cuda: |
| i = torch.from_numpy(np.arange(0, cols)).cuda().float() |
| j = torch.from_numpy(np.arange(0, rows)).cuda().float() |
| else: |
| i = torch.from_numpy(np.arange(0, cols)).float() |
| j = torch.from_numpy(np.arange(0, rows)).float() |
| X = torch.round((gt.sum(dim=0) * i).sum() / total + 1e-20) |
| Y = torch.round((gt.sum(dim=1) * j).sum() / total + 1e-20) |
| return X.long(), Y.long() |
|
|
| def _divideGT(self, gt, X, Y): |
| h, w = gt.size()[-2:] |
| area = h * w |
| gt = gt.view(h, w) |
| LT = gt[:Y, :X] |
| RT = gt[:Y, X:w] |
| LB = gt[Y:h, :X] |
| RB = gt[Y:h, X:w] |
| X = X.float() |
| Y = Y.float() |
| w1 = X * Y / area |
| w2 = (w - X) * Y / area |
| w3 = X * (h - Y) / area |
| w4 = 1 - w1 - w2 - w3 |
| return LT, RT, LB, RB, w1, w2, w3, w4 |
|
|
| def _dividePrediction(self, pred, X, Y): |
| h, w = pred.size()[-2:] |
| pred = pred.view(h, w) |
| LT = pred[:Y, :X] |
| RT = pred[:Y, X:w] |
| LB = pred[Y:h, :X] |
| RB = pred[Y:h, X:w] |
| return LT, RT, LB, RB |
|
|
| def _ssim(self, pred, gt): |
| gt = gt.float() |
| h, w = pred.size()[-2:] |
| N = h * w |
| x = pred.mean() |
| y = gt.mean() |
| sigma_x2 = ((pred - x) * (pred - x)).sum() / (N - 1 + 1e-20) |
| sigma_y2 = ((gt - y) * (gt - y)).sum() / (N - 1 + 1e-20) |
| sigma_xy = ((pred - x) * (gt - y)).sum() / (N - 1 + 1e-20) |
|
|
| aplha = 4 * x * y * sigma_xy |
| beta = (x * x + y * y) * (sigma_x2 + sigma_y2) |
|
|
| if aplha != 0: |
| Q = aplha / (beta + 1e-20) |
| elif aplha == 0 and beta == 0: |
| Q = 1.0 |
| else: |
| Q = 0 |
| return Q |
|
|
| def Eval_AP(self, prec, recall): |
| |
| |
| print('Evaluating AP...') |
| ap_r = np.concatenate(([0.], recall, [1.])) |
| ap_p = np.concatenate(([0.], prec, [0.])) |
| sorted_idxes = np.argsort(ap_r) |
| ap_r = ap_r[sorted_idxes] |
| ap_p = ap_p[sorted_idxes] |
| count = ap_r.shape[0] |
|
|
| for i in range(count - 1, 0, -1): |
| ap_p[i - 1] = max(ap_p[i], ap_p[i - 1]) |
|
|
| i = np.where(ap_r[1:] != ap_r[:-1])[0] |
| ap = np.sum((ap_r[i + 1] - ap_r[i]) * ap_p[i + 1]) |
| return ap |
|
|