### Implements distance metrics for ROI annotations

parent a45a0d7c
Pipeline #5199 failed with stages
in 2 minutes and 27 seconds
 ... ... @@ -199,3 +199,110 @@ def show_mask_over_image(image, mask, color='red'): red = Image.new('RGBA', img.size, color=color) img.paste(red, mask=msk) img.show() def jaccard_index(a, b): """Calculates the intersection over union for two masks This function calculates the Jaccard index: .. math:: J(A,B) = \frac{|A \cap B|}{|A \cup B|} = \frac{|A \cap B|}{|A|+|B|-|A \cup B|} Parameters: a (numpy.ndarray): A 2D numpy array with dtype :py:obj:`bool` b (numpy.ndarray): A 2D numpy array with dtype :py:obj:`bool` Returns: float: The floating point number that corresponds to the Jaccard index. The float value lies inside the interval :math:`[0, 1]`. If ``a`` and ``b`` are equal, then the similarity is maximum and the value output is ``1.0``. If the areas are exclusive, then the value output by this function is ``0.0``. """ return (a & b).sum().astype(float) / (a | b).sum().astype(float) def intersect_ratio(a, b): """Calculates the intersection ratio between a probe and ground-truth This function calculates the intersection ratio between a probe mask (:math:`B`) and a ground-truth mask (:math:`A`; probably generated from an annotation), and returns the ratio of overlap when the probe is compared to the ground-truth data: .. math:: R(A,B) = \frac{|A \cap B|}{|A|} So, if the probe occupies the entirety of the ground-truth data, then the output of this function is ``1.0``, otherwise, if areas are exclusive, then this function returns ``0.0`. The output of this function should be analyzed against the output of :py:func:`intersect_ratio_of_complement`, which provides the complementary information about the intersection of the areas being analyzed. Parameters: a (numpy.ndarray): A 2D numpy array with dtype :py:obj:`bool` b (numpy.ndarray): A 2D numpy array with dtype :py:obj:`bool` Returns: float: The floating point number that corresponds to the overlap ratio. The float value lies inside the interval :math:`[0, 1]`. """ return (a & b).sum().astype(float) / a.sum().astype(float) def intersect_ratio_of_complement(a, b): """Calculates the intersection ratio between a probe and the ground-truth complement This function calculates the intersection ratio between a probe mask (:math:`B`) and *the complement* of a ground-truth mask (:math:`A`; probably generated from an annotation), and returns the ratio of overlap when the probe is compared to the ground-truth data: .. math:: R(A,B) = \frac{|A^c \cap B|}{|A|} = B \\ A So, if the probe is totally inside the ground-truth data, then the output of this function is ``0.0``, otherwise, if areas are exclusive for example, then this function outputs greater than zero. The output of this function should be analyzed against the output of :py:func:`intersect_ratio`, which provides the complementary information about the intersection of the areas being analyzed. Parameters: a (numpy.ndarray): A 2D numpy array with dtype :py:obj:`bool` b (numpy.ndarray): A 2D numpy array with dtype :py:obj:`bool` Returns: float: The floating point number that corresponds to the overlap ratio between the probe area and the *complement* of the ground-truth area. There are no bounds for the float value on the right side: :math:`[0, +\inf]`. """ return ((~a) & b).sum().astype(float) / a.sum().astype(float)
 ... ... @@ -277,3 +277,57 @@ def test_mask_to_image(): assert 'int16' in str(e) else: raise AssertionError('Conversion to int16 did not trigger a TypeError') def test_jaccard_index(): # Tests to verify the Jaccard index calculation is accurate a = numpy.array([ [False, False], [True, True], ]) b = numpy.array([ [True, True], [True, False], ]) nose.tools.eq_(utils.jaccard_index(a, b), 1.0/4.0) nose.tools.eq_(utils.jaccard_index(a, a), 1.0) nose.tools.eq_(utils.jaccard_index(b, b), 1.0) nose.tools.eq_(utils.jaccard_index(a, numpy.ones(a.shape, dtype=bool)), 2.0/4.0) nose.tools.eq_(utils.jaccard_index(a, numpy.zeros(a.shape, dtype=bool)), 0.0) nose.tools.eq_(utils.jaccard_index(b, numpy.ones(b.shape, dtype=bool)), 3.0/4.0) nose.tools.eq_(utils.jaccard_index(b, numpy.zeros(b.shape, dtype=bool)), 0.0) def test_intersection_ratio(): # Tests to verify the intersection ratio calculation is accurate a = numpy.array([ [False, False], [True, True], ]) b = numpy.array([ [True, False], [True, False], ]) nose.tools.eq_(utils.intersect_ratio(a, b), 1.0/2.0) nose.tools.eq_(utils.intersect_ratio(a, a), 1.0) nose.tools.eq_(utils.intersect_ratio(b, b), 1.0) nose.tools.eq_(utils.intersect_ratio(a, numpy.ones(a.shape, dtype=bool)), 1.0) nose.tools.eq_(utils.intersect_ratio(a, numpy.zeros(a.shape, dtype=bool)), 0) nose.tools.eq_(utils.intersect_ratio(b, numpy.ones(b.shape, dtype=bool)), 1.0) nose.tools.eq_(utils.intersect_ratio(b, numpy.zeros(b.shape, dtype=bool)), 0) nose.tools.eq_(utils.intersect_ratio_of_complement(a, b), 1.0/2.0) nose.tools.eq_(utils.intersect_ratio_of_complement(a, a), 0.0) nose.tools.eq_(utils.intersect_ratio_of_complement(b, b), 0.0) nose.tools.eq_(utils.intersect_ratio_of_complement(a, numpy.ones(a.shape, dtype=bool)), 1.0) nose.tools.eq_(utils.intersect_ratio_of_complement(a, numpy.zeros(a.shape, dtype=bool)), 0) nose.tools.eq_(utils.intersect_ratio_of_complement(b, numpy.ones(b.shape, dtype=bool)), 1.0) nose.tools.eq_(utils.intersect_ratio_of_complement(b, numpy.zeros(b.shape, dtype=bool)), 0)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!