Medical AI · Deep learning & imaging · Lesson 3 of 5
Image segmentation with U-Net
Reviewed by Dr. Ram Kumar G, MBBS, MD (Internal Medicine), MPH (USA), on · Last updated
- Advanced
- Technical focus
- About 35 minutes
- Includes exercise
Before this lesson: CNNs for chest X-ray and retinal images
In short
Segmentation assigns a label to every pixel, outlining a lesion, organ or wound rather than just saying whether it is present. The U-Net architecture does this with an encoder that captures context, a decoder that restores detail, and skip connections that carry fine detail across. Results are usually scored with the Dice coefficient, which measures overlap between the predicted and true outlines. Report the spread of scores, not only the average, because a model with a good mean Dice can still fail badly on some images.
You will learn to
- Distinguish classification, detection and segmentation
- Explain the encoder, decoder and skip connections of a U-Net
- Train a small U-Net in PyTorch and score it with the Dice coefficient
- Choose segmentation metrics and report them responsibly
Classification, detection and segmentation
Segmentation supports measurement: wound area, tumour volume, organ size, plaque burden. It is also a step in many larger systems, such as finding the retina's layers before grading disease.
| Task | Output | Clinical example |
|---|---|---|
| Classification | One label per image | Referable diabetic retinopathy: yes or no |
| Detection | Boxes around objects | Locating lung nodules on CT |
| Segmentation | A label for every pixel | Outlining a foot ulcer to measure its area over time |
The U-Net idea
Ronneberger and colleagues introduced U-Net in 2015 for biomedical images. nnU-Net, which configures U-Nets automatically for each dataset, became a strong baseline across many segmentation challenges.
- Encoder: convolution and pooling layers shrink the image, capturing context ('this is the edge of a lesion').
- Decoder: upsampling layers restore full resolution so every pixel gets a prediction.
- Skip connections: feature maps from the encoder are passed directly to the decoder at the same resolution, so fine boundaries are not lost.
A tiny U-Net in PyTorch
The synthetic 32 × 32 scans below contain one or two irregular 'lesions' with known pixel masks. The U-Net has one downsampling step and one skip connection, the smallest version of the idea.
import numpy as np
import torch
from torch import nn
def make_scans(n, seed, size=32):
"""Synthetic scans with one or two irregular 'lesions', and the true pixel mask for each."""
rng = np.random.default_rng(seed)
yy, xx = np.mgrid[:size, :size]
images, masks = rng.normal(0, 1, (n, size, size)), np.zeros((n, size, size))
for i in range(n):
for _ in range(rng.integers(1, 3)):
cy, cx = rng.integers(6, size - 6, 2)
ry, rx = rng.uniform(2, 5, 2)
masks[i] = np.maximum(masks[i], ((yy - cy) / ry) ** 2 + ((xx - cx) / rx) ** 2 < 1)
images[i] += 1.5 * masks[i]
to_t = lambda a: torch.tensor(a, dtype=torch.float32).unsqueeze(1)
return to_t(images), to_t(masks)
def block(c_in, c_out):
return nn.Sequential(nn.Conv2d(c_in, c_out, 3, padding=1), nn.ReLU(),
nn.Conv2d(c_out, c_out, 3, padding=1), nn.ReLU())
class TinyUNet(nn.Module):
"""Encoder-decoder with a skip connection: the U-Net idea at the smallest useful size."""
def __init__(self):
super().__init__()
self.enc1, self.enc2 = block(1, 8), block(8, 16)
self.pool, self.up = nn.MaxPool2d(2), nn.ConvTranspose2d(16, 8, 2, stride=2)
self.dec1 = block(16, 8) # 8 upsampled + 8 from the skip connection
self.out = nn.Conv2d(8, 1, 1)
def forward(self, x):
e1 = self.enc1(x) # full resolution: detail
e2 = self.enc2(self.pool(e1)) # half resolution: context
d1 = self.dec1(torch.cat([self.up(e2), e1], dim=1))
return self.out(d1) # one logit per pixel
def dice(pred_mask, true_mask, eps=1e-6):
inter = (pred_mask * true_mask).sum(dim=(1, 2, 3))
return ((2 * inter + eps) / (pred_mask.sum(dim=(1, 2, 3)) + true_mask.sum(dim=(1, 2, 3)) + eps))
X_train, M_train = make_scans(1500, seed=1)
X_test, M_test = make_scans(300, seed=2)
torch.manual_seed(0)
unet = TinyUNet()
opt = torch.optim.Adam(unet.parameters(), lr=2e-3)
for epoch in range(1, 9):
for idx in torch.randperm(len(X_train)).split(32):
opt.zero_grad()
nn.functional.binary_cross_entropy_with_logits(unet(X_train[idx]), M_train[idx]).backward()
opt.step()
if epoch in (1, 4, 8):
with torch.no_grad():
d = dice((unet(X_test) > 0).float(), M_test)
print(f"epoch {epoch}: mean Dice on test scans {d.mean():.3f}")
# epoch 1: mean Dice on test scans 0.000
# epoch 4: mean Dice on test scans 0.864
# epoch 8: mean Dice on test scans 0.891- After one epoch the Dice is 0: the network predicts 'no lesion' everywhere. Because lesion pixels are a small fraction of each image, that is a cheap way to lower the per-pixel loss early on. Losses based on Dice, or weighting lesion pixels more, help with this imbalance.
- By epoch 8 the mean Dice is 0.891.
About the numbers
- The code uses fixed random seeds, PyTorch 2.8 and a laptop CPU. Other versions or hardware can give slightly different numbers; the pattern of results is what matters.
- Images in this track are synthetic, so they run anywhere in seconds. The exercises point to MedMNIST, an openly licensed collection of small medical-image datasets, for real images.
Comparing with a baseline, and looking at the worst cases
with torch.no_grad():
d = dice((unet(X_test) > 0).float(), M_test)
threshold_only = dice((X_test > 0.75).float(), M_test) # naive: bright pixels = lesion
print(f"Simple brightness threshold: mean Dice {threshold_only.mean():.3f}")
print(f"U-Net: mean Dice {d.mean():.3f}; worst 5% of scans below {torch.quantile(d, 0.05):.3f}")
# Simple brightness threshold: mean Dice 0.270
# U-Net: mean Dice 0.891; worst 5% of scans below 0.769- A brightness threshold, which marks noise as lesion, scores 0.270; the U-Net uses shape and context to reach 0.891.
- One scan in twenty scores below 0.769. In real use those worst cases, often small or unusual lesions, are where errors matter most. Report the distribution of scores and inspect the failures.
Choosing and reporting segmentation metrics
The 'Metrics Reloaded' recommendations (Maier-Hein et al., 2024) guide the choice of metric by the clinical question. Also report agreement between human annotators: if experts disagree on boundaries, a model cannot be expected to agree with any one of them perfectly.
| Metric | Measures | Caution |
|---|---|---|
| Dice coefficient | Overlap between predicted and true regions (0 to 1) | Unstable for very small structures; one missed pixel matters a lot |
| Intersection over union (IoU) | Overlap divided by combined area | Closely related to Dice; do not report both as independent evidence |
| Hausdorff distance (95th percentile) | How far apart the boundaries are | Sensitive to outliers; the 95th percentile version is more robust |
| Volume or area error | Difference in measured size | Directly relevant when the output is used for measurement |
| Per-lesion detection | Whether each lesion was found at all | Mean Dice can hide missed small lesions |
Exercise
Train and evaluate a U-Net, improve it for small lesions, and report results responsibly.
Data: Synthetic scans and masks in the notebook; optional open segmentation dataset under its licence (Generated in the notebook)
- Reproduce the tiny U-Net and plot predicted masks for the five worst-scoring test scans.
- Replace the loss with a combination of cross-entropy and Dice loss and compare early epochs and final Dice.
- Make some lesions much smaller and report per-lesion detection rate alongside mean Dice.
- Add a second downsampling level and compare results and training time.
- Write a results paragraph that reports the mean, the spread and the failure cases.
Open the notebook in ColabView on GitHubWorked solution coming soon
Key takeaways
- Segmentation labels every pixel, enabling measurement of lesions and organs.
- U-Net combines an encoder, a decoder and skip connections to keep fine detail.
- Dice measures overlap; small lesions and imbalance need special care.
- Report score distributions and failure cases, not only the mean.
Terms used: Convolutional neural network (CNN), Dice coefficient, Image segmentation
Related: CNNs for chest X-ray and retinal images, Diabetic foot ulcers, Applied case studies: foot-ulcer image classification
FAQ
What is image segmentation?
Assigning a label to every pixel of an image, for example marking which pixels belong to a lesion, so its outline, area or volume can be measured.
What is a U-Net?
A neural network for segmentation with an encoder that captures context, a decoder that restores resolution, and skip connections that pass fine detail from encoder to decoder.
What is a good Dice score?
It depends on the structure and task. Large organs often reach above 0.9, while small or poorly defined lesions score lower. Compare with agreement between human experts on the same images.
Why did the model predict nothing after the first epoch?
Lesion pixels are a small fraction of each image, so predicting background everywhere quickly reduces the loss. More training, Dice-based losses or weighting lesion pixels overcomes this.
Can segmentation measure diabetic foot ulcers?
Research systems segment wounds in photographs to track area over time. Lighting, camera angle and skin tone affect accuracy, so such tools need validation in the populations where they will be used.
Sources
- Ronneberger O, Fischer P, Brox T. U-Net: convolutional networks for biomedical image segmentation. MICCAI 2015. arXiv:1505.04597
- Isensee F, et al. nnU-Net: a self-configuring method for deep learning-based biomedical image segmentation. Nature Methods 2021;18:203–211. doi:10.1038/s41592-020-01008-z
- Maier-Hein L, et al. Metrics reloaded: recommendations for image analysis validation. Nature Methods 2024;21:195–212. doi:10.1038/s41592-023-02151-z
- Dice LR. Measures of the amount of ecologic association between species. Ecology 1945;26(3):297–302. doi:10.2307/1932409
Educational content only. This material teaches methods; it is not clinical advice. Code, models and examples here are not validated medical devices and must not be used to make decisions about real patients without proper clinical validation, governance and any required regulatory approval. Examples use synthetic or openly licensed de-identified data only. Never paste identifiable patient information into public AI tools. Mentions of models, tools or vendors are for identification, not endorsement.