-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathconfusion_matrix.py
189 lines (154 loc) · 7.56 KB
/
confusion_matrix.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
import numpy as np
import argparse
from pycocotools.coco import COCO
import pandas as pd
import json
from tqdm import tqdm
import matplotlib.pyplot as plt
from pathlib import Path
import os
def box_iou_calc(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)
class ConfusionMatrix:
def __init__(self, num_classes: int, CONF_THRESHOLD=0.3, IOU_THRESHOLD=0.5):
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 plot(self, file_name='./', names=["General trash", "Paper", "Paper pack", "Metal", "Glass",
"Plastic", "Styrofoam", "Plastic bag", "Battery", "Clothing"]):
try:
import seaborn as sn
array = self.matrix / (self.matrix.sum(0).reshape(1, self.num_classes + 1) + 1E-6) # normalize
array[array < 0.005] = np.nan # don't annotate (would appear as 0.00)
fig = plt.figure(figsize=(12, 9), tight_layout=True)
sn.set(font_scale=1.0 if self.num_classes < 50 else 0.8) # for label size
labels = (0 < len(names) < 99) and len(names) == self.num_classes # apply names to ticklabels
sn.heatmap(array, annot=self.num_classes < 30, annot_kws={"size": 8}, cmap='Blues', fmt='.2f', square=True,
xticklabels=names + ['background FP'] if labels else "auto",
yticklabels=names + ['background FN'] if labels else "auto").set_facecolor((1, 1, 1))
fig.axes[0].set_xlabel('True')
fig.axes[0].set_ylabel('Predicted')
fig.savefig(os.path.join(args.work_dir, args.eval_name + '.png'), dpi=250)
except Exception as e:
print(e)
pass
def process_batch(self, detections, labels: np.ndarray):
"""
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
Returns:
None, updates confusion matrix accordingly
"""
gt_classes = labels[:, 0].astype(np.int16)
try:
detections = detections[detections[:, 4] > self.CONF_THRESHOLD]
except IndexError or TypeError:
# detections are empty, end of process
for i, label in enumerate(labels):
gt_class = gt_classes[i]
self.matrix[self.num_classes, gt_class] += 1
return
detection_classes = detections[:, 5].astype(np.int16)
all_ious = box_iou_calc(labels[:, 1:], detections[:, :4])
want_idx = np.where(all_ious > self.IOU_THRESHOLD)
all_matches = [[want_idx[0][i], want_idx[1][i], all_ious[want_idx[0][i], want_idx[1][i]]]
for i in range(want_idx[0].shape[0])]
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):
gt_class = gt_classes[i]
if all_matches.shape[0] > 0 and all_matches[all_matches[:, 0] == i].shape[0] == 1:
detection_class = detection_classes[int(all_matches[all_matches[:, 0] == i, 1][0])]
self.matrix[detection_class, gt_class] += 1
else:
self.matrix[self.num_classes, gt_class] += 1
for i, detection in enumerate(detections):
if not all_matches.shape[0] or ( all_matches.shape[0] and all_matches[all_matches[:, 1] == i].shape[0] == 0 ):
detection_class = detection_classes[i]
self.matrix[detection_class, self.num_classes] += 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 main(args):
conf_mat = ConfusionMatrix(num_classes = 10, CONF_THRESHOLD = 0.01, IOU_THRESHOLD = 0.5)
gt_path = args.gt_json
pred_path = os.path.join(args.work_dir, args.eval_name) #args.pred_csv
with open(gt_path, 'r') as outfile:
test_anno = (json.load(outfile))
pred_df = pd.read_csv(pred_path)
new_pred = []
gt = []
file_names = pred_df['image_id'].values.tolist() # type: ignore
bboxes = pred_df['PredictionString'].values.tolist() # type: ignore
for i, bbox in enumerate(bboxes):
if isinstance(bbox, float):
print(f'{file_names[i]} empty box')
for file_name, bbox in tqdm(zip(file_names, bboxes)):
new_pred.append([])
boxes = np.array(str(bbox).split(' '))
if len(boxes) % 6 == 1:
boxes = boxes[:-1].reshape(-1, 6)
elif len(boxes) % 6 == 0:
boxes = boxes.reshape(-1, 6)
else:
raise Exception('error', 'invalid box count')
for box in boxes:
new_pred[-1].append([float(box[2]), float(box[3]), float(box[4]), float(box[5]), float(box[1]), float(box[0])])
coco = COCO(gt_path)
for image_id in coco.getImgIds():
gt.append([])
image_info = coco.loadImgs(image_id)[0]
ann_ids = coco.getAnnIds(imgIds=image_info['id'])
anns = coco.loadAnns(ann_ids)
file_name = image_info['file_name']
for ann in anns:
gt[-1].append([
float(ann['category_id']),
float(ann['bbox'][0]),
float(ann['bbox'][1]),
float(ann['bbox'][0]) + float(ann['bbox'][2]),
(float(ann['bbox'][1]) + float(ann['bbox'][3])),
]
)
for p, g in zip(new_pred, gt):
conf_mat.process_batch(np.array(p), np.array(g))
conf_mat.plot(os.path.join(args.work_dir, args.eval_name + '.png'))
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('--gt_json', type=str, default='../../dataset/train.json')
parser.add_argument('--work_dir', type=str)
parser.add_argument('--eval_name', type=str, default='latest.pth_eval.csv')
# parser.add_argument('--pred_csv', type=str)
# parser.add_argument('--file_name', type=str, default='confusion_matrix.png')
args = parser.parse_args()
main(args)