1
- from labelbox .data .metrics .iou .calculation import _get_mask_pairs , _get_vector_pairs , miou
2
-
3
- from labelbox .data .annotation_types .metrics .confusion_matrix import \
4
- ConfusionMatrixMetricValue
5
-
6
- from labelbox .data .annotation_types .metrics .scalar import ScalarMetricValue
7
1
from typing import List , Optional , Tuple , Union
2
+
8
3
import numpy as np
4
+
5
+ from ..iou .calculation import _get_mask_pairs , _get_vector_pairs , miou
9
6
from ...annotation_types import (ObjectAnnotation , ClassificationAnnotation ,
10
- Mask , Geometry , Checklist , Radio )
11
- from ..processing import get_feature_pairs , get_identifying_key , has_no_annotations , has_no_matching_annotations
7
+ Mask , Geometry , Checklist , Radio ,
8
+ ScalarMetricValue , ConfusionMatrixMetricValue )
9
+ from ..processing import (get_feature_pairs , get_identifying_key ,
10
+ has_no_annotations , has_no_matching_annotations )
12
11
13
12
14
13
def confusion_matrix (ground_truths : List [Union [ObjectAnnotation ,
@@ -17,34 +16,58 @@ def confusion_matrix(ground_truths: List[Union[ObjectAnnotation,
17
16
ClassificationAnnotation ]],
18
17
include_subclasses : bool ,
19
18
iou : float ) -> ConfusionMatrixMetricValue :
19
+ """
20
+ Computes the confusion matrix for an arbitrary set of ground truth and predicted annotations.
21
+ It first computes the confusion matrix for each metric and then sums across all classes
22
+
23
+ Args:
24
+ ground_truth : Label containing human annotations or annotations known to be correct
25
+ prediction: Label representing model predictions
26
+ include_subclasses (bool): Whether or not to include subclasses in the calculation.
27
+ If set to True, the iou between two overlapping objects of the same type is 0 if the subclasses are not the same.
28
+ iou: minimum overlap between objects for them to count as matching
29
+ Returns:
30
+ confusion matrix as a list: [TP,FP,TN,FN]
31
+ Returns None if there are no annotations in ground_truth or prediction annotations
32
+ """
20
33
21
34
annotation_pairs = get_feature_pairs (predictions , ground_truths )
22
- ious = [
35
+ conf_matrix = [
23
36
feature_confusion_matrix (annotation_pair [0 ], annotation_pair [1 ],
24
37
include_subclasses , iou )
25
38
for annotation_pair in annotation_pairs .values ()
26
39
]
27
- ious = [iou for iou in ious if iou is not None ]
28
-
29
- return None if not len (ious ) else np .sum (ious , axis = 0 ).tolist ()
40
+ matrices = [matrix for matrix in conf_matrix if matrix is not None ]
41
+ return None if not len (matrices ) else np .sum (matrices , axis = 0 ).tolist ()
30
42
31
43
32
44
def feature_confusion_matrix (
33
45
ground_truths : List [Union [ObjectAnnotation , ClassificationAnnotation ]],
34
46
predictions : List [Union [ObjectAnnotation , ClassificationAnnotation ]],
35
47
include_subclasses : bool ,
36
48
iou : float ) -> Optional [ConfusionMatrixMetricValue ]:
49
+ """
50
+ Computes confusion matrix for all features of the same class.
51
+
52
+ Args:
53
+ ground_truths: List of ground truth annotations belonging to the same class.
54
+ predictions: List of annotations belonging to the same class.
55
+ include_subclasses (bool): Whether or not to include subclasses in the calculation.
56
+ If set to True, the iou between two overlapping objects of the same type is 0 if the subclasses are not the same.
57
+ Returns:
58
+ confusion matrix as a list: [TP,FP,TN,FN]
59
+ Returns None if there are no annotations in ground_truth or prediction annotations
60
+ """
37
61
if has_no_matching_annotations (ground_truths , predictions ):
38
62
return [0 , int (len (predictions ) > 0 ), 0 , int (len (ground_truths ) > 0 )]
39
63
elif has_no_annotations (ground_truths , predictions ):
40
- # Note that we could return [0,0,0,0] but that will bloat the imports for no reason
41
64
return None
42
65
elif isinstance (predictions [0 ].value , Mask ):
43
- return mask_confusion_matrix (ground_truths , predictions , iou ,
44
- include_subclasses )
66
+ return mask_confusion_matrix (ground_truths , predictions ,
67
+ include_subclasses , iou )
45
68
elif isinstance (predictions [0 ].value , Geometry ):
46
- return vector_confusion_matrix (ground_truths , predictions , iou ,
47
- include_subclasses )
69
+ return vector_confusion_matrix (ground_truths , predictions ,
70
+ include_subclasses , iou )
48
71
elif isinstance (predictions [0 ], ClassificationAnnotation ):
49
72
return classification_confusion_matrix (ground_truths , predictions )
50
73
else :
@@ -63,7 +86,8 @@ def classification_confusion_matrix(
63
86
ground_truths: List of ground truth classification annotations
64
87
predictions: List of prediction classification annotations
65
88
Returns:
66
- float representing the iou score for the classification
89
+ confusion matrix as a list: [TP,FP,TN,FN]
90
+ Returns None if there are no annotations in ground_truth or prediction annotations
67
91
"""
68
92
69
93
if has_no_matching_annotations (ground_truths , predictions ):
@@ -86,27 +110,56 @@ def classification_confusion_matrix(
86
110
elif isinstance (prediction .value , Checklist ):
87
111
return checklist_confusion_matrix (ground_truth .value , prediction .value )
88
112
else :
89
- raise ValueError (f"Unsupported subclass. { prediction } ." )
113
+ raise ValueError (
114
+ f"Unsupported subclass. { prediction } . Only Radio and Checklist are supported"
115
+ )
90
116
91
117
92
118
def vector_confusion_matrix (ground_truths : List [ObjectAnnotation ],
93
119
predictions : List [ObjectAnnotation ],
94
- iou : float ,
95
120
include_subclasses : bool ,
121
+ iou : float ,
96
122
buffer = 70. ) -> Optional [ConfusionMatrixMetricValue ]:
123
+ """
124
+ Computes confusion matrix for any vector class (point, polygon, line, rectangle).
125
+ Ground truths and predictions should all belong to the same class.
126
+
127
+ Args:
128
+ ground_truths: List of ground truth vector annotations
129
+ predictions: List of prediction vector annotations
130
+ iou: minimum overlap between objects for them to count as matching
131
+ include_subclasses (bool): Whether or not to include subclasses in the calculation.
132
+ If set to True, the iou between two overlapping objects of the same type is 0 if the subclasses are not the same.
133
+ buffer: How much to buffer point and lines (used for determining if overlap meets iou threshold )
134
+ Returns:
135
+ confusion matrix as a list: [TP,FP,TN,FN]
136
+ Returns None if there are no annotations in ground_truth or prediction annotations
137
+ """
97
138
if has_no_matching_annotations (ground_truths , predictions ):
98
139
return [0 , int (len (predictions ) > 0 ), 0 , int (len (ground_truths ) > 0 )]
99
140
elif has_no_annotations (ground_truths , predictions ):
100
141
return None
101
142
102
143
pairs = _get_vector_pairs (ground_truths , predictions , buffer = buffer )
103
- return object_pair_confusion_matrix (pairs , iou , include_subclasses )
144
+ return object_pair_confusion_matrix (pairs , include_subclasses , iou )
104
145
105
146
106
- def object_pair_confusion_matrix (
107
- pairs : List [Tuple [ObjectAnnotation , ObjectAnnotation ,
108
- ScalarMetricValue ]], iou ,
109
- include_subclasses ) -> ConfusionMatrixMetricValue :
147
+ def object_pair_confusion_matrix (pairs : List [Tuple [ObjectAnnotation ,
148
+ ObjectAnnotation ,
149
+ ScalarMetricValue ]],
150
+ include_subclasses : bool ,
151
+ iou : float ) -> ConfusionMatrixMetricValue :
152
+ """
153
+ Computes the confusion matrix for a list of object annotation pairs.
154
+ Performs greedy matching of pairs.
155
+
156
+ Args:
157
+ pairs : A list of object annotation pairs with an iou score.
158
+ This is used to determine matching priority (or if objects are matching at all) since objects can only be matched once.
159
+ iou : iou threshold to deterine if objects are matching
160
+ Returns:
161
+ confusion matrix as a list: [TP,FP,TN,FN]
162
+ """
110
163
pairs .sort (key = lambda triplet : triplet [2 ], reverse = True )
111
164
prediction_ids = set ()
112
165
ground_truth_ids = set ()
@@ -144,11 +197,10 @@ def radio_confusion_matrix(ground_truth: Radio,
144
197
"""
145
198
Calculates confusion between ground truth and predicted radio values
146
199
147
- The way we are calculating confusion matrix metrics:
148
- - TNs aren't defined because we don't know how many other classes exist ... etc
149
-
150
- When P == L, then we get [1,0,0,0]
151
- when P != L, we get [0,1,0,1]
200
+ Calculation:
201
+ - TNs aren't defined because we don't know how many other classes exist
202
+ - When P == L, then we get [1,0,0,0]
203
+ - when P != L, we get [0,1,0,1]
152
204
153
205
This is because we are aggregating the stats for the entire radio. Not for each class.
154
206
Since we are not tracking TNs (P == L) only adds to TP.
@@ -169,9 +221,16 @@ def checklist_confusion_matrix(
169
221
ground_truth : Checklist ,
170
222
prediction : Checklist ) -> ConfusionMatrixMetricValue :
171
223
"""
172
- Calculates agreement between ground truth and predicted checklist items
224
+ Calculates agreement between ground truth and predicted checklist items:
225
+
226
+ Calculation:
227
+ - When a prediction matches a label that counts as a true postivie.
228
+ - When a prediction was made and does not have a corresponding label this is counted as a false postivie
229
+ - When a label does not have a corresponding prediction this is counted as a false negative
230
+
231
+ We are also not tracking TNs since we don't know the number of possible classes
232
+ (and they aren't necessary for precision/recall/f1).
173
233
174
- Also not tracking TNs
175
234
"""
176
235
key = get_identifying_key (prediction .answer , ground_truth .answer )
177
236
schema_ids_pred = {getattr (answer , key ) for answer in prediction .answer }
@@ -185,33 +244,35 @@ def checklist_confusion_matrix(
185
244
return [tps , fps , 0 , fns ]
186
245
187
246
188
- def mask_confusion_matrix (
189
- ground_truths : List [ObjectAnnotation ],
190
- predictions : List [ ObjectAnnotation ], iou ,
191
- include_subclasses : bool ) -> Optional [ScalarMetricValue ]:
247
+ def mask_confusion_matrix (ground_truths : List [ ObjectAnnotation ],
248
+ predictions : List [ObjectAnnotation ],
249
+ include_subclasses : bool ,
250
+ iou : float ) -> Optional [ScalarMetricValue ]:
192
251
"""
193
- Computes iou score for all features with the same feature schema id.
194
- Calculation includes subclassifications.
252
+ Computes confusion matrix metric for two masks
253
+
254
+ Important:
255
+ - If including subclasses in the calculation, then the metrics are computed the same as if it were object detection.
256
+ - Each mask is its own instance. Otherwise this metric is computed as pixel level annotations.
195
257
196
258
Args:
197
259
ground_truths: List of ground truth mask annotations
198
260
predictions: List of prediction mask annotations
199
261
Returns:
200
- float representing the iou score for the masks
262
+ confusion matrix as a list: [TP,FP,TN,FN]
201
263
"""
202
264
if has_no_matching_annotations (ground_truths , predictions ):
203
265
return [0 , int (len (predictions ) > 0 ), 0 , int (len (ground_truths ) > 0 )]
204
266
elif has_no_annotations (ground_truths , predictions ):
205
267
return None
206
268
207
269
if include_subclasses :
208
- # This results in a faily drastically different value.
270
+ # This results in a faily drastically different value than without subclasses .
209
271
# If we have subclasses set to True, then this is object detection with masks
210
- # Otherwise this will flatten the masks.
211
- # TODO: Make this more apprent in the configuration.
272
+ # Otherwise this will compute metrics on each pixel.
212
273
pairs = _get_mask_pairs (ground_truths , predictions )
213
274
return object_pair_confusion_matrix (
214
- pairs , iou , include_subclasses = include_subclasses )
275
+ pairs , include_subclasses = include_subclasses , iou = iou )
215
276
216
277
prediction_np = np .max ([pred .value .draw (color = 1 ) for pred in predictions ],
217
278
axis = 0 )
0 commit comments