Repository: Project-MONAI/MONAI

Issue:
Fix implementation errors for dynUnet
The implementations have two  different places compared with the refered network (see [here](https://github.com/Project-MONAI/MONAI/pull/1003)). 
Generally speaking, two places need to be changed:

1) the number of output channel for the transposed conv layer should be the same as the number for the whole upsample block (now it is the same as the number of input channel)
2) the kernel size and stride for the transposed conv layer need extra parameters to control, since they are not always the same as in other conv layers in the upsample block.


Discussion:

Patch:
diff --git a/monai/networks/blocks/convolutions.py b/monai/networks/blocks/convolutions.py
--- a/monai/networks/blocks/convolutions.py
+++ b/monai/networks/blocks/convolutions.py
@@ -87,7 +87,7 @@ def __init__(
         self.in_channels = in_channels
         self.out_channels = out_channels
         self.is_transposed = is_transposed
-        if not padding:
+        if padding is None:
             padding = same_padding(kernel_size, dilation)
         conv_type = Conv[Conv.CONVTRANS if is_transposed else Conv.CONV, dimensions]
         # define the normalisation type and the arguments to the constructor
@@ -119,7 +119,7 @@ def __init__(
             drop_type = Dropout[drop_name, dropout_dim]
 
         if is_transposed:
-            if not output_padding:
+            if output_padding is None:
                 output_padding = stride_minus_kernel_padding(1, strides)
             conv = conv_type(
                 in_channels,
diff --git a/monai/networks/blocks/dynunet_block.py b/monai/networks/blocks/dynunet_block.py
--- a/monai/networks/blocks/dynunet_block.py
+++ b/monai/networks/blocks/dynunet_block.py
@@ -163,6 +163,7 @@ class UnetUpBlock(nn.Module):
         out_channels: number of output channels.
         kernel_size: convolution kernel size.
         stride: convolution stride.
+        upsamle_kernel_size: convolution kernel size for transposed convolution layers.
         norm_name: [``"batch"``, ``"instance"``, ``"group"``]
             feature normalization type and arguments. In this module, if using ``"group"``,
             `in_channels` should be divisible by 16 (default value for ``num_groups``).
@@ -175,22 +176,23 @@ def __init__(
         out_channels: int,
         kernel_size: Union[Sequence[int], int],
         stride: Union[Sequence[int], int],
+        upsamle_kernel_size: Union[Sequence[int], int],
         norm_name: str,
     ):
         super(UnetUpBlock, self).__init__()
-        self.stride = stride
+        upsample_stride = upsamle_kernel_size
         self.transp_conv = get_conv_layer(
             spatial_dims,
             in_channels,
-            in_channels,
-            kernel_size=kernel_size,
-            stride=stride,
+            out_channels,
+            kernel_size=upsamle_kernel_size,
+            stride=upsample_stride,
             conv_only=True,
             is_transposed=True,
         )
         self.conv_block = UnetBasicBlock(
             spatial_dims,
-            in_channels + out_channels,
+            out_channels + out_channels,
             out_channels,
             kernel_size=kernel_size,
             stride=1,
@@ -251,7 +253,6 @@ def get_conv_layer(
     output_padding = None
     if is_transposed:
         output_padding = get_output_padding(kernel_size, stride, padding)
-
     return Convolution(
         spatial_dims,
         in_channels,
diff --git a/monai/networks/nets/dynunet.py b/monai/networks/nets/dynunet.py
--- a/monai/networks/nets/dynunet.py
+++ b/monai/networks/nets/dynunet.py
@@ -10,7 +10,7 @@
 # limitations under the License.
 
 
-from typing import List, Sequence, Union
+from typing import List, Optional, Sequence, Union
 
 import torch.nn as nn
 
@@ -43,6 +43,7 @@ class DynUNet(nn.Module):
         out_channels: number of output channels.
         kernel_size: convolution kernel size.
         strides: convolution strides for each blocks.
+        upsamle_kernel_size: convolution kernel size for transposed convolution layers.
         norm_name: [``"batch"``, ``"instance"``, ``"group"``]
             feature normalization type and arguments.
         deep_supervision: whether to add deep supervision head before output. Defaults to ``True``.
@@ -51,7 +52,7 @@ class DynUNet(nn.Module):
             from the intermediate up sample layers.
         deep_supr_num: number of feature maps that will output during deep supervision head. The
             value should be less than the number of up sample layers. Defaults to 1.
-        block_type: whether to use residual connection based convolution blocks during the network.
+        res_block: whether to use residual connection based convolution blocks during the network.
             Defaults to ``True``.
     """
 
@@ -62,10 +63,11 @@ def __init__(
         out_channels: int,
         kernel_size: Sequence[Union[Sequence[int], int]],
         strides: Sequence[Union[Sequence[int], int]],
+        upsamle_kernel_size: Sequence[Union[Sequence[int], int]],
         norm_name: str = "instance",
         deep_supervision: bool = True,
         deep_supr_num: int = 1,
-        res_block: bool = True,
+        res_block: bool = False,
     ):
         super(DynUNet, self).__init__()
         self.spatial_dims = spatial_dims
@@ -73,6 +75,7 @@ def __init__(
         self.out_channels = out_channels
         self.kernel_size = kernel_size
         self.strides = strides
+        self.upsamle_kernel_size = upsamle_kernel_size
         self.norm_name = norm_name
         self.deep_supervision = deep_supervision
         self.conv_block = UnetResBlock if res_block else UnetBasicBlock
@@ -161,7 +164,8 @@ def get_downsamples(self):
     def get_upsamples(self):
         inp, out = self.filters[1:][::-1], self.filters[:-1][::-1]
         strides, kernel_size = self.strides[1:][::-1], self.kernel_size[1:][::-1]
-        return self.get_module_list(inp, out, kernel_size, strides, UnetUpBlock)
+        upsamle_kernel_size = self.upsamle_kernel_size[::-1]
+        return self.get_module_list(inp, out, kernel_size, strides, UnetUpBlock, upsamle_kernel_size)
 
     def get_module_list(
         self,
@@ -170,18 +174,36 @@ def get_module_list(
         kernel_size: Sequence[Union[Sequence[int], int]],
         strides: Sequence[Union[Sequence[int], int]],
         conv_block: nn.Module,
+        upsamle_kernel_size: Optional[Sequence[Union[Sequence[int], int]]] = None,
     ):
         layers = []
-        for in_c, out_c, kernel, stride in zip(in_channels, out_channels, kernel_size, strides):
-            layer = conv_block(
-                self.spatial_dims,
-                in_c,
-                out_c,
-                kernel,
-                stride,
-                self.norm_name,
-            )
-            layers.append(layer)
+        if upsamle_kernel_size is not None:
+            for in_c, out_c, kernel, stride, up_kernel in zip(
+                in_channels, out_channels, kernel_size, strides, upsamle_kernel_size
+            ):
+                params = {
+                    "spatial_dims": self.spatial_dims,
+                    "in_channels": in_c,
+                    "out_channels": out_c,
+                    "kernel_size": kernel,
+                    "stride": stride,
+                    "norm_name": self.norm_name,
+                    "upsamle_kernel_size": up_kernel,
+                }
+                layer = conv_block(**params)
+                layers.append(layer)
+        else:
+            for in_c, out_c, kernel, stride in zip(in_channels, out_channels, kernel_size, strides):
+                params = {
+                    "spatial_dims": self.spatial_dims,
+                    "in_channels": in_c,
+                    "out_channels": out_c,
+                    "kernel_size": kernel,
+                    "stride": stride,
+                    "norm_name": self.norm_name,
+                }
+                layer = conv_block(**params)
+                layers.append(layer)
         return nn.ModuleList(layers)
 
     def get_deep_supervision_heads(self):


Test patch:
diff --git a/tests/test_dynunet.py b/tests/test_dynunet.py
--- a/tests/test_dynunet.py
+++ b/tests/test_dynunet.py
@@ -37,6 +37,7 @@
                         "out_channels": out_channels,
                         "kernel_size": kernel_size,
                         "strides": strides,
+                        "upsamle_kernel_size": strides[1:],
                         "norm_name": "batch",
                         "deep_supervision": False,
                         "res_block": res_block,
@@ -59,6 +60,7 @@
                 "out_channels": out_channels,
                 "kernel_size": (3, (1, 1, 3), 3, 3),
                 "strides": ((1, 2, 1), 2, 2, 1),
+                "upsamle_kernel_size": (2, 2, 1),
                 "norm_name": "instance",
                 "deep_supervision": False,
                 "res_block": res_block,
@@ -80,6 +82,7 @@
                         "out_channels": 2,
                         "kernel_size": [3] * len(strides),
                         "strides": strides,
+                        "upsamle_kernel_size": strides[1:],
                         "norm_name": "group",
                         "deep_supervision": True,
                         "deep_supr_num": deep_supr_num,
diff --git a/tests/test_dynunet_block.py b/tests/test_dynunet_block.py
--- a/tests/test_dynunet_block.py
+++ b/tests/test_dynunet_block.py
@@ -56,6 +56,7 @@
                             "kernel_size": kernel_size,
                             "norm_name": norm_name,
                             "stride": stride,
+                            "upsamle_kernel_size": stride,
                         },
                         torch.randn(1, in_channels, *([in_size] * spatial_dims)),
                         (1, out_channels, *([out_size] * spatial_dims)),