Skip to content
Rupak Dey

Navigate

Work

News

Links

Theme

Projects

Engineering

Brain Tumor MRI Classification

Role
The Vision Transformer, the evaluation harness, and the GAN augmentation
Period
Feb 2026 – May 2026
Stack
  • Python
  • PyTorch
  • ResNet-50
  • ViT-B/16
  • Scikit-learn
  • Streamlit

Context#

Four-way classification of 2D brain MRI slices into glioma, meningioma, pituitary and no tumor, plus a binary tumor against no-tumor variant. The data is Masoud Nickparvar's Brain Tumor MRI Dataset on Kaggle, itself a merge of three upstream collections, and it mixes acquisition planes and contrast types freely inside each class.

Four brain scans side by side, labelled glioma, meningioma, notumor and pituitary. The glioma is an axial slice with a small bright ring near the centre and dark ventricles. The meningioma is a coronal slice with a rounded bright mass low on the left near the skull base. The notumor slice is symmetric with clear grey and white matter and no lesion. The pituitary is a coronal slice with a small bright mass at the centre of the skull base, eye sockets and sinuses visible.
One image per class, each chosen as the closest to its class mean rather than the most dramatic example. Two are axial and two coronal: the dataset mixes planes freely inside a single class.

What we built#

Five model families, chosen to span the space rather than to win: a Random Forest over HOG and LBP features, a custom CNN with squeeze-and-excitation blocks at 6.6M parameters, a fine-tuned ResNet-50 at 24M, a fine-tuned ViT-B/16 at 86M, and a class-conditional GAN generating extra training data. Eleven runs, about a hundred minutes total on one H100.

Four of us built it over a semester. I worked on the Vision Transformer, the evaluation harness, the GAN augmentation pipeline and the Streamlit inference app. Mario Saenz wrote the preprocessing module and contributed more commits than I did.

The best 4-class result was 0.9575 accuracy, from the GAN-augmented ResNet-50.

Duplicates across the split#

Hashing the JPEG files finds no duplicates anywhere in the dataset. Hashing the decoded pixel arrays finds 114 test images, 7.125% of the split, that are byte-identical to an image in training. The containers differ; the pixels do not.

Two identical coronal brain scans side by side. Each shows a head in cross section with a large bright irregular mass in the upper left of the brain, about a third the width of the skull, with a darker cavity at its lower edge. The filename beneath the left panel reads Tr-me_1289.jpg and beneath the right, Te-me_285.jpg.
One of the 114. The same picture in training on the left and test on the right, down to the last pixel: both decode to md5 f273fd56f2f765fa32bf7c4161784e47, while their JPEG files do not match.

At a cosine threshold of 0.99, 455 test images (28.4%) have a near-duplicate in training. The same query against training images of a different class returns 2 hits out of 1,600, so the threshold separates real duplicates from generic head-shape similarity rather than flagging everything.

Recomputed over the 1,145 test images that have no near-duplicate:

                 reported   clean subset    delta
resnet50_aug      0.9575       0.9424      -0.0151
resnet50          0.9487       0.9319      -0.0168
vit               0.9487       0.9310      -0.0177
custom_cnn        0.9450       0.9275      -0.0175
baseline_rf       0.8806       0.8358      -0.0448

That column is indicative rather than a drop-in replacement, since removing duplicates guts the per-class support unevenly: no-tumor falls from 400 test images to 58. The same ordering shows up a step earlier, at 0.9971 train accuracy, 0.9902 validation and 0.9575 test, because training and validation are split file-wise out of one directory and share its duplication while test comes from a separate one.

The source is traceable. This copy of the dataset had been rebalanced by hand to exactly 1400 training and 400 test images per class against a canonical 7,023 total, with no script anywhere that does it. Images were copied until the classes came out even, and the copies crossed the split.

The GAN collapsed#

Sampling eight near-orthogonal latent vectors per class from the trained generator produces outputs with 0.9999 mean pairwise cosine similarity. The generator ignores its input and emits one fixed image per class.

Two rows of eight greyscale panels. The top row is eight outputs from the trained generator: soft blurred grey ovals on dark backgrounds, faintly suggesting a skull outline, with no internal anatomy and no visible lesion, and all eight are indistinguishable from one another. The bottom row is eight real pituitary training scans, sharply detailed and all different: three axial views through the skull base showing eye sockets and sinuses, three sagittal mid-line views showing the corpus callosum and brain stem in profile, and two coronal views, several with a bright lesion at the pituitary fossa.
Top row: eight draws from the generator, from latent vectors with mean pairwise similarity 0.0158. Their outputs sit at 0.9999. Bottom row: eight real images of the same class, at 0.6774. Identical greyscale mapping on both rows.

Run with --gen-per-class 500, it therefore added four distinct images duplicated 500 times each, not 2,000 synthetic samples. The augmented run finished 0.88 points above the plain ResNet-50, but that margin cannot be attributed to the synthetic data: each configuration was trained exactly once, and the augmented run is the only one of the four whose best validation loss lands on its final epoch, so it stopped at the 20-epoch cap while still improving where the plain run stopped on patience.

A two-column inference app. The left sidebar holds a classification mode selector set to binary, checkboxes for Grad-CAM heatmaps and test-time augmentation, the binary and multiclass label sets, and a list of eleven loaded checkpoints. The main panel shows an uploaded input image, a blurred grey oval, beside five model cards: Custom CNN says tumor at 72.1%, ResNet-50 says no_tumor at 94.2%, ViT-B/16 says no_tumor at 68.4%, ResNet-50 plus GAN says no_tumor at 92.3%, and Random Forest says tumor at 63.0%. Three cards carry a blue-to-red Grad-CAM heatmap beneath them.
The inference app, fed one of the generator's own outputs. The five models split three to two on an image with no anatomy in it, at confidences from 63% to 94%. The Grad-CAM overlays are illustrative only: the dataset ships no segmentation masks, so there is no ground truth to score attention against.

Glioma sets the ceiling#

Per-class recall on the 4-class problem, test split:

                 glioma  mening  notumor  pituit
resnet50_aug     0.8375  0.9975   1.0000  0.9950
resnet50         0.8150  0.9925   0.9925  0.9950
vit              0.8125  0.9875   1.0000  0.9950
custom_cnn       0.8075  0.9725   1.0000  1.0000
baseline_rf      0.6675  0.8800   1.0000  0.9750

Every model is worst on glioma and at or near perfect on no-tumor. Across the five families the spread is 0.1700 on glioma and 0.0075 on no-tumor. ViT-B/16 carries thirteen times the custom CNN's parameters and buys 0.005 of glioma recall with them, and a Random Forest doing no representation learning at all fails in the same direction as everything above it.

Twelve brain scans in two rows of six, all gliomas the best model classified as having no tumor. Every panel carries a large obvious lesion. They vary widely in appearance: bright grainy scans, dark scans with bright tissue at the skull base, pale flat-background scans, one with a thick solid white skull ring around a smooth grey brain and a large dark cavity, several bright images with pale masses, and one coronal view with a mass in the upper left frontal region.
Twelve of the 21 gliomas the best model called no-tumor. The lesions are not subtle; the images are not consistent. The fourth panel on the top row is the clearest case.

The failures are not the subtle tumors. Nearly every one carries a large, unmistakable lesion. What they share is that they do not look like the training distribution as images: set against the 335 gliomas the same model got right, they are roughly twice as bright, carry five times the bright-area fraction, and hold 57.8 times the fraction of near-saturated white pixels.

At least three have the thick bright skull ring of a CT rather than an MRI, though the dataset ships no DICOM headers and states no modality, so that reads the pixels rather than establishing a fact. Most of the rest are bright-lesion-on-dark images in a collection whose dominant appearance is the reverse. The model fails on unfamiliar image types rather than on hard tumors, and no-tumor is what it returns when an image resembles nothing in training.

Those 21 errors are also only 19 distinct images. Two pairs among them are pixel-identical to each other, inside the test set.

What this shows#

Every number here comes from a single run. The seed is set but cuDNN determinism is not, and no configuration was trained twice, so differences of a few tenths of a point are not separable from run-to-run variance.

The binary results need their floor quoted alongside them. That split is 1,200 tumor against 400 no-tumor, so always answering tumor scores 0.7500, and the best binary accuracy of 0.9831 is a gain of 13.31 points over that. The Random Forest baseline is also not like-for-like: it was selected on test accuracy and trained on 5,600 images where the deep models trained on 4,480.

This is coursework on 2D slices from a public dataset, never evaluated against radiologist reads or on clinical data. It is not a diagnostic tool.