Spaces:
Sleeping
Sleeping
from typing import Dict, Tuple, Union | |
import numpy as np | |
import torch | |
from scipy.optimize import linear_sum_assignment | |
def average_precision(recalls: np.ndarray, | |
precisions: np.ndarray, | |
mode: str = 'area') -> np.ndarray: | |
"""Calculate average precision (for single or multiple scales). | |
Args: | |
recalls (np.ndarray): Recalls with shape of (num_scales, num_dets) | |
or (num_dets, ). | |
precisions (np.ndarray): Precisions with shape of | |
(num_scales, num_dets) or (num_dets, ). | |
mode (str): 'area' or '11points', 'area' means calculating the area | |
under precision-recall curve, '11points' means calculating | |
the average precision of recalls at [0, 0.1, ..., 1] | |
Defaults to 'area'. | |
Returns: | |
np.ndarray: Calculated average precision. | |
""" | |
if recalls.ndim == 1: | |
recalls = recalls[np.newaxis, :] | |
precisions = precisions[np.newaxis, :] | |
assert recalls.shape == precisions.shape | |
assert recalls.ndim == 2 | |
num_scales = recalls.shape[0] | |
ap = np.zeros(num_scales, dtype=np.float32) | |
if mode == 'area': | |
zeros = np.zeros((num_scales, 1), dtype=recalls.dtype) | |
ones = np.ones((num_scales, 1), dtype=recalls.dtype) | |
mrec = np.hstack((zeros, recalls, ones)) | |
mpre = np.hstack((zeros, precisions, zeros)) | |
for i in range(mpre.shape[1] - 1, 0, -1): | |
mpre[:, i - 1] = np.maximum(mpre[:, i - 1], mpre[:, i]) | |
for i in range(num_scales): | |
ind = np.where(mrec[i, 1:] != mrec[i, :-1])[0] | |
ap[i] = np.sum( | |
(mrec[i, ind + 1] - mrec[i, ind]) * mpre[i, ind + 1]) | |
elif mode == '11points': | |
for i in range(num_scales): | |
for thr in np.arange(0, 1 + 1e-3, 0.1): | |
precs = precisions[i, recalls[i, :] >= thr] | |
prec = precs.max() if precs.size > 0 else 0 | |
ap[i] += prec | |
ap /= 11 | |
else: | |
raise ValueError( | |
'Unrecognized mode, only "area" and "11points" are supported') | |
return ap | |
def get_f1_scores(iou_matrix: Union[np.ndarray, torch.tensor], | |
iou_threshold) -> float: | |
"""Refer to the algorithm in Multi3DRefer to compute the F1 score. | |
Args: | |
iou_matrix (ndarray/tensor): | |
The iou matrix of the predictions and ground truths with | |
shape (num_preds , num_gts) | |
iou_threshold (float): 0.25/0.5 | |
Returns: | |
float: the f1 score as the result | |
""" | |
iou_thr_tp = 0 | |
pred_bboxes_count, gt_bboxes_count = iou_matrix.shape | |
square_matrix_len = max(gt_bboxes_count, pred_bboxes_count) | |
iou_matrix_fill = np.zeros(shape=(square_matrix_len, square_matrix_len), | |
dtype=np.float32) | |
iou_matrix_fill[:pred_bboxes_count, :gt_bboxes_count] = iou_matrix | |
# apply matching algorithm | |
row_idx, col_idx = linear_sum_assignment(iou_matrix_fill * -1) | |
# iterate matched pairs, check ious | |
for i in range(pred_bboxes_count): | |
iou = iou_matrix[row_idx[i], col_idx[i]] | |
# calculate true positives | |
if iou >= iou_threshold: | |
iou_thr_tp += 1 | |
# calculate precision, recall and f1-score for the current scene | |
f1_score = 2 * iou_thr_tp / (pred_bboxes_count + gt_bboxes_count) | |
return f1_score | |
def __get_fp_tp_array__(iou_array: Union[np.ndarray, torch.tensor], | |
iou_threshold: float) \ | |
-> Tuple[np.ndarray, np.ndarray]: | |
"""Compute the False-positive and True-positive array for each prediction. | |
Args: | |
iou_array (ndarray/tensor): | |
the iou matrix of the predictions and ground truths | |
(shape num_preds, num_gts) | |
iou_threshold (float): 0.25/0.5 | |
Returns: | |
np.ndarray, np.ndarray: (len(preds)), | |
the false-positive and true-positive array for each prediction. | |
""" | |
gt_matched_records = np.zeros((len(iou_array[0])), dtype=bool) | |
tp_thr = np.zeros((len(iou_array))) | |
fp_thr = np.zeros((len(iou_array))) | |
for d, _ in enumerate(range(len(iou_array))): | |
iou_max = -np.inf | |
cur_iou = iou_array[d] | |
num_gts = cur_iou.shape[0] | |
if num_gts > 0: | |
for j in range(num_gts): | |
iou = cur_iou[j] | |
if iou > iou_max: | |
iou_max = iou | |
jmax = j | |
if iou_max >= iou_threshold: | |
if not gt_matched_records[jmax]: | |
gt_matched_records[jmax] = True | |
tp_thr[d] = 1.0 | |
else: | |
fp_thr[d] = 1.0 | |
else: | |
fp_thr[d] = 1.0 | |
return fp_thr, tp_thr | |
def subset_get_average_precision(subset_results: dict, | |
iou_thr: float)\ | |
-> Tuple[np.ndarray, np.ndarray]: | |
"""Return the average precision and max recall for a given iou array, | |
"subset" version while the num_gt of each sample may differ. | |
Args: | |
subset_results (dict): | |
The results, consisting of scores, sample_indices, ious. | |
sample_indices means which sample the prediction belongs to. | |
iou_threshold (float): 0.25/0.5 | |
Returns: | |
Tuple[np.ndarray, np.ndarray]: the average precision and max recall. | |
""" | |
confidences = subset_results['scores'] | |
sample_indices = subset_results['sample_indices'] | |
ious = subset_results['ious'] | |
gt_matched_records = {} | |
total_gt_boxes = 0 | |
for i, sample_idx in enumerate(sample_indices): | |
if sample_idx not in gt_matched_records: | |
gt_matched_records[sample_idx] = np.zeros((len(ious[i]), ), | |
dtype=bool) | |
total_gt_boxes += ious[i].shape[0] | |
confidences = np.array(confidences) | |
sorted_inds = np.argsort(-confidences) | |
sample_indices = [sample_indices[i] for i in sorted_inds] | |
ious = [ious[i] for i in sorted_inds] | |
tp_thr = np.zeros(len(sample_indices)) | |
fp_thr = np.zeros(len(sample_indices)) | |
for d, sample_idx in enumerate(sample_indices): | |
iou_max = -np.inf | |
cur_iou = ious[d] | |
num_gts = cur_iou.shape[0] | |
if num_gts > 0: | |
for j in range(num_gts): | |
iou = cur_iou[j] | |
if iou > iou_max: | |
iou_max = iou | |
jmax = j | |
if iou_max >= iou_thr: | |
if not gt_matched_records[sample_idx][jmax]: | |
gt_matched_records[sample_idx][jmax] = True | |
tp_thr[d] = 1.0 | |
else: | |
fp_thr[d] = 1.0 | |
else: | |
fp_thr[d] = 1.0 | |
fp = np.cumsum(fp_thr) | |
tp = np.cumsum(tp_thr) | |
recall = tp / float(total_gt_boxes) | |
precision = tp / np.maximum(tp + fp, np.finfo(np.float64).eps) | |
return average_precision(recall, precision), np.max(recall) | |
def get_average_precision(iou_array: np.ndarray, iou_threshold: float) \ | |
-> Tuple[np.ndarray, np.ndarray]: | |
"""Return the average precision and max recall for a given iou array. | |
Args: | |
iou_array (ndarray/tensor): | |
The iou matrix of the predictions and ground truths | |
(shape len(preds)*len(gts)) | |
iou_threshold (float): 0.25/0.5 | |
Returns: | |
Tuple[np.ndarray, np.ndarray]: the average precision and max recall. | |
""" | |
fp, tp = __get_fp_tp_array__(iou_array, iou_threshold) | |
fp_cum = np.cumsum(fp) | |
tp_cum = np.cumsum(tp) | |
recall = tp_cum / float(iou_array.shape[1]) | |
precision = tp_cum / np.maximum(tp_cum + fp_cum, np.finfo(np.float64).eps) | |
return average_precision(recall, precision), np.max(recall) | |
def get_general_topk_scores(iou_array: Union[np.ndarray, torch.tensor], | |
iou_threshold: float, | |
mode: str = 'sigma') -> Dict[str, float]: | |
"""Compute the multi-topk metric, we provide two modes. | |
Args: | |
iou_array (ndarray/tensor): | |
the iou matrix of the predictions and ground truths | |
(shape len(preds)*len(gts)) | |
iou_threshold (float): 0.25/0.5 | |
mode (str): 'sigma'/'simple' | |
"simple": 1/N * Hit(min(N*k,len(pred))) | |
"sigma": 1/N * Sigma [Hit(min(n*k,len(pred)))>=n] n = 1~N | |
Hit(M) return the number of gtound truths hitted by | |
the first M predictions. | |
N = the number of gtound truths | |
Default to 'sigma'. | |
Returns: | |
Dict[str,float]: the score of multi-topk metric. | |
""" | |
assert mode in ['sigma', 'simple'] | |
topk_scores = [] | |
gt_matched_records = np.zeros(len(iou_array[0])) | |
num_gt = len(gt_matched_records) | |
for d, _ in enumerate(range(len(iou_array))): | |
iou_max = -np.inf | |
cur_iou = iou_array[d] | |
for j in range(len(iou_array[d])): | |
iou = cur_iou[j] | |
if iou > iou_max: | |
iou_max = iou | |
j_max = j | |
if iou_max >= iou_threshold: | |
gt_matched_records[j_max] = True | |
topk_scores.append(gt_matched_records.copy()) | |
topk_results = {} | |
for topk in [1, 3, 5, 10]: | |
if mode == 'sigma': | |
scores = [ | |
int( | |
np.sum(topk_scores[min(n * topk, len(topk_scores)) - | |
1]) >= n) for n in range(1, num_gt + 1) | |
] | |
result = np.sum(scores) / num_gt | |
else: | |
query_index = min(num_gt * topk, len(topk_scores)) - 1 | |
result = np.sum(topk_scores[query_index]) / num_gt | |
topk_results[f'gTop-{topk}@{iou_threshold}'] = result | |
return topk_results | |