6
6
|
7
7
"""
8
8
import itertools
9
+ import logging
9
10
10
11
import numpy as np
11
12
import sklearn .metrics as skm
12
13
13
14
import fiftyone .core .evaluation as foe
14
15
import fiftyone .core .plots as fop
16
+ import fiftyone .core .utils as fou
17
+
18
+ foo = fou .lazy_import ("fiftyone.operators" )
19
+
20
+
21
+ logger = logging .getLogger (__name__ )
22
+
23
+
24
+ class BaseEvaluationMethodConfig (foe .EvaluationMethodConfig ):
25
+ """Base class for configuring evaluation methods.
26
+
27
+ Args:
28
+ **kwargs: any leftover keyword arguments after subclasses have done
29
+ their parsing
30
+ """
31
+
32
+ pass
33
+
34
+
35
+ class BaseEvaluationMethod (foe .EvaluationMethod ):
36
+ """Base class for evaluation methods.
37
+
38
+ Args:
39
+ config: an :class:`BaseEvaluationMethodConfig`
40
+ """
41
+
42
+ def _get_custom_metrics (self ):
43
+ if not self .config .custom_metrics :
44
+ return {}
45
+
46
+ if isinstance (self .config .custom_metrics , list ):
47
+ return {m : None for m in self .config .custom_metrics }
48
+
49
+ return self .config .custom_metrics
50
+
51
+ def compute_custom_metrics (self , samples , eval_key , results ):
52
+ results .custom_metrics = {}
53
+
54
+ for metric , kwargs in self ._get_custom_metrics ().items ():
55
+ try :
56
+ operator = foo .get_operator (metric )
57
+ value = operator .compute (
58
+ samples , eval_key , results , ** kwargs or {}
59
+ )
60
+ if value is not None :
61
+ results .custom_metrics [operator .config .label ] = value
62
+ except Exception as e :
63
+ logger .warning (
64
+ "Failed to compute metric '%s': Reason: %s" ,
65
+ operator .uri ,
66
+ e ,
67
+ )
68
+
69
+ def get_custom_metric_fields (self , samples , eval_key ):
70
+ fields = []
71
+
72
+ for metric in self ._get_custom_metrics ().keys ():
73
+ try :
74
+ operator = foo .get_operator (metric )
75
+ fields .extend (operator .get_fields (samples , eval_key ))
76
+ except Exception as e :
77
+ logger .warning (
78
+ "Failed to get fields for metric '%s': Reason: %s" ,
79
+ operator .uri ,
80
+ e ,
81
+ )
82
+
83
+ return fields
84
+
85
+ def rename_custom_metrics (self , samples , eval_key , new_eval_key ):
86
+ for metric in self ._get_custom_metrics ().keys ():
87
+ try :
88
+ operator = foo .get_operator (metric )
89
+ operator .rename (samples , eval_key , new_eval_key )
90
+ except Exception as e :
91
+ logger .warning (
92
+ "Failed to rename fields for metric '%s': Reason: %s" ,
93
+ operator .uri ,
94
+ e ,
95
+ )
96
+
97
+ def cleanup_custom_metrics (self , samples , eval_key ):
98
+ for metric in self ._get_custom_metrics ().keys ():
99
+ try :
100
+ operator = foo .get_operator (metric )
101
+ operator .cleanup (samples , eval_key )
102
+ except Exception as e :
103
+ logger .warning (
104
+ "Failed to cleanup metric '%s': Reason: %s" ,
105
+ operator .uri ,
106
+ e ,
107
+ )
15
108
16
109
17
110
class BaseEvaluationResults (foe .EvaluationResults ):
18
111
"""Base class for evaluation results.
19
112
113
+ Args:
114
+ samples: the :class:`fiftyone.core.collections.SampleCollection` used
115
+ config: the :class:`BaseEvaluationMethodConfig` used
116
+ eval_key: the evaluation key
117
+ backend (None): an :class:`EvaluationMethod` backend
118
+ """
119
+
120
+ pass
121
+
122
+
123
+ class BaseClassificationResults (BaseEvaluationResults ):
124
+ """Base class for evaluation results that expose classification metrics
125
+ like P/R/F1 and confusion matrices.
126
+
20
127
Args:
21
128
samples: the :class:`fiftyone.core.collections.SampleCollection` used
22
129
config: the :class:`fiftyone.core.evaluation.EvaluationMethodConfig`
@@ -32,8 +139,7 @@ class BaseEvaluationResults(foe.EvaluationResults):
32
139
observed ground truth/predicted labels are used
33
140
missing (None): a missing label string. Any None-valued labels are
34
141
given this label for evaluation purposes
35
- samples (None): the :class:`fiftyone.core.collections.SampleCollection`
36
- for which the results were computed
142
+ custom_metrics (None): an optional dict of custom metrics
37
143
backend (None): a :class:`fiftyone.core.evaluation.EvaluationMethod`
38
144
backend
39
145
"""
@@ -51,6 +157,7 @@ def __init__(
51
157
ypred_ids = None ,
52
158
classes = None ,
53
159
missing = None ,
160
+ custom_metrics = None ,
54
161
backend = None ,
55
162
):
56
163
super ().__init__ (samples , config , eval_key , backend = backend )
@@ -72,6 +179,7 @@ def __init__(
72
179
)
73
180
self .classes = np .asarray (classes )
74
181
self .missing = missing
182
+ self .custom_metrics = custom_metrics
75
183
76
184
def report (self , classes = None ):
77
185
"""Generates a classification report for the results via
0 commit comments