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.
Files changed (45) hide show
  1. xttmp/__init__.py +1 -0
  2. xttmp/api/__init__.py +5 -0
  3. xttmp/api/evaluate.py +163 -0
  4. xttmp/api/get_visualize_handle.py +29 -0
  5. xttmp/api/instancing_model.py +35 -0
  6. xttmp/core/__init__.py +0 -0
  7. xttmp/core/apgstmd_core.py +188 -0
  8. xttmp/core/apgstmdv2_core.py +79 -0
  9. xttmp/core/base_core.py +36 -0
  10. xttmp/core/dstmd_core.py +213 -0
  11. xttmp/core/estmd_backbone.py +110 -0
  12. xttmp/core/estmd_core.py +356 -0
  13. xttmp/core/feedbackstmd_core.py +61 -0
  14. xttmp/core/fracstmd_core.py +98 -0
  15. xttmp/core/fstmd_core.py +15 -0
  16. xttmp/core/fstmdv2_core.py +42 -0
  17. xttmp/core/haarstmd_core.py +140 -0
  18. xttmp/core/math_operator.py +307 -0
  19. xttmp/core/stfeedbackstmd_core.py +233 -0
  20. xttmp/core/stmdplus_core.py +187 -0
  21. xttmp/core/stmdplusv2_core.py +82 -0
  22. xttmp/core/vstmd_core.py +420 -0
  23. xttmp/demo/evaluate_model.py +92 -0
  24. xttmp/demo/inference_gui.py +148 -0
  25. xttmp/demo/inference_gui_single_process.py +134 -0
  26. xttmp/demo/inference_image_stream.py +67 -0
  27. xttmp/demo/inference_video.py +66 -0
  28. xttmp/main.py +14 -0
  29. xttmp/model/__init__.py +13 -0
  30. xttmp/model/backbone.py +514 -0
  31. xttmp/model/facilitated_model.py +230 -0
  32. xttmp/model/feedback_model.py +271 -0
  33. xttmp/model/haarstmd.py +61 -0
  34. xttmp/model/vstmd.py +457 -0
  35. xttmp/util/__init__.py +0 -0
  36. xttmp/util/compute_module.py +402 -0
  37. xttmp/util/create_kernel.py +363 -0
  38. xttmp/util/evaluate_module.py +697 -0
  39. xttmp/util/iostream.py +660 -0
  40. xttmp-2.3.0.dist-info/METADATA +85 -0
  41. xttmp-2.3.0.dist-info/RECORD +45 -0
  42. xttmp-2.3.0.dist-info/WHEEL +5 -0
  43. xttmp-2.3.0.dist-info/entry_points.txt +2 -0
  44. xttmp-2.3.0.dist-info/licenses/LICENSE +201 -0
  45. 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
+