sunfan-bvb / BoundaryDoULoss

Code for Boundary Difference Over Union Loss For Medical Image Segmentation
42 stars 4 forks source link

BounDDoULoss for 3D dataset #1

Open AN-AN-369 opened 11 months ago

AN-AN-369 commented 11 months ago

Thank you for your contributions in the article! May I use BounDDoULoss for 3D dataset?"

sunfan-bvb commented 11 months ago

Of course this is a good idea! But since our alpha is calculated based on the boundary length and size, it may need to be converted to surface area and volume in the 3D dataset, or slice the 3D result to calculate the loss, thanks for using and looking forward to your results!

Overflowu7 commented 10 months ago

I have modified the Bou as follows, and it can run successfully in 3D tasks, but I don't know if this change is reasonable or conforms to the theory. Could you please help me have a look? class BoundaryDoULoss(nn.Module): def init(self, n_classes): super(BoundaryDoULoss, self).init() self.n_classes = n_classes

def _one_hot_encoder(self, input_tensor):
    tensor_list = []
    for i in range(self.n_classes):
        temp_prob = input_tensor == i
        tensor_list.append(temp_prob.unsqueeze(1))
    output_tensor = torch.cat(tensor_list, dim=1)
    return output_tensor.float()

def _adaptive_size(self, score, target):
    kernel = torch.Tensor([[[[0, 0, 0], [0, 1, 0], [0, 0, 0]],
                            [[0, 1, 0], [1, 1, 1], [0, 1, 0]],
                            [[0, 0, 0], [0, 1, 0], [0, 0, 0]]]])
    padding_out = torch.zeros((target.shape[0], target.shape[-3] + 2, target.shape[-2] + 2, target.shape[-1] + 2))
    padding_out[:, 1:-1, 1:-1, 1:-1] = target
    h, w, d = 3, 3, 3

    Y = torch.zeros((padding_out.shape[0], padding_out.shape[1] - d + 1, padding_out.shape[2] - h + 1,
                     padding_out.shape[3] - w + 1)).cuda()
    for i in range(Y.shape[0]):
        Y[i, :, :,:] = torch.conv3d(target[i].unsqueeze(0),kernel.unsqueeze(0).cuda(), padding=1)
    Y = Y * target
    Y[Y == 5] = 0
    C = torch.count_nonzero(Y)
    S = torch.count_nonzero(target)
    smooth = 1e-5
    alpha = 1 - (C + smooth) / (S + smooth)
    alpha = 2 * alpha - 1

    intersect = torch.sum(score * target)
    y_sum = torch.sum(target * target)
    z_sum = torch.sum(score * score)
    alpha = min(alpha, 0.8)
    loss = (z_sum + y_sum - 2 * intersect + smooth) / (z_sum + y_sum - (1 + alpha) * intersect + smooth)

    return loss

def forward(self, inputs, target):
    inputs = torch.softmax(inputs, dim=1)
    target = self._one_hot_encoder(target)

    assert inputs.size() == target.size(), 'predict {} & target {} shape do not match'.format(inputs.size(),
                                                                                              target.size())

    loss = 0.0
    for i in range(0, self.n_classes):
        loss += self._adaptive_size(inputs[:, i], target[:, i])
    return loss / self.n_classes
sunfan-bvb commented 10 months ago

Hi, in theory this code does have a bit of a problem. Calculating the side length and area of an object on a 2D image looks like this: calcuate So in 3D, based on the kernel you are using, you should modify Y[Y == 5] = 0 to Y[Y == 7] = 0. Since I am not familiar with 3D convolution, you can refer to the above diagram to modify the code further.

Overflowu7 commented 10 months ago

Thanks for the advice.Since Bou doesn't perform as well as dice or cross-entropy plus dice in 3D tasks, I'm wondering if there's something wrong with my application. I'll make changes to the code first and come back if I have any questions

Om-Pandey commented 10 months ago

Would it be a better way to use the same way of 2D but in 3D take dimensions 2 at a time and average by 3?

sunfan-bvb commented 10 months ago

I'm not sure. Just give it a try!