set_metrics.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import warnings
  2. import numpy as np
  3. from scipy.spatial import cKDTree
  4. def hausdorff_distance(image0, image1, method="standard"):
  5. """Calculate the Hausdorff distance between nonzero elements of given images.
  6. Parameters
  7. ----------
  8. image0, image1 : ndarray
  9. Arrays where ``True`` represents a point that is included in a
  10. set of points. Both arrays must have the same shape.
  11. method : {'standard', 'modified'}, optional, default = 'standard'
  12. The method to use for calculating the Hausdorff distance.
  13. ``standard`` is the standard Hausdorff distance, while ``modified``
  14. is the modified Hausdorff distance.
  15. Returns
  16. -------
  17. distance : float
  18. The Hausdorff distance between coordinates of nonzero pixels in
  19. ``image0`` and ``image1``, using the Euclidean distance.
  20. Notes
  21. -----
  22. The Hausdorff distance [1]_ is the maximum distance between any point on
  23. ``image0`` and its nearest point on ``image1``, and vice-versa.
  24. The Modified Hausdorff Distance (MHD) has been shown to perform better
  25. than the directed Hausdorff Distance (HD) in the following work by
  26. Dubuisson et al. [2]_. The function calculates forward and backward
  27. mean distances and returns the largest of the two.
  28. References
  29. ----------
  30. .. [1] http://en.wikipedia.org/wiki/Hausdorff_distance
  31. .. [2] M. P. Dubuisson and A. K. Jain. A Modified Hausdorff distance for object
  32. matching. In ICPR94, pages A:566-568, Jerusalem, Israel, 1994.
  33. :DOI:`10.1109/ICPR.1994.576361`
  34. http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.1.8155
  35. Examples
  36. --------
  37. >>> points_a = (3, 0)
  38. >>> points_b = (6, 0)
  39. >>> shape = (7, 1)
  40. >>> image_a = np.zeros(shape, dtype=bool)
  41. >>> image_b = np.zeros(shape, dtype=bool)
  42. >>> image_a[points_a] = True
  43. >>> image_b[points_b] = True
  44. >>> hausdorff_distance(image_a, image_b)
  45. 3.0
  46. """
  47. if method not in ('standard', 'modified'):
  48. raise ValueError(f'unrecognized method {method}')
  49. a_points = np.transpose(np.nonzero(image0))
  50. b_points = np.transpose(np.nonzero(image1))
  51. # Handle empty sets properly:
  52. # - if both sets are empty, return zero
  53. # - if only one set is empty, return infinity
  54. if len(a_points) == 0:
  55. return 0 if len(b_points) == 0 else np.inf
  56. elif len(b_points) == 0:
  57. return np.inf
  58. fwd, bwd = (
  59. cKDTree(a_points).query(b_points, k=1)[0],
  60. cKDTree(b_points).query(a_points, k=1)[0],
  61. )
  62. if method == 'standard': # standard Hausdorff distance
  63. return max(max(fwd), max(bwd))
  64. elif method == 'modified': # modified Hausdorff distance
  65. return max(np.mean(fwd), np.mean(bwd))
  66. def hausdorff_pair(image0, image1):
  67. """Returns pair of points that are Hausdorff distance apart between nonzero
  68. elements of given images.
  69. The Hausdorff distance [1]_ is the maximum distance between any point on
  70. ``image0`` and its nearest point on ``image1``, and vice-versa.
  71. Parameters
  72. ----------
  73. image0, image1 : ndarray
  74. Arrays where ``True`` represents a point that is included in a
  75. set of points. Both arrays must have the same shape.
  76. Returns
  77. -------
  78. point_a, point_b : array
  79. A pair of points that have Hausdorff distance between them.
  80. References
  81. ----------
  82. .. [1] http://en.wikipedia.org/wiki/Hausdorff_distance
  83. Examples
  84. --------
  85. >>> points_a = (3, 0)
  86. >>> points_b = (6, 0)
  87. >>> shape = (7, 1)
  88. >>> image_a = np.zeros(shape, dtype=bool)
  89. >>> image_b = np.zeros(shape, dtype=bool)
  90. >>> image_a[points_a] = True
  91. >>> image_b[points_b] = True
  92. >>> hausdorff_pair(image_a, image_b)
  93. (array([3, 0]), array([6, 0]))
  94. """
  95. a_points = np.transpose(np.nonzero(image0))
  96. b_points = np.transpose(np.nonzero(image1))
  97. # If either of the sets are empty, there is no corresponding pair of points
  98. if len(a_points) == 0 or len(b_points) == 0:
  99. warnings.warn("One or both of the images is empty.", stacklevel=2)
  100. return (), ()
  101. nearest_dists_from_b, nearest_a_point_indices_from_b = cKDTree(a_points).query(
  102. b_points
  103. )
  104. nearest_dists_from_a, nearest_b_point_indices_from_a = cKDTree(b_points).query(
  105. a_points
  106. )
  107. max_index_from_a = nearest_dists_from_b.argmax()
  108. max_index_from_b = nearest_dists_from_a.argmax()
  109. max_dist_from_a = nearest_dists_from_b[max_index_from_a]
  110. max_dist_from_b = nearest_dists_from_a[max_index_from_b]
  111. if max_dist_from_b > max_dist_from_a:
  112. return (
  113. a_points[max_index_from_b],
  114. b_points[nearest_b_point_indices_from_a[max_index_from_b]],
  115. )
  116. else:
  117. return (
  118. a_points[nearest_a_point_indices_from_b[max_index_from_a]],
  119. b_points[max_index_from_a],
  120. )