jpdefrutos commited on
Commit
081d04d
·
1 Parent(s): 49e1f69

Made HausdorffDistanceErosion channel sentinel.

Browse files

If 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._erode(diff, kernel)
39
- ret += tf.reduce_sum(tf.multiply(er, tf.pow(i + 1., alpha)))
40
 
41
- img_vol = tf.cast(tf.reduce_prod(y_true.shape), tf.float32)
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):