Keras

YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

🧠 Brain Tumor Segmentation — BRISC2025

Binary segmentation of brain tumors from MRI scans using DeepLabV3+ (MobileNetV2) and EfficientNet-B1 U-Net with two-stage transfer learning.

TensorFlow Python License Dataset


📋 Table of Contents


Overview

This project tackles binary brain tumor segmentation on MRI scans from the BRISC2025 challenge. Given an MRI image, the model outputs a binary mask where:

  • 0 = background (healthy tissue)
  • 1 = tumor region

Two architectures are implemented and compared:

  1. DeepLabV3+ with MobileNetV2 backbone — lightweight and fast
  2. EfficientNet-B1 U-Net with ASPP bridge — higher accuracy

Both models are trained using a two-stage transfer learning strategy: frozen backbone warm-up followed by full fine-tuning.


Dataset

BRISC2025 — Brain Image Segmentation Challenge 2025

Split Images Masks
Train 3,933 3,933
Test 860 860

File naming convention:

images/brisc2025_train_00001_gl_ax_t1.jpg
masks/ brisc2025_train_00001_gl_ax_t1.png   ← same stem, different extension

Directory structure:

brisc2025/
├── classification_task/
│   ├── train/   {glioma/, meningioma/, no_tumor/, pituitary/}
│   └── test/    {glioma/, meningioma/, no_tumor/, pituitary/}
└── segmentation_task/
    ├── train/
    │   ├── images/   ← MRI scans (.jpg)
    │   └── masks/    ← binary tumor masks (.png)
    └── test/
        ├── images/
        └── masks/

Key characteristics:

  • Modality: MRI (axial T1-weighted)
  • Classes: Background (0) / Tumor (1) — binary segmentation
  • Class imbalance: ~95% background pixels → requires Tversky Loss
  • Input resolution: resized to 128×128 for training

⬇️ Download the dataset from the BRISC2025 challenge page. ⬇️ Download the tfrecord file


Models

1. DeepLabV3+ — MobileNetV2 Backbone

Input (128×128×3)
    └── MobileNetV2 (ImageNet pretrained)
        ├── Low-level features  [block_2_add]        → 128×128
        └── High-level features [block_13_expand_relu] → 32×32
                └── ASPP (rates: 1×1, 6, 12, 18 + Global Avg Pool)
                        └── Decoder (skip connection + upsampling)
                                └── Output: softmax (128×128×2)

2. EfficientNet-B1 U-Net

Input (128×128×3)
    └── EfficientNetB1 (ImageNet pretrained)
        ├── s1: block2a_expand_activation
        ├── s2: block3a_expand_activation
        ├── s3: block4a_expand_activation
        ├── s4: block6a_expand_activation
        └── s5: top_activation
                └── ASPP Bridge
                        └── Decoder (4× skip connections)
                                └── Output: softmax (128×128×2)

Training Strategy — 2 Stages

Stage Epochs Backbone Learning Rate
Stage 1 — Warm-up 20 Frozen Cosine Decay (1e-4 → 1e-6)
Stage 2 — Fine-tune 80 Trainable Cosine Decay Restarts

Loss function — Tversky Loss (optimized for class imbalance):

Tversky = TP / (TP + α·FN + β·FP)    α=0.7, β=0.3

Higher α penalizes false negatives (missed tumors) more than false positives.


Results

Results shown for EfficientNet-B1 U-Net (best performing model):

Training Curves (Stage 2 — Fine-tuning)

history_stage2_eff_net

Evaluation Curves

evaluation_curves

Metrics on Test Set

Group 1 — Pixel-level Metrics

Metric Value
Accuracy 0.9633
Precision 0.9910
Recall (Sensitivity) 0.9660
Specificity 0.9465
F1-Score 0.9784

Group 2 — Segmentation Metrics

Metric Value
Dice Coefficient 0.9783 ± 0.0211
IoU / Jaccard 0.9582 ± 0.0359
Mean IoU 0.9582
Hausdorff Distance (HD) 5.10 px
HD95 0.15 px

Group 3 — Medical Metrics

Metric Value
Volumetric Similarity (VS) 0.9834
Avg Surface Distance (ASD) 0.05 px
Sensitivity @ Specificity ≥ 0.95 0.9639

Group 4 — AUC Metrics

Metric Value
AUC-ROC 0.9681
AUC-PR 0.9914

Project Structure

brain-tumor-segmentation/
├── train.py                        # Main training script
├── brain_tumor_segmentation.ipynb  # Full pipeline notebook
├── load_data.ipynb                 # Data loading & exploration
├── model/
│   ├── deeplab_mobilenet.h5        # Trained DeepLabV3+ weights
│   └── eff_net.h5                  # Trained EfficientNet-UNet weights
├── tfrecord/
│   ├── train_*.tfrecord            # Training data (GZIP compressed)
│   ├── val_*.tfrecord              # Validation data
│   └── test_*.tfrecord             # Test data
├── assets/
│   ├── history_stage2_eff_net.png
│   └── evaluation_curves.png
└── README.md

Installation

# Clone repository
git clone https://github.com/your-username/brain-tumor-segmentation.git
cd brain-tumor-segmentation

# Create conda environment
conda create -n seg python=3.11 -y
conda activate seg

# Install dependencies
pip install tensorflow==2.x
pip install scikit-learn scipy matplotlib opencv-python
pip install numpy pandas

GPU setup (optional but recommended):

pip install nvidia-cuda-nvcc

Usage

Quick Inference — Load model and predict

import tensorflow as tf
import numpy as np
from PIL import Image

# Load model (no need to declare custom objects)
model = tf.keras.models.load_model('./model/eff_net.h5', compile=False)

# Prepare input image
img = Image.open('path/to/mri.jpg').resize((128, 128))
img_array = np.array(img, dtype=np.float32) / 255.0
img_array = np.expand_dims(img_array, axis=0)  # (1, 128, 128, 3)

# Predict
pred = model.predict(img_array)           # (1, 128, 128, 2)
mask = np.argmax(pred[0], axis=-1)        # (128, 128) — 0 or 1

print(f'Tumor pixels: {mask.sum()} / {mask.size}')

Batch Inference on Test Set

import tensorflow as tf
import numpy as np
from pathlib import Path

def load_image(path, size=(128, 128)):
    raw   = tf.io.read_file(path)
    image = tf.image.decode_jpeg(raw, channels=3)
    image = tf.image.resize(image, size)
    return tf.cast(image, tf.float32) / 255.0

# Load model
model = tf.keras.models.load_model('./model/eff_net.h5', compile=False)

# Get test image paths
test_imgs = sorted(Path('./segmentation_task/test/images').glob('*.jpg'))

predictions = []
for img_path in test_imgs:
    img   = load_image(str(img_path))
    img   = tf.expand_dims(img, 0)
    pred  = model.predict(img, verbose=0)
    mask  = np.argmax(pred[0], axis=-1)
    predictions.append(mask)

print(f'Predicted {len(predictions)} masks')

Visualize Predictions

import matplotlib.pyplot as plt

def visualize(image_path, mask_path, model, size=(128, 128)):
    # Load
    img  = load_image(image_path, size).numpy()
    true = tf.image.decode_png(tf.io.read_file(mask_path), channels=1)
    true = tf.image.resize(true, size, method='nearest').numpy().squeeze()

    # Predict
    pred_prob = model.predict(np.expand_dims(img, 0), verbose=0)
    pred_mask = np.argmax(pred_prob[0], axis=-1)

    # Overlay
    overlay = img.copy()
    overlay[pred_mask == 1] = [1.0, 0.2, 0.2]   # red = prediction
    overlay[true > 0]       = [0.2, 1.0, 0.2]   # green = ground truth
    both = (pred_mask == 1) & (true > 0)
    overlay[both]           = [1.0, 1.0, 0.0]   # yellow = overlap

    fig, axes = plt.subplots(1, 4, figsize=(16, 4))
    titles = ['MRI Input', 'Ground Truth', 'Prediction', 'Overlay']
    imgs   = [img, true, pred_mask, overlay]
    cmaps  = [None, 'gray', 'gray', None]

    for ax, title, im, cmap in zip(axes, titles, imgs, cmaps):
        ax.imshow(im, cmap=cmap)
        ax.set_title(title); ax.axis('off')

    plt.tight_layout()
    plt.show()

# Example
model = tf.keras.models.load_model('./model/eff_net.h5', compile=False)
visualize(
    './segmentation_task/test/images/brisc2025_train_00001_gl_ax_t1.jpg',
    './segmentation_task/test/masks/brisc2025_train_00001_gl_ax_t1.png',
    model
)

Training

Step 1 — Prepare TFRecord files

The training pipeline uses TFRecord format (GZIP compressed) for efficient data loading:

import tensorflow as tf
from pathlib import Path

def create_tfrecord(image_paths, mask_paths, output_path):
    writer_options = tf.io.TFRecordOptions(compression_type='GZIP')
    with tf.io.TFRecordWriter(output_path, options=writer_options) as writer:
        for img_path, mask_path in zip(image_paths, mask_paths):
            img_bytes  = tf.io.read_file(img_path).numpy()
            mask_bytes = tf.io.read_file(mask_path).numpy()

            feature = {
                'image': tf.train.Feature(bytes_list=tf.train.BytesList(value=[img_bytes])),
                'mask' : tf.train.Feature(bytes_list=tf.train.BytesList(value=[mask_bytes])),
            }
            example = tf.train.Example(features=tf.train.Features(feature=feature))
            writer.write(example.SerializeToString())

Step 2 — Run training

python train.py

Training configuration:

BATCH_SIZE     = 8
EPOCHS_STAGE_1 = 20    # Frozen backbone warm-up
EPOCHS_STAGE_2 = 80    # Full fine-tuning
TARGET_SIZE    = (128, 128)

Optimizer — Adam with Cosine Decay Restarts:

lr_schedule = tf.keras.optimizers.schedules.CosineDecayRestarts(
    initial_learning_rate=1e-4,
    first_decay_steps=1000,
    t_mul=2.0,
    m_mul=0.9,
    alpha=1e-6,
)
optimizer = tf.keras.optimizers.Adam(
    learning_rate=lr_schedule,
    clipnorm=1.0,
)

Augmentation strategy:

  • 50% random crop (50–100% of original size)
  • 50% focused crop (centered on rare/tumor region)
  • Random horizontal flip
  • Random brightness (±15%), contrast (±15%), saturation (±15%)

Evaluation

Run full evaluation with all metric groups:

import numpy as np
from sklearn.metrics import confusion_matrix, roc_auc_score, average_precision_score

model = tf.keras.models.load_model('./model/eff_net.h5', compile=False)

# Collect predictions
y_true_list, y_pred_list = [], []
for images, masks in test_ds:
    preds = model.predict(images, verbose=0)
    preds_bin = np.argmax(preds, axis=-1, keepdims=True)
    y_true_list.append(masks.numpy())
    y_pred_list.append(preds_bin)

y_true = np.concatenate(y_true_list).flatten()
y_pred = np.concatenate(y_pred_list).flatten()

# Metrics
tn, fp, fn, tp = confusion_matrix(y_true, y_pred).ravel()
dice = (2*tp) / (2*tp + fp + fn + 1e-6)
iou  = tp / (tp + fp + fn + 1e-6)

print(f'Dice : {dice:.4f}')
print(f'IoU  : {iou:.4f}')

Requirements

tensorflow>=2.13
numpy
scikit-learn
scipy
matplotlib
opencv-python
Pillow

Citation

If you use this code or models in your research, please cite:

@misc{brisc2025-segmentation,
  title   = {Brain Tumor Segmentation on BRISC2025 using DeepLabV3+ and EfficientNet-UNet},
  author  = {Your Name},
  year    = {2025},
  url     = {https://github.com/your-username/brain-tumor-segmentation}
}

Dataset:

@dataset{brisc2025,
  title  = {BRISC2025: Brain Image Segmentation Challenge 2025},
  year   = {2025},
  url    = {https://brisc2025.grand-challenge.org}
}

License

This project is licensed under the MIT License — see LICENSE for details.


Acknowledgements

Downloads last month
17
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 1 Ask for provider support

Papers for ViettNguyen21/brain_msi_segmentation_effunet