Skip to main content

Architecture Overview

The DenseNet U-Net model combines DenseNet121 as the encoder with a U-Net decoder, leveraging dense connectivity for efficient feature reuse and gradient flow.

Key Features

  • Encoder: DenseNet121 with dense blocks
  • Dense blocks: [6, 12, 24, 16] layers per block
  • Growth rate: 32 filters per dense layer
  • Decoder: U-Net-style upsampling with skip connections
  • Output: 2-class softmax segmentation

Dense Block Architecture

Dense blocks implement dense connectivity where each layer receives feature maps from all preceding layers:
models/densenet.py (50-88)
def dense_conv_block(x, growth_rate, name):
    """A building block for a dense block.
    # Arguments
        x: input tensor.
        growth_rate: float, growth rate at dense layers.
        name: string, block label.
    # Returns
        Output tensor for the block.
    """
    bn_axis = 3
    x1 = BatchNormalization(axis=bn_axis,
                                   epsilon=1.001e-5,
                                   name=name + '_0_bn')(x)
    x1 = Activation('relu', name=name + '_0_relu')(x1)
    x1 = Conv2D(4 * growth_rate, 1,
                       use_bias=False,
                       name=name + '_1_conv')(x1)
    x1 = BatchNormalization(axis=bn_axis, epsilon=1.001e-5,
                                   name=name + '_1_bn')(x1)
    x1 = Activation('relu', name=name + '_1_relu')(x1)
    x1 = Conv2D(growth_rate, 3,
                       padding='same',
                       use_bias=False,
                       name=name + '_2_conv')(x1)
    x = Concatenate(axis=bn_axis, name=name + '_concat')([x, x1])
    return x

def dense_block(x, blocks, name):
    """A dense block.
    # Arguments
        x: input tensor.
        blocks: integer, the number of building blocks.
        name: string, block label.
    # Returns
        output tensor for the block.
    """
    for i in range(blocks):
        x = dense_conv_block(x, 32, name=name + '_block' + str(i + 1))
    return x

Dense Connectivity Pattern

Each dense layer:
  1. 1×1 Conv: Reduces channels (bottleneck layer)
  2. 3×3 Conv: Extracts features with growth_rate=32 filters
  3. Concatenation: Concatenates output with input features
This creates exponentially growing feature maps while maintaining efficiency.

Transition Blocks

Transition blocks reduce spatial dimensions and compress feature maps:
models/densenet.py (91-108)
def transition_block(x, reduction, name):
    """A transition block.
    # Arguments
        x: input tensor.
        reduction: float, compression rate at transition layers.
        name: string, block label.
    # Returns
        output tensor for the block.
    """
    bn_axis = 3
    x = BatchNormalization(axis=bn_axis, epsilon=1.001e-5,
                                  name=name + '_bn')(x)
    x = Activation('relu', name=name + '_relu')(x)
    x = Conv2D(int(K.int_shape(x)[bn_axis] * reduction), 1,
                      use_bias=False,
                      name=name + '_conv')(x)
    x = AveragePooling2D(2, strides=2, name=name + '_pool')(x)
    return x
  • Compression: reduction=0.5 reduces channels by 50%
  • Downsampling: 2×2 average pooling with stride 2

Complete Model Architecture

models/densenet.py (110-159)
def unet_densenet121(input_shape, weights='imagenet'):
    blocks = [6, 12, 24, 16]
    n_channel = 3
    n_class = 2
    img_input = Input(input_shape + (n_channel,))
    
    # Initial convolution
    x = ZeroPadding2D(padding=((3, 3), (3, 3)))(img_input)
    x = Conv2D(64, 7, strides=2, use_bias=False, name='conv1/conv')(x)
    x = BatchNormalization(axis=bn_axis, epsilon=1.001e-5,
                           name='conv1/bn')(x)
    x = Activation('relu', name='conv1/relu')(x)
    conv1 = x
    
    # Encoder path
    x = ZeroPadding2D(padding=((1, 1), (1, 1)))(x)
    x = MaxPooling2D(3, strides=2, name='pool1')(x)
    x = dense_block(x, blocks[0], name='conv2')  # 6 layers
    conv2 = x
    x = transition_block(x, 0.5, name='pool2')
    x = dense_block(x, blocks[1], name='conv3')  # 12 layers
    conv3 = x
    x = transition_block(x, 0.5, name='pool3')
    x = dense_block(x, blocks[2], name='conv4')  # 24 layers
    conv4 = x
    x = transition_block(x, 0.5, name='pool4')
    x = dense_block(x, blocks[3], name='conv5')  # 16 layers
    x = BatchNormalization(axis=bn_axis, epsilon=1.001e-5,
                           name='bn')(x)
    conv5 = x 
    
    # Decoder path with skip connections
    conv6 = conv_block(UpSampling2D()(conv5), 320)
    conv6 = concatenate([conv6, conv4], axis=-1)
    conv6 = conv_block(conv6, 320)

    conv7 = conv_block(UpSampling2D()(conv6), 256)
    conv7 = concatenate([conv7, conv3], axis=-1)
    conv7 = conv_block(conv7, 256)

    conv8 = conv_block(UpSampling2D()(conv7), 128)
    conv8 = concatenate([conv8, conv2], axis=-1)
    conv8 = conv_block(conv8, 128)

    conv9 = conv_block(UpSampling2D()(conv8), 96)
    conv9 = concatenate([conv9, conv1], axis=-1)
    conv9 = conv_block(conv9, 96)

    conv10 = conv_block(UpSampling2D()(conv9), 64)
    conv10 = conv_block(conv10, 64)
    res = Conv2D(n_class, (1, 1), activation='softmax')(conv10)
    model = Model(img_input, res)

    return model

Network Structure

BlockLayersOutput ChannelsResolution
conv1164H/2 × W/2
pool1-64H/4 × W/4
conv26~256H/4 × W/4
pool2-~128H/8 × W/8
conv312~512H/8 × W/8
pool3-~256H/16 × W/16
conv424~1024H/16 × W/16
pool4-~512H/32 × W/32
conv516~1024H/32 × W/32

Advantages of DenseNet U-Net

Dense Connectivity

  • Feature Reuse: All layers access features from preceding layers
  • Gradient Flow: Improved backpropagation through dense connections
  • Parameter Efficiency: Fewer parameters than ResNet for similar performance

U-Net Decoder

  • Skip Connections: Preserves spatial information from encoder
  • Progressive Upsampling: Gradual resolution recovery
  • Multi-scale Features: Combines low and high-level features

Model Weights

Pretrained Weights

The model can optionally load ImageNet-pretrained weights for the DenseNet121 encoder:
# With ImageNet pretraining (encoder only)
model = unet_densenet121(input_shape=(256, 256), weights='imagenet')

# Random initialization
model = unet_densenet121(input_shape=(256, 256), weights=None)

DigiPathAI Weights

Task-specific weights are available for different datasets:
  • digestpath_densenet.h5: Trained on DigestPath dataset
  • paip_densenet.h5: Trained on PAIP dataset
  • camelyon_densenet.h5: Trained on Camelyon dataset

Input/Output Specifications

Input

  • Shape: (batch, height, width, 3)
  • Flexible dimensions: (None, None, 3) for variable sizes
  • Preprocessing: Standard ImageNet normalization

Output

  • Shape: (batch, height, width, 2)
  • Classes: [background, tissue]
  • Activation: Softmax probabilities
The DenseNet U-Net model is particularly effective for tissue segmentation due to its ability to capture fine-grained details through dense connections while maintaining global context through skip connections.

Usage Example

from DigiPathAI.models.densenet import unet_densenet121
from DigiPathAI.helpers.utils import load_trained_models

# Create model architecture
model = unet_densenet121(input_shape=(256, 256), weights=None)

# Load pretrained weights
model = load_trained_models(
    model='dense',
    path='~/.DigiPathAI/digestpath_models/digestpath_densenet.h5',
    patch_size=256
)

# Predict on image patch
import numpy as np
image_patch = np.random.rand(1, 256, 256, 3)
prediction = model.predict(image_patch)

Inception-ResNet

Multi-scale feature extraction

DeepLabv3+

Atrous spatial pyramid pooling

Build docs developers (and LLMs) love