Kumita← Open visual lesson

Average precision

The code behind the picture

Follow the lesson’s implementation, one code cell at a time.

Choose a language and read the cells in order. These are small teaching examples; the visual lesson runs separately.

Follow the calculation

Same inputs · Same operation · Same result

3 teaching cells · Python standard library · Read-only

  1. 1. Prepare the pictures' references and the predictions

    Five reference watermelons in three pictures, as [id, picture, x, y, w, h], and eight illustrative predictions written by hand (no model made them), as [id, picture, x, y, w, h, score in hundredths]. Coordinates are picture pixels.

    import json
    
    refs = [
        ["a", 1, 12, 19, 36, 34],
        ["b", 1, 70, 62, 40, 24],
        ["c", 2, 24, 31, 36, 34],
        ["d", 3, 14, 41, 36, 34],
        ["e", 3, 72, 18, 40, 24],
    ]
    preds = [
        ["A", 1, 14, 20, 36, 34, 95],
        ["B", 1, 80, 12, 32, 30, 80],
        ["C", 1, 68, 60, 40, 26, 40],
        ["D", 2, 22, 30, 38, 36, 90],
        ["E", 2, 28, 34, 34, 32, 60],
        ["F", 2, 84, 46, 32, 30, 30],
        ["G", 3, 12, 42, 36, 32, 70],
        ["H", 3, 60, 10, 34, 26, 50],
    ]
    IOU_MATCH = 0.5
  2. 2. Rank, match, accumulate, then measure the area

    Rank by score (ties by letter). Each prediction takes the highest-IoU reference in its own picture; IoU ≥ 0.5 and still unmatched is a true positive, a taken reference makes a duplicate (false positive). Precision and recall after each prediction; the envelope takes the best precision at the same or a later rank; AP adds recall rise × envelope. Round only the report.

    def iou(a, b):
        w = max(0, min(a[0] + a[2], b[0] + b[2]) - max(a[0], b[0]))
        h = max(0, min(a[1] + a[3], b[1] + b[3]) - max(a[1], b[1]))
        inter = w * h
        union = a[2] * a[3] + b[2] * b[3] - inter
        return inter / union if union > 0 else 0
    
    ranked = sorted(preds, key=lambda p: (-p[6], p[0]))
    matched, flags = set(), []
    for p in ranked:
        best, best_iou = None, -1
        for r in refs:
            if r[1] == p[1]:
                v = iou(p[2:6], r[2:6])
                if v > best_iou:
                    best, best_iou = r, v
        tp = best is not None and best_iou >= IOU_MATCH and best[0] not in matched
        if tp:
            matched.add(best[0])
        flags.append(tp)
    
    tp = fp = 0
    precision, recall = [], []
    for f in flags:
        tp, fp = tp + f, fp + (not f)
        precision.append(tp / (tp + fp))
        recall.append(tp / len(refs))
    envelope = precision[:]
    for i in range(len(envelope) - 2, -1, -1):
        envelope[i] = max(envelope[i], envelope[i + 1])
    ap, prev = 0, 0
    for r, e in zip(recall, envelope):
        if r > prev:
            ap, prev = ap + (r - prev) * e, r
    result = {
        "order": "".join(p[0] for p in ranked),
        "verdicts": "".join("T" if f else "F" for f in flags),
        "ap": round(ap, 6),
    }
  3. 3. Inspect the result

    Print the result as JSON so both languages use the same output format.

    print(json.dumps(result))

Expected result

Running the three cells in order produces this result in either language.

{
  "order": "ADBGEHCF",
  "verdicts": "TTFTFFTF",
  "ap": 0.664286
}