xttmp 2.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xttmp/__init__.py +1 -0
- xttmp/api/__init__.py +5 -0
- xttmp/api/evaluate.py +163 -0
- xttmp/api/get_visualize_handle.py +29 -0
- xttmp/api/instancing_model.py +35 -0
- xttmp/core/__init__.py +0 -0
- xttmp/core/apgstmd_core.py +188 -0
- xttmp/core/apgstmdv2_core.py +79 -0
- xttmp/core/base_core.py +36 -0
- xttmp/core/dstmd_core.py +213 -0
- xttmp/core/estmd_backbone.py +110 -0
- xttmp/core/estmd_core.py +356 -0
- xttmp/core/feedbackstmd_core.py +61 -0
- xttmp/core/fracstmd_core.py +98 -0
- xttmp/core/fstmd_core.py +15 -0
- xttmp/core/fstmdv2_core.py +42 -0
- xttmp/core/haarstmd_core.py +140 -0
- xttmp/core/math_operator.py +307 -0
- xttmp/core/stfeedbackstmd_core.py +233 -0
- xttmp/core/stmdplus_core.py +187 -0
- xttmp/core/stmdplusv2_core.py +82 -0
- xttmp/core/vstmd_core.py +420 -0
- xttmp/demo/evaluate_model.py +92 -0
- xttmp/demo/inference_gui.py +148 -0
- xttmp/demo/inference_gui_single_process.py +134 -0
- xttmp/demo/inference_image_stream.py +67 -0
- xttmp/demo/inference_video.py +66 -0
- xttmp/main.py +14 -0
- xttmp/model/__init__.py +13 -0
- xttmp/model/backbone.py +514 -0
- xttmp/model/facilitated_model.py +230 -0
- xttmp/model/feedback_model.py +271 -0
- xttmp/model/haarstmd.py +61 -0
- xttmp/model/vstmd.py +457 -0
- xttmp/util/__init__.py +0 -0
- xttmp/util/compute_module.py +402 -0
- xttmp/util/create_kernel.py +363 -0
- xttmp/util/evaluate_module.py +697 -0
- xttmp/util/iostream.py +660 -0
- xttmp-2.3.0.dist-info/METADATA +85 -0
- xttmp-2.3.0.dist-info/RECORD +45 -0
- xttmp-2.3.0.dist-info/WHEEL +5 -0
- xttmp-2.3.0.dist-info/entry_points.txt +2 -0
- xttmp-2.3.0.dist-info/licenses/LICENSE +201 -0
- xttmp-2.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,697 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
def evaluation_model_by_video(modelOpt: list,
|
|
4
|
+
groundTruth: list,
|
|
5
|
+
confidenceThreshold: float = 0.5,
|
|
6
|
+
gTError: int = 1,
|
|
7
|
+
ROIThreshold: float = 0.5):
|
|
8
|
+
"""
|
|
9
|
+
Evaluates the performance of the detection results over a video sequence.
|
|
10
|
+
|
|
11
|
+
Parameters:
|
|
12
|
+
- modelOpt (list of lists): Detection results for each frame in the video.
|
|
13
|
+
Each item should be in the format:
|
|
14
|
+
- Center indices: [ [[i1, j1, confidence1], ], ... ] with shape(totalFrame, numOfTarget, 3)
|
|
15
|
+
- Bounding boxes: [ [[i1, j1, h1, w1, confidence1], ], ... ] with shape(totalFrame, numOfTarget, 5)
|
|
16
|
+
- groundTruth (list of lists): Ground truth data for each frame.
|
|
17
|
+
Each item should be in the format:
|
|
18
|
+
- Center indices: [ [[i1, j1], [i2, j2]], ... ] with shape(totalFrame, numOfTarget, 2)
|
|
19
|
+
- Bounding boxes: [ [[i1, j1, h1, w1], ], ... ] with shape(totalFrame, numOfTarget, 4)
|
|
20
|
+
- confidenceThreshold (float): Minimum confidence score to consider a prediction.
|
|
21
|
+
- gTError (int or float): Maximum allowable error distance for matching ground truth data.
|
|
22
|
+
- ROIThreshold (float): Threshold for the region of interest.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
- listTP: List of true positives for each frame.
|
|
26
|
+
- listFN: List of false negatives for each frame.
|
|
27
|
+
- listFP: List of false positives for each frame.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
# Initialize the metrics lists with zeros
|
|
31
|
+
totalLen = min(len(modelOpt), len(groundTruth))
|
|
32
|
+
listTP = [0 for _ in range(totalLen)]
|
|
33
|
+
listFN = [0 for _ in range(totalLen)]
|
|
34
|
+
listFP = [0 for _ in range(totalLen)]
|
|
35
|
+
|
|
36
|
+
# Iterate over each frame
|
|
37
|
+
for idx in range(totalLen):
|
|
38
|
+
# Check if current frame data is available
|
|
39
|
+
if len(modelOpt[idx]) and len(groundTruth[idx]):
|
|
40
|
+
if isinstance(modelOpt[idx][0], int):
|
|
41
|
+
modelOpt[idx] = [modelOpt[idx], ]
|
|
42
|
+
# Compute metrics for the current frame
|
|
43
|
+
TP, FN, FP = compute_metrics_by_frame(modelOpt[idx],
|
|
44
|
+
groundTruth[idx],
|
|
45
|
+
confidenceThreshold,
|
|
46
|
+
gTError,
|
|
47
|
+
ROIThreshold)
|
|
48
|
+
elif len(groundTruth[idx]):
|
|
49
|
+
TP, FN, FP = 0, len(groundTruth[idx]), 0
|
|
50
|
+
else:
|
|
51
|
+
# If data for the frame is not available, set metrics to zero
|
|
52
|
+
TP, FN, FP = 1, 0, 0
|
|
53
|
+
|
|
54
|
+
# Store the metrics for the current frame
|
|
55
|
+
listTP[idx] = TP
|
|
56
|
+
listFN[idx] = FN
|
|
57
|
+
listFP[idx] = FP
|
|
58
|
+
|
|
59
|
+
return listTP, listFN, listFP
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def compute_metrics_by_frame(prediction, groundTruth, confidenceThreshold=0.5, gTError=1, ROIThreshold=0.5):
|
|
63
|
+
"""
|
|
64
|
+
Computes evaluation metrics based on predicted and ground truth data.
|
|
65
|
+
|
|
66
|
+
Parameters:
|
|
67
|
+
- prediction (list of lists or tuples): Predicted data from an image/frame.
|
|
68
|
+
Each item should have a confidence score as the last element.
|
|
69
|
+
- groundTruth (list of lists or tuples): Ground truth data.
|
|
70
|
+
Format depends on the type of data.
|
|
71
|
+
- confidenceThreshold (float): Minimum confidence score to consider a prediction.
|
|
72
|
+
- gTError (int or float): Error margin for matching ground truth data.
|
|
73
|
+
- ROIThreshold (float): Threshold for region of interest.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
- TP (int): Number of true positives.
|
|
77
|
+
- FN (int): Number of false negatives.
|
|
78
|
+
- FP (int): Number of false positives.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
TP = FP = 0
|
|
82
|
+
|
|
83
|
+
# Determine the matching function based on the dimensions of the prediction and ground truth data
|
|
84
|
+
if len(prediction[0]) == 3:
|
|
85
|
+
if len(groundTruth[0]) == 2:
|
|
86
|
+
matchFun = match_two_dots
|
|
87
|
+
elif len(groundTruth[0]) == 4:
|
|
88
|
+
matchFun = match_dot_in_bbox
|
|
89
|
+
elif len(prediction[0]) == 5:
|
|
90
|
+
if len(groundTruth[0]) == 2:
|
|
91
|
+
matchFun = match_bbox_cover_dot
|
|
92
|
+
elif len(groundTruth[0]) == 4:
|
|
93
|
+
matchFun = match_two_bboxs
|
|
94
|
+
|
|
95
|
+
# Initialize a list to track which ground truth items are false negatives
|
|
96
|
+
isGTaFN = [True for _ in range(len(groundTruth))]
|
|
97
|
+
|
|
98
|
+
# Iterate through predictions
|
|
99
|
+
for pre in prediction:
|
|
100
|
+
if pre[-1] < confidenceThreshold:
|
|
101
|
+
continue # Skip predictions with confidence below the threshold
|
|
102
|
+
|
|
103
|
+
isTP = False
|
|
104
|
+
for i, gT in enumerate(groundTruth):
|
|
105
|
+
if matchFun(pre, gT, gTError, ROIThreshold):
|
|
106
|
+
isTP = True
|
|
107
|
+
isGTaFN[i] = False
|
|
108
|
+
break # Exit the loop once a match is found
|
|
109
|
+
|
|
110
|
+
if isTP:
|
|
111
|
+
TP += 1
|
|
112
|
+
else:
|
|
113
|
+
FP += 1
|
|
114
|
+
|
|
115
|
+
# Calculate false negatives
|
|
116
|
+
FN = isGTaFN.count(True)
|
|
117
|
+
|
|
118
|
+
return TP, FN, FP
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def match_two_dots(dot1, dot2, gTError, _):
|
|
122
|
+
"""
|
|
123
|
+
Determines if two points are within a specified error range of each other.
|
|
124
|
+
|
|
125
|
+
Parameters:
|
|
126
|
+
- dot1 (tuple or list): The first point, containing coordinates (x1, y1).
|
|
127
|
+
- dot2 (tuple or list): The second point, containing coordinates (x2, y2).
|
|
128
|
+
- gTError (int or float): The maximum allowable error distance between the points.
|
|
129
|
+
- _ : Placeholder for an unused parameter.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
- bool: True if the points are within the specified error range in all dimensions, otherwise False.
|
|
133
|
+
|
|
134
|
+
Example:
|
|
135
|
+
>>> match_two_dots((10, 15), (12, 18), 3, None)
|
|
136
|
+
True
|
|
137
|
+
>>> match_two_dots((10, 15), (14, 18), 1, None)
|
|
138
|
+
False
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
return all(abs(d1 - d2) <= gTError for d1, d2 in zip(dot1, dot2))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def match_dot_in_bbox(dot1, bbox2, gTError, _):
|
|
145
|
+
"""
|
|
146
|
+
Determines if a point is within a bounding box with a specified error range.
|
|
147
|
+
|
|
148
|
+
Parameters:
|
|
149
|
+
- dot1 (tuple or list): The point to check, containing coordinates (x, y), that is (column, row).
|
|
150
|
+
- bbox2 (tuple or list): The bounding box, specified as (x, y, width, height).
|
|
151
|
+
- gTError (int or float): The maximum allowable error distance.
|
|
152
|
+
- _ : Placeholder for an unused parameter.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
- bool: True if the point is within the bounding box extended by the error range, otherwise False.
|
|
156
|
+
|
|
157
|
+
Example:
|
|
158
|
+
>>> match_dot_in_bbox((5, 5), (4, 4, 2, 2), 1, None)
|
|
159
|
+
True
|
|
160
|
+
>>> match_dot_in_bbox((5, 5), (4, 4, 1, 1), 0.5, None)
|
|
161
|
+
False
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
# Check if the point's x coordinate is within the bounding box's x range, including the error margin
|
|
165
|
+
x_within_bbox = (dot1[0] >= bbox2[0] - gTError) and (dot1[0] <= bbox2[0] + bbox2[2] + gTError)
|
|
166
|
+
|
|
167
|
+
# Check if the point's y coordinate is within the bounding box's y range, including the error margin
|
|
168
|
+
y_within_bbox = (dot1[1] >= bbox2[1] - gTError) and (dot1[1] <= bbox2[1] + bbox2[3] + gTError)
|
|
169
|
+
|
|
170
|
+
# Return True if both x and y coordinates are within the extended bounding box, otherwise False
|
|
171
|
+
return x_within_bbox and y_within_bbox
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def match_bbox_cover_dot(bbox1, dot2, gTError, _):
|
|
175
|
+
"""
|
|
176
|
+
Determines if a point is within a bounding box with a specified error range,
|
|
177
|
+
where the bounding box and point roles are swapped compared to the `match_dot_in_bbox` function.
|
|
178
|
+
|
|
179
|
+
Parameters:
|
|
180
|
+
- bbox1 (tuple or list): The bounding box to check, specified as (x, y, width, height).
|
|
181
|
+
- dot2 (tuple or list): The point to check, containing coordinates (x, y).
|
|
182
|
+
- gTError (int or float): The maximum allowable error distance.
|
|
183
|
+
- _ : Placeholder for an unused parameter.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
- bool: True if the point is within the bounding box extended by the error range, otherwise False.
|
|
187
|
+
|
|
188
|
+
Example:
|
|
189
|
+
>>> match_bbox_cover_dot((4, 4, 2, 2), (5, 5), 1, None)
|
|
190
|
+
True
|
|
191
|
+
>>> match_bbox_cover_dot((4, 4, 2, 2), (6, 6), 0.5, None)
|
|
192
|
+
False
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
# Use `match_dot_in_bbox` to determine if `dot2` is within `bbox1` extended by `gTError`
|
|
196
|
+
return match_dot_in_bbox(dot2, bbox1, gTError, None)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def match_two_bboxs(bbox1, bbox2, _, ROIThreshold):
|
|
200
|
+
"""
|
|
201
|
+
Checks if the ROI between two rectangles is greater than a specified threshold.
|
|
202
|
+
|
|
203
|
+
Parameters:
|
|
204
|
+
- bbox1: The first rectangle in the format [x, y, w, h].
|
|
205
|
+
- bbox2: The second rectangle in the format [x, y, w, h].
|
|
206
|
+
- _: Placeholder for an unused parameter.
|
|
207
|
+
- ROIThreshold: The threshold value to compare the ROI against.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
- bool: True if the ROI is greater than the ROIThreshold, otherwise False.
|
|
211
|
+
"""
|
|
212
|
+
# Compute the ROI between the two rectangles
|
|
213
|
+
ROI = compute_ROI(bbox1, bbox2)
|
|
214
|
+
|
|
215
|
+
# Return True if the ROI is greater than the threshold, otherwise False
|
|
216
|
+
return ROI > ROIThreshold
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def compute_ROI(rect1, rect2):
|
|
220
|
+
"""
|
|
221
|
+
Computes the region of interest (ROI) between two rectangles.
|
|
222
|
+
|
|
223
|
+
Parameters:
|
|
224
|
+
- rect1: A rectangle region in the format [x, y, w, h], where (x, y) is the top-left corner,
|
|
225
|
+
w is the width, and h is the height.
|
|
226
|
+
- rect2: A rectangle region in the format [x, y, w, h], where (x, y) is the top-left corner,
|
|
227
|
+
w is the width, and h is the height.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
- ROI: The region of interest between the two rectangles, defined as the intersection area
|
|
231
|
+
divided by the union of the areas of the two rectangles.
|
|
232
|
+
"""
|
|
233
|
+
# Unpack rectangle coordinates and dimensions
|
|
234
|
+
x1, y1, w1, h1 = rect1[:4]
|
|
235
|
+
x2, y2, w2, h2 = rect2
|
|
236
|
+
|
|
237
|
+
# Determine the coordinates of the intersection rectangle
|
|
238
|
+
left = max(x1, x2)
|
|
239
|
+
top = max(y1, y2)
|
|
240
|
+
right = min(x1 + w1, x2 + w2)
|
|
241
|
+
bottom = min(y1 + h1, y2 + h2)
|
|
242
|
+
|
|
243
|
+
# Calculate the area of the intersection rectangle
|
|
244
|
+
if left < right and top < bottom:
|
|
245
|
+
intersection_area = (right - left) * (bottom - top)
|
|
246
|
+
else:
|
|
247
|
+
intersection_area = 0
|
|
248
|
+
|
|
249
|
+
# Calculate the area of both rectangles
|
|
250
|
+
area1 = w1 * h1
|
|
251
|
+
area2 = w2 * h2
|
|
252
|
+
|
|
253
|
+
# Calculate the ROI (Intersection over Union)
|
|
254
|
+
union_area = area1 + area2 - intersection_area
|
|
255
|
+
ROI = intersection_area / union_area if union_area > 0 else 0
|
|
256
|
+
|
|
257
|
+
return ROI
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def get_RFI_by_fixFPPI(modelOpt: list,
|
|
261
|
+
groundTruth: list,
|
|
262
|
+
aimFPPI: float = 1,
|
|
263
|
+
gTError: int = 1,
|
|
264
|
+
ROIThreshold: float = 0.5,
|
|
265
|
+
startFrame=0,
|
|
266
|
+
endFrame=None):
|
|
267
|
+
"""
|
|
268
|
+
Calculates the RFI (Recall Per Image) based on a fixed FPPI (False Positives Per Image) for a given model output.
|
|
269
|
+
|
|
270
|
+
Parameters:
|
|
271
|
+
- modelOpt (list of lists): Model output data for each frame. Each item can be:
|
|
272
|
+
- Center indices: [ [[i1, j1, confidence1]], ... ] with shape(totalFrame, numOfTarget, 3)
|
|
273
|
+
- Bounding boxes: [ [[i1, j1, h1, w1, confidence1]], ... ] with shape(totalFrame, numOfTarget, 5)
|
|
274
|
+
- groundTruth (list of lists): Ground truth data for each frame. Each item can be:
|
|
275
|
+
- Center indices: [ [[i1, j1]], ... ] with shape(totalFrame, numOfTarget, 2)
|
|
276
|
+
- Bounding boxes: [ [[i1, j1, h1, w1]], ... ] with shape(totalFrame, numOfTarget, 4)
|
|
277
|
+
- aimFPPI (float): Target value for False Positive Per Image.
|
|
278
|
+
- gTError (int or float): Maximum allowable error distance for matching ground truth data.
|
|
279
|
+
- ROIThreshold (float): Threshold for the region of interest.
|
|
280
|
+
- startFrame (int): Starting frame of the dataset.
|
|
281
|
+
- endFrame (int): Ending frame of the dataset (optional).
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
- RFI (float): Recall Per Image corresponding to the aimFPPI.
|
|
285
|
+
"""
|
|
286
|
+
|
|
287
|
+
# Initialize threshold boundaries and value
|
|
288
|
+
thresholdTop = 1.0
|
|
289
|
+
thresholdDown = 0.0
|
|
290
|
+
thresholdValue = 0.5
|
|
291
|
+
preThresholdValue = 0.0
|
|
292
|
+
|
|
293
|
+
while True:
|
|
294
|
+
# Filter data based on the current threshold
|
|
295
|
+
threInput = [[data for data in frame if data[-1] >= thresholdValue]
|
|
296
|
+
for frame in modelOpt]
|
|
297
|
+
|
|
298
|
+
# Compute metrics
|
|
299
|
+
listTP, listFN, listFP = evaluation_model_by_video(threInput,
|
|
300
|
+
groundTruth,
|
|
301
|
+
confidenceThreshold=thresholdValue,
|
|
302
|
+
gTError=gTError,
|
|
303
|
+
ROIThreshold=ROIThreshold)
|
|
304
|
+
|
|
305
|
+
# Calculate RFI and FPPI
|
|
306
|
+
numberAT = sum(listFN[startFrame:endFrame+1]) + sum(listTP[startFrame:endFrame+1])
|
|
307
|
+
# Calculate Recall Per Image (RFI) and False Positive Per Image (FPPI)
|
|
308
|
+
RFI = sum(listTP[startFrame:endFrame+1]) / numberAT if numberAT > 0 else 0
|
|
309
|
+
FPPI = sum(listFP[startFrame:endFrame + 1]) / (endFrame - startFrame + 1) \
|
|
310
|
+
if len(listFP[startFrame:endFrame+1]) > 0 else 0
|
|
311
|
+
|
|
312
|
+
# Check for convergence
|
|
313
|
+
if abs(FPPI - aimFPPI) < 0.01 or abs(thresholdValue - preThresholdValue) < 1e-5:
|
|
314
|
+
break
|
|
315
|
+
else:
|
|
316
|
+
# Update thresholds
|
|
317
|
+
if FPPI < aimFPPI:
|
|
318
|
+
thresholdTop = thresholdValue
|
|
319
|
+
else:
|
|
320
|
+
thresholdDown = thresholdValue
|
|
321
|
+
preThresholdValue = thresholdValue
|
|
322
|
+
thresholdValue = (thresholdTop + thresholdDown) / 2
|
|
323
|
+
|
|
324
|
+
return RFI
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def get_ROC_curve_data(modelOpt: list,
|
|
328
|
+
groundTruth: list,
|
|
329
|
+
rangeOfFPPI: list = [0, 1],
|
|
330
|
+
gTError: int = 1,
|
|
331
|
+
ROIThreshold: float = 0.5,
|
|
332
|
+
startFrame: int = 0,
|
|
333
|
+
endFrame: int = None):
|
|
334
|
+
"""
|
|
335
|
+
Calculates the RFI (Recall Per Image) based on a list of FPPI (False Positives Per Image) for a given model output.
|
|
336
|
+
|
|
337
|
+
Parameters:
|
|
338
|
+
- modelOpt: Model output data.
|
|
339
|
+
- groundTruth: Ground truth data.
|
|
340
|
+
- rangeOfFPPI: Range of False Positive Per Image.
|
|
341
|
+
- gTError: Distance error scope for ground truth.
|
|
342
|
+
- ROIThreshold: ROI threshold.
|
|
343
|
+
- startFrame: Starting frame of the dataset.
|
|
344
|
+
- endFrame: Ending frame of the dataset (optional).
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
- RPIList: List of Recall Per Image.
|
|
348
|
+
- FPPIList: List of False Positives Per Image.
|
|
349
|
+
- thresholdList: List of thresholds.
|
|
350
|
+
"""
|
|
351
|
+
|
|
352
|
+
if endFrame is None:
|
|
353
|
+
endFrame = len(modelOpt) - 1
|
|
354
|
+
|
|
355
|
+
lowerFPPI, upperFPPI = rangeOfFPPI
|
|
356
|
+
intervalFPPI = (upperFPPI - lowerFPPI) / 20
|
|
357
|
+
errorFPPI = (upperFPPI - lowerFPPI) / 100
|
|
358
|
+
|
|
359
|
+
thresholdList = [1, 0.5, 0]
|
|
360
|
+
FPPIList = [None] * len(thresholdList)
|
|
361
|
+
RPIList = [None] * len(thresholdList)
|
|
362
|
+
listMark = [True] * len(thresholdList)
|
|
363
|
+
shouldFoundLower = shouldFoundUpper = True
|
|
364
|
+
|
|
365
|
+
while any(listMark):
|
|
366
|
+
idx = next((i for i, marked in enumerate(listMark) if marked), None)
|
|
367
|
+
|
|
368
|
+
thresholdValue = thresholdList[idx]
|
|
369
|
+
|
|
370
|
+
# Filter data based on the threshold value
|
|
371
|
+
threInput = [[data for data in frame if data[-1] > thresholdValue] for frame in modelOpt]
|
|
372
|
+
|
|
373
|
+
# Evaluate the model by video
|
|
374
|
+
listTP, listFN, listFP = evaluation_model_by_video(threInput,
|
|
375
|
+
groundTruth,
|
|
376
|
+
confidenceThreshold=thresholdValue,
|
|
377
|
+
gTError=gTError,
|
|
378
|
+
ROIThreshold=ROIThreshold)
|
|
379
|
+
|
|
380
|
+
# total TP, FN and FP
|
|
381
|
+
totalTP = sum(listTP[startFrame:endFrame + 1])
|
|
382
|
+
totalFN = sum(listFN[startFrame:endFrame + 1])
|
|
383
|
+
totalFP = sum(listFP[startFrame:endFrame + 1])
|
|
384
|
+
|
|
385
|
+
# Calculate Recall Per Image (RFI) and False Positive Per Image (FPPI)
|
|
386
|
+
RFI = totalTP / (totalTP + totalFN) if (totalTP + totalFN) > 0 else 0
|
|
387
|
+
FPPI = totalFP / (endFrame - startFrame + 1) if (endFrame - startFrame + 1) > 0 else 0
|
|
388
|
+
|
|
389
|
+
RPIList[idx] = RFI
|
|
390
|
+
FPPIList[idx] = FPPI
|
|
391
|
+
listMark[idx] = False
|
|
392
|
+
|
|
393
|
+
if not any(listMark):
|
|
394
|
+
if shouldFoundLower:
|
|
395
|
+
# Find all indices where FPPI value is greater than or equal to the lower
|
|
396
|
+
eligibleIndices1 = [i for i, fp in enumerate(FPPIList) if fp >= lowerFPPI]
|
|
397
|
+
# Find the index among eligible indices that is closest to the lower
|
|
398
|
+
lowerIdx = min(eligibleIndices1)
|
|
399
|
+
if FPPIList[lowerIdx] - lowerFPPI <= errorFPPI:
|
|
400
|
+
shouldFoundLower = False
|
|
401
|
+
del FPPIList[:lowerIdx]
|
|
402
|
+
del RPIList[:lowerIdx]
|
|
403
|
+
del thresholdList[:lowerIdx]
|
|
404
|
+
del listMark[:lowerIdx]
|
|
405
|
+
elif lowerIdx == 0:
|
|
406
|
+
shouldFoundLower = False
|
|
407
|
+
else:
|
|
408
|
+
mean = (thresholdList[lowerIdx-1] + thresholdList[lowerIdx]) / 2
|
|
409
|
+
thresholdList.insert(lowerIdx-1, mean)
|
|
410
|
+
RPIList.insert(lowerIdx, None)
|
|
411
|
+
FPPIList.insert(lowerIdx, None)
|
|
412
|
+
listMark.insert(lowerIdx, True)
|
|
413
|
+
continue
|
|
414
|
+
|
|
415
|
+
if shouldFoundUpper:
|
|
416
|
+
# Find all indices where FPPI value is less than or equal to the upper
|
|
417
|
+
eligibleIndices2 = [i for i, fp in enumerate(FPPIList) if fp <= upperFPPI]
|
|
418
|
+
# Find the index among eligible indices that is closest to the upper
|
|
419
|
+
upperIdx = max(eligibleIndices2)
|
|
420
|
+
if upperFPPI - FPPIList[upperIdx] <= errorFPPI:
|
|
421
|
+
shouldFoundUpper = False
|
|
422
|
+
del FPPIList[upperIdx+1:]
|
|
423
|
+
del RPIList[upperIdx+1:]
|
|
424
|
+
del thresholdList[upperIdx+1:]
|
|
425
|
+
del listMark[upperIdx+1:]
|
|
426
|
+
elif upperIdx == len(FPPIList)-1:
|
|
427
|
+
shouldFoundUpper = False
|
|
428
|
+
else:
|
|
429
|
+
mean = (thresholdList[upperIdx] + thresholdList[upperIdx+1]) / 2
|
|
430
|
+
thresholdList.insert(upperIdx+1, mean)
|
|
431
|
+
RPIList.insert(upperIdx+1, None)
|
|
432
|
+
FPPIList.insert(upperIdx+1, None)
|
|
433
|
+
listMark.insert(upperIdx+1, True)
|
|
434
|
+
continue
|
|
435
|
+
|
|
436
|
+
# Add intermediate thresholds to reduce FPPI interval
|
|
437
|
+
i = 0
|
|
438
|
+
while i < len(thresholdList) - 1:
|
|
439
|
+
if abs(FPPIList[i + 1] - FPPIList[i]) > intervalFPPI \
|
|
440
|
+
and thresholdList[i] - thresholdList[i + 1] > 1e-4:
|
|
441
|
+
mean = (thresholdList[i] + thresholdList[i + 1]) / 2
|
|
442
|
+
thresholdList.insert(i + 1, mean)
|
|
443
|
+
FPPIList.insert(i + 1, None)
|
|
444
|
+
RPIList.insert(i + 1, None)
|
|
445
|
+
listMark.insert(i + 1, True)
|
|
446
|
+
break
|
|
447
|
+
else:
|
|
448
|
+
i += 1
|
|
449
|
+
|
|
450
|
+
return RPIList, FPPIList, thresholdList
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def get_P_R_curve_data(modelOpt: list,
|
|
454
|
+
groundTruth: list,
|
|
455
|
+
intervalOfRecall: float = 0.05,
|
|
456
|
+
gTError: int = 1,
|
|
457
|
+
ROIThreshold: float = 0.5,
|
|
458
|
+
startFrame: int = 0,
|
|
459
|
+
endFrame: int = None):
|
|
460
|
+
"""
|
|
461
|
+
Calculates the data of Precision-Recall (P-R) Curves for a given model output.
|
|
462
|
+
|
|
463
|
+
Parameters:
|
|
464
|
+
- modelOpt: Model output data.
|
|
465
|
+
- groundTruth: Ground truth data.
|
|
466
|
+
- intervalOfRecall: interval of Recall
|
|
467
|
+
- gTError: Distance error scope for ground truth.
|
|
468
|
+
- ROIThreshold: ROI threshold.
|
|
469
|
+
- startFrame: Starting frame of the dataset.
|
|
470
|
+
- endFrame: Ending frame of the dataset (optional).
|
|
471
|
+
|
|
472
|
+
Returns:
|
|
473
|
+
- rList: List of Recall.
|
|
474
|
+
- pList: List of Precision.
|
|
475
|
+
- thresholdList: List of thresholds.
|
|
476
|
+
"""
|
|
477
|
+
|
|
478
|
+
if endFrame is None:
|
|
479
|
+
endFrame = len(modelOpt) - 1
|
|
480
|
+
|
|
481
|
+
thresholdList = [1, 0.5, 0]
|
|
482
|
+
rList = [None] * len(thresholdList)
|
|
483
|
+
pList = [None] * len(thresholdList)
|
|
484
|
+
listMark = [True] * len(thresholdList)
|
|
485
|
+
|
|
486
|
+
while any(listMark):
|
|
487
|
+
idx = next((i for i, marked in enumerate(listMark) if marked), None)
|
|
488
|
+
|
|
489
|
+
thresholdValue = thresholdList[idx]
|
|
490
|
+
|
|
491
|
+
# Filter data based on the threshold value
|
|
492
|
+
threInput = [[data for data in frame if data[-1] > thresholdValue] for frame in modelOpt]
|
|
493
|
+
|
|
494
|
+
# Evaluate the model by video
|
|
495
|
+
listTP, listFN, listFP = evaluation_model_by_video(threInput,
|
|
496
|
+
groundTruth,
|
|
497
|
+
confidenceThreshold=thresholdValue,
|
|
498
|
+
gTError=gTError,
|
|
499
|
+
ROIThreshold=ROIThreshold)
|
|
500
|
+
|
|
501
|
+
# total TP, FN and FP
|
|
502
|
+
totalTP = sum(listTP[startFrame:endFrame + 1])
|
|
503
|
+
totalFN = sum(listFN[startFrame:endFrame + 1])
|
|
504
|
+
totalFP = sum(listFP[startFrame:endFrame + 1])
|
|
505
|
+
|
|
506
|
+
# Calculate Recall Per Image (RFI) and False Positive Per Image (FPPI)
|
|
507
|
+
recall = totalTP / (totalTP + totalFN) if (totalTP + totalFN) > 0 else 0
|
|
508
|
+
precision = totalTP / (totalTP + totalFP) if (totalTP + totalFP) > 0 else 1
|
|
509
|
+
|
|
510
|
+
rList[idx] = recall
|
|
511
|
+
pList[idx] = precision
|
|
512
|
+
listMark[idx] = False
|
|
513
|
+
|
|
514
|
+
if not any(listMark):
|
|
515
|
+
|
|
516
|
+
# Add intermediate thresholds to reduce FPPI interval
|
|
517
|
+
i = 0
|
|
518
|
+
while i < len(thresholdList) - 1:
|
|
519
|
+
if abs(rList[i + 1] - rList[i]) > intervalOfRecall \
|
|
520
|
+
and thresholdList[i] - thresholdList[i + 1] > 1e-4:
|
|
521
|
+
mean = (thresholdList[i] + thresholdList[i + 1]) / 2
|
|
522
|
+
thresholdList.insert(i + 1, mean)
|
|
523
|
+
rList.insert(i + 1, None)
|
|
524
|
+
pList.insert(i + 1, None)
|
|
525
|
+
listMark.insert(i + 1, True)
|
|
526
|
+
break
|
|
527
|
+
else:
|
|
528
|
+
i += 1
|
|
529
|
+
|
|
530
|
+
return rList, pList, thresholdList
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def get_thres_recall_data(modelOpt: list,
|
|
534
|
+
groundTruth: list,
|
|
535
|
+
rangeOfThreshold: list = [0.5, 1],
|
|
536
|
+
thresholdInteval: float = 0.01,
|
|
537
|
+
gTError: int = 1,
|
|
538
|
+
ROIThreshold = 0.5,
|
|
539
|
+
startFrame=0,
|
|
540
|
+
endFrame=None):
|
|
541
|
+
"""
|
|
542
|
+
Calculates the RFI (Recall Per Image) based on list Threshold for a given model output.
|
|
543
|
+
|
|
544
|
+
Parameters:
|
|
545
|
+
- modelOpt: List containing the detection results.
|
|
546
|
+
- groundTruth: List containing the ground truth.
|
|
547
|
+
- rangeOfThreshold: List defining the range of thresholds [lowerBound, upperBound].
|
|
548
|
+
- thresholdInteval: Interval between thresholds.
|
|
549
|
+
- gTError: Distance error scope for ground truth.
|
|
550
|
+
- ROIThreshold: ROI threshold.
|
|
551
|
+
- startFrame: Starting frame of the dataset.
|
|
552
|
+
- endFrame: Ending frame of the dataset (optional).
|
|
553
|
+
|
|
554
|
+
Returns:
|
|
555
|
+
- RPIList: List of Recall Per Image.
|
|
556
|
+
- thresholdList: List of thresholds.
|
|
557
|
+
"""
|
|
558
|
+
|
|
559
|
+
if endFrame is None:
|
|
560
|
+
endFrame = len(modelOpt) - 1
|
|
561
|
+
|
|
562
|
+
lowerBound, upperBound = rangeOfThreshold
|
|
563
|
+
totalLen = int((upperBound - lowerBound) / thresholdInteval)
|
|
564
|
+
|
|
565
|
+
thresholdList = [lowerBound + i * thresholdInteval for i in range(totalLen)] + [upperBound]
|
|
566
|
+
RPIList = [None for _ in range(totalLen + 1)]
|
|
567
|
+
|
|
568
|
+
for idx, thresholdValue in enumerate(thresholdList):
|
|
569
|
+
# Filter data based on the threshold value
|
|
570
|
+
threInput = [[data for data in frame if data[-1] >= thresholdValue]
|
|
571
|
+
for frame in modelOpt]
|
|
572
|
+
|
|
573
|
+
# Evaluate the model by video
|
|
574
|
+
listTP, listFN, listFP = evaluation_model_by_video(threInput,
|
|
575
|
+
groundTruth,
|
|
576
|
+
confidenceThreshold=thresholdValue,
|
|
577
|
+
gTError=gTError,
|
|
578
|
+
ROIThreshold=ROIThreshold)
|
|
579
|
+
|
|
580
|
+
# Calculate RFI and FPPI
|
|
581
|
+
numberAT = sum(listFN[startFrame:endFrame+1]) + sum(listTP[startFrame:endFrame+1])
|
|
582
|
+
# Calculate Recall Per Image (RFI) and False Positive Per Image (FPPI)
|
|
583
|
+
RFI = sum(listTP[startFrame:endFrame+1]) / numberAT if numberAT > 0 else 0
|
|
584
|
+
|
|
585
|
+
RPIList[idx] = RFI
|
|
586
|
+
|
|
587
|
+
return RPIList, thresholdList
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def compute_AUC(RPIList, FPPIList, rangeOfFPPI=[0, 1]) -> float:
|
|
591
|
+
"""
|
|
592
|
+
Computes the Area Under the Curve (AUC) for the given RPI (Recall Per Image) and FPPI (False Positives Per Image) lists.
|
|
593
|
+
|
|
594
|
+
Parameters:
|
|
595
|
+
- RPIList: List of Recall Per Image values.
|
|
596
|
+
- FPPIList: List of False Positives Per Image values.
|
|
597
|
+
- rangeOfFPPI: Range of False Positives Per Image to consider for AUC calculation (default is [0, 1]).
|
|
598
|
+
|
|
599
|
+
Returns:
|
|
600
|
+
- AUC: Area Under the Curve.
|
|
601
|
+
"""
|
|
602
|
+
lowerFPPI, upperFPPI = rangeOfFPPI
|
|
603
|
+
|
|
604
|
+
# Ensure the lists are sorted by FPPIList
|
|
605
|
+
sorted_indices = sorted(range(len(FPPIList)), key=lambda i: FPPIList[i])
|
|
606
|
+
sorted_RPI = [RPIList[i] for i in sorted_indices]
|
|
607
|
+
sorted_FPPI = [FPPIList[i] for i in sorted_indices]
|
|
608
|
+
|
|
609
|
+
# Filter the lists based on the specified rangeOfFPPI
|
|
610
|
+
filtered_RPI = []
|
|
611
|
+
filtered_FPPI = []
|
|
612
|
+
for i in range(len(sorted_FPPI)):
|
|
613
|
+
if lowerFPPI <= sorted_FPPI[i] <= upperFPPI:
|
|
614
|
+
filtered_RPI.append(sorted_RPI[i])
|
|
615
|
+
filtered_FPPI.append(sorted_FPPI[i])
|
|
616
|
+
|
|
617
|
+
# Compute AUC using the trapezoidal rule
|
|
618
|
+
weighted_sum = 0.0
|
|
619
|
+
total_weight = 0.0
|
|
620
|
+
for i in range(1, len(filtered_RPI)):
|
|
621
|
+
deltaFPPI = (filtered_FPPI[i] - filtered_FPPI[i - 1])
|
|
622
|
+
weighted_sum += (filtered_RPI[i] + filtered_RPI[i - 1]) * deltaFPPI / 2.0
|
|
623
|
+
total_weight += deltaFPPI
|
|
624
|
+
|
|
625
|
+
AUC = weighted_sum / total_weight if total_weight !=0 else 0.0
|
|
626
|
+
|
|
627
|
+
return AUC
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def compute_AR(RPIList, thresholdList, rangeOfThreshold=[0.5, 1]) -> float:
|
|
631
|
+
"""
|
|
632
|
+
Computes the Average Recall (AR) based on the given RPI (Recall Per Image) and threshold lists.
|
|
633
|
+
|
|
634
|
+
Parameters:
|
|
635
|
+
- RPIList: List of Recall Per Image values.
|
|
636
|
+
- thresholdList: List of threshold values.
|
|
637
|
+
- rangeOfThreshold: Range of threshold values to consider for mean recall calculation (default is [0.5, 1]).
|
|
638
|
+
|
|
639
|
+
Returns:
|
|
640
|
+
- meanRecall: Weighted mean recall value.
|
|
641
|
+
"""
|
|
642
|
+
lowerThreshold, upperThreshold = rangeOfThreshold
|
|
643
|
+
|
|
644
|
+
# Ensure the lists are sorted by thresholdList
|
|
645
|
+
sorted_indices = sorted(range(len(thresholdList)), key=lambda i: thresholdList[i])
|
|
646
|
+
sorted_RPI = [RPIList[i] for i in sorted_indices]
|
|
647
|
+
sorted_thresholds = [thresholdList[i] for i in sorted_indices]
|
|
648
|
+
|
|
649
|
+
# Filter the lists based on the specified rangeOfThreshold
|
|
650
|
+
filtered_RPI = []
|
|
651
|
+
filtered_thresholds = []
|
|
652
|
+
for i in range(len(sorted_thresholds)):
|
|
653
|
+
if lowerThreshold <= sorted_thresholds[i] <= upperThreshold:
|
|
654
|
+
filtered_RPI.append(sorted_RPI[i])
|
|
655
|
+
filtered_thresholds.append(sorted_thresholds[i])
|
|
656
|
+
|
|
657
|
+
# Compute weighted mean recall using the trapezoidal rule
|
|
658
|
+
weighted_sum = 0.0
|
|
659
|
+
total_weight = 0.0
|
|
660
|
+
for i in range(1, len(filtered_RPI)):
|
|
661
|
+
delta_threshold = filtered_thresholds[i] - filtered_thresholds[i - 1]
|
|
662
|
+
weighted_sum += (filtered_RPI[i] + filtered_RPI[i - 1]) / 2.0 * delta_threshold
|
|
663
|
+
total_weight += delta_threshold
|
|
664
|
+
|
|
665
|
+
AR = weighted_sum / total_weight if total_weight != 0 else 0.0
|
|
666
|
+
|
|
667
|
+
return AR
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def compute_AP(rList: list, pList: list) -> float:
|
|
671
|
+
"""
|
|
672
|
+
Computes the Average Precision (AP) based on the given Recall and Precision lists.
|
|
673
|
+
|
|
674
|
+
Parameters:
|
|
675
|
+
- rList: List of Recall values (recall at different thresholds).
|
|
676
|
+
- pList: List of Precision values (precision at different thresholds).
|
|
677
|
+
|
|
678
|
+
Returns:
|
|
679
|
+
- AP: Average Precision (the area under the PR curve)
|
|
680
|
+
"""
|
|
681
|
+
# Ensure Recall and Precision lists have the same length
|
|
682
|
+
assert len(rList) == len(pList), "Recall and Precision lists must have the same length."
|
|
683
|
+
|
|
684
|
+
# Initialize the AP variable
|
|
685
|
+
AP = 0.0
|
|
686
|
+
|
|
687
|
+
# Calculate the area under the PR curve using the trapezoidal rule
|
|
688
|
+
for i in range(1, len(rList)):
|
|
689
|
+
# Calculate the difference in recall between two consecutive points
|
|
690
|
+
delta_r = rList[i] - rList[i-1]
|
|
691
|
+
# Calculate the average precision between two consecutive points
|
|
692
|
+
avg_p = (pList[i] + pList[i-1]) / 2.0
|
|
693
|
+
# Add the area of the trapezoid to the total AP
|
|
694
|
+
AP += delta_r * avg_p
|
|
695
|
+
|
|
696
|
+
return AP
|
|
697
|
+
|