# Copyright (c) MONAI Consortium
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from collections.abc import Sequence
import numpy as np
import torch
import torch.nn as nn
from monai.networks.blocks import ADN
from monai.networks.layers.convutils import same_padding, stride_minus_kernel_padding
from monai.networks.layers.factories import Conv
[docs]class Convolution(nn.Sequential):
"""
Constructs a convolution with normalization, optional dropout, and optional activation layers::
-- (Conv|ConvTrans) -- (Norm -- Dropout -- Acti) --
if ``conv_only`` set to ``True``::
-- (Conv|ConvTrans) --
For example:
.. code-block:: python
from monai.networks.blocks import Convolution
conv = Convolution(
spatial_dims=3,
in_channels=1,
out_channels=1,
adn_ordering="ADN",
act=("prelu", {"init": 0.2}),
dropout=0.1,
norm=("layer", {"normalized_shape": (10, 10, 10)}),
)
print(conv)
output::
Convolution(
(conv): Conv3d(1, 1, kernel_size=(3, 3, 3), stride=(1, 1, 1), padding=(1, 1, 1))
(adn): ADN(
(A): PReLU(num_parameters=1)
(D): Dropout(p=0.1, inplace=False)
(N): LayerNorm((10, 10, 10), eps=1e-05, elementwise_affine=True)
)
)
Args:
spatial_dims: number of spatial dimensions.
in_channels: number of input channels.
out_channels: number of output channels.
strides: convolution stride. Defaults to 1.
kernel_size: convolution kernel size. Defaults to 3.
adn_ordering: a string representing the ordering of activation, normalization, and dropout.
Defaults to "NDA".
act: activation type and arguments. Defaults to PReLU.
norm: feature normalization type and arguments. Defaults to instance norm.
dropout: dropout ratio. Defaults to no dropout.
dropout_dim: determine the spatial dimensions of dropout. Defaults to 1.
- When dropout_dim = 1, randomly zeroes some of the elements for each channel.
- When dropout_dim = 2, Randomly zeroes out entire channels (a channel is a 2D feature map).
- When dropout_dim = 3, Randomly zeroes out entire channels (a channel is a 3D feature map).
The value of dropout_dim should be no larger than the value of `spatial_dims`.
dilation: dilation rate. Defaults to 1.
groups: controls the connections between inputs and outputs. Defaults to 1.
bias: whether to have a bias term. Defaults to True.
conv_only: whether to use the convolutional layer only. Defaults to False.
is_transposed: if True uses ConvTrans instead of Conv. Defaults to False.
padding: controls the amount of implicit zero-paddings on both sides for padding number of points
for each dimension. Defaults to None.
output_padding: controls the additional size added to one side of the output shape.
Defaults to None.
See also:
:py:class:`monai.networks.layers.Conv`
:py:class:`monai.networks.blocks.ADN`
"""
def __init__(
self,
spatial_dims: int,
in_channels: int,
out_channels: int,
strides: Sequence[int] | int = 1,
kernel_size: Sequence[int] | int = 3,
adn_ordering: str = "NDA",
act: tuple | str | None = "PRELU",
norm: tuple | str | None = "INSTANCE",
dropout: tuple | str | float | None = None,
dropout_dim: int | None = 1,
dilation: Sequence[int] | int = 1,
groups: int = 1,
bias: bool = True,
conv_only: bool = False,
is_transposed: bool = False,
padding: Sequence[int] | int | None = None,
output_padding: Sequence[int] | int | None = None,
) -> None:
super().__init__()
self.spatial_dims = spatial_dims
self.in_channels = in_channels
self.out_channels = out_channels
self.is_transposed = is_transposed
if padding is None:
padding = same_padding(kernel_size, dilation)
conv_type = Conv[Conv.CONVTRANS if is_transposed else Conv.CONV, self.spatial_dims]
conv: nn.Module
if is_transposed:
if output_padding is None:
output_padding = stride_minus_kernel_padding(1, strides)
conv = conv_type(
in_channels,
out_channels,
kernel_size=kernel_size,
stride=strides,
padding=padding,
output_padding=output_padding,
groups=groups,
bias=bias,
dilation=dilation,
)
else:
conv = conv_type(
in_channels,
out_channels,
kernel_size=kernel_size,
stride=strides,
padding=padding,
dilation=dilation,
groups=groups,
bias=bias,
)
self.add_module("conv", conv)
if conv_only:
return
if act is None and norm is None and dropout is None:
return
self.add_module(
"adn",
ADN(
ordering=adn_ordering,
in_channels=out_channels,
act=act,
norm=norm,
norm_dim=self.spatial_dims,
dropout=dropout,
dropout_dim=dropout_dim,
),
)
[docs]class ResidualUnit(nn.Module):
"""
Residual module with multiple convolutions and a residual connection.
For example:
.. code-block:: python
from monai.networks.blocks import ResidualUnit
convs = ResidualUnit(
spatial_dims=3,
in_channels=1,
out_channels=1,
adn_ordering="AN",
act=("prelu", {"init": 0.2}),
norm=("layer", {"normalized_shape": (10, 10, 10)}),
)
print(convs)
output::
ResidualUnit(
(conv): Sequential(
(unit0): Convolution(
(conv): Conv3d(1, 1, kernel_size=(3, 3, 3), stride=(1, 1, 1), padding=(1, 1, 1))
(adn): ADN(
(A): PReLU(num_parameters=1)
(N): LayerNorm((10, 10, 10), eps=1e-05, elementwise_affine=True)
)
)
(unit1): Convolution(
(conv): Conv3d(1, 1, kernel_size=(3, 3, 3), stride=(1, 1, 1), padding=(1, 1, 1))
(adn): ADN(
(A): PReLU(num_parameters=1)
(N): LayerNorm((10, 10, 10), eps=1e-05, elementwise_affine=True)
)
)
)
(residual): Identity()
)
Args:
spatial_dims: number of spatial dimensions.
in_channels: number of input channels.
out_channels: number of output channels.
strides: convolution stride. Defaults to 1.
kernel_size: convolution kernel size. Defaults to 3.
subunits: number of convolutions. Defaults to 2.
adn_ordering: a string representing the ordering of activation, normalization, and dropout.
Defaults to "NDA".
act: activation type and arguments. Defaults to PReLU.
norm: feature normalization type and arguments. Defaults to instance norm.
dropout: dropout ratio. Defaults to no dropout.
dropout_dim: determine the dimensions of dropout. Defaults to 1.
- When dropout_dim = 1, randomly zeroes some of the elements for each channel.
- When dropout_dim = 2, Randomly zero out entire channels (a channel is a 2D feature map).
- When dropout_dim = 3, Randomly zero out entire channels (a channel is a 3D feature map).
The value of dropout_dim should be no larger than the value of `dimensions`.
dilation: dilation rate. Defaults to 1.
bias: whether to have a bias term. Defaults to True.
last_conv_only: for the last subunit, whether to use the convolutional layer only.
Defaults to False.
padding: controls the amount of implicit zero-paddings on both sides for padding number of points
for each dimension. Defaults to None.
See also:
:py:class:`monai.networks.blocks.Convolution`
"""
def __init__(
self,
spatial_dims: int,
in_channels: int,
out_channels: int,
strides: Sequence[int] | int = 1,
kernel_size: Sequence[int] | int = 3,
subunits: int = 2,
adn_ordering: str = "NDA",
act: tuple | str | None = "PRELU",
norm: tuple | str | None = "INSTANCE",
dropout: tuple | str | float | None = None,
dropout_dim: int | None = 1,
dilation: Sequence[int] | int = 1,
bias: bool = True,
last_conv_only: bool = False,
padding: Sequence[int] | int | None = None,
) -> None:
super().__init__()
self.spatial_dims = spatial_dims
self.in_channels = in_channels
self.out_channels = out_channels
self.conv = nn.Sequential()
self.residual = nn.Identity()
if not padding:
padding = same_padding(kernel_size, dilation)
schannels = in_channels
sstrides = strides
subunits = max(1, subunits)
for su in range(subunits):
conv_only = last_conv_only and su == (subunits - 1)
unit = Convolution(
self.spatial_dims,
schannels,
out_channels,
strides=sstrides,
kernel_size=kernel_size,
adn_ordering=adn_ordering,
act=act,
norm=norm,
dropout=dropout,
dropout_dim=dropout_dim,
dilation=dilation,
bias=bias,
conv_only=conv_only,
padding=padding,
)
self.conv.add_module(f"unit{su:d}", unit)
# after first loop set channels and strides to what they should be for subsequent units
schannels = out_channels
sstrides = 1
# apply convolution to input to change number of output channels and size to match that coming from self.conv
if np.prod(strides) != 1 or in_channels != out_channels:
rkernel_size = kernel_size
rpadding = padding
if np.prod(strides) == 1: # if only adapting number of channels a 1x1 kernel is used with no padding
rkernel_size = 1
rpadding = 0
conv_type = Conv[Conv.CONV, self.spatial_dims]
self.residual = conv_type(in_channels, out_channels, rkernel_size, strides, rpadding, bias=bias)
[docs] def forward(self, x: torch.Tensor) -> torch.Tensor:
res: torch.Tensor = self.residual(x) # create the additive residual from x
cx: torch.Tensor = self.conv(x) # apply x to sequence of operations
return cx + res # add the residual to the output