| | |
| | |
| | |
| | |
| |
|
| | import numpy as np |
| | from scipy.ndimage import convolve |
| | from scipy.ndimage import distance_transform_edt as bwdist |
| | import cv2 |
| | from PIL import Image |
| |
|
| | _EPS = 1e-16 |
| | _TYPE = np.float64 |
| |
|
| |
|
| | def _prepare_data(pred: np.ndarray, gt: np.ndarray) -> tuple: |
| | """ |
| | A numpy-based function for preparing ``pred`` and ``gt``. |
| | |
| | - for ``pred``, it looks like ``mapminmax(im2double(...))`` of matlab; |
| | - ``gt`` will be binarized by 128. |
| | |
| | :param pred: prediction |
| | :param gt: mask |
| | :return: pred, gt |
| | """ |
| | gt = gt > 128 |
| | pred = pred / 255 |
| | if pred.max() != pred.min(): |
| | pred = (pred - pred.min()) / (pred.max() - pred.min()) |
| | return pred, gt |
| |
|
| |
|
| | def _get_adaptive_threshold(matrix: np.ndarray, max_value: float = 1) -> float: |
| | """ |
| | Return an adaptive threshold, which is equal to twice the mean of ``matrix``. |
| | |
| | :param matrix: a data array |
| | :param max_value: the upper limit of the threshold |
| | :return: min(2 * matrix.mean(), max_value) |
| | """ |
| | return min(2 * matrix.mean(), max_value) |
| |
|
| |
|
| | class IoU(object): |
| | def __init__(self): |
| | self.ious = [] |
| |
|
| | def step(self, pred: np.ndarray, gt: np.ndarray): |
| | pred, gt = _prepare_data(pred, gt) |
| |
|
| | ious = self.cal_iou(pred=pred, gt=gt) |
| | self.ious.append(ious) |
| |
|
| | def cal_iou(self, pred, gt): |
| | pred = (pred * 255).astype(np.uint8) |
| | |
| | bins = np.linspace(0, 256, 257) |
| | fg_hist, _ = np.histogram(pred[gt], bins=bins) |
| | bg_hist, _ = np.histogram(pred[~gt], bins=bins) |
| | fg_w_thrs = np.cumsum(np.flip(fg_hist), axis=0) |
| | bg_w_thrs = np.cumsum(np.flip(bg_hist), axis=0) |
| | TPs = fg_w_thrs |
| | Ps = fg_w_thrs + bg_w_thrs |
| | Ps[Ps == 0] = 1 |
| | T = max(np.count_nonzero(gt), 1) |
| | |
| | ious = TPs / (T + bg_w_thrs) |
| | return ious |
| |
|
| | def get_results(self) -> dict: |
| | iou = np.mean(np.array(self.ious, dtype=_TYPE), axis=0) |
| | return dict(iou=dict(curve=iou)) |
| | |
| | class BIoU(object): |
| | def __init__(self, dilation_ratio=0.02): |
| | self.bious = [] |
| | self.dilation_ratio = dilation_ratio |
| | |
| | def mask_to_boundary(self, mask): |
| | h, w = mask.shape |
| | img_diag = np.sqrt(h ** 2 + w ** 2) |
| | dilation = int(round(self.dilation_ratio * img_diag)) |
| | if dilation < 1: |
| | dilation = 1 |
| | |
| | new_mask = cv2.copyMakeBorder(mask, 1, 1, 1, 1, cv2.BORDER_CONSTANT, value=0) |
| | kernel = np.ones((3, 3), dtype=np.uint8) |
| | new_mask_erode = cv2.erode(new_mask, kernel, iterations=dilation) |
| | mask_erode = new_mask_erode[1 : h + 1, 1 : w + 1] |
| | |
| | return mask - mask_erode |
| |
|
| | def step(self, pred: np.ndarray, gt: np.ndarray): |
| | pred, gt = _prepare_data(pred, gt) |
| |
|
| | bious = self.cal_biou(pred=pred, gt=gt) |
| | self.bious.append(bious) |
| |
|
| | def cal_biou(self, pred, gt): |
| | pred = (pred * 255).astype(np.uint8) |
| | pred = self.mask_to_boundary(pred) |
| | gt = (gt * 255).astype(np.uint8) |
| | gt = self.mask_to_boundary(gt) |
| | gt = gt > 128 |
| | |
| | bins = np.linspace(0, 256, 257) |
| | fg_hist, _ = np.histogram(pred[gt], bins=bins) |
| | bg_hist, _ = np.histogram(pred[~gt], bins=bins) |
| | fg_w_thrs = np.cumsum(np.flip(fg_hist), axis=0) |
| | bg_w_thrs = np.cumsum(np.flip(bg_hist), axis=0) |
| | TPs = fg_w_thrs |
| | Ps = fg_w_thrs + bg_w_thrs |
| | Ps[Ps == 0] = 1 |
| | T = max(np.count_nonzero(gt), 1) |
| | |
| | ious = TPs / (T + bg_w_thrs) |
| | return ious |
| |
|
| | def get_results(self) -> dict: |
| | biou = np.mean(np.array(self.bious, dtype=_TYPE), axis=0) |
| | return dict(biou=dict(curve=biou)) |
| | |
| | class TIoU(object): |
| | def __init__(self, dilation_ratio=0.001): |
| | self.tious = [] |
| | self.dilation_ratio = dilation_ratio |
| | |
| | def mask_to_boundary(self, mask): |
| | h, w = mask.shape |
| | img_diag = np.sqrt(h ** 2 + w ** 2) |
| | dilation = int(round(self.dilation_ratio * img_diag)) |
| | if dilation < 1: |
| | dilation = 1 |
| | |
| | new_mask = cv2.copyMakeBorder(mask, 1, 1, 1, 1, cv2.BORDER_CONSTANT, value=0) |
| | kernel = np.ones((3, 3), dtype=np.uint8) |
| | new_mask_erode = cv2.erode(new_mask, kernel, iterations=dilation) |
| | mask_erode = new_mask_erode[1 : h + 1, 1 : w + 1] |
| | |
| | return mask - mask_erode |
| |
|
| | def step(self, pred: np.ndarray, gt: np.ndarray): |
| | pred, gt = _prepare_data(pred, gt) |
| |
|
| | tious = self.cal_tiou(pred=pred, gt=gt) |
| | self.tious.append(tious) |
| |
|
| | def cal_tiou(self, pred, gt): |
| | pred = (pred * 255).astype(np.uint8) |
| | |
| | gt = (gt * 255).astype(np.uint8) |
| | gt = self.mask_to_boundary(gt) |
| | gt = gt > 128 |
| | |
| | pred = pred * gt |
| | |
| | bins = np.linspace(0, 256, 257) |
| | fg_hist, _ = np.histogram(pred[gt], bins=bins) |
| | bg_hist, _ = np.histogram(pred[~gt], bins=bins) |
| | fg_w_thrs = np.cumsum(np.flip(fg_hist), axis=0) |
| | bg_w_thrs = np.cumsum(np.flip(bg_hist), axis=0) |
| | TPs = fg_w_thrs |
| | Ps = fg_w_thrs + bg_w_thrs |
| | Ps[Ps == 0] = 1 |
| | T = max(np.count_nonzero(gt), 1) |
| | |
| | ious = TPs / (T + bg_w_thrs) |
| | return ious |
| |
|
| | def get_results(self) -> dict: |
| | tiou = np.mean(np.array(self.tious, dtype=_TYPE), axis=0) |
| | return dict(tiou=dict(curve=tiou)) |
| |
|
| |
|
| | class Fmeasure(object): |
| | def __init__(self, beta: float = 0.3): |
| | """ |
| | F-measure for SOD. |
| | |
| | :: |
| | |
| | @inproceedings{Fmeasure, |
| | title={Frequency-tuned salient region detection}, |
| | author={Achanta, Radhakrishna and Hemami, Sheila and Estrada, Francisco and S{\"u}sstrunk, Sabine}, |
| | booktitle=CVPR, |
| | number={CONF}, |
| | pages={1597--1604}, |
| | year={2009} |
| | } |
| | |
| | :param beta: the weight of the precision |
| | """ |
| | self.beta = beta |
| | self.precisions = [] |
| | self.recalls = [] |
| | self.adaptive_fms = [] |
| | self.changeable_fms = [] |
| |
|
| | def step(self, pred: np.ndarray, gt: np.ndarray): |
| | pred, gt = _prepare_data(pred, gt) |
| |
|
| | adaptive_fm = self.cal_adaptive_fm(pred=pred, gt=gt) |
| | self.adaptive_fms.append(adaptive_fm) |
| |
|
| | precisions, recalls, changeable_fms = self.cal_pr(pred=pred, gt=gt) |
| | self.precisions.append(precisions) |
| | self.recalls.append(recalls) |
| | self.changeable_fms.append(changeable_fms) |
| |
|
| | def cal_adaptive_fm(self, pred: np.ndarray, gt: np.ndarray) -> float: |
| | """ |
| | Calculate the adaptive F-measure. |
| | |
| | :return: adaptive_fm |
| | """ |
| | adaptive_threshold = _get_adaptive_threshold(pred, max_value=1) |
| | binary_predcition = pred >= adaptive_threshold |
| | area_intersection = binary_predcition[gt].sum() |
| | if area_intersection == 0: |
| | adaptive_fm = 0 |
| | else: |
| | pre = area_intersection / np.count_nonzero(binary_predcition) |
| | rec = area_intersection / np.count_nonzero(gt) |
| | adaptive_fm = (1 + self.beta) * pre * rec / (self.beta * pre + rec) |
| | return adaptive_fm |
| |
|
| | def cal_pr(self, pred: np.ndarray, gt: np.ndarray) -> tuple: |
| | """ |
| | Calculate the corresponding precision and recall when the threshold changes from 0 to 255. |
| | |
| | These precisions and recalls can be used to obtain the mean F-measure, maximum F-measure, |
| | precision-recall curve and F-measure-threshold curve. |
| | |
| | For convenience, ``changeable_fms`` is provided here, which can be used directly to obtain |
| | the mean F-measure, maximum F-measure and F-measure-threshold curve. |
| | |
| | :return: precisions, recalls, changeable_fms |
| | """ |
| | pred = (pred * 255).astype(np.uint8) |
| | bins = np.linspace(0, 256, 257) |
| | fg_hist, _ = np.histogram(pred[gt], bins=bins) |
| | bg_hist, _ = np.histogram(pred[~gt], bins=bins) |
| | fg_w_thrs = np.cumsum(np.flip(fg_hist), axis=0) |
| | bg_w_thrs = np.cumsum(np.flip(bg_hist), axis=0) |
| | TPs = fg_w_thrs |
| | Ps = fg_w_thrs + bg_w_thrs |
| | Ps[Ps == 0] = 1 |
| | T = max(np.count_nonzero(gt), 1) |
| | precisions = TPs / Ps |
| | recalls = TPs / T |
| |
|
| | numerator = (1 + self.beta) * precisions * recalls |
| | denominator = np.where(numerator == 0, 1, self.beta * precisions + recalls) |
| | changeable_fms = numerator / denominator |
| | return precisions, recalls, changeable_fms |
| |
|
| | def get_results(self) -> dict: |
| | """ |
| | Return the results about F-measure. |
| | |
| | :return: dict(fm=dict(adp=adaptive_fm, curve=changeable_fm), pr=dict(p=precision, r=recall)) |
| | """ |
| | adaptive_fm = np.mean(np.array(self.adaptive_fms, _TYPE)) |
| | changeable_fm = np.mean(np.array(self.changeable_fms, dtype=_TYPE), axis=0) |
| | precision = np.mean(np.array(self.precisions, dtype=_TYPE), axis=0) |
| | recall = np.mean(np.array(self.recalls, dtype=_TYPE), axis=0) |
| | return dict(fm=dict(adp=adaptive_fm, curve=changeable_fm), pr=dict(p=precision, r=recall)) |
| |
|
| |
|
| | class Mae(object): |
| | def __init__(self): |
| | """ |
| | MAE(mean absolute error) for SOD. |
| | |
| | :: |
| | |
| | @inproceedings{MAE, |
| | title={Saliency filters: Contrast based filtering for salient region detection}, |
| | author={Perazzi, Federico and Kr{\"a}henb{\"u}hl, Philipp and Pritch, Yael and Hornung, Alexander}, |
| | booktitle=CVPR, |
| | pages={733--740}, |
| | year={2012} |
| | } |
| | """ |
| | self.maes = [] |
| |
|
| | def step(self, pred: np.ndarray, gt: np.ndarray): |
| | pred, gt = _prepare_data(pred, gt) |
| |
|
| | mae = self.cal_mae(pred, gt) |
| | self.maes.append(mae) |
| |
|
| | def cal_mae(self, pred: np.ndarray, gt: np.ndarray) -> np.ndarray: |
| | """ |
| | Calculate the mean absolute error. |
| | |
| | :return: mae |
| | """ |
| | mae = np.mean(np.abs(pred - gt)) |
| | return mae |
| |
|
| | def get_results(self) -> dict: |
| | """ |
| | Return the results about MAE. |
| | |
| | :return: dict(mae=mae) |
| | """ |
| | mae = np.mean(np.array(self.maes, _TYPE)) |
| | return dict(mae=mae) |
| |
|
| |
|
| | class Mse(object): |
| | def __init__(self): |
| | """ |
| | MAE(mean absolute error) for SOD. |
| | |
| | :: |
| | |
| | @inproceedings{MAE, |
| | title={Saliency filters: Contrast based filtering for salient region detection}, |
| | author={Perazzi, Federico and Kr{\"a}henb{\"u}hl, Philipp and Pritch, Yael and Hornung, Alexander}, |
| | booktitle=CVPR, |
| | pages={733--740}, |
| | year={2012} |
| | } |
| | """ |
| | self.mses = [] |
| |
|
| | def step(self, pred: np.ndarray, gt: np.ndarray): |
| | pred, gt = _prepare_data(pred, gt) |
| |
|
| | mse = self.cal_mse(pred, gt) |
| | self.mses.append(mse) |
| |
|
| | def cal_mse(self, pred: np.ndarray, gt: np.ndarray) -> np.ndarray: |
| | """ |
| | Calculate the mean absolute error. |
| | |
| | :return: mse |
| | """ |
| | mse = np.mean((pred - gt) ** 2) |
| | return mse |
| |
|
| | def get_results(self) -> dict: |
| | """ |
| | Return the results about MSE. |
| | |
| | :return: dict(mse=mse) |
| | """ |
| | mse = np.mean(np.array(self.mses, _TYPE)) |
| | return dict(mse=mse) |
| |
|
| |
|
| | class Smeasure(object): |
| | def __init__(self, alpha: float = 0.5): |
| | """ |
| | S-measure(Structure-measure) of SOD. |
| | |
| | :: |
| | |
| | @inproceedings{Smeasure, |
| | title={Structure-measure: A new way to eval foreground maps}, |
| | author={Fan, Deng-Ping and Cheng, Ming-Ming and Liu, Yun and Li, Tao and Borji, Ali}, |
| | booktitle=ICCV, |
| | pages={4548--4557}, |
| | year={2017} |
| | } |
| | |
| | :param alpha: the weight for balancing the object score and the region score |
| | """ |
| | self.sms = [] |
| | self.alpha = alpha |
| |
|
| | def step(self, pred: np.ndarray, gt: np.ndarray): |
| | pred, gt = _prepare_data(pred=pred, gt=gt) |
| |
|
| | sm = self.cal_sm(pred, gt) |
| | self.sms.append(sm) |
| |
|
| | def cal_sm(self, pred: np.ndarray, gt: np.ndarray) -> float: |
| | """ |
| | Calculate the S-measure. |
| | |
| | :return: s-measure |
| | """ |
| | y = np.mean(gt) |
| | if y == 0: |
| | sm = 1 - np.mean(pred) |
| | elif y == 1: |
| | sm = np.mean(pred) |
| | else: |
| | sm = self.alpha * self.object(pred, gt) + (1 - self.alpha) * self.region(pred, gt) |
| | sm = max(0, sm) |
| | return sm |
| |
|
| | def object(self, pred: np.ndarray, gt: np.ndarray) -> float: |
| | """ |
| | Calculate the object score. |
| | """ |
| | fg = pred * gt |
| | bg = (1 - pred) * (1 - gt) |
| | u = np.mean(gt) |
| | object_score = u * self.s_object(fg, gt) + (1 - u) * self.s_object(bg, 1 - gt) |
| | return object_score |
| |
|
| | def s_object(self, pred: np.ndarray, gt: np.ndarray) -> float: |
| | x = np.mean(pred[gt == 1]) |
| | sigma_x = np.std(pred[gt == 1]) |
| | score = 2 * x / (np.power(x, 2) + 1 + sigma_x + _EPS) |
| | return score |
| |
|
| | def region(self, pred: np.ndarray, gt: np.ndarray) -> float: |
| | """ |
| | Calculate the region score. |
| | """ |
| | x, y = self.centroid(gt) |
| | part_info = self.divide_with_xy(pred, gt, x, y) |
| | w1, w2, w3, w4 = part_info["weight"] |
| |
|
| | pred1, pred2, pred3, pred4 = part_info["pred"] |
| | gt1, gt2, gt3, gt4 = part_info["gt"] |
| | score1 = self.ssim(pred1, gt1) |
| | score2 = self.ssim(pred2, gt2) |
| | score3 = self.ssim(pred3, gt3) |
| | score4 = self.ssim(pred4, gt4) |
| |
|
| | return w1 * score1 + w2 * score2 + w3 * score3 + w4 * score4 |
| |
|
| | def centroid(self, matrix: np.ndarray) -> tuple: |
| | """ |
| | To ensure consistency with the matlab code, one is added to the centroid coordinate, |
| | so there is no need to use the redundant addition operation when dividing the region later, |
| | because the sequence generated by ``1:X`` in matlab will contain ``X``. |
| | |
| | :param matrix: a data array |
| | :return: the centroid coordinate |
| | """ |
| | h, w = matrix.shape |
| | if matrix.sum() == 0: |
| | x = np.round(w / 2) |
| | y = np.round(h / 2) |
| | else: |
| | area_object = np.sum(matrix) |
| | row_ids = np.arange(h) |
| | col_ids = np.arange(w) |
| | x = np.round(np.sum(np.sum(matrix, axis=0) * col_ids) / area_object) |
| | y = np.round(np.sum(np.sum(matrix, axis=1) * row_ids) / area_object) |
| | return int(x) + 1, int(y) + 1 |
| |
|
| | def divide_with_xy(self, pred: np.ndarray, gt: np.ndarray, x: int, y: int) -> dict: |
| | """ |
| | Use (x,y) to divide the ``pred`` and the ``gt`` into four submatrices, respectively. |
| | """ |
| | h, w = gt.shape |
| | area = h * w |
| |
|
| | gt_LT = gt[0:y, 0:x] |
| | gt_RT = gt[0:y, x:w] |
| | gt_LB = gt[y:h, 0:x] |
| | gt_RB = gt[y:h, x:w] |
| |
|
| | pred_LT = pred[0:y, 0:x] |
| | pred_RT = pred[0:y, x:w] |
| | pred_LB = pred[y:h, 0:x] |
| | pred_RB = pred[y:h, x:w] |
| |
|
| | w1 = x * y / area |
| | w2 = y * (w - x) / area |
| | w3 = (h - y) * x / area |
| | w4 = 1 - w1 - w2 - w3 |
| |
|
| | return dict( |
| | gt=(gt_LT, gt_RT, gt_LB, gt_RB), |
| | pred=(pred_LT, pred_RT, pred_LB, pred_RB), |
| | weight=(w1, w2, w3, w4), |
| | ) |
| |
|
| | def ssim(self, pred: np.ndarray, gt: np.ndarray) -> float: |
| | """ |
| | Calculate the ssim score. |
| | """ |
| | h, w = pred.shape |
| | N = h * w |
| |
|
| | x = np.mean(pred) |
| | y = np.mean(gt) |
| |
|
| | sigma_x = np.sum((pred - x) ** 2) / (N - 1) |
| | sigma_y = np.sum((gt - y) ** 2) / (N - 1) |
| | sigma_xy = np.sum((pred - x) * (gt - y)) / (N - 1) |
| |
|
| | alpha = 4 * x * y * sigma_xy |
| | beta = (x ** 2 + y ** 2) * (sigma_x + sigma_y) |
| |
|
| | if alpha != 0: |
| | score = alpha / (beta + _EPS) |
| | elif alpha == 0 and beta == 0: |
| | score = 1 |
| | else: |
| | score = 0 |
| | return score |
| |
|
| | def get_results(self) -> dict: |
| | """ |
| | Return the results about S-measure. |
| | |
| | :return: dict(sm=sm) |
| | """ |
| | sm = np.mean(np.array(self.sms, dtype=_TYPE)) |
| | return dict(sm=sm) |
| |
|
| |
|
| | class Emeasure(object): |
| | def __init__(self): |
| | """ |
| | E-measure(Enhanced-alignment Measure) for SOD. |
| | |
| | More details about the implementation can be found in https://www.yuque.com/lart/blog/lwgt38 |
| | |
| | :: |
| | |
| | @inproceedings{Emeasure, |
| | title="Enhanced-alignment Measure for Binary Foreground Map Evaluation", |
| | author="Deng-Ping {Fan} and Cheng {Gong} and Yang {Cao} and Bo {Ren} and Ming-Ming {Cheng} and Ali {Borji}", |
| | booktitle=IJCAI, |
| | pages="698--704", |
| | year={2018} |
| | } |
| | """ |
| | self.adaptive_ems = [] |
| | self.changeable_ems = [] |
| |
|
| | def step(self, pred: np.ndarray, gt: np.ndarray): |
| | pred, gt = _prepare_data(pred=pred, gt=gt) |
| | self.gt_fg_numel = np.count_nonzero(gt) |
| | self.gt_size = gt.shape[0] * gt.shape[1] |
| |
|
| | changeable_ems = self.cal_changeable_em(pred, gt) |
| | self.changeable_ems.append(changeable_ems) |
| | adaptive_em = self.cal_adaptive_em(pred, gt) |
| | self.adaptive_ems.append(adaptive_em) |
| |
|
| | def cal_adaptive_em(self, pred: np.ndarray, gt: np.ndarray) -> float: |
| | """ |
| | Calculate the adaptive E-measure. |
| | |
| | :return: adaptive_em |
| | """ |
| | adaptive_threshold = _get_adaptive_threshold(pred, max_value=1) |
| | adaptive_em = self.cal_em_with_threshold(pred, gt, threshold=adaptive_threshold) |
| | return adaptive_em |
| |
|
| | def cal_changeable_em(self, pred: np.ndarray, gt: np.ndarray) -> np.ndarray: |
| | """ |
| | Calculate the changeable E-measure, which can be used to obtain the mean E-measure, |
| | the maximum E-measure and the E-measure-threshold curve. |
| | |
| | :return: changeable_ems |
| | """ |
| | changeable_ems = self.cal_em_with_cumsumhistogram(pred, gt) |
| | return changeable_ems |
| |
|
| | def cal_em_with_threshold(self, pred: np.ndarray, gt: np.ndarray, threshold: float) -> float: |
| | """ |
| | Calculate the E-measure corresponding to the specific threshold. |
| | |
| | Variable naming rules within the function: |
| | ``[pred attribute(foreground fg, background bg)]_[gt attribute(foreground fg, background bg)]_[meaning]`` |
| | |
| | If only ``pred`` or ``gt`` is considered, another corresponding attribute location is replaced with '``_``'. |
| | """ |
| | binarized_pred = pred >= threshold |
| | fg_fg_numel = np.count_nonzero(binarized_pred & gt) |
| | fg_bg_numel = np.count_nonzero(binarized_pred & ~gt) |
| |
|
| | fg___numel = fg_fg_numel + fg_bg_numel |
| | bg___numel = self.gt_size - fg___numel |
| |
|
| | if self.gt_fg_numel == 0: |
| | enhanced_matrix_sum = bg___numel |
| | elif self.gt_fg_numel == self.gt_size: |
| | enhanced_matrix_sum = fg___numel |
| | else: |
| | parts_numel, combinations = self.generate_parts_numel_combinations( |
| | fg_fg_numel=fg_fg_numel, |
| | fg_bg_numel=fg_bg_numel, |
| | pred_fg_numel=fg___numel, |
| | pred_bg_numel=bg___numel, |
| | ) |
| |
|
| | results_parts = [] |
| | for i, (part_numel, combination) in enumerate(zip(parts_numel, combinations)): |
| | align_matrix_value = ( |
| | 2 |
| | * (combination[0] * combination[1]) |
| | / (combination[0] ** 2 + combination[1] ** 2 + _EPS) |
| | ) |
| | enhanced_matrix_value = (align_matrix_value + 1) ** 2 / 4 |
| | results_parts.append(enhanced_matrix_value * part_numel) |
| | enhanced_matrix_sum = sum(results_parts) |
| |
|
| | em = enhanced_matrix_sum / (self.gt_size - 1 + _EPS) |
| | return em |
| |
|
| | def cal_em_with_cumsumhistogram(self, pred: np.ndarray, gt: np.ndarray) -> np.ndarray: |
| | """ |
| | Calculate the E-measure corresponding to the threshold that varies from 0 to 255.. |
| | |
| | Variable naming rules within the function: |
| | ``[pred attribute(foreground fg, background bg)]_[gt attribute(foreground fg, background bg)]_[meaning]`` |
| | |
| | If only ``pred`` or ``gt`` is considered, another corresponding attribute location is replaced with '``_``'. |
| | """ |
| | pred = (pred * 255).astype(np.uint8) |
| | bins = np.linspace(0, 256, 257) |
| | fg_fg_hist, _ = np.histogram(pred[gt], bins=bins) |
| | fg_bg_hist, _ = np.histogram(pred[~gt], bins=bins) |
| | fg_fg_numel_w_thrs = np.cumsum(np.flip(fg_fg_hist), axis=0) |
| | fg_bg_numel_w_thrs = np.cumsum(np.flip(fg_bg_hist), axis=0) |
| |
|
| | fg___numel_w_thrs = fg_fg_numel_w_thrs + fg_bg_numel_w_thrs |
| | bg___numel_w_thrs = self.gt_size - fg___numel_w_thrs |
| |
|
| | if self.gt_fg_numel == 0: |
| | enhanced_matrix_sum = bg___numel_w_thrs |
| | elif self.gt_fg_numel == self.gt_size: |
| | enhanced_matrix_sum = fg___numel_w_thrs |
| | else: |
| | parts_numel_w_thrs, combinations = self.generate_parts_numel_combinations( |
| | fg_fg_numel=fg_fg_numel_w_thrs, |
| | fg_bg_numel=fg_bg_numel_w_thrs, |
| | pred_fg_numel=fg___numel_w_thrs, |
| | pred_bg_numel=bg___numel_w_thrs, |
| | ) |
| |
|
| | results_parts = np.empty(shape=(4, 256), dtype=np.float64) |
| | for i, (part_numel, combination) in enumerate(zip(parts_numel_w_thrs, combinations)): |
| | align_matrix_value = ( |
| | 2 |
| | * (combination[0] * combination[1]) |
| | / (combination[0] ** 2 + combination[1] ** 2 + _EPS) |
| | ) |
| | enhanced_matrix_value = (align_matrix_value + 1) ** 2 / 4 |
| | results_parts[i] = enhanced_matrix_value * part_numel |
| | enhanced_matrix_sum = results_parts.sum(axis=0) |
| |
|
| | em = enhanced_matrix_sum / (self.gt_size - 1 + _EPS) |
| | return em |
| |
|
| | def generate_parts_numel_combinations( |
| | self, fg_fg_numel, fg_bg_numel, pred_fg_numel, pred_bg_numel |
| | ): |
| | bg_fg_numel = self.gt_fg_numel - fg_fg_numel |
| | bg_bg_numel = pred_bg_numel - bg_fg_numel |
| |
|
| | parts_numel = [fg_fg_numel, fg_bg_numel, bg_fg_numel, bg_bg_numel] |
| |
|
| | mean_pred_value = pred_fg_numel / self.gt_size |
| | mean_gt_value = self.gt_fg_numel / self.gt_size |
| |
|
| | demeaned_pred_fg_value = 1 - mean_pred_value |
| | demeaned_pred_bg_value = 0 - mean_pred_value |
| | demeaned_gt_fg_value = 1 - mean_gt_value |
| | demeaned_gt_bg_value = 0 - mean_gt_value |
| |
|
| | combinations = [ |
| | (demeaned_pred_fg_value, demeaned_gt_fg_value), |
| | (demeaned_pred_fg_value, demeaned_gt_bg_value), |
| | (demeaned_pred_bg_value, demeaned_gt_fg_value), |
| | (demeaned_pred_bg_value, demeaned_gt_bg_value), |
| | ] |
| | return parts_numel, combinations |
| |
|
| | def get_results(self) -> dict: |
| | """ |
| | Return the results about E-measure. |
| | |
| | :return: dict(em=dict(adp=adaptive_em, curve=changeable_em)) |
| | """ |
| | adaptive_em = np.mean(np.array(self.adaptive_ems, dtype=_TYPE)) |
| | changeable_em = np.mean(np.array(self.changeable_ems, dtype=_TYPE), axis=0) |
| | return dict(em=dict(adp=adaptive_em, curve=changeable_em)) |
| |
|
| |
|
| | class WeightedFmeasure(object): |
| | def __init__(self, beta: float = 1): |
| | """ |
| | Weighted F-measure for SOD. |
| | |
| | :: |
| | |
| | @inproceedings{wFmeasure, |
| | title={How to eval foreground maps?}, |
| | author={Margolin, Ran and Zelnik-Manor, Lihi and Tal, Ayellet}, |
| | booktitle=CVPR, |
| | pages={248--255}, |
| | year={2014} |
| | } |
| | |
| | :param beta: the weight of the precision |
| | """ |
| | self.beta = beta |
| | self.weighted_fms = [] |
| |
|
| | def step(self, pred: np.ndarray, gt: np.ndarray): |
| | pred, gt = _prepare_data(pred=pred, gt=gt) |
| |
|
| | if np.all(~gt): |
| | wfm = 0 |
| | else: |
| | wfm = self.cal_wfm(pred, gt) |
| | self.weighted_fms.append(wfm) |
| |
|
| | def cal_wfm(self, pred: np.ndarray, gt: np.ndarray) -> float: |
| | """ |
| | Calculate the weighted F-measure. |
| | """ |
| | Dst, Idxt = bwdist(gt == 0, return_indices=True) |
| |
|
| | E = np.abs(pred - gt) |
| | Et = np.copy(E) |
| | Et[gt == 0] = Et[Idxt[0][gt == 0], Idxt[1][gt == 0]] |
| |
|
| | K = self.matlab_style_gauss2D((7, 7), sigma=5) |
| | EA = convolve(Et, weights=K, mode="constant", cval=0) |
| | MIN_E_EA = np.where(gt & (EA < E), EA, E) |
| |
|
| | B = np.where(gt == 0, 2 - np.exp(np.log(0.5) / 5 * Dst), np.ones_like(gt)) |
| | Ew = MIN_E_EA * B |
| |
|
| | TPw = np.sum(gt) - np.sum(Ew[gt == 1]) |
| | FPw = np.sum(Ew[gt == 0]) |
| |
|
| | R = 1 - np.mean(Ew[gt == 1]) |
| | P = TPw / (TPw + FPw + _EPS) |
| |
|
| | Q = (1 + self.beta) * R * P / (R + self.beta * P + _EPS) |
| |
|
| | return Q |
| |
|
| | def matlab_style_gauss2D(self, shape: tuple = (7, 7), sigma: int = 5) -> np.ndarray: |
| | """ |
| | 2D gaussian mask - should give the same result as MATLAB's |
| | fspecial('saliency',[shape],[sigma]) |
| | """ |
| | m, n = [(ss - 1) / 2 for ss in shape] |
| | y, x = np.ogrid[-m : m + 1, -n : n + 1] |
| | h = np.exp(-(x * x + y * y) / (2 * sigma * sigma)) |
| | h[h < np.finfo(h.dtype).eps * h.max()] = 0 |
| | sumh = h.sum() |
| | if sumh != 0: |
| | h /= sumh |
| | return h |
| |
|
| | def get_results(self) -> dict: |
| | """ |
| | Return the results about weighted F-measure. |
| | |
| | :return: dict(wfm=weighted_fm) |
| | """ |
| | weighted_fm = np.mean(np.array(self.weighted_fms, dtype=_TYPE)) |
| | return dict(wfm=weighted_fm) |
| |
|
| | class BoundaryAccuracy(object): |
| | def __init__(self): |
| | """ |
| | MAE(mean absolute error) for SOD. |
| | |
| | :: |
| | |
| | @inproceedings{MAE, |
| | title={Saliency filters: Contrast based filtering for salient region detection}, |
| | author={Perazzi, Federico and Kr{\"a}henb{\"u}hl, Philipp and Pritch, Yael and Hornung, Alexander}, |
| | booktitle=CVPR, |
| | pages={733--740}, |
| | year={2012} |
| | } |
| | """ |
| | self.bas = [] |
| | self.all_h = 0 |
| | self.all_w = 0 |
| | self.all_max = 0 |
| |
|
| | def step(self, pred: np.ndarray, gt: np.ndarray): |
| | |
| | |
| | refined = gt.copy() |
| |
|
| | rmin = cmin = 0 |
| | rmax, cmax = gt.shape |
| |
|
| | self.all_h += rmax |
| | self.all_w += cmax |
| | self.all_max += max(rmax, cmax) |
| |
|
| | refined_h, refined_w = refined.shape |
| | if refined_h != cmax: |
| | refined = np.array(Image.fromarray(pred).resize((cmax, rmax), Image.BILINEAR)) |
| |
|
| | if not(gt.sum() < 32*32): |
| | if not((cmax==cmin) or (rmax==rmin)): |
| | class_refined_prob = np.array(Image.fromarray(pred).resize((cmax-cmin, rmax-rmin), Image.BILINEAR)) |
| | refined[rmin:rmax, cmin:cmax] = class_refined_prob |
| | |
| | pred = pred > 128 |
| | gt = gt > 128 |
| |
|
| | ba = self.cal_ba(pred, gt) |
| | self.bas.append(ba) |
| | |
| | def get_disk_kernel(self, radius): |
| | return cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (radius*2+1, radius*2+1)) |
| |
|
| | def cal_ba(self, pred: np.ndarray, gt: np.ndarray) -> np.ndarray: |
| | """ |
| | Calculate the mean absolute error. |
| | |
| | :return: ba |
| | """ |
| | |
| | gt = gt.astype(np.uint8) |
| | pred = pred.astype(np.uint8) |
| |
|
| | h, w = gt.shape |
| |
|
| | min_radius = 1 |
| | max_radius = (w+h)/300 |
| | num_steps = 5 |
| |
|
| | pred_acc = [None] * num_steps |
| |
|
| | for i in range(num_steps): |
| | curr_radius = min_radius + int((max_radius-min_radius)/num_steps*i) |
| |
|
| | kernel = self.get_disk_kernel(curr_radius) |
| | boundary_region = cv2.morphologyEx(gt, cv2.MORPH_GRADIENT, kernel) > 0 |
| |
|
| | gt_in_bound = gt[boundary_region] |
| | pred_in_bound = pred[boundary_region] |
| |
|
| | num_edge_pixels = (boundary_region).sum() |
| | num_pred_gd_pix = ((gt_in_bound) * (pred_in_bound) + (1-gt_in_bound) * (1-pred_in_bound)).sum() |
| |
|
| | pred_acc[i] = num_pred_gd_pix / num_edge_pixels |
| |
|
| | ba = sum(pred_acc)/num_steps |
| | return ba |
| |
|
| | def get_results(self) -> dict: |
| | """ |
| | Return the results about MAE. |
| | |
| | :return: dict(mae=mae) |
| | """ |
| | mba = np.mean(np.array(self.bas, _TYPE)) |
| | return dict(mba=mba) |