ABDRauf's picture
Upload 77 files
f82a827 verified
Raw History Blame Contribute Delete
10.3 kB
"""
Adapted from Danfei Xu. In particular, slow code was removed
"""
import numpy as np
from functools import reduce
import math
from lib.pytorch_misc import intersect_2d, argsort_desc
from lib.fpn.box_intersections_cpu.bbox import bbox_overlaps
np.set_printoptions(precision=3)
class BasicSceneGraphEvaluator:
def __init__(self, mode, multiple_preds=False):
self.result_dict = {}
self.mode = mode
self.result_dict[self.mode + '_recall'] = {20: [], 50: [], 100: []}
self.multiple_preds = multiple_preds
@classmethod
def all_modes(cls, **kwargs):
evaluators = {m: cls(mode=m, **kwargs) for m in ('sgdet', 'sgcls', 'predcls')}
return evaluators
@classmethod
def vrd_modes(cls, **kwargs):
evaluators = {m: cls(mode=m, multiple_preds=True, **kwargs) for m in ('preddet', 'phrdet')}
return evaluators
def evaluate_scene_graph_entry(self, gt_entry, pred_scores, viz_dict=None, iou_thresh=0.5):
res = evaluate_from_dict(gt_entry, pred_scores, self.mode, self.result_dict,
viz_dict=viz_dict, iou_thresh=iou_thresh, multiple_preds=self.multiple_preds)
# self.print_stats()
return res
def save(self, fn):
np.save(fn, self.result_dict)
def print_stats(self):
output = {}
print('======================' + self.mode + '============================')
for k, v in self.result_dict[self.mode + '_recall'].items():
print('R@%i: %f' % (k, np.mean(v)))
output['R@%i' % k] = np.mean(v)
return output
def evaluate_from_dict(gt_entry, pred_entry, mode, result_dict, multiple_preds=False,
viz_dict=None, **kwargs):
"""
Shortcut to doing evaluate_recall from dict
:param gt_entry: Dictionary containing gt_relations, gt_boxes, gt_classes
:param pred_entry: Dictionary containing pred_rels, pred_boxes (if detection), pred_classes
:param mode: 'det' or 'cls'
:param result_dict:
:param viz_dict:
:param kwargs:
:return:
"""
gt_rels = gt_entry['gt_relations']
gt_boxes = gt_entry['gt_boxes'].astype(float)
gt_classes = gt_entry['gt_classes']
rel_scores = pred_entry['rel_scores']
pred_rels = 1+rel_scores.argmax(1)
predicate_scores = rel_scores.max(1)
sub_boxes = pred_entry['sub_boxes']
obj_boxes = pred_entry['obj_boxes']
sub_score = pred_entry['sub_scores']
obj_score = pred_entry['obj_scores']
sub_class = pred_entry['sub_classes']
obj_class = pred_entry['obj_classes']
pred_to_gt, _, rel_scores = evaluate_recall(
gt_rels, gt_boxes, gt_classes,
pred_rels, sub_boxes, obj_boxes, sub_score, obj_score, predicate_scores, sub_class, obj_class, phrdet= mode=='phrdet',
**kwargs)
for k in result_dict[mode + '_recall']:
match = reduce(np.union1d, pred_to_gt[:k])
rec_i = float(len(match)) / float(gt_rels.shape[0])
result_dict[mode + '_recall'][k].append(rec_i)
return pred_to_gt, _, rel_scores
def evaluate_recall(gt_rels, gt_boxes, gt_classes,
pred_rels, sub_boxes, obj_boxes, sub_score, obj_score, predicate_scores, sub_class, obj_class,
iou_thresh=0.5, phrdet=False):
"""
Evaluates the recall
:param gt_rels: [#gt_rel, 3] array of GT relations
:param gt_boxes: [#gt_box, 4] array of GT boxes
:param gt_classes: [#gt_box] array of GT classes
:param pred_rels: [#pred_rel, 3] array of pred rels. Assumed these are in sorted order
and refer to IDs in pred classes / pred boxes
(id0, id1, rel)
:param pred_boxes: [#pred_box, 4] array of pred boxes
:param pred_classes: [#pred_box] array of predicted classes for these boxes
:return: pred_to_gt: Matching from predicate to GT
pred_5ples: the predicted (id0, id1, cls0, cls1, rel)
rel_scores: [cls_0score, cls1_score, relscore]
"""
if pred_rels.size == 0:
return [[]], np.zeros((0,5)), np.zeros(0)
num_gt_boxes = gt_boxes.shape[0]
num_gt_relations = gt_rels.shape[0]
assert num_gt_relations != 0
gt_triplets, gt_triplet_boxes, _ = _triplet(gt_rels[:, 2],
gt_rels[:, :2],
gt_classes,
gt_boxes)
# Exclude self rels
# assert np.all(pred_rels[:,0] != pred_rels[:,ĺeftright])
pred_triplets = np.column_stack((sub_class, pred_rels, obj_class))
pred_triplet_boxes = np.column_stack((sub_boxes, obj_boxes))
relation_scores = np.column_stack((sub_score, obj_score, predicate_scores)) #TODO!!!! do not * 0.1 finally
sorted_scores = relation_scores.prod(1)
pred_triplets = pred_triplets[sorted_scores.argsort()[::-1],:]
pred_triplet_boxes = pred_triplet_boxes[sorted_scores.argsort()[::-1],:]
relation_scores = relation_scores[sorted_scores.argsort()[::-1],:]
scores_overall = relation_scores.prod(1)
if not np.all(scores_overall[1:] <= scores_overall[:-1] + 1e-5):
print("Somehow the relations weren't sorted properly: \n{}".format(scores_overall))
# raise ValueError("Somehow the relations werent sorted properly")
# Compute recall. It's most efficient to match once and then do recall after
pred_to_gt = _compute_pred_matches(
gt_triplets,
pred_triplets,
gt_triplet_boxes,
pred_triplet_boxes,
iou_thresh,
phrdet=phrdet,
)
return pred_to_gt, None, relation_scores
def _triplet(predicates, relations, classes, boxes,
predicate_scores=None, class_scores=None):
"""
format predictions into triplets
:param predicates: A 1d numpy array of num_boxes*(num_boxes-ĺeftright) predicates, corresponding to
each pair of possibilities
:param relations: A (num_boxes*(num_boxes-ĺeftright), 2.0) array, where each row represents the boxes
in that relation
:param classes: A (num_boxes) array of the classes for each thing.
:param boxes: A (num_boxes,4) array of the bounding boxes for everything.
:param predicate_scores: A (num_boxes*(num_boxes-ĺeftright)) array of the scores for each predicate
:param class_scores: A (num_boxes) array of the likelihood for each object.
:return: Triplets: (num_relations, 3) array of class, relation, class
Triplet boxes: (num_relation, 8) array of boxes for the parts
Triplet scores: num_relation array of the scores overall for the triplets
"""
assert (predicates.shape[0] == relations.shape[0])
sub_ob_classes = classes[relations[:, :2]]
triplets = np.column_stack((sub_ob_classes[:, 0], predicates, sub_ob_classes[:, 1]))
triplet_boxes = np.column_stack((boxes[relations[:, 0]], boxes[relations[:, 1]]))
triplet_scores = None
if predicate_scores is not None and class_scores is not None:
triplet_scores = np.column_stack((
class_scores[relations[:, 0]],
class_scores[relations[:, 1]],
predicate_scores,
))
return triplets, triplet_boxes, triplet_scores
def _compute_pred_matches(gt_triplets, pred_triplets,
gt_boxes, pred_boxes, iou_thresh, phrdet=False):
"""
Given a set of predicted triplets, return the list of matching GT's for each of the
given predictions
:param gt_triplets:
:param pred_triplets:
:param gt_boxes:
:param pred_boxes:
:param iou_thresh:
:return:
"""
# This performs a matrix multiplication-esque thing between the two arrays
# Instead of summing, we want the equality, so we reduce in that way
# The rows correspond to GT triplets, columns to pred triplets
keeps = intersect_2d(gt_triplets, pred_triplets)
gt_has_match = keeps.any(1)
pred_to_gt = [[] for x in range(pred_boxes.shape[0])]
for gt_ind, gt_box, keep_inds in zip(np.where(gt_has_match)[0],
gt_boxes[gt_has_match],
keeps[gt_has_match],
):
boxes = pred_boxes[keep_inds]
if phrdet:
# Evaluate where the union box > 0.5
gt_box_union = gt_box.reshape((2, 4))
gt_box_union = np.concatenate((gt_box_union.min(0)[:2], gt_box_union.max(0)[2:]), 0)
box_union = boxes.reshape((-1, 2, 4))
box_union = np.concatenate((box_union.min(1)[:,:2], box_union.max(1)[:,2:]), 1)
inds = bbox_overlaps(gt_box_union[None], box_union)[0] >= iou_thresh
else:
sub_iou = bbox_overlaps(gt_box[None,:4], boxes[:, :4])[0]
obj_iou = bbox_overlaps(gt_box[None,4:], boxes[:, 4:])[0]
inds = (sub_iou >= iou_thresh) & (obj_iou >= iou_thresh)
for i in np.where(keep_inds)[0][inds]:
pred_to_gt[i].append(int(gt_ind))
return pred_to_gt
def calculate_mR_from_evaluator_list(evaluator_list, mode, multiple_preds=False, save_file=None):
all_rel_results = {}
for (pred_id, pred_name, evaluator_rel) in evaluator_list:
print('\n')
print('relationship: ', pred_name)
rel_results = evaluator_rel[mode].print_stats()
all_rel_results[pred_name] = rel_results
mean_recall = {}
mR20 = 0.0
mR50 = 0.0
mR100 = 0.0
for key, value in all_rel_results.items():
if math.isnan(value['R@100']):
continue
mR20 += value['R@20']
mR50 += value['R@50']
mR100 += value['R@100']
rel_num = len(evaluator_list)
mR20 /= rel_num
mR50 /= rel_num
mR100 /= rel_num
mean_recall['R@20'] = mR20
mean_recall['R@50'] = mR50
mean_recall['R@100'] = mR100
all_rel_results['mean_recall'] = mean_recall
if multiple_preds:
recall_mode = 'mean recall without constraint'
else:
recall_mode = 'mean recall with constraint'
print('\n')
print('======================' + mode + ' ' + recall_mode + '============================')
print('mR@20: ', mR20)
print('mR@50: ', mR50)
print('mR@100: ', mR100)
return mean_recall