RepConTUR / nnunet.py
shlee472's picture
Add RepConTUR-1B weights, remote code, tokenizer and model card
c6be546 verified
Raw History Blame Contribute Delete
6.14 kB
import torch
import torch.nn as nn
from einops import rearrange
class SEBlock(nn.Module):
"""Squeeze-and-Excitation Block."""
def __init__(self, channels, reduction=16):
super(SEBlock, self).__init__()
self.fc1 = nn.Linear(channels, channels // reduction, bias=False)
self.fc2 = nn.Linear(channels // reduction, channels, bias=False)
self.relu = nn.ReLU(inplace=True)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
b, c, d, h, w = x.shape
y = x.mean(dim=[2, 3, 4])
y = self.fc1(y)
y = self.relu(y)
y = self.fc2(y)
y = self.sigmoid(y).view(b, c, 1, 1, 1)
return x * y
class ResNetBlock(nn.Module):
"""Residual Block with convolutional layers and an SE module."""
def __init__(self, in_channels, out_channels, dropout_rate=0.1):
super(ResNetBlock, self).__init__()
self.conv1 = nn.Conv3d(in_channels, out_channels, kernel_size=3, padding=1)
self.in1 = nn.InstanceNorm3d(out_channels, affine=True)
self.prelu = nn.PReLU()
self.dropout = nn.Dropout3d(dropout_rate)
self.conv2 = nn.Conv3d(out_channels, out_channels, kernel_size=3, padding=1)
self.in2 = nn.InstanceNorm3d(out_channels, affine=True)
self.se = SEBlock(out_channels)
self.residual = (
nn.Conv3d(in_channels, out_channels, kernel_size=1)
if in_channels != out_channels else nn.Identity()
)
def forward(self, x):
residual = self.residual(x)
x = self.conv1(x)
x = self.in1(x)
x = self.prelu(x)
x = self.dropout(x)
x = self.conv2(x)
x = self.in2(x)
x = self.se(x)
x += residual
return self.prelu(x)
class Down(nn.Module):
"""Downsampling block with strided convolution, then a ResNetBlock."""
def __init__(self, in_channels, out_channels, stride=(2,2,2), dropout_rate=0.1):
super().__init__()
self.stride_conv = nn.Conv3d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1)
self.res_block = ResNetBlock(out_channels, out_channels, dropout_rate)
def forward(self, x):
x = self.stride_conv(x)
x = self.res_block(x)
return x
class Up(nn.Module):
"""Upsampling block: skip-connection concat, convolutions, then transposed conv."""
def __init__(self, in_channels, out_channels, upsample_channels,
stride=(2,2,2), dropout_rate=0.1):
super().__init__()
self.double_conv = nn.Sequential(
nn.Conv3d(in_channels, out_channels, kernel_size=3, padding=1),
nn.InstanceNorm3d(out_channels, affine=True),
nn.PReLU(),
nn.Dropout3d(dropout_rate),
nn.Conv3d(out_channels, out_channels, kernel_size=3, padding=1),
nn.InstanceNorm3d(out_channels, affine=True),
nn.PReLU(),
nn.Dropout3d(dropout_rate),
)
self.up = nn.ConvTranspose3d(out_channels, upsample_channels,
kernel_size=stride, stride=stride)
def forward(self, decoder_x, skip_x):
x = torch.cat([skip_x, decoder_x], dim=1)
x = self.double_conv(x)
x = self.up(x)
return x
class Bottleneck(nn.Module):
"""Bottleneck block with strided conv, ResNetBlock, and transposed conv."""
def __init__(self, in_channels, out_channels, stride=(2,2,2), dropout_rate=0.1):
super().__init__()
self.stride_conv = nn.Conv3d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1)
self.res_block = ResNetBlock(out_channels, out_channels, dropout_rate)
self.up = nn.ConvTranspose3d(out_channels, out_channels, kernel_size=stride, stride=stride)
def forward(self, x):
x = self.stride_conv(x)
x = self.res_block(x)
x = self.up(x)
return x
class Final(nn.Module):
"""Final block that merges the top-level up with the inc output, then outputs num_classes."""
def __init__(self, num_classes):
super().__init__()
self.final_conv = nn.Sequential(
nn.Conv3d(64, 32, kernel_size=3, padding=1),
nn.InstanceNorm3d(32, affine=True),
nn.PReLU(),
nn.Conv3d(32, 32, kernel_size=3, padding=1),
nn.InstanceNorm3d(32, affine=True),
nn.PReLU(),
nn.Conv3d(32, num_classes, kernel_size=1),
)
def forward(self, top_x, inc_x):
x = torch.cat([inc_x, top_x], dim=1)
return self.final_conv(x)
class NNUNet3DEncoder(nn.Module):
"""
3D UNet-like architecture with dynamic depth.
If depth=4, it replicates the original structure:
inc -> down1..down4 -> bottleneck -> up4..up1 -> final
plus 4 auxiliary outputs.
"""
def __init__(
self,
dropout_rate=0.1,
depth=5,
stride_list=[[1, 2, 2], [2, 2, 2], [2, 2, 2], [2, 2, 2], [2, 2, 2]],
channels=[32, 64, 128, 256, 320, 512]
):
super().__init__()
self.depth = depth
self.channels = channels
if depth >= len(channels):
raise ValueError(f"depth={depth} too large for {channels}")
self.inc = ResNetBlock(1, channels[0], dropout_rate)
self.down_blocks = nn.ModuleList()
for i in range(depth):
self.down_blocks.append(
Down(channels[i], channels[i+1],
stride=stride_list[i], dropout_rate=dropout_rate)
)
def forward(self, x, return_encoded_tokens=False):
"""
Args:
x: (batch_size, channels, depth, height, width)
return_encoded_tokens: (bool) if True, return the encoded tokens
Returns:
encoded_tokens: (batch_size, depth, height, width, channels)
"""
x0 = self.inc(x)
downs = x0
for i in range(self.depth):
downs = self.down_blocks[i](downs)
downs = rearrange(downs, 'b c d h w -> b d h w c')
if return_encoded_tokens:
return downs
return downs