Last active
February 14, 2022 03:42
-
-
Save egparedes/b3aa721381a889e3be590711f979688d to your computer and use it in GitHub Desktop.
Python implementation of the Weighted F-beta measure (as proposed in "How to Evaluate Foreground Maps?" [Margolin et al. - CVPR'14])
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import numpy as np | |
import scipy | |
import scipy.ndimage | |
def weighted_f_beta_score(candidate, gt, beta=1.0): | |
""" | |
Compute the Weighted F-beta measure (as proposed in "How to Evaluate Foreground Maps?" [Margolin et al. - CVPR'14]) | |
Original MATLAB source code from: | |
[https://cgm.technion.ac.il/Computer-Graphics-Multimedia/Software/FGEval/resources/WFb.m] | |
:param candidate: FG - Binary/Non binary candidate map with values in the range [0-1] | |
:param gt: Binary ground truth map | |
:param beta: attach 'beta' times as much importance to Recall as to Precision (default=1) | |
:result: the Weighted F-beta score | |
""" | |
if np.min(candidate) < 0.0 or np.max(candidate) > 1.0: | |
raise ValueError("'candidate' values must be inside range [0 - 1]") | |
if gt.dtype in [np.bool, np.bool_, np.bool8]: | |
gt_mask = gt | |
not_gt_mask = np.logical_not(gt_mask) | |
gt = np.array(gt, dtype=candidate.dtype) | |
else: | |
if not np.all(np.isclose(gt, 0) | np.isclose(gt, 1)): | |
raise ValueError("'gt' must be a 0/1 or boolean array") | |
gt_mask = np.isclose(gt, 1) | |
not_gt_mask = np.logical_not(gt_mask) | |
gt = np.asarray(gt, dtype=candidate.dtype) | |
E = np.abs(candidate - gt) | |
dist, idx = scipy.ndimage.morphology.distance_transform_edt(not_gt_mask, return_indices=True) | |
# Pixel dependency | |
Et = np.array(E) | |
# To deal correctly with the edges of the foreground region: | |
Et[not_gt_mask] = E[idx[0, not_gt_mask], idx[1, not_gt_mask]] | |
sigma = 5.0 | |
EA = scipy.ndimage.gaussian_filter(Et, sigma=sigma, truncate=3 / sigma, | |
mode='constant', cval=0.0) | |
min_E_EA = np.minimum(E, EA, where=gt_mask, out=np.array(E)) | |
# Pixel importance | |
B = np.ones(gt.shape) | |
B[not_gt_mask] = 2 - np.exp(np.log(1 - 0.5) / 5 * dist[not_gt_mask]) | |
Ew = min_E_EA * B | |
# Final metric computation | |
eps = np.spacing(1) | |
TPw = np.sum(gt) - np.sum(Ew[gt_mask]) | |
FPw = np.sum(Ew[not_gt_mask]) | |
R = 1 - np.mean(Ew[gt_mask]) # Weighed Recall | |
P = TPw / (eps + TPw + FPw) # Weighted Precision | |
# Q = 2 * (R * P) / (eps + R + P) # Beta=1 | |
Q = (1 + beta**2) * (R * P) / (eps + R + (beta * P)) | |
return Q |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment