- Extensible user interfaces to deal with different data formats.
- Support customized evaluation breakdowns, e.g., object size in COCO, difficulty in KITTI, velocity and range in Waymo.
- Interface for general matching scores, e.g. 2D IoU, 3D rotated IoU, center distance.
- Support widely-used metrics like AP, AR and customized metrics like average regression errors, average IoUs, etc.
- Purely based on Python, easy to develop your customized metrics.
pip install treelib
- Clone this repo and run
pip install .
in the cloned directory, orpip install -e .
if you want to modify the source code.
You need to define a function to read the predictions and groundtruth.
def read_prediction(path):
...
return results
where the results
is a dictionary using sample_id (image_id) as key and each item is a dictionary contains at least box
, score
and type
:
# ndarray in shape of [N, C], where N is the number of bboxes in this sample and C is the box dimension
boxes = ...
# ndarray in shape [N,]
scores = ...
# ndarray in shape of [N,]
types = ...
results[sample_id] = dict(box=boxes, score=scores, type=types)
And you need to define another function to read groundtruth as the same way. The items of returned dict contain at least box
and type
.
If you are going to evaluate a 2D detector, you may want to define a 2D IoU function, which will be automatically used in the matching process.
def iou_2d(box1, box2):
# box1 in shape of [N1, 4], which is the box item defined above.
# box2 in shape of [N2, 4], which is the box item defined above.
iou_matrix = ... # [N1, N2]
return iou_matrix
You are free to define any other matching score calculators (e.g., 3D IoU), as long as keeping the same function prototype. For example:
def customized_iou_calculator(box1, box2):
# box1 in shape of [N1, box_dim]
# box2 in shape of [N2, box_dim]
iou_matrix = ... # [N1, N2]
return iou_matrix
To correctly use customized breakdowns, here we define two kinds of breakdowns: separable breakdowns
and inseparable breakdowns
.
Separable breakdowns
indicate those can be used to partition prediction set and groundtruth set before the matching process. For example, object category
is a typical separable breakdown. We usually first partition the predictions and groundtruth and only pass the predictions and groundtruth in the same category to the evaluator.
Inseparable breakdowns
indicate those can NOT be simply used to partition prediction set and groundtruth set before the matching process. A typical inseparable breakdown is object size
in COCO. We deal with those inseparable breakdowns in the matching process.
Follow the following step to add the breakdowns you need:
For example, if you want to evaluate conditioned on vehicle length in Waymo:
def waymo_length_breakdown(object_item):
# the object item is defined in Sec. prepare-predictions-and-groundtruth
return object_item['box'][:, 4] # 4th number indicates vehicle length
If you want to add category and size as breakdowns in COCO:
def get_object_type(object_item):
return object_item['type'] # 4th number indicates vehicle length
def get_object_size(object_item):
box = object_item['box']
return (box[:, 2] * box[:, 3]) ** 0.5 # height x width
separable_breakdown_dict = {'type': [None, 'person', 'bus']} # None means evaluate over all categories.
inseparable_breakdown_dict = {'size': [None, (0, 32), (32, 96), (96, 1e10)]} # None means evaluate over all sizes.
breakdown_func_dict = {'type': get_object_type, 'size': get_object_size}
For example in Waymo:
from od_evaluation.params import BaseParam
class WaymoBaseParam(BaseParam):
def __init__(self, pd_path, gt_path, interval=10, update_sep=None, update_insep=None):
super().__init__(pd_path, gt_path, interval, update_sep, update_insep)
self.iouThrs = [0.7, 0.5]
def add_breakdowns(self):
self.separable_breakdowns = {
'type':('Vehicle', 'Pedestrian', 'Cyclist'),
'range':([0, 30], [30, 50], [50, 80], None), # None means the union of all ranges
}
self.inseparable_breakdowns = {'length':[(0, 4), (4, 8), (8, 20)]}
self.breakdown_func_dict = {'range': get_waymo_object_range, 'length': get_waymo_object_length}
def add_iou_function(self):
self.iou_calculate_func = get_waymo_iou_matrix
def add_input_function(self):
self.read_prediction_func = read_waymo_object
self.read_groundtruth_func = read_waymo_object
Note that you must implement add_breakdowns
, add_iou_function
and add_input_function
after inherit BaseParam
.
from od_evaluation.eval import Evaluator
pd_path = xxx
gt_path = xxx
params = WaymoBaseParam(pd_path, gt_path)
e = Evaluator(params)
e.run()
We use treelib to format the evaluation results, where multiple breakdowns are nested: