text: import logging
  
  import torch
  from torch import nn
  import torch.nn.functional as F
  from torch.autograd import Function
  
  
  __all__ = ['sigmoid_dice_loss', 'softmax_dice_loss', 'GeneralizedDiceLoss', 'FocalLoss']
  
  cross_entropy = F.cross_entropy
  
  
  def FocalLoss(output, target, alpha=0.25, gamma=2.0):
      # target[target == 4] = 3  # label [4] -> [3]
      # target = expand_target(target, n_class=output.size()[1]) # [N,H,W,D] -> [N,4,H,W,D]
      if output.dim() > 2:
          output = output.view(output.size(0), output.size(1), -1)  # N,C,H,W,D => N,C,H*W*D
          output = output.transpose(1, 2)  # N,C,H*W*D => N,H*W*D,C
          output = output.contiguous().view(-1, output.size(2))  # N,H*W*D,C => N*H*W*D,C
      if target.dim() == 5:
          target = target.contiguous().view(target.size(0), target.size(1), -1)
          target = target.transpose(1, 2)
          target = target.contiguous().view(-1, target.size(2))
      if target.dim() == 4:
          target = target.view(-1)  # N*H*W*D
      # compute the negative likelyhood
      logpt = -F.cross_entropy(output, target)
      pt = torch.exp(logpt)
      # compute the loss
      loss = -((1 - pt) ** gamma) * logpt
      # return loss.sum()
      return loss.mean()
  
  
  def dice(output, target, eps=1e-5):  # soft dice loss
      target = target.float()
      # num = 2*(output*target).sum() + eps
      num = 2 * (output * target).sum()
      den = output.sum() + target.sum() + eps
      return 1.0 - num / den
  
  
  def sigmoid_dice_loss(output, target, alpha=1e-5):
      # output: [-1,3,H,W,T]
      # target: [-1,H,W,T] noted that it includes 0,1,2,4 here
      loss1 = dice(output[:, 0, ...], (target == 1).float(), eps=alpha)
      loss2 = dice(output[:, 1, ...], (target == 2).float(), eps=alpha)
      loss3 = dice(output[:, 2, ...], (target == 3).float(), eps=alpha)
      logging.info('1:{:.4f} | 2:{:.4f} | 4:{:.4f}'.format(1 - loss1.data, 1 - loss2.data, 1 - loss3.data))
      return loss1 + loss2 + loss3
  
  
  def softmax_dice_loss(output, target, eps=1e-5):  #
      # output : [bsize,c,H,W,D]
      # target : [bsize,H,W,D]
      loss1 = dice(output[:, 1, ...], (target == 1).float())
      loss2 = dice(output[:, 2, ...], (target == 2).float())
      loss3 = dice(output[:, 3, ...], (target == 3).float())
      logging.info('1:{:.4f} | 2:{:.4f} | 4:{:.4f}'.format(1 - loss1.data, 1 - loss2.data, 1 - loss3.data))
  
      return loss1 + loss2 + loss3
  
  
  # Generalised Dice : 'Generalised dice overlap as a deep learning loss function for highly unbalanced segmentations'
  def GeneralizedDiceLoss(output, target, eps=1e-5, weight_type='square'):  # Generalized dice loss
      """
          Generalised Dice : 'Generalised dice overlap as a deep learning loss function for highly unbalanced segmentations'
      """
  
      # target = target.float()
  
      if target.dim() == 4:
          # target[target == 4] = 3  # label [4] -> [3]
          target = expand_target(target, n_class=output.size()[1])  # [N,H,W,D] -> [N,4，H,W,D]
  
      output = flatten(output)[1:, ...]  # transpose [N,4，H,W,D] -> [4，N,H,W,D] -> [3, N*H*W*D] voxels
      target = flatten(target)[1:, ...]  # [class, N*H*W*D]
  
      target_sum = target.sum(-1)  # sub_class_voxels [3,1] -> 3个voxels
      if weight_type == 'square':
          class_weights = 1. / (target_sum * target_sum + eps)
      elif weight_type == 'identity':
          class_weights = 1. / (target_sum + eps)
      elif weight_type == 'sqrt':
          class_weights = 1. / (torch.sqrt(target_sum) + eps)
      else:
          raise ValueError('Check out the weight_type :', weight_type)
  
      # print(class_weights)
      intersect = (output * target).sum(-1)
      intersect_sum = (intersect * class_weights).sum()
      denominator = (output + target).sum(-1)
      denominator_sum = (denominator * class_weights).sum() + eps
  
      loss1 = 2 * intersect[0] / (denominator[0] + eps)
      loss2 = 2 * intersect[1] / (denominator[1] + eps)
      loss3 = 2 * intersect[2] / (denominator[2] + eps)
      logging.info('1:{:.4f} | 2:{:.4f} | 4:{:.4f}'.format(loss1.data, loss2.data, loss3.data))
  
      return 1 - 2. * intersect_sum / denominator_sum
  
  
  def expand_target(x, n_class, mode='softmax'):
      """
          Converts NxDxHxW label image to NxCxDxHxW, where each label is stored in a separate channel
          :param input: 4D input image (NxDxHxW)
          :param C: number of channels/labels
          :return: 5D output image (NxCxDxHxW)
          """
      assert x.dim() == 4
      shape = list(x.size())
      shape.insert(1, n_class)
      shape = tuple(shape)
      xx = torch.zeros(shape)
      if mode.lower() == 'softmax':
          xx[:, 1, :, :, :] = (x == 1)
          xx[:, 2, :, :, :] = (x == 2)
          xx[:, 3, :, :, :] = (x == 3)
      if mode.lower() == 'sigmoid':
          xx[:, 0, :, :, :] = (x == 1)
          xx[:, 1, :, :, :] = (x == 2)
          xx[:, 2, :, :, :] = (x == 3)
      return xx.to(x.device)
  
  
  def flatten(tensor):
      """Flattens a given tensor such that the channel axis is first.
      The shapes are transformed as follows:
         (N, C, D, H, W) -> (C, N * D * H * W)
      """
      C = tensor.size(1)
      # new axis order
      axis_order = (1, 0) + tuple(range(2, tensor.dim()))
      # Transpose: (N, C, D, H, W) -> (C, N, D, H, W)
      transposed = tensor.permute(axis_order)
      # Flatten: (C, N, D, H, W) -> (C, N * D * H * W)
      return transposed.reshape(C, -1)
  
  
  class FocalBinaryTverskyLoss(Function):
      def __init__(ctx, alpha=0.5, beta=0.5, gamma=1.0, reduction='mean'):
          """
          :param alpha: controls the penalty for false positives.
          :param beta: penalty for false negative.
          :param gamma : focal coefficient range[1,3]
          :param reduction: return mode
          Notes:
          alpha = beta = 0.5 -> dice coeff
          alpha = beta = 1 -> tanimoto coeff
          alpha + beta = 1 -> F beta coeff
          add focal index -> loss=(1-T_index)**(1/gamma)
          """
          ctx.alpha = alpha
          ctx.beta = beta
          ctx.epsilon = 1e-6
          ctx.reduction = reduction
          ctx.gamma = gamma
          s = ctx.beta + ctx.alpha
          if sum != 1:
              ctx.beta = ctx.beta / s
              ctx.alpha = ctx.alpha / s
  
      # @staticmethod
      def forward(ctx, input, target):
          batch_size = input.size(0)
          _, input_label = input.max(1)
  
          input_label = input_label.float()
          target_label = target.float()
  
          ctx.save_for_backward(input, target_label)
  
          input_label = input_label.view(batch_size, -1)
          target_label = target_label.view(batch_size, -1)
  
          ctx.P_G = torch.sum(input_label * target_label, 1)  # TP
          ctx.P_NG = torch.sum(input_label * (1 - target_label), 1)  # FP
          ctx.NP_G = torch.sum((1 - input_label) * target_label, 1)  # FN
  
          index = ctx.P_G / (ctx.P_G + ctx.alpha * ctx.P_NG + ctx.beta * ctx.NP_G + ctx.epsilon)
          loss = torch.pow((1 - index), 1 / ctx.gamma)
          # target_area = torch.sum(target_label, 1)
          # loss[target_area == 0] = 0
          if ctx.reduction == 'none':
              loss = loss
          elif ctx.reduction == 'sum':
              loss = torch.sum(loss)
          else:
              loss = torch.mean(loss)
          return loss
  
      # @staticmethod
      def backward(ctx, grad_out):
          """
          :param ctx:
          :param grad_out:
          :return:
          d_loss/dT_loss=(1/gamma)*(T_loss)**(1/gamma-1)
          (dT_loss/d_P1)  = 2*P_G*[G*(P_G+alpha*P_NG+beta*NP_G)-(G+alpha*NG)]/[(P_G+alpha*P_NG+beta*NP_G)**2]
                          = 2*P_G
          (dT_loss/d_p0)=
          """
          inputs, target = ctx.saved_tensors
          inputs = inputs.float()
          target = target.float()
          batch_size = inputs.size(0)
          sum = ctx.P_G + ctx.alpha * ctx.P_NG + ctx.beta * ctx.NP_G + ctx.epsilon
          P_G = ctx.P_G.view(batch_size, 1, 1, 1, 1)
          if inputs.dim() == 5:
              sum = sum.view(batch_size, 1, 1, 1, 1)
          elif inputs.dim() == 4:
              sum = sum.view(batch_size, 1, 1, 1)
              P_G = ctx.P_G.view(batch_size, 1, 1, 1)
          sub = (ctx.alpha * (1 - target) + target) * P_G
  
          dL_dT = (1 / ctx.gamma) * torch.pow((P_G / sum), (1 / ctx.gamma - 1))
          dT_dp0 = -2 * (target / sum - sub / sum / sum)
          dL_dp0 = dL_dT * dT_dp0
  
          dT_dp1 = ctx.beta * (1 - target) * P_G / sum / sum
          dL_dp1 = dL_dT * dT_dp1
          grad_input = torch.cat((dL_dp1, dL_dp0), dim=1)
          # grad_input = torch.cat((grad_out.item() * dL_dp0, dL_dp0 * grad_out.item()), dim=1)
          return grad_input, None
  
  
  class MultiTverskyLoss(nn.Module):
      """
      Tversky Loss for segmentation adaptive with multi class segmentation
      """
  
      def __init__(self, alpha=0.5, beta=0.5, gamma=1.0, weights=None):
          """
          :param alpha (Tensor, float, optional): controls the penalty for false positives.
          :param beta (Tensor, float, optional): controls the penalty for false negative.
          :param gamma (Tensor, float, optional): focal coefficient
          :param weights (Tensor, optional): a manual rescaling weight given to each
              class. If given, it has to be a Tensor of size `C`
          """
          super(MultiTverskyLoss, self).__init__()
          self.alpha = alpha
          self.beta = beta
          self.gamma = gamma
          self.weights = weights
  
      def forward(self, inputs, targets):
          """
              data = torch.rand(4, in_channels, 16, 64, 64)
              target = torch.randint(0, n_classes, size=(4, 1, 16, 64, 64)).long()
          :param inputs:
          :param targets:
          :return:
          """
          # if targets.dim() == 4:
          #     targets[targets == 4] = 3  # label [4] -> [3]
          #     targets = expand_target(targets, n_class=inputs.size()[1])  # [N,H,W,D] -> [N,4，H,W,D]
  
          targets = targets.unsqueeze(1)
          num_class = inputs.size(1)
          weight_losses = 0.0
          if self.weights is not None:
              assert len(self.weights) == num_class, 'number of classes should be equal to length of weights '
              weights = self.weights
          else:
              weights = [1.0 / num_class] * num_class
          input_slices = torch.split(inputs, [1] * num_class, dim=1)
          for idx in range(num_class):
              input_idx = input_slices[idx]
              input_idx = torch.cat((1 - input_idx, input_idx), dim=1)
              target_idx = (targets == idx) * 1
              loss_func = FocalBinaryTverskyLoss(self.alpha, self.beta, self.gamma)
              loss_idx = loss_func(input_idx, target_idx)
              weight_losses+=loss_idx * weights[idx]
          # loss = torch.Tensor(weight_losses)
          # loss = loss.to(inputs.device)
          # loss = torch.sum(loss)
          return weight_losses
  
  
  TverskyLoss = MultiTverskyLoss(alpha=0.7, beta=0.3, gamma=4.0/3)
  