ML Grind

Segmentation Metrics - CNNs & Computer Vision

Implement Segmentation Metrics in Python with NumPy. medium exercise with examples, hints, and instant feedback. No setup or signup required.

Segmentation Metrics

What you'll build

Three standard scores for a binary segmentation mask: IoU (Jaccard index), pixel accuracy and the Dice coefficient.

Background

Segmentation models (U-Net, DeepLab, Mask R-CNN's mask head) predict a label for every pixel. Pixel accuracy is easy to read but misleading when the object is small: predicting "background everywhere" on an image that is 95% background scores 0.95. IoU and Dice only look at the foreground, so they are the metrics reported in papers and medical-imaging challenges. Dice also exists as a training loss (1 - soft Dice).

Formula

Treat each mask as the set of pixels equal to 1. With TP = |P ∩ T| (both 1), |P| = number of predicted foreground pixels, |T| = number of target foreground pixels:

IoU            = |P ∩ T| / |P ∪ T|          = TP / (|P| + |T| - TP)
Dice           = 2 |P ∩ T| / (|P| + |T|)     = 2 TP / (2 TP + FP + FN)
Pixel accuracy = (number of pixels where prediction == target) / (total pixels)

Dice is the F1 score over pixels, and the two overlap scores are linked by Dice = 2 IoU / (1 + IoU), so Dice >= IoU.

Inputs

  • prediction, target: 2D arrays of the same shape (H, W) containing only 0 and 1 (any integer or float dtype).

Expected return format

A dictionary with exactly these keys, each a Python float rounded to 4 decimal places:

  • 'iou': foreground intersection over union
  • 'pixel_accuracy': fraction of pixels that agree (foreground and background)
  • 'dice': Dice coefficient

Edge cases and conventions

  • If both masks are empty (no foreground at all), the IoU and Dice denominators are 0: return 0.0 for 'iou' and 'dice' here. Pixel accuracy is then 1.0. (Some libraries return 1.0 for an empty-vs-empty match; be explicit about which you use.)
  • Disjoint masks give IoU and Dice of 0.0.

Constraints

NumPy only, vectorised (no loops over pixels).

Follow-up

  • How do you extend these to K classes (per-class IoU, then mean IoU)?
  • Write a differentiable soft Dice loss on predicted probabilities.

Examples

Input: prediction = [[1, 1], [0, 0]], target = [[1, 0], [0, 1]]
Output: {'iou': 0.3333, 'pixel_accuracy': 0.5, 'dice': 0.5}

TP = 1, |P| = |T| = 2: IoU = 1 / (2 + 2 - 1) = 1/3; 2 of 4 pixels agree; Dice = 2 * 1 / 4 = 0.5.

Input: prediction = [[1, 1, 0], [1, 1, 0], [0, 0, 0]], target = [[1, 1, 1], [1, 0, 0], [0, 0, 0]]
Output: {'iou': 0.6, 'pixel_accuracy': 0.7778, 'dice': 0.75}

TP = 3, |P| = |T| = 4: IoU = 3/5, Dice = 6/8, and 7 of 9 pixels agree.

Practice in Python

import numpy as np

def segmentation_metrics(prediction, target):
    """
    Binary segmentation metrics.

    Args:
        prediction: Predicted mask, shape (H, W), values 0 or 1
        target: Ground-truth mask, shape (H, W), values 0 or 1

    Returns:
        dict with Python floats rounded to 4 decimals:
            'iou': foreground IoU (0.0 if both masks are empty)
            'pixel_accuracy': fraction of matching pixels
            'dice': Dice coefficient (0.0 if both masks are empty)
    """
    # Your code here
    pass

The interactive editor loads with Python and NumPy. Run your implementation against 6 tests, or use the progressive hints.

More exercises in this section →