torch_em.metric.cldice

 1import numpy as np
 2import torch
 3from skimage.morphology import skeletonize
 4
 5from ..loss.cldice import SoftSkeletonize
 6
 7# From "clDice -- A Novel Topology-Preserving Loss Function for Tubular Structure Segmentation":
 8# https://arxiv.org/abs/2003.07311
 9
10
11def cl_score(img, skel):
12    """Compute the skeleton volume intersection.
13
14    Args:
15        img: image
16        skel: skeleton
17
18    Returns:
19        Skeleton volume intersection.
20    """
21    return np.sum(img * skel) / np.sum(skel)
22
23
24def clDice(input_, target, skeletonize_method="skimage", num_iter=5):
25    """Compute the clDice score between binary input and target.
26
27    Args:
28        input_: The binary input.
29        target: The binary target.
30        skeletonize_method: The skeletonziation method. Either `skimage` for
31            `skimage.morphology.skeletonize` or `soft` for `torch_em.loss.SoftSkeletonize`.
32        num_iter: Number of iterations for soft skeletonization.
33            Only used if skeletonize_method is `soft`
34
35    Returns:
36        The clDice score.
37    """
38    if input_.shape != target.shape:
39        raise ValueError(f"Expect input and target of same shape, got: {input_.shape}, {target.shape}.")
40
41    if skeletonize_method == "skimage":
42        skel_input = skeletonize(input_)
43        skel_target = skeletonize(target)
44
45    elif skeletonize_method == "soft":
46        soft_skeletonize = SoftSkeletonize(num_iter=num_iter)
47
48        # add batch and channel dims for `SoftSkeletonize`
49        input_tensor = torch.from_numpy(input_).float().unsqueeze(0).unsqueeze(0)
50        target_tensor = torch.from_numpy(target).float().unsqueeze(0).unsqueeze(0)
51
52        # convert skeletons back to numpy
53        skel_input = soft_skeletonize(input_tensor).squeeze().numpy()
54        skel_target = soft_skeletonize(target_tensor).squeeze().numpy()
55    else:
56        raise ValueError("Unknown option for `skeletonize_method`. Valid options are `skimage` and `soft`.")
57
58    # Tprec = |S_P ∩ V_L| / |S_P|
59    # Tsens = |S_L ∩ V_P| / |S_L|
60    t_prec = cl_score(target, skel_input)
61    t_sens = cl_score(input_, skel_target)
62
63    return 2.*(t_prec*t_sens) / max(t_prec+t_sens, 1e-7)
def cl_score(img, skel):
12def cl_score(img, skel):
13    """Compute the skeleton volume intersection.
14
15    Args:
16        img: image
17        skel: skeleton
18
19    Returns:
20        Skeleton volume intersection.
21    """
22    return np.sum(img * skel) / np.sum(skel)

Compute the skeleton volume intersection.

Arguments:
  • img: image
  • skel: skeleton
Returns:

Skeleton volume intersection.

def clDice(input_, target, skeletonize_method='skimage', num_iter=5):
25def clDice(input_, target, skeletonize_method="skimage", num_iter=5):
26    """Compute the clDice score between binary input and target.
27
28    Args:
29        input_: The binary input.
30        target: The binary target.
31        skeletonize_method: The skeletonziation method. Either `skimage` for
32            `skimage.morphology.skeletonize` or `soft` for `torch_em.loss.SoftSkeletonize`.
33        num_iter: Number of iterations for soft skeletonization.
34            Only used if skeletonize_method is `soft`
35
36    Returns:
37        The clDice score.
38    """
39    if input_.shape != target.shape:
40        raise ValueError(f"Expect input and target of same shape, got: {input_.shape}, {target.shape}.")
41
42    if skeletonize_method == "skimage":
43        skel_input = skeletonize(input_)
44        skel_target = skeletonize(target)
45
46    elif skeletonize_method == "soft":
47        soft_skeletonize = SoftSkeletonize(num_iter=num_iter)
48
49        # add batch and channel dims for `SoftSkeletonize`
50        input_tensor = torch.from_numpy(input_).float().unsqueeze(0).unsqueeze(0)
51        target_tensor = torch.from_numpy(target).float().unsqueeze(0).unsqueeze(0)
52
53        # convert skeletons back to numpy
54        skel_input = soft_skeletonize(input_tensor).squeeze().numpy()
55        skel_target = soft_skeletonize(target_tensor).squeeze().numpy()
56    else:
57        raise ValueError("Unknown option for `skeletonize_method`. Valid options are `skimage` and `soft`.")
58
59    # Tprec = |S_P ∩ V_L| / |S_P|
60    # Tsens = |S_L ∩ V_P| / |S_L|
61    t_prec = cl_score(target, skel_input)
62    t_sens = cl_score(input_, skel_target)
63
64    return 2.*(t_prec*t_sens) / max(t_prec+t_sens, 1e-7)

Compute the clDice score between binary input and target.

Arguments:
  • input_: The binary input.
  • target: The binary target.
  • skeletonize_method: The skeletonziation method. Either skimage for skimage.morphology.skeletonize or soft for torch_em.loss.SoftSkeletonize.
  • num_iter: Number of iterations for soft skeletonization. Only used if skeletonize_method is soft
Returns:

The clDice score.