Repository: Project-MONAI/MONAI

Issue:
Focal Loss
Can the focal loss be used for the segmentation task or is it only for the classification?


Discussion:
<img width="996" alt="Screen Shot 2020-10-01 at 12 11 08 PM" src="https://user-images.githubusercontent.com/33342277/94775293-43803c80-03df-11eb-860b-d54ab25fbbc6.png">
 error occured while performing segmentation with focal loss. 
Here predicted output shape is [1,1,256,256] and label is also [1,1,256,256]
These device-side problems are not common, they come up when doing custom CUDA operations or using CUDA through other programs. Are you running something else other than this program? If you restart the program/notebook does it reoccur or does it take a few runs before this happens? What hardware are you running on? What's the output from `monai.config.print_config()`?

Patch:
diff --git a/monai/data/utils.py b/monai/data/utils.py
--- a/monai/data/utils.py
+++ b/monai/data/utils.py
@@ -455,7 +455,7 @@ def create_file_basename(
 
     # get the filename and directory
     filedir, filename = os.path.split(input_file_name)
-    # remove exntension
+    # remove extension
     filename, ext = os.path.splitext(filename)
     if ext == ".gz":
         filename, ext = os.path.splitext(filename)
diff --git a/monai/losses/focal_loss.py b/monai/losses/focal_loss.py
--- a/monai/losses/focal_loss.py
+++ b/monai/losses/focal_loss.py
@@ -61,36 +61,40 @@ def __init__(
         self.gamma = gamma
         self.weight: Optional[torch.Tensor] = None
 
-    def forward(self, input: torch.Tensor, target: torch.Tensor) -> torch.Tensor:
+    def forward(self, logits: torch.Tensor, target: torch.Tensor) -> torch.Tensor:
         """
         Args:
-            input: the shape should be BCH[WD].
-                where C is the number of classes.
+            logits: the shape should be BCH[WD].
+                where C (greater than 1) is the number of classes.
+                Softmax over the logits is integrated in this module for improved numerical stability.
             target: the shape should be B1H[WD] or BCH[WD].
                 If the target's shape is B1H[WD], the target that this loss expects should be a class index
                 in the range [0, C-1] where C is the number of classes.
 
         Raises:
-            ValueError: When ``target`` ndim differs from ``input``.
-            ValueError: When ``target`` channel is not 1 and ``target`` shape differs from ``input``.
+            ValueError: When ``target`` ndim differs from ``logits``.
+            ValueError: When ``target`` channel is not 1 and ``target`` shape differs from ``logits``.
             ValueError: When ``self.reduction`` is not one of ["mean", "sum", "none"].
 
         """
-        i = input
+        i = logits
         t = target
 
         if i.ndimension() != t.ndimension():
-            raise ValueError(f"input and target ndim must match, got input={i.ndimension()} target={t.ndimension()}.")
+            raise ValueError(f"logits and target ndim must match, got logits={i.ndimension()} target={t.ndimension()}.")
 
-        if target.shape[1] != 1 and target.shape[1] != i.shape[1]:
+        if t.shape[1] != 1 and t.shape[1] != i.shape[1]:
             raise ValueError(
-                "target must have one channel or have the same shape as the input. "
+                "target must have one channel or have the same shape as the logits. "
                 "If it has one channel, it should be a class index in the range [0, C-1] "
-                f"where C is the number of classes inferred from 'input': C={i.shape[1]}. "
+                f"where C is the number of classes inferred from 'logits': C={i.shape[1]}. "
             )
-        # Change the shape of input and target to
+        if i.shape[1] == 1:
+            raise NotImplementedError("Single-channel predictions not supported.")
+
+        # Change the shape of logits and target to
         # num_batch x num_class x num_voxels.
-        if input.dim() > 2:
+        if i.dim() > 2:
             i = i.view(i.size(0), i.size(1), -1)  # N,C,H,W => N,C,H*W
             t = t.view(t.size(0), t.size(1), -1)  # N,1,H,W => N,1,H*W or N,C,H*W
         else:  # Compatibility with classification.
diff --git a/monai/metrics/surface_distance.py b/monai/metrics/surface_distance.py
--- a/monai/metrics/surface_distance.py
+++ b/monai/metrics/surface_distance.py
@@ -26,7 +26,7 @@ def compute_average_surface_distance(
 ):
     """
     This function is used to compute the Average Surface Distance from `seg_pred` to `seg_gt`
-    under the default seeting.
+    under the default setting.
     In addition, if sets ``symmetric = True``, the average symmetric surface distance between
     these two inputs will be returned.
 
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,7 +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.
+        upsample_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``).
@@ -176,16 +176,16 @@ def __init__(
         out_channels: int,
         kernel_size: Union[Sequence[int], int],
         stride: Union[Sequence[int], int],
-        upsamle_kernel_size: Union[Sequence[int], int],
+        upsample_kernel_size: Union[Sequence[int], int],
         norm_name: str,
     ):
         super(UnetUpBlock, self).__init__()
-        upsample_stride = upsamle_kernel_size
+        upsample_stride = upsample_kernel_size
         self.transp_conv = get_conv_layer(
             spatial_dims,
             in_channels,
             out_channels,
-            kernel_size=upsamle_kernel_size,
+            kernel_size=upsample_kernel_size,
             stride=upsample_stride,
             conv_only=True,
             is_transposed=True,
diff --git a/monai/networks/layers/spatial_transforms.py b/monai/networks/layers/spatial_transforms.py
--- a/monai/networks/layers/spatial_transforms.py
+++ b/monai/networks/layers/spatial_transforms.py
@@ -251,7 +251,7 @@ def grid_count(grid: torch.Tensor, shape=None, interpolation="linear", bound="ze
         - etc.
 
     A list of values can be provided, in the order [W, H, D],
-    to specify dimension-specific interpoaltion orders.
+    to specify dimension-specific interpolation orders.
 
     `bound` can be an int, a string or a BoundType.
     Possible values are::
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
@@ -43,7 +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.
+        upsample_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``.
@@ -63,7 +63,7 @@ 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]],
+        upsample_kernel_size: Sequence[Union[Sequence[int], int]],
         norm_name: str = "instance",
         deep_supervision: bool = True,
         deep_supr_num: int = 1,
@@ -75,7 +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.upsample_kernel_size = upsample_kernel_size
         self.norm_name = norm_name
         self.deep_supervision = deep_supervision
         self.conv_block = UnetResBlock if res_block else UnetBasicBlock
@@ -164,8 +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]
-        upsamle_kernel_size = self.upsamle_kernel_size[::-1]
-        return self.get_module_list(inp, out, kernel_size, strides, UnetUpBlock, upsamle_kernel_size)
+        upsample_kernel_size = self.upsample_kernel_size[::-1]
+        return self.get_module_list(inp, out, kernel_size, strides, UnetUpBlock, upsample_kernel_size)
 
     def get_module_list(
         self,
@@ -174,12 +174,12 @@ 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,
+        upsample_kernel_size: Optional[Sequence[Union[Sequence[int], int]]] = None,
     ):
         layers = []
-        if upsamle_kernel_size is not None:
+        if upsample_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
+                in_channels, out_channels, kernel_size, strides, upsample_kernel_size
             ):
                 params = {
                     "spatial_dims": self.spatial_dims,
@@ -188,7 +188,7 @@ def get_module_list(
                     "kernel_size": kernel,
                     "stride": stride,
                     "norm_name": self.norm_name,
-                    "upsamle_kernel_size": up_kernel,
+                    "upsample_kernel_size": up_kernel,
                 }
                 layer = conv_block(**params)
                 layers.append(layer)
diff --git a/monai/networks/nets/segresnet.py b/monai/networks/nets/segresnet.py
--- a/monai/networks/nets/segresnet.py
+++ b/monai/networks/nets/segresnet.py
@@ -32,7 +32,7 @@ class SegResNet(nn.Module):
         init_filters: number of output channels for initial convolution layer. Defaults to 8.
         in_channels: number of input channels for the network. Defaults to 1.
         out_channels: number of output channels for the network. Defaults to 2.
-        droupout_prob: probability of an element to be zero-ed. Defaults to ``None``.
+        dropout_prob: probability of an element to be zero-ed. Defaults to ``None``.
         norm_name: feature normalization type, this module only supports group norm,
             batch norm and instance norm. Defaults to ``group``.
         num_groups: number of groups to separate the channels into. Defaults to 8.
@@ -180,7 +180,7 @@ class SegResNetVAE(SegResNet):
         init_filters: number of output channels for initial convolution layer. Defaults to 8.
         in_channels: number of input channels for the network. Defaults to 1.
         out_channels: number of output channels for the network. Defaults to 2.
-        droupout_prob: probability of an element to be zero-ed. Defaults to ``None``.
+        dropout_prob: probability of an element to be zero-ed. Defaults to ``None``.
         norm_name: feature normalization type, this module only supports group norm,
             batch norm and instance norm. Defaults to ``group``.
         num_groups: number of groups to separate the channels into. Defaults to 8.
diff --git a/monai/networks/utils.py b/monai/networks/utils.py
--- a/monai/networks/utils.py
+++ b/monai/networks/utils.py
@@ -33,7 +33,7 @@ def one_hot(labels: torch.Tensor, num_classes: int, dtype: torch.dtype = torch.f
     """
     assert labels.dim() > 0, "labels should have dim of 1 or more."
 
-    # if `dim` is bigger, add singelton dim at the end
+    # if `dim` is bigger, add singleton dim at the end
     if labels.ndimension() < dim + 1:
         shape = ensure_tuple_size(labels.shape, dim + 1, 1)
         labels = labels.reshape(*shape)
diff --git a/monai/utils/misc.py b/monai/utils/misc.py
--- a/monai/utils/misc.py
+++ b/monai/utils/misc.py
@@ -163,7 +163,7 @@ def progress_bar(index: int, count: int, desc: Optional[str] = None, bar_len: in
     """print a progress bar to track some time consuming task.
 
     Args:
-        index: current satus in progress.
+        index: current status in progress.
         count: total steps of the progress.
         desc: description of the progress bar, if not None, show before the progress bar.
         bar_len: the total length of the bar on screen, default is 30 char.


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,7 +37,7 @@
                         "out_channels": out_channels,
                         "kernel_size": kernel_size,
                         "strides": strides,
-                        "upsamle_kernel_size": strides[1:],
+                        "upsample_kernel_size": strides[1:],
                         "norm_name": "batch",
                         "deep_supervision": False,
                         "res_block": res_block,
@@ -60,7 +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),
+                "upsample_kernel_size": (2, 2, 1),
                 "norm_name": "instance",
                 "deep_supervision": False,
                 "res_block": res_block,
@@ -82,7 +82,7 @@
                         "out_channels": 2,
                         "kernel_size": [3] * len(strides),
                         "strides": strides,
-                        "upsamle_kernel_size": strides[1:],
+                        "upsample_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,7 +56,7 @@
                             "kernel_size": kernel_size,
                             "norm_name": norm_name,
                             "stride": stride,
-                            "upsamle_kernel_size": stride,
+                            "upsample_kernel_size": stride,
                         },
                         torch.randn(1, in_channels, *([in_size] * spatial_dims)),
                         (1, out_channels, *([out_size] * spatial_dims)),
diff --git a/tests/test_focal_loss.py b/tests/test_focal_loss.py
--- a/tests/test_focal_loss.py
+++ b/tests/test_focal_loss.py
@@ -159,6 +159,10 @@ def test_ill_shape(self):
         chn_target = torch.ones((1, 3))
         with self.assertRaisesRegex(ValueError, ""):
             FocalLoss(reduction="mean")(chn_input, chn_target)
+        chn_input = torch.ones((1, 1, 30))
+        chn_target = torch.ones((1, 1, 30))
+        with self.assertRaisesRegex(NotImplementedError, ""):
+            FocalLoss()(chn_input, chn_target)
 
 
 if __name__ == "__main__":