Module benchmark

Expand source code
import numpy as np
import pandas as pd

pd.options.mode.chained_assignment = None
__all__ = ['generate_precision_recall']


class _ConfusionMatrix:
    def __init__(self, num_classes, CONF_THRESHOLD, IOU_THRESHOLD):
        self.matrix = np.zeros((num_classes + 1, num_classes + 1))
        self.num_classes = num_classes
        self.CONF_THRESHOLD = CONF_THRESHOLD
        self.IOU_THRESHOLD = IOU_THRESHOLD

    def box_iou_calc(self, boxes1, boxes2):
        # https://github.com/pytorch/vision/blob/master/torchvision/ops/boxes.py
        """
        Return intersection-over-union (Jaccard index) of boxes.
        Both sets of boxes are expected to be in (x1, y1, x2, y2) format.
        Arguments:
            boxes1 (Array[N, 4])
            boxes2 (Array[M, 4])
        Returns:
            iou (Array[N, M]): the NxM matrix containing the pairwise
                IoU values for every element in boxes1 and boxes2
        This implementation is taken from the above link and changed so that it only uses numpy..
        """

        def box_area(box):
            # box = 4xn
            return (box[2] - box[0]) * (box[3] - box[1])
        

        area1 = box_area(boxes1.T)
        area2 = box_area(boxes2.T)

        lt = np.maximum(boxes1[:, None, :2], boxes2[:, :2])  # [N,M,2]
        rb = np.minimum(boxes1[:, None, 2:], boxes2[:, 2:])  # [N,M,2]

        inter = np.prod(np.clip(rb - lt, a_min = 0, a_max = None), 2)
        return inter / (area1[:, None] + area2 - inter)  # iou = inter / (area1 + area2 - inter)
    
    def process_batch(self, detections, labels):
        '''
        Return intersection-over-union (Jaccard index) of boxes.
        Both sets of boxes are expected to be in (x1, y1, x2, y2) format.
        Arguments:
            detections (Array[N, 6]), x1, y1, x2, y2, conf, class
            labels (Array[M, 5]), class, x1, y1, x2, y2
        '''
        conf_detections = []
        for detection in detections:
            if detection[4] >= self.CONF_THRESHOLD:
                conf_detections.append(detection)
        
        detections = np.array(conf_detections)
        labels = np.array(labels)
        if len(labels) != 0:
            gt_classes = labels[:, 0].astype(np.int16)
        else:
            if len(detections):
                detection_classes = detections[:, 5].astype(np.int16)
                for detection_class in detection_classes:
                    self.matrix[self.num_classes, detection_class] += 1
            return
        if len(detections) != 0:
            detection_classes = detections[:, 5].astype(np.int16)
        else:
            if len(labels):
                for gt_class in gt_classes:
                    self.matrix[gt_class, self.num_classes] += 1      
            return

        if len(labels) != 0 and len(detections) != 0:
            all_ious = self.box_iou_calc(labels[:, 1:], detections[:, :4])
            want_idx = np.where(all_ious >= self.IOU_THRESHOLD)
        else:
            all_ious = np.array([[]])
            want_idx = np.array([[]])

        all_matches = []
        for i in range(want_idx[0].shape[0]):
            all_matches.append([want_idx[0][i], want_idx[1][i], all_ious[want_idx[0][i], want_idx[1][i]]])
        
        all_matches = np.array(all_matches)
        if all_matches.shape[0] > 0: # if there is match
            all_matches = all_matches[all_matches[:, 2].argsort()[::-1]]
            all_matches = all_matches[np.unique(all_matches[:, 1], return_index = True)[1]]
            all_matches = all_matches[all_matches[:, 2].argsort()[::-1]]
            all_matches = all_matches[np.unique(all_matches[:, 0], return_index = True)[1]]
        
        for i, label in enumerate(labels):
            if all_matches.shape[0] > 0 and all_matches[all_matches[:, 0] == i].shape[0] == 1:
                gt_class = gt_classes[i]
                detection_class = detection_classes[int(all_matches[all_matches[:, 0] == i, 1][0])]
                self.matrix[(gt_class), detection_class] += 1

            else:
                gt_class = gt_classes[i]
                self.matrix[(gt_class), self.num_classes] += 1
        
        for i, detection in enumerate(detections):
            if all_matches.shape[0] and all_matches[all_matches[:, 1] == i].shape[0] == 0:
                detection_class = detection_classes[i]
                self.matrix[self.num_classes, detection_class] += 1

            elif all_matches.shape[0] == 0:
                detection_class = detection_classes[i]
                self.matrix[self.num_classes, detection_class] += 1

        
    def return_matrix(self):
        return self.matrix

    def print_matrix(self):
        for i in range(self.num_classes + 1):
            print(' '.join(map(str, self.matrix[i])))


def generate_precision_recall(df_gt, df_pred, conf_value=0.5, iou_value=0.5):
    """Return the precision and recall for a given confidence and iou value.

    Args:
        df_gt (pandas dataframe): ground truth dataframe with labels order: "label,frame_id,x0,y0,x1,y1"
        df_pred (pandas dataframe): prediction dataframe with labels order: "x0,y0,x1,y1,confidence,label,frame_id"
        conf_value (float, optional): Confidence value. Defaults to 0.5.
        iou_value (float, optional): IOU threshold value. Defaults to 0.5.

    Returns:
        dict: Dictionary containing list of precision and recall values for each label
    """
    num_frames = df_gt.frame_id.unique().tolist()
    num_frames.extend(df_pred.frame_id.unique().tolist())
    num_frames = list(set(num_frames))
    num_frames.sort()
    labels = df_gt.label.unique().tolist()
    labels.extend(df_pred.label.unique().tolist())
    labels = list(set(labels))
    num_labels = len(labels)
    encoding = [i for i in range(num_labels)]
    cm_obj = _ConfusionMatrix(num_classes=num_labels, CONF_THRESHOLD=conf_value, IOU_THRESHOLD=iou_value)
    for i in num_frames:

        df_gt1 = df_gt[df_gt.frame_id == i]
        df_gt1.drop(columns='frame_id', inplace=True, axis=1)
        df_gt1['label'].replace(labels, encoding, inplace=True)

        df_pred1 = df_pred[df_pred.frame_id == i]
        df_pred1.drop(columns='frame_id', inplace=True, axis=1)
        df_pred1['label'].replace(labels, encoding, inplace=True)

        pred = df_pred1.values.tolist()
        gt = df_gt1.values.tolist()

        cm_obj.process_batch(pred, gt)

    matrix = cm_obj.return_matrix()
    for i in range(num_labels+1):
        for j in range(i+1, num_labels+1):
            matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]

    labels_copy = labels.copy()
    labels_copy.append('blank')

    precision=[]
    recall = []
    for i in range(num_labels):
        tp = matrix[i][i]
        tp_fp = sum(matrix[i])
        tp_fn = sum([mat[i] for mat in matrix])
        if tp != 0:
            precision.append(tp/tp_fp)
            recall.append(tp/tp_fn)
        else:
            precision.append(0)
            recall.append(0)

    data_plot = {
                'labels': labels,
                'precision': precision, 
                'recall': recall, 
                }

    return data_plot

if __name__ == '__main__':
    df_gt = pd.DataFrame() # dataframe containing columns "label,frame_id,x0,y0,x1,y1"
    df_pred = pd.DataFrame() # dataframe containing columns "x0,y0,x1,y1,confidence,label,frame_id"
    res = generate_precision_recall(df_gt, df_pred, conf_value=0.5, iou_value=0.5)
    print(res)

Functions

def generate_precision_recall(df_gt, df_pred, conf_value=0.5, iou_value=0.5)

Return the precision and recall for a given confidence and iou value.

Args

df_gt : pandas dataframe
ground truth dataframe with labels order: "label,frame_id,x0,y0,x1,y1"
df_pred : pandas dataframe
prediction dataframe with labels order: "x0,y0,x1,y1,confidence,label,frame_id"
conf_value : float, optional
Confidence value. Defaults to 0.5.
iou_value : float, optional
IOU threshold value. Defaults to 0.5.

Returns

dict
Dictionary containing list of precision and recall values for each label
Expand source code
def generate_precision_recall(df_gt, df_pred, conf_value=0.5, iou_value=0.5):
    """Return the precision and recall for a given confidence and iou value.

    Args:
        df_gt (pandas dataframe): ground truth dataframe with labels order: "label,frame_id,x0,y0,x1,y1"
        df_pred (pandas dataframe): prediction dataframe with labels order: "x0,y0,x1,y1,confidence,label,frame_id"
        conf_value (float, optional): Confidence value. Defaults to 0.5.
        iou_value (float, optional): IOU threshold value. Defaults to 0.5.

    Returns:
        dict: Dictionary containing list of precision and recall values for each label
    """
    num_frames = df_gt.frame_id.unique().tolist()
    num_frames.extend(df_pred.frame_id.unique().tolist())
    num_frames = list(set(num_frames))
    num_frames.sort()
    labels = df_gt.label.unique().tolist()
    labels.extend(df_pred.label.unique().tolist())
    labels = list(set(labels))
    num_labels = len(labels)
    encoding = [i for i in range(num_labels)]
    cm_obj = _ConfusionMatrix(num_classes=num_labels, CONF_THRESHOLD=conf_value, IOU_THRESHOLD=iou_value)
    for i in num_frames:

        df_gt1 = df_gt[df_gt.frame_id == i]
        df_gt1.drop(columns='frame_id', inplace=True, axis=1)
        df_gt1['label'].replace(labels, encoding, inplace=True)

        df_pred1 = df_pred[df_pred.frame_id == i]
        df_pred1.drop(columns='frame_id', inplace=True, axis=1)
        df_pred1['label'].replace(labels, encoding, inplace=True)

        pred = df_pred1.values.tolist()
        gt = df_gt1.values.tolist()

        cm_obj.process_batch(pred, gt)

    matrix = cm_obj.return_matrix()
    for i in range(num_labels+1):
        for j in range(i+1, num_labels+1):
            matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]

    labels_copy = labels.copy()
    labels_copy.append('blank')

    precision=[]
    recall = []
    for i in range(num_labels):
        tp = matrix[i][i]
        tp_fp = sum(matrix[i])
        tp_fn = sum([mat[i] for mat in matrix])
        if tp != 0:
            precision.append(tp/tp_fp)
            recall.append(tp/tp_fn)
        else:
            precision.append(0)
            recall.append(0)

    data_plot = {
                'labels': labels,
                'precision': precision, 
                'recall': recall, 
                }

    return data_plot