PATCHGAN PARADOX/ brats-2020
Dataset BraTS 2020 · 369 volumes Ablation Models A–D Runs 5 × 35 epochs Compute 12.2 GPU-h Author Dhruv Gupta · Bennett University
Models

Nine trained artefacts, and how to run them

Every weight file the study produced, with its provenance, its measured performance and the code needed to load it and segment a scan. All models share one architecture — the 7.76 M-parameter 2D U-Net — so a single loader works for all of them.

Format Keras 3 .keras and HDF5 Input 128×128×2 · T1ce + FLAIR Output 128×128×4 softmax
Start here

Which one should you use?

Best measured result
cgan_unet_braTS
_generator.keras
Model D — the 4×4, stride-2 generator. 25.96 dB / 0.9583, the best structural score in the study.
Most thoroughly evaluated
my_model.keras
Model A. The only run with a full per-class Dice, precision, sensitivity and specificity report on a held-out test set.
Latest pipeline
final_champion_model
.keras
Model D + CLAHE. Better segmentation loss and readability, lower PSNR — the perception–distortion trade-off, and the authors’ pick for clinical use.

Two champions, and why. On the measured structural metrics the winner is the plain 4×4 generator (Model D). final_champion_model.keras is Model D with CLAHE: it improves segmentation loss and clinical readability while PSNR penalises the deliberate contrast shift — the perception–distortion trade-off. File names are left exactly as the notebooks wrote them.

Catalogue

Every artefact

All files live in the source repositories; click a name to download from GitHub.
FileProduced byContentsSizePSNR / SSIM
cgan_unet_braTS_generator.keras Model D full model, inference-ready29.7 MB25.96 / 0.9583
cgan_unet_braTS_generator.h5 Model D same model, legacy HDF529.7 MB25.96 / 0.9583
cgan_unet_braTS.weights.h5 Model D weights only — rebuild the graph first29.7 MB25.96 / 0.9583
final_champion_model.keras Model D + CLAHE full model, inference-ready29.7 MB24.76 / 0.9582
cgan_unet_braTS_3x3cGAN_generator.keras Model B full model, inference-ready29.7 MB24.64 / 0.9510
cgan_unet_braTS_3x3cGAN.weights.h5 Model B weights only29.7 MB24.64 / 0.9510
cgan_unet_braTS_3x3_IIcGAN_generator.keras Model C full model, inference-ready29.7 MB21.62 / 0.9341
cgan_unet_braTS_3x3_IIcGAN.weights.h5 Model C weights only29.7 MB21.62 / 0.9341
my_model.keras Model A full model + optimiser state89.0 MB25.38 / 0.9577

Nine files, four distinct sets of learned weights — each run was saved in more than one format. The baseline is 3× larger than the rest because model.save() was called on a compiled model, storing Adam's two momentum slots per variable alongside the weights. Loading with compile=False discards them.

Running inference

All four generators share one architecture, so the same three steps apply to every file: load with compile=False, reproduce the exact preprocessing the model was trained on, and take an argmax over the four output channels.

1 · Load

Custom metrics are not needed at inference time, so skip compilation entirely — this also sidesteps the custom-object registration that the training notebooks require.

import tensorflow as tf

model = tf.keras.models.load_model(
    "cgan_unet_braTS_generator.keras",
    compile=False,          # skips the custom dice_coef / precision / sensitivity metrics
)
model.summary()             # 128x128x2 in, 128x128x4 out, 7,759,908 params

2 · Preprocess a patient volume

This must match training exactly. The channel order (FLAIR first, T1ce second), the slice window and the normalisation are all load-bearing — swapping the two channels alone will produce confident nonsense.

import numpy as np, nibabel as nib, cv2

IMG_SIZE, VOLUME_SLICES, VOLUME_START_AT = 128, 100, 22

def preprocess(case_dir, case_id):
    flair = nib.load(f"{case_dir}/{case_id}_flair.nii").get_fdata()
    t1ce  = nib.load(f"{case_dir}/{case_id}_t1ce.nii").get_fdata()

    X = np.zeros((VOLUME_SLICES, IMG_SIZE, IMG_SIZE, 2), dtype=np.float32)
    for j in range(VOLUME_SLICES):
        k = j + VOLUME_START_AT
        X[j, :, :, 0] = cv2.resize(flair[:, :, k], (IMG_SIZE, IMG_SIZE))   # channel 0 = FLAIR
        X[j, :, :, 1] = cv2.resize(t1ce[:, :, k],  (IMG_SIZE, IMG_SIZE))   # channel 1 = T1ce

    m = X.max()
    return X / m if m > 0 else X

3 · Predict and decode

SEGMENT_CLASSES = {
    0: "background",
    1: "necrotic / non-enhancing core",
    2: "peritumoral edema",
    3: "GD-enhancing tumour",       # label 4 in the raw BraTS files
}

X = preprocess("BraTS20_Training_001", "BraTS20_Training_001")
probs = model.predict(X, verbose=0)        # (100, 128, 128, 4) softmax
labels = np.argmax(probs, axis=-1)         # (100, 128, 128) int class map

for c, name in SEGMENT_CLASSES.items():
    print(f"{name:32s} {(labels == c).sum():>8,} voxels")

Loading a weights-only file

The .weights.h5 files contain no graph, so the architecture has to be rebuilt first — use build_unet exactly as defined in the notebooks, then load into it.

from tensorflow.keras.layers import Input

model = build_unet(Input((128, 128, 2)), "he_normal", 0.2)   # from any notebook
model.load_weights("cgan_unet_braTS.weights.h5")

Prerequisites and limits. These models expect skull-stripped, co-registered BraTS-style volumes. They were never trained on raw clinical DICOM, on other scanners' intensity distributions, or on paediatric or post-operative cases. Behaviour outside that distribution is undefined. This is a research artefact, not a medical device, and must not inform any clinical decision.

Environment

Python
3.10
Core
tensorflow (Keras 3), numpy
I/O
nibabel for NIfTI, opencv-python for resizing
Optional
scikit-image, scikit-learn, matplotlib for the notebook visualisations
Hardware
Inference runs on CPU in a few seconds per volume; training needs a GPU

See the architecture these weights fill → · Read the notebooks that produced them → · How they reach a radiologist →