|
|
|
|
|
|
| import json
|
| from tqdm import tqdm
|
|
|
| PRED_PATH = "eval/proactive/charades/results/test.jsonl"
|
| GT_PATH = "xxxx/proactive/charades-sta/gt.jsonl"
|
|
|
| THRESHOLDS = [0.4,0.5,0.6,0.7]
|
|
|
| WINDOW_SIZES = [2, 3, 4, 5]
|
|
|
| IOU_THRESHOLDS = [0.5, 0.7]
|
|
|
|
|
| def load_jsonl(path):
|
| data = []
|
| with open(path, "r", encoding="utf-8") as f:
|
| for line in f:
|
| line = line.strip()
|
| if not line:
|
| continue
|
| data.append(json.loads(line))
|
| return data
|
|
|
|
|
| def load_ground_truth(path):
|
| """
|
| Return: {id_str: segments}
|
| segments: [[start, end], ...], which are interpreted directly as frame indices.
|
| """
|
| gt = {}
|
| for item in load_jsonl(path):
|
| vid = str(item["id"])
|
| segments = []
|
| if "answer" in item and item["answer"]:
|
| segs = item["answer"][0].get("segment", [])
|
| segments = segs
|
| gt[vid] = segments
|
| return gt
|
|
|
|
|
| def minmax_normalize(nums):
|
| if not nums:
|
| return []
|
| mn = min(nums)
|
| mx = max(nums)
|
| if mx == mn:
|
|
|
| return [0.0 for _ in nums]
|
| return [(x - mn) / (mx - mn) for x in nums]
|
|
|
|
|
| def smooth_scores(scores, w):
|
| """
|
| For each frame i, take the mean score of frames within [i-w, i+w]
|
| as the smoothed score. Boundary conditions are automatically clipped.
|
| """
|
| n = len(scores)
|
| out = [0.0] * n
|
| for i in range(n):
|
| l = max(0, i - w)
|
| r = min(n - 1, i + w)
|
| window = scores[l : r + 1]
|
| out[i] = sum(window) / len(window)
|
| return out
|
|
|
|
|
| def segments_to_frame_labels(segments, num_frames):
|
| """
|
| Generate per-frame 0/1 labels from segment annotations.
|
| Here [start, end] is treated as a closed interval (both ends included).
|
| """
|
| labels = [0] * num_frames
|
| for seg in segments:
|
| if not isinstance(seg, (list, tuple)) or len(seg) != 2:
|
| continue
|
| s, e = seg
|
| s = int(s)
|
| e = int(e)
|
| if e < 0 or s >= num_frames:
|
| continue
|
| s = max(0, s)
|
| e = min(num_frames - 1, e)
|
| for i in range(s, e + 1):
|
| labels[i] = 1
|
| return labels
|
|
|
|
|
| def frame_iou(pred, labels):
|
| """
|
| Frame-level IoU: treat all frames with value 1 as a set
|
| and compute IoU (intersection / union).
|
| """
|
| assert len(pred) == len(labels)
|
| inter = 0
|
| union = 0
|
| for p, l in zip(pred, labels):
|
| if p == 1 and l == 1:
|
| inter += 1
|
| if p == 1 or l == 1:
|
| union += 1
|
| if union == 0:
|
|
|
| return 1.0
|
| return inter / union
|
|
|
|
|
| def safe_mean(arr):
|
| return sum(arr) / len(arr) if arr else float("nan")
|
|
|
|
|
| def main():
|
| print("Loading ground truth...")
|
| gt_dict = load_ground_truth(GT_PATH)
|
|
|
| print("Loading predictions...")
|
| pred_items = load_jsonl(PRED_PATH)
|
|
|
|
|
| recall_no_smooth = {
|
| thr: {alpha: [] for alpha in IOU_THRESHOLDS} for thr in THRESHOLDS
|
| }
|
|
|
|
|
| recall_smooth = {
|
| w: {thr: {alpha: [] for alpha in IOU_THRESHOLDS} for thr in THRESHOLDS}
|
| for w in WINDOW_SIZES
|
| }
|
|
|
| missing_gt = 0
|
| used_samples = 0
|
|
|
| print("Evaluating...")
|
| for item in tqdm(pred_items):
|
| vid = str(item["id"])
|
| raw_probs = item.get("raw_probs", [])
|
| num_frames = len(raw_probs)
|
| if num_frames == 0:
|
| continue
|
|
|
| gt_segments = gt_dict.get(vid)
|
| if gt_segments is None:
|
| missing_gt += 1
|
| continue
|
|
|
| labels = segments_to_frame_labels(gt_segments, num_frames)
|
| norm_scores = minmax_normalize(raw_probs)
|
|
|
|
|
| for thr in THRESHOLDS:
|
| pred_bin = [1 if s >= thr else 0 for s in norm_scores]
|
| iou = frame_iou(pred_bin, labels)
|
| for alpha in IOU_THRESHOLDS:
|
| hit = 1.0 if iou >= alpha else 0.0
|
| recall_no_smooth[thr][alpha].append(hit)
|
|
|
|
|
| for w in WINDOW_SIZES:
|
| sm = smooth_scores(norm_scores, w)
|
| for thr in THRESHOLDS:
|
| pred_bin = [1 if s >= thr else 0 for s in sm]
|
| iou = frame_iou(pred_bin, labels)
|
| for alpha in IOU_THRESHOLDS:
|
| hit = 1.0 if iou >= alpha else 0.0
|
| recall_smooth[w][thr][alpha].append(hit)
|
|
|
| used_samples += 1
|
|
|
| print("\n===== Summary =====")
|
| print(f"Total prediction items: {len(pred_items)}")
|
| print(f"Used items with GT : {used_samples}")
|
| print(f"Missing GT items : {missing_gt}")
|
|
|
|
|
| print("\nCharades-STA R@α (no smoothing, varying threshold on min-max scores):")
|
| for thr in THRESHOLDS:
|
| line = [f"thr={thr:.2f}"]
|
| for alpha in IOU_THRESHOLDS:
|
| r = safe_mean(recall_no_smooth[thr][alpha])
|
| line.append(f"R@{alpha:.1f}={r:.4f}")
|
| print(" " + ", ".join(line))
|
|
|
|
|
| print("\nCharades-STA R@α (smoothed scores, varying window size w and threshold):")
|
| for w in WINDOW_SIZES:
|
| print(f"\n w = {w}:")
|
| for thr in THRESHOLDS:
|
| line = [f" thr={thr:.2f}"]
|
| for alpha in IOU_THRESHOLDS:
|
| r = safe_mean(recall_smooth[w][thr][alpha])
|
| line.append(f"R@{alpha:.1f}={r:.4f}")
|
| print(" " + ", ".join(line))
|
|
|
|
|
| if __name__ == "__main__":
|
| main() |