Commit
·
081d04d
1
Parent(s):
49e1f69
Made HausdorffDistanceErosion channel sentinel.
Browse filesIf value_per_channel is True, the function returns an array of shape (C,), where C is the number of channels of the input image. Otherwise it returns the sum of the hausdorff distance across the channel dimension. Both images must have the same shape.
DeepDeformationMapRegistration/losses.py
CHANGED
@@ -6,7 +6,7 @@ from DeepDeformationMapRegistration.utils.constants import EPS_tf
|
|
6 |
|
7 |
|
8 |
class HausdorffDistanceErosion:
|
9 |
-
def __init__(self, ndim=3, nerosion=10):
|
10 |
"""
|
11 |
Approximation of the Hausdorff distance based on erosion operations based on the work done by Karimi D., et al.
|
12 |
Karimi D., et al., "Reducing the Hausdorff Distance in Medical Image Segmentation with Convolutional Neural
|
@@ -14,15 +14,24 @@ class HausdorffDistanceErosion:
|
|
14 |
|
15 |
:param ndim: Dimensionality of the images
|
16 |
:param nerosion: Number of erosion steps. Defaults to 10.
|
|
|
17 |
"""
|
18 |
self.ndims = ndim
|
19 |
self.conv = getattr(tf.nn, 'conv%dd' % self.ndims)
|
20 |
self.nerosions = nerosion
|
|
|
21 |
|
22 |
def _erode(self, in_tensor, kernel):
|
23 |
out = 1. - tf.squeeze(self.conv(tf.expand_dims(1. - in_tensor, 0), kernel, [1] * (self.ndims + 2), 'SAME'), axis=0)
|
24 |
return soft_threshold(out, 0.5, name='soft_thresholding')
|
25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
def _erosion_distance_single(self, y_true, y_pred):
|
27 |
diff = tf.math.pow(y_pred - y_true, 2)
|
28 |
alpha = 2.
|
@@ -30,15 +39,15 @@ class HausdorffDistanceErosion:
|
|
30 |
norm = 1 / (self.ndims * 2 + 1)
|
31 |
kernel = generate_binary_structure(self.ndims, 1).astype(int) * norm
|
32 |
kernel = tf.constant(kernel, tf.float32)
|
33 |
-
kernel = tf.expand_dims(tf.expand_dims(kernel, -1), -1)
|
34 |
|
35 |
ret = 0.
|
36 |
for i in range(self.nerosions):
|
37 |
for j in range(i + 1):
|
38 |
-
er = self.
|
39 |
-
ret += tf.reduce_sum(tf.multiply(er, tf.pow(i + 1., alpha)))
|
40 |
|
41 |
-
img_vol = tf.cast(tf.reduce_prod(
|
42 |
return tf.divide(ret, img_vol) # Divide by the image size
|
43 |
|
44 |
def loss(self, y_true, y_pred):
|
|
|
6 |
|
7 |
|
8 |
class HausdorffDistanceErosion:
|
9 |
+
def __init__(self, ndim=3, nerosion=10, value_per_channel=False):
|
10 |
"""
|
11 |
Approximation of the Hausdorff distance based on erosion operations based on the work done by Karimi D., et al.
|
12 |
Karimi D., et al., "Reducing the Hausdorff Distance in Medical Image Segmentation with Convolutional Neural
|
|
|
14 |
|
15 |
:param ndim: Dimensionality of the images
|
16 |
:param nerosion: Number of erosion steps. Defaults to 10.
|
17 |
+
:param value_per_channel: Return an array with the HD distance computed on each channel independently or the sum
|
18 |
"""
|
19 |
self.ndims = ndim
|
20 |
self.conv = getattr(tf.nn, 'conv%dd' % self.ndims)
|
21 |
self.nerosions = nerosion
|
22 |
+
self.sum_range = tf.range(0, self.ndims) if value_per_channel else None
|
23 |
|
24 |
def _erode(self, in_tensor, kernel):
|
25 |
out = 1. - tf.squeeze(self.conv(tf.expand_dims(1. - in_tensor, 0), kernel, [1] * (self.ndims + 2), 'SAME'), axis=0)
|
26 |
return soft_threshold(out, 0.5, name='soft_thresholding')
|
27 |
|
28 |
+
def _erode_per_channel(self, in_tensor, kernel):
|
29 |
+
# In the lambda function we add a fictitious channel and then remove it, so the final shape is [1, H, W, D]
|
30 |
+
er_tensor = tf.map_fn(lambda tens: tf.squeeze(self._erode(tf.expand_dims(tens, -1), kernel)),
|
31 |
+
tf.transpose(in_tensor, [3, 0, 1, 2]), tf.float32) # Iterate along the channel dimension (3)
|
32 |
+
|
33 |
+
return tf.transpose(er_tensor, [1, 2, 3, 0]) # move the channels back to the end
|
34 |
+
|
35 |
def _erosion_distance_single(self, y_true, y_pred):
|
36 |
diff = tf.math.pow(y_pred - y_true, 2)
|
37 |
alpha = 2.
|
|
|
39 |
norm = 1 / (self.ndims * 2 + 1)
|
40 |
kernel = generate_binary_structure(self.ndims, 1).astype(int) * norm
|
41 |
kernel = tf.constant(kernel, tf.float32)
|
42 |
+
kernel = tf.expand_dims(tf.expand_dims(kernel, -1), -1) # [H, W, D, C_in, C_out]
|
43 |
|
44 |
ret = 0.
|
45 |
for i in range(self.nerosions):
|
46 |
for j in range(i + 1):
|
47 |
+
er = self._erode_per_channel(diff, kernel)
|
48 |
+
ret += tf.reduce_sum(tf.multiply(er, tf.pow(i + 1., alpha)), self.sum_range)
|
49 |
|
50 |
+
img_vol = tf.cast(tf.reduce_prod(tf.shape(y_true)[:-1]), tf.float32) # Volume of each channel
|
51 |
return tf.divide(ret, img_vol) # Divide by the image size
|
52 |
|
53 |
def loss(self, y_true, y_pred):
|