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.
Which one should you use?
_generator.keras
.keras
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.
Every artefact
| File | Produced by | Contents | Size | PSNR / SSIM |
|---|---|---|---|---|
cgan_unet_braTS_generator.keras |
Model D | full model, inference-ready | 29.7 MB | 25.96 / 0.9583 |
cgan_unet_braTS_generator.h5 |
Model D | same model, legacy HDF5 | 29.7 MB | 25.96 / 0.9583 |
cgan_unet_braTS.weights.h5 |
Model D | weights only — rebuild the graph first | 29.7 MB | 25.96 / 0.9583 |
final_champion_model.keras |
Model D + CLAHE | full model, inference-ready | 29.7 MB | 24.76 / 0.9582 |
cgan_unet_braTS_3x3cGAN_generator.keras |
Model B | full model, inference-ready | 29.7 MB | 24.64 / 0.9510 |
cgan_unet_braTS_3x3cGAN.weights.h5 |
Model B | weights only | 29.7 MB | 24.64 / 0.9510 |
cgan_unet_braTS_3x3_IIcGAN_generator.keras |
Model C | full model, inference-ready | 29.7 MB | 21.62 / 0.9341 |
cgan_unet_braTS_3x3_IIcGAN.weights.h5 |
Model C | weights only | 29.7 MB | 21.62 / 0.9341 |
my_model.keras |
Model A | full model + optimiser state | 89.0 MB | 25.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
nibabelfor NIfTI,opencv-pythonfor resizing- Optional
scikit-image,scikit-learn,matplotlibfor 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 →