Spaces:
Running
Running
File size: 14,761 Bytes
eafbf97 |
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 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 |
"""Helpers for metric functions"""
import numpy as np
import pandas as pd
def calculate_iou(box1, box2):
"""
Calculate Intersection over Union (IoU) between two bounding boxes.
Args:
box1 (tuple): Coordinates of the first bounding box in the format (x1, y1, x2, y2).
box2 (tuple): Coordinates of the second bounding box in the format (x1, y1, x2, y2).
Returns:
float: Intersection over Union (IoU) score.
"""
# Extract coordinates
x1, y1, x2, y2 = box1
x1_, y1_, x2_, y2_ = box2
# Calculate the intersection area
intersection_area = max(0, min(x2, x2_) - max(x1, x1_)) * max(0, min(y2, y2_) - max(y1, y1_))
# Calculate the areas of each bounding box
box1_area = (x2 - x1) * (y2 - y1)
box2_area = (x2_ - x1_) * (y2_ - y1_)
# Calculate IoU
iou = intersection_area / float(box1_area + box2_area - intersection_area)
return iou
def compute_intersection_1d(x, y):
# sort the boxes
x1, x2 = sorted(x)
y1, y2 = sorted(y)
# compute the intersection
intersection = max(0, min(x2, y2) - max(x1, y1))
return intersection
def compute_union_1d(x, y):
# sort the boxes
x1, x2 = sorted(x)
y1, y2 = sorted(y)
# compute the union
union = max(x2, y2) - min(x1, y1)
return union
def compute_iou_1d(pred_box, true_box):
"""
Compute IoU for 1D boxes.
Args:
pred_box (float): Predicted box, [x1, x2]
true_box (float): Ground truth box, [x1, x2]
Returns:
float: IoU
"""
intersection = compute_intersection_1d(pred_box, true_box)
union = compute_union_1d(pred_box, true_box)
iou = intersection / union
return iou
def compute_iou_1d_single_candidate_multiple_targets(pred_box, true_boxes):
"""
Compute IoU for 1D boxes.
Args:
pred_box (float): Predicted box, [x1, x2]
true_boxes (np.ndarray): Ground truth boxes, shape: (N, 2)
Returns:
float: IoU
"""
ious = []
for i, true_box in enumerate(true_boxes):
ious.append(compute_iou_1d(pred_box, true_box))
return np.array(ious)
def compute_iou_1d_multiple_candidates_multiple_targets(pred_boxes, true_boxes):
"""
Compute IoU for 1D boxes.
Args:
pred_boxes (np.ndarray): Predicted boxes, shape: (N, 2)
true_boxes (np.ndarray): Ground truth boxes, shape: (N, 2)
Returns:
float: IoU
"""
iou_matrix = np.zeros((len(pred_boxes), len(true_boxes)))
for i, pred_box in enumerate(pred_boxes):
for j, true_box in enumerate(true_boxes):
iou_matrix[i, j] = compute_iou_1d(pred_box, true_box)
return iou_matrix
def compute_mean_iou_1d(pred_boxes, gt_boxes, threshold=0.5):
"""
Computes mean IOU for 1D bounding boxes.
Args:
pred_boxes (np.ndarray): Predicted boxes, shape: (N, 2)
gt_boxes (np.ndarray): Ground truth boxes, shape: (N, 2)
threshold (float): Threshold to consider a prediction correct
Returns:
float: Mean IOU
"""
# Compute IoU for each pair of boxes
iou_matrix = np.zeros((len(pred_boxes), len(gt_boxes)))
for i, pred_box in enumerate(pred_boxes):
for j, gt_box in enumerate(gt_boxes):
iou_matrix[i, j] = compute_iou_1d(pred_box, gt_box)
# Compute the max IoU for each predicted box
max_iou_indices = np.argmax(iou_matrix, axis=1)
max_iou = iou_matrix[np.arange(len(pred_boxes)), max_iou_indices]
# For each predicted box, compute TP and FP ground truth boxes
tp = np.zeros(len(pred_boxes))
fp = np.zeros(len(pred_boxes))
iou = np.zeros(len(pred_boxes))
tp = np.where(iou_matrix >= threshold, 1, 0)
tp = max_iou >= threshold
fp = max_iou < threshold
iou = max_iou
mean_iou = np.mean(iou)
import ipdb; ipdb.set_trace()
def calculate_mAP_1d(pred_boxes, pred_scores, true_boxes, iou_thresh=0.5):
"""Calculate mean average precision for 1D boxes.
Args:
pred_boxes (numpy array): Predicted boxes, shape (num_boxes,)
pred_scores (numpy array): Predicted scores, shape (num_boxes,)
true_boxes (numpy array): Ground truth boxes, shape (num_boxes,)
iou_thresh (float): IoU threshold to consider a prediction correct
Returns:
float: Mean average precision (mAP)
"""
# Sort predicted boxes by score (in descending order)
sort_inds = np.argsort(pred_scores)[::-1]
pred_boxes = pred_boxes[sort_inds]
pred_scores = pred_scores[sort_inds]
# Compute true positives and false positives at each threshold
tp = np.zeros(len(pred_boxes))
fp = np.zeros(len(pred_boxes))
for i, box in enumerate(pred_boxes):
ious = np.abs(box - true_boxes) / np.maximum(1e-9, np.abs(box) + np.abs(true_boxes))
if len(ious) > 0:
max_iou_idx = np.argmax(ious)
if ious[max_iou_idx] >= iou_thresh:
if tp[max_iou_idx] == 0:
tp[i] = 1
fp[i] = 0
else:
fp[i] = 1
else:
fp[i] = 1
# Compute precision and recall at each threshold
tp_cumsum = np.cumsum(tp)
fp_cumsum = np.cumsum(fp)
recall = tp_cumsum / len(true_boxes)
precision = tp_cumsum / (tp_cumsum + fp_cumsum)
# Compute AP as area under precision-recall curve
ap = 0
for t in np.arange(0, 1.1, 0.1):
if np.sum(recall >= t) == 0:
p = 0
else:
p = np.max(precision[recall >= t])
ap += p / 11
return ap
def segment_iou(target_segment, candidate_segments):
"""Compute the temporal intersection over union between a
target segment and all the test segments.
Parameters
----------
target_segment : 1d array
Temporal target segment containing [starting, ending] times.
candidate_segments : 2d array
Temporal candidate segments containing N x [starting, ending] times.
Outputs
-------
tiou : 1d array
Temporal intersection over union score of the N's candidate segments.
"""
tt1 = np.maximum(target_segment[0], candidate_segments[:, 0])
tt2 = np.minimum(target_segment[1], candidate_segments[:, 1])
# Intersection including Non-negative overlap score.
segments_intersection = (tt2 - tt1).clip(0)
# Segment union.
segments_union = (candidate_segments[:, 1] - candidate_segments[:, 0]) \
+ (target_segment[1] - target_segment[0]) - segments_intersection
# Compute overlap as the ratio of the intersection
# over union of two segments.
tIoU = segments_intersection.astype(float) / segments_union
return tIoU
def interpolated_prec_rec(prec, rec):
"""Interpolated AP - VOCdevkit from VOC 2011.
"""
mprec = np.hstack([[0], prec, [0]])
mrec = np.hstack([[0], rec, [1]])
for i in range(len(mprec) - 1)[::-1]:
mprec[i] = max(mprec[i], mprec[i + 1])
idx = np.where(mrec[1::] != mrec[0:-1])[0] + 1
ap = np.sum((mrec[idx] - mrec[idx - 1]) * mprec[idx])
return ap
from tqdm import tqdm
def compute_average_precision_detection(
ground_truth,
prediction,
tiou_thresholds=np.linspace(0.5, 0.95, 10),
):
"""Compute average precision (detection task) between ground truth and
predictions data frames. If multiple predictions occurs for the same
predicted segment, only the one with highest score is matches as
true positive. This code is greatly inspired by Pascal VOC devkit.
Ref: https://github.com/zhang-can/CoLA/blob/\
d21f1b5a4c6c13f9715cfd4ac1ebcd065d179157/eval/eval_detection.py#L200
Parameters
----------
ground_truth : df
Data frame containing the ground truth instances.
Required fields: ['video-id', 't-start', 't-end']
prediction : df
Data frame containing the prediction instances.
Required fields: ['video-id, 't-start', 't-end', 'score']
tiou_thresholds : 1darray, optional
Temporal intersection over union threshold.
Outputs
-------
ap : float
Average precision score.
"""
ap = np.zeros(len(tiou_thresholds))
if prediction.empty:
return ap
npos = float(len(ground_truth))
lock_gt = np.ones((len(tiou_thresholds),len(ground_truth))) * -1
# Sort predictions by decreasing score order.
sort_idx = prediction['score'].values.argsort()[::-1]
prediction = prediction.loc[sort_idx].reset_index(drop=True)
# Initialize true positive and false positive vectors.
tp = np.zeros((len(tiou_thresholds), len(prediction)))
fp = np.zeros((len(tiou_thresholds), len(prediction)))
# Adaptation to query faster
ground_truth_gbvn = ground_truth.groupby('video-id')
# Assigning true positive to truly grount truth instances.
for idx, this_pred in prediction.iterrows():
try:
# Check if there is at least one ground truth in the video associated.
ground_truth_videoid = ground_truth_gbvn.get_group(this_pred['video-id'])
except Exception as e:
fp[:, idx] = 1
continue
this_gt = ground_truth_videoid.reset_index()
tiou_arr = segment_iou(this_pred[['t-start', 't-end']].values,
this_gt[['t-start', 't-end']].values)
# We would like to retrieve the predictions with highest tiou score.
tiou_sorted_idx = tiou_arr.argsort()[::-1]
for tidx, tiou_thr in enumerate(tiou_thresholds):
for jdx in tiou_sorted_idx:
if tiou_arr[jdx] < tiou_thr:
fp[tidx, idx] = 1
break
if lock_gt[tidx, this_gt.loc[jdx]['index']] >= 0:
continue
# Assign as true positive after the filters above.
tp[tidx, idx] = 1
lock_gt[tidx, this_gt.loc[jdx]['index']] = idx
break
if fp[tidx, idx] == 0 and tp[tidx, idx] == 0:
fp[tidx, idx] = 1
tp_cumsum = np.cumsum(tp, axis=1).astype(float)
fp_cumsum = np.cumsum(fp, axis=1).astype(float)
recall_cumsum = tp_cumsum / npos
precision_cumsum = tp_cumsum / (tp_cumsum + fp_cumsum)
for tidx in range(len(tiou_thresholds)):
ap[tidx] = interpolated_prec_rec(precision_cumsum[tidx,:], recall_cumsum[tidx,:])
return ap
def ap_wrapper(
true_clips,
pred_clips,
pred_scores,
tiou_thresholds=np.linspace(0.5, 0.95, 10),
):
assert isinstance(true_clips, np.ndarray)
assert len(true_clips.shape) == 2 and true_clips.shape[1] == 2
assert isinstance(pred_clips, np.ndarray)
assert len(pred_clips.shape) == 2 and pred_clips.shape[1] == 2
assert isinstance(pred_scores, np.ndarray)
assert len(pred_scores.shape) == 1 and len(pred_scores) == pred_clips.shape[0]
true_df = pd.DataFrame(
{
"video-id": ["video1"] * len(true_clips),
"t-start": true_clips[:, 0],
"t-end": true_clips[:, 1],
}
)
pred_df = pd.DataFrame(
{
"video-id": ["video1"] * len(pred_clips),
"t-start": pred_clips[:, 0],
"t-end": pred_clips[:, 1],
"score": pred_scores,
}
)
return compute_average_precision_detection(
true_df,
pred_df,
tiou_thresholds=tiou_thresholds,
)
def nms_1d(df: pd.DataFrame, score_col="score", iou_thresh=0.5):
"""Applies NMS on 1D (start, end) box predictions."""
columns = set(df.columns)
# assert columns == set(["video_id", "start", "end", "score"])
assert set(["start", "end", "video_id", score_col]).issubset(columns)
video_ids = df["video_id"].unique()
# Group by video_id
groups = df.groupby("video_id")
# Loop over videos
keep_indices = []
net_success_fraction = []
tqdm._instances.clear()
iterator = tqdm(
video_ids,
desc="Applying NMS to each video",
bar_format='{l_bar}{bar:10}{r_bar}{bar:-10b}',
)
for video_id in iterator:
# Get rows for this video
rows = groups.get_group(video_id)
# Sort by score
rows = rows.sort_values(score_col, ascending=False)
# Loop over rows until empty
n_clips = len(rows)
n_clips_selected_in_video = 0
while len(rows):
# Add top row to keep_indices
top_row = rows.iloc[0]
keep_indices.append(rows.index[0])
n_clips_selected_in_video += 1
top_row = top_row.to_dict()
top_segment = np.array([top_row["start"], top_row["end"]])
rows = rows.iloc[1:]
other_segments = rows[["start", "end"]].values
iou_values = segment_iou(top_segment, other_segments)
# Remove rows IoU > iou_thresh
rows = rows[iou_values < iou_thresh]
net_success_fraction.append(n_clips_selected_in_video / n_clips)
net_success_fraction = np.array(net_success_fraction).mean()
print("> Net success fraction: {:.2f}".format(net_success_fraction))
return keep_indices
if __name__ == "__main__":
true_clips = np.array(
[
[0.1, 0.7],
[3.4, 7.8],
[3.9, 5.4],
]
)
pred_clips = np.array(
[
[0.2, 0.8],
[3.5, 7.9],
[3.9, 5.4],
[5.6, 6.7],
[6.0, 6.5],
],
)
pred_scores = np.array([0.9, 0.8, 0.7, 0.6, 0.5])
# 1. Check IoU for a single pair of boxes
iou = compute_iou_1d(pred_clips[0], true_clips[0])
# Manually check that the result is correct
# Clips are [0.1, 0.7] and [0.2, 0.8]
# Intersection: [0.2, 0.7] - length = 0.5
# Union: [0.1, 0.8] - length = 0.7
# Ratio: 0.5 / 0.7 = 0.714
assert np.isclose(iou, 0.714, 3), "Incorrect IoU"
# 2. Check IoU for a single predicted box and multiple ground truth boxes
ious = compute_iou_1d_single_candidate_multiple_targets(pred_clips[0], true_clips)
assert np.allclose(ious, [0.714, 0.0, 0.0], 3), "Incorrect IoU"
# 3. Check mean IoU for multiple predicted boxes and multiple ground truth boxes
ious = compute_iou_1d_multiple_candidates_multiple_targets(pred_clips, true_clips)
assert ious.shape == (5, 3), "Incorrect shape"
ap = ap_wrapper(
true_clips,
pred_clips,
pred_scores,
tiou_thresholds=np.linspace(0.5, 0.95, 3),
)
# Take the mean of the APs across IoU thresholds
final_ap = np.mean(ap)
import ipdb; ipdb.set_trace()
|