Images & Labels#

Navigation: ← Previous: Project Overview | Next: Classical Models →

BreastMNIST 128×128 ultrasound: official splits, class balance, and what malignant vs benign looks like at this resolution.

Disclaimer. Educational benchmark only — not a diagnostic tool. Images are centre-cropped/resized BUSI scans at 128×128, far coarser than a clinical workstation.

The task#

Each image is a breast ultrasound patch labelled malignant or benign/normal. MedMNIST stores malignant as class 0; we remap so that 1 = malignant (the positive class for precision and recall). Prevalence is about 27% in every official split, so a classifier that never predicts cancer still records ~73% accuracy.

That is why later chapters lead with recall, precision, balanced accuracy, ROC-AUC and PR-AUC rather than accuracy alone.

import sys
from pathlib import Path
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings("ignore")
plt.style.use("seaborn-v0_8-whitegrid")

PROJ_DIR = Path(".").resolve()
if not (PROJ_DIR / "cancer_cv_utils.py").exists():
    PROJ_DIR = Path("projects/cancer-imaging").resolve()
if str(PROJ_DIR) not in sys.path:
    sys.path.insert(0, str(PROJ_DIR))

from cancer_cv_utils import (
    CLASS_NAMES,
    METRIC_ORDER,
    artifacts_dir,
    classification_metrics,
    display_plotly,
    extract_cv_features,
    figures_dir,
    grouped_importance,
    load_metrics,
    load_predictions,
    load_splits,
    metrics_frame,
    overlay_heatmap,
    tune_threshold,
)

SPLITS = load_splits()
FIG = figures_dir()
ART = artifacts_dir()
print("Splits:", {k: v["labels"].shape[0] for k, v in SPLITS.items()})
print("Malignant rates:", {k: f"{v['labels'].mean():.1%}" for k, v in SPLITS.items()})
Splits: {'train': 546, 'val': 78, 'test': 156}
Malignant rates: {'train': '26.9%', 'val': '26.9%', 'test': '26.9%'}

Split sizes and class balance#

rows = []
for name, split in SPLITS.items():
    y = split['labels']
    rows.append({
        'split': name,
        'n': int(len(y)),
        'malignant': int(y.sum()),
        'benign / normal': int((y == 0).sum()),
        'prevalence': float(y.mean()),
    })
pd.DataFrame(rows).set_index('split')
n malignant benign / normal prevalence
split
train 546 147 399 0.269231
val 78 21 57 0.269231
test 156 42 114 0.269231
fig, ax = plt.subplots(figsize=(6.2, 3.6))
names = list(SPLITS)
mal = [SPLITS[k]['labels'].mean() for k in names]
ben = [1 - m for m in mal]
ax.bar(names, ben, label='benign / normal', color='#1a6b7a')
ax.bar(names, mal, bottom=ben, label='malignant', color='#c0392b')
ax.set_ylabel('Share of images')
ax.set_ylim(0, 1)
ax.set_title('Class balance is the same in train, val, and test')
ax.legend(frameon=False)
plt.tight_layout()
plt.show()
../../_images/f05613aac1de6b3c16500dc2e4a8e09be980007f547a5851bb635067ddbcffd4.png

What the two classes look like#

Malignant lesions on ultrasound often appear as darker (hypoechoic) regions with irregular margins and posterior shadowing. Benign cysts and fibroadenomas tend to be more oval and sharply bounded. At 128×128 those cues are still visible, but measurement crosshairs from the original scans remain in some frames — a shortcut a model could cheat with.

from IPython.display import Image, display
display(Image(str(FIG / '01_class_montage.png')))
../../_images/08564bb124c9d3f080c29d5ee5f3372edd6a043f213d5931520ea2a4599043c5.png

Mean images#

Averaging every training example of each class highlights where they differ on average.

display(Image(str(FIG / '01_mean_images.png')))
../../_images/6e96fb49f0e2bcb1d1c394baefc558ee87bc2f3fda44a13a122a28b33aa59163.png

Intensity distributions#

If malignant patches are systematically darker, a one-pixel statistic already carries signal — which is why the later random forest includes intensity summaries alongside HOG and LBP.

fig, ax = plt.subplots(figsize=(7.2, 3.8))
for cls, colour, label in [(0, '#1a6b7a', 'benign / normal'), (1, '#c0392b', 'malignant')]:
    pix = SPLITS['train']['images'][SPLITS['train']['labels'] == cls].ravel()
    ax.hist(pix, bins=40, density=True, alpha=0.55, color=colour, label=label)
ax.set_xlabel('Pixel intensity (0–1)')
ax.set_ylabel('Density')
ax.set_title('Training-set intensity histograms')
ax.legend(frameon=False)
plt.tight_layout()
plt.show()
../../_images/2c4e41194af9ed29c2dadbdadcdccaf7bf114fb08169729f1726f6a90e8c8627.png

Attribution#

Yang et al., MedMNIST v2, Scientific Data (2023). Source images: Al-Dhabyani et al., Dataset of breast ultrasound images, Data in Brief (2020). See data/README.md.


Navigation: ← Previous: Project Overview | Next: Classical Models →