3798 lines
146 KiB
Python
3798 lines
146 KiB
Python
from __future__ import print_function, division, absolute_import
|
|
|
|
import warnings
|
|
import sys
|
|
# unittest only added in 3.4 self.subTest()
|
|
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
|
|
import unittest2 as unittest
|
|
else:
|
|
import unittest
|
|
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
|
|
try:
|
|
import unittest.mock as mock
|
|
except ImportError:
|
|
import mock
|
|
|
|
import numpy as np
|
|
import six.moves as sm
|
|
import shapely
|
|
import shapely.geometry
|
|
|
|
import imgaug as ia
|
|
import imgaug.random as iarandom
|
|
from imgaug.testutils import reseed, wrap_shift_deprecation, assertWarns
|
|
from imgaug.augmentables.polys import _ConcavePolygonRecoverer
|
|
|
|
|
|
class TestPolygon___init__(unittest.TestCase):
|
|
def test_exterior_is_list_of_keypoints(self):
|
|
kps = [ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1),
|
|
ia.Keypoint(x=0.5, y=2.5)]
|
|
|
|
poly = ia.Polygon(kps)
|
|
|
|
assert poly.exterior.dtype.name == "float32"
|
|
assert np.allclose(
|
|
poly.exterior,
|
|
np.float32([
|
|
[0.0, 0.0],
|
|
[1.0, 1.0],
|
|
[0.5, 2.5]
|
|
])
|
|
)
|
|
|
|
def test_exterior_is_list_of_tuples_of_floats(self):
|
|
poly = ia.Polygon([(0.0, 0.0), (1.0, 1.0), (0.5, 2.5)])
|
|
|
|
assert poly.exterior.dtype.name == "float32"
|
|
assert np.allclose(
|
|
poly.exterior,
|
|
np.float32([
|
|
[0.0, 0.0],
|
|
[1.0, 1.0],
|
|
[0.5, 2.5]
|
|
])
|
|
)
|
|
|
|
def test_exterior_is_list_of_tuples_of_ints(self):
|
|
poly = ia.Polygon([(0, 0), (1, 1), (1, 3)])
|
|
|
|
assert poly.exterior.dtype.name == "float32"
|
|
assert np.allclose(
|
|
poly.exterior,
|
|
np.float32([
|
|
[0.0, 0.0],
|
|
[1.0, 1.0],
|
|
[1.0, 3.0]
|
|
])
|
|
)
|
|
|
|
def test_exterior_is_float32_array(self):
|
|
poly = ia.Polygon(
|
|
np.float32([
|
|
[0.0, 0.0],
|
|
[1.0, 1.0],
|
|
[0.5, 2.5]
|
|
])
|
|
)
|
|
|
|
assert poly.exterior.dtype.name == "float32"
|
|
assert np.allclose(
|
|
poly.exterior,
|
|
np.float32([
|
|
[0.0, 0.0],
|
|
[1.0, 1.0],
|
|
[0.5, 2.5]
|
|
])
|
|
)
|
|
|
|
def test_exterior_is_float64_array(self):
|
|
poly = ia.Polygon(
|
|
np.float64([
|
|
[0.0, 0.0],
|
|
[1.0, 1.0],
|
|
[0.5, 2.5]
|
|
])
|
|
)
|
|
|
|
assert poly.exterior.dtype.name == "float32"
|
|
assert np.allclose(
|
|
poly.exterior,
|
|
np.float32([
|
|
[0.0, 0.0],
|
|
[1.0, 1.0],
|
|
[0.5, 2.5]
|
|
])
|
|
)
|
|
|
|
def test_exterior_is_empty_list(self):
|
|
poly = ia.Polygon([])
|
|
|
|
assert poly.exterior.dtype.name == "float32"
|
|
assert poly.exterior.shape == (0, 2)
|
|
|
|
def test_exterior_is_empty_array(self):
|
|
poly = ia.Polygon(np.zeros((0, 2), dtype=np.float32))
|
|
|
|
assert poly.exterior.dtype.name == "float32"
|
|
assert poly.exterior.shape == (0, 2)
|
|
|
|
def test_fails_if_exterior_is_array_with_wrong_shape(self):
|
|
with self.assertRaises(AssertionError):
|
|
_ = ia.Polygon(np.zeros((8,), dtype=np.float32))
|
|
|
|
def test_label_is_none(self):
|
|
poly = ia.Polygon([(0, 0)])
|
|
|
|
assert poly.label is None
|
|
|
|
def test_label_is_string(self):
|
|
poly = ia.Polygon([(0, 0)], label="test")
|
|
|
|
assert poly.label == "test"
|
|
|
|
|
|
class TestPolygon_coords(unittest.TestCase):
|
|
def test_with_three_points(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0.5), (1.5, 2.0)])
|
|
assert poly.coords is poly.exterior
|
|
|
|
|
|
class TestPolygon_xx(unittest.TestCase):
|
|
def test_filled_polygon(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1.5, 0), (4.1, 1), (2.9, 2.0)])
|
|
|
|
assert poly.xx.dtype.name == "float32"
|
|
assert np.allclose(poly.xx, np.float32([0.0, 1.0, 1.5, 4.1, 2.9]))
|
|
|
|
def test_empty_polygon(self):
|
|
poly = ia.Polygon([])
|
|
|
|
assert poly.xx.dtype.name == "float32"
|
|
assert poly.xx.shape == (0,)
|
|
|
|
|
|
class TestPolygon_yy(unittest.TestCase):
|
|
def test_filled_polygon(self):
|
|
poly = ia.Polygon([(0, 0), (0, 1), (0, 1.5), (1, 4.1), (2.0, 2.9)])
|
|
|
|
assert poly.yy.dtype.name == "float32"
|
|
assert np.allclose(poly.yy, np.float32([0.0, 1.0, 1.5, 4.1, 2.9]))
|
|
|
|
def test_empty_polygon(self):
|
|
poly = ia.Polygon([])
|
|
|
|
assert poly.yy.dtype.name == "float32"
|
|
assert poly.yy.shape == (0,)
|
|
|
|
|
|
class TestPolygon_xx_int(unittest.TestCase):
|
|
def test_filled_polygon(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1.5, 0), (4.1, 1), (2.9, 2.0)])
|
|
|
|
assert poly.xx_int.dtype.name == "int32"
|
|
assert np.allclose(poly.xx_int, np.int32([0, 1, 2, 4, 3]))
|
|
|
|
def test_empty_polygon(self):
|
|
poly = ia.Polygon([])
|
|
|
|
assert poly.xx_int.dtype.name == "int32"
|
|
assert poly.xx_int.shape == (0,)
|
|
|
|
|
|
class TestPolygon_yy_int(unittest.TestCase):
|
|
def test_filled_polygon(self):
|
|
poly = ia.Polygon([(0, 0), (0, 1), (0, 1.5), (1, 4.1), (2.0, 2.9)])
|
|
|
|
assert poly.yy_int.dtype.name == "int32"
|
|
assert np.allclose(poly.yy_int, np.int32([0, 1, 2, 4, 3]))
|
|
|
|
def test_empty_polygon(self):
|
|
poly = ia.Polygon([])
|
|
|
|
assert poly.yy_int.dtype.name == "int32"
|
|
assert poly.yy_int.shape == (0,)
|
|
|
|
|
|
class TestPolygon_is_valid(unittest.TestCase):
|
|
def test_filled_polygon(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
assert poly.is_valid
|
|
|
|
def test_empty_polygon(self):
|
|
poly = ia.Polygon([])
|
|
assert not poly.is_valid
|
|
|
|
def test_polygon_with_one_point(self):
|
|
poly = ia.Polygon([(0, 0)])
|
|
assert not poly.is_valid
|
|
|
|
def test_polygon_with_two_points(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0)])
|
|
assert not poly.is_valid
|
|
|
|
def test_polygon_with_self_intersection(self):
|
|
# self intersection around the line segment from (0, 1) to (0, 0)
|
|
poly = ia.Polygon([(0, 0), (1, 0), (-1, 0.5), (1, 1), (0, 1)])
|
|
assert not poly.is_valid
|
|
|
|
def test_polygon_with_consecutive_identical_points(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 0), (1, 1), (0, 1)])
|
|
assert poly.is_valid
|
|
|
|
|
|
class TestPolygon_area(unittest.TestCase):
|
|
def test_square_polygon(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
assert 1.0 - 1e-8 < poly.area < 1.0 + 1e-8
|
|
|
|
def test_rectangular_polygon(self):
|
|
poly = ia.Polygon([(0, 0), (2, 0), (2, 1), (0, 1)])
|
|
assert 2.0 - 1e-8 < poly.area < 2.0 + 1e-8
|
|
|
|
def test_triangular_polygon(self):
|
|
poly = ia.Polygon([(0, 0), (1, 1), (0, 1)])
|
|
assert 1/2 - 1e-8 < poly.area < 1/2 + 1e-8
|
|
|
|
def test_polygon_with_two_points(self):
|
|
poly = ia.Polygon([(0, 0), (1, 1)])
|
|
assert np.isclose(poly.area, 0.0)
|
|
|
|
def test_polygon_with_one_point(self):
|
|
poly = ia.Polygon([(0, 0)])
|
|
assert np.isclose(poly.area, 0.0)
|
|
|
|
def test_polygon_with_zero_points(self):
|
|
poly = ia.Polygon([])
|
|
assert np.isclose(poly.area, 0.0)
|
|
|
|
|
|
class TestPolygon_height(unittest.TestCase):
|
|
def test_square_polygon(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
assert np.allclose(poly.height, 1.0, atol=1e-8, rtol=0)
|
|
|
|
def test_rectangular_polygon(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 2), (0, 2)])
|
|
assert np.allclose(poly.height, 2.0, atol=1e-8, rtol=0)
|
|
|
|
def test_triangular_polygon(self):
|
|
poly = ia.Polygon([(0, 0), (1, 1), (0, 1)])
|
|
assert np.allclose(poly.height, 1.0, atol=1e-8, rtol=0)
|
|
|
|
def test_polygon_with_two_points(self):
|
|
poly = ia.Polygon([(0, 0), (1, 1)])
|
|
assert np.allclose(poly.height, 1.0, atol=1e-8, rtol=0)
|
|
|
|
def test_polygon_with_one_point(self):
|
|
poly = ia.Polygon([(0, 0)])
|
|
assert np.allclose(poly.height, 0.0, atol=1e-8, rtol=0)
|
|
|
|
|
|
class TestPolygon_width(unittest.TestCase):
|
|
def test_square_polygon(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
assert np.allclose(poly.width, 1.0, atol=1e-8, rtol=0)
|
|
|
|
def test_rectangular_polygon(self):
|
|
poly = ia.Polygon([(0, 0), (2, 0), (2, 1), (0, 1)])
|
|
assert np.allclose(poly.width, 2.0, atol=1e-8, rtol=0)
|
|
|
|
def test_triangular_polygon(self):
|
|
poly = ia.Polygon([(0, 0), (1, 1), (0, 1)])
|
|
assert np.allclose(poly.width, 1.0, atol=1e-8, rtol=0)
|
|
|
|
def test_polygon_with_two_points(self):
|
|
poly = ia.Polygon([(0, 0), (1, 1)])
|
|
assert np.allclose(poly.width, 1.0, atol=1e-8, rtol=0)
|
|
|
|
def test_polygon_with_one_point(self):
|
|
poly = ia.Polygon([(0, 0)])
|
|
assert np.allclose(poly.width, 0.0, atol=1e-8, rtol=0)
|
|
|
|
|
|
class TestPolygon_project_(unittest.TestCase):
|
|
@property
|
|
def _is_inplace(self):
|
|
return True
|
|
|
|
def _func(self, poly, from_shape, to_shape):
|
|
return poly.project_(from_shape, to_shape)
|
|
|
|
def test_project_square_to_image_of_identical_shape(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
|
|
poly_proj = self._func(poly, (1, 1), (1, 1))
|
|
|
|
assert poly_proj.exterior.dtype.name == "float32"
|
|
assert poly_proj.exterior.shape == (4, 2)
|
|
assert np.allclose(
|
|
poly_proj.exterior,
|
|
np.float32([
|
|
[0, 0],
|
|
[1, 0],
|
|
[1, 1],
|
|
[0, 1]
|
|
])
|
|
)
|
|
|
|
def test_project_square_to_image_with_twice_the_height_and_width(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
|
|
poly_proj = self._func(poly, (1, 1), (2, 2))
|
|
|
|
assert poly_proj.exterior.dtype.name == "float32"
|
|
assert poly_proj.exterior.shape == (4, 2)
|
|
assert np.allclose(
|
|
poly_proj.exterior,
|
|
np.float32([
|
|
[0, 0],
|
|
[2, 0],
|
|
[2, 2],
|
|
[0, 2]
|
|
])
|
|
)
|
|
|
|
def test_project_square_to_image_with_twice_the_height_but_same_width(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
|
|
poly_proj = self._func(poly, (1, 1), (2, 1))
|
|
|
|
assert poly_proj.exterior.dtype.name == "float32"
|
|
assert poly_proj.exterior.shape == (4, 2)
|
|
assert np.allclose(
|
|
poly_proj.exterior,
|
|
np.float32([
|
|
[0, 0],
|
|
[1, 0],
|
|
[1, 2],
|
|
[0, 2]
|
|
])
|
|
)
|
|
|
|
def test_project_empty_exterior(self):
|
|
poly = ia.Polygon([])
|
|
poly_proj = self._func(poly, (1, 1), (2, 2))
|
|
assert poly_proj.exterior.dtype.name == "float32"
|
|
assert poly_proj.exterior.shape == (0, 2)
|
|
|
|
def test_inplaceness(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
|
|
poly2 = self._func(poly, (1, 1), (1, 1))
|
|
|
|
if self._is_inplace:
|
|
assert poly is poly2
|
|
else:
|
|
assert poly is not poly2
|
|
|
|
|
|
class TestPolygon_project(TestPolygon_project_):
|
|
@property
|
|
def _is_inplace(self):
|
|
return False
|
|
|
|
def _func(self, poly, from_shape, to_shape):
|
|
return poly.project(from_shape, to_shape)
|
|
|
|
|
|
class TestPolygon_find_closest_point_idx(unittest.TestCase):
|
|
def test_without_return_distance(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
coords = [(0, 0), (1, 0), (1.0001, -0.001), (0.2, 0.2)]
|
|
expected_indices = [0, 1, 1, 0]
|
|
|
|
for (x, y), expected_index in zip(coords, expected_indices):
|
|
with self.subTest(x=x, y=0):
|
|
closest_idx = poly.find_closest_point_index(x=x, y=y)
|
|
assert closest_idx == expected_index
|
|
|
|
def test_with_return_distance(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
coords = [(0, 0), (0.1, 0.15), (0.9, 0.15)]
|
|
expected_indices = [0, 0, 1]
|
|
expected_distances = [
|
|
0.0,
|
|
np.sqrt((0.1**2) + (0.15**2)),
|
|
np.sqrt(((1.0-0.9)**2) + (0.15**2))
|
|
]
|
|
|
|
gen = zip(coords, expected_indices, expected_distances)
|
|
for (x, y), expected_index, expected_dist in gen:
|
|
with self.subTest(x=x, y=y):
|
|
closest_idx, distance = poly.find_closest_point_index(
|
|
x=x, y=y, return_distance=True)
|
|
assert closest_idx == expected_index
|
|
assert np.allclose(distance, expected_dist)
|
|
|
|
def test_fails_for_empty_exterior(self):
|
|
poly = ia.Polygon([])
|
|
with self.assertRaises(AssertionError):
|
|
_ = poly.find_closest_point_index(x=0, y=0)
|
|
|
|
|
|
class TestPolygon_compute_out_of_image_area(unittest.TestCase):
|
|
def test_fully_inside_image_plane(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
image_shape = (10, 20, 3)
|
|
area_ooi = poly.compute_out_of_image_area(image_shape)
|
|
assert np.isclose(area_ooi, 0.0)
|
|
|
|
def test_partially_outside_of_image_plane(self):
|
|
poly = ia.Polygon([(-1, 0), (1, 0), (1, 2), (-1, 2)])
|
|
image_shape = (10, 20, 3)
|
|
area_ooi = poly.compute_out_of_image_area(image_shape)
|
|
assert np.isclose(area_ooi, 2.0)
|
|
|
|
def test_fully_outside_of_image_plane(self):
|
|
poly = ia.Polygon([(-1, 0), (0, 0), (0, 1), (-1, 1)])
|
|
image_shape = (10, 20, 3)
|
|
area_ooi = poly.compute_out_of_image_area(image_shape)
|
|
assert np.isclose(area_ooi, 1.0)
|
|
|
|
def test_multiple_polygons_after_clip(self):
|
|
# two polygons inside the image area remain after clipping
|
|
# result is (area - poly1 - poly2) or here the part of the polygon
|
|
# that is left of the y-axis (x=0.0)
|
|
poly = ia.Polygon([(-10, 0), (5, 0), (5, 5), (-5, 5),
|
|
(-5, 10), (5, 10),
|
|
(5, 15), (-10, 15)])
|
|
image_shape = (15, 10, 3)
|
|
|
|
area_ooi = poly.compute_out_of_image_area(image_shape)
|
|
|
|
# the part left of the y-axis is not exactly square, but has a hole
|
|
# on its right (vertically centered), hence we have to subtract 5*5
|
|
assert np.isclose(area_ooi, 10*15 - 5*5)
|
|
|
|
|
|
class TestPolygon_compute_out_of_image_fraction(unittest.TestCase):
|
|
def test_polygon_with_zero_points(self):
|
|
poly = ia.Polygon([])
|
|
image_shape = (10, 10, 3)
|
|
factor = poly.compute_out_of_image_fraction(image_shape)
|
|
assert np.isclose(factor, 0.0)
|
|
|
|
def test_polygon_with_one_point(self):
|
|
poly = ia.Polygon([(1.0, 1.0)])
|
|
image_shape = (10, 10, 3)
|
|
factor = poly.compute_out_of_image_fraction(image_shape)
|
|
assert np.isclose(factor, 0.0)
|
|
|
|
def test_polygon_with_one_point_ooi(self):
|
|
poly = ia.Polygon([(-1.0, 1.0)])
|
|
image_shape = (10, 10, 3)
|
|
factor = poly.compute_out_of_image_fraction(image_shape)
|
|
assert np.isclose(factor, 1.0)
|
|
|
|
def test_polygon_with_two_points(self):
|
|
poly = ia.Polygon([(1.0, 1.0), (2.0, 1.0)])
|
|
image_shape = (10, 10, 3)
|
|
factor = poly.compute_out_of_image_fraction(image_shape)
|
|
assert np.isclose(factor, 0.0)
|
|
|
|
def test_polygon_with_two_points_one_ooi(self):
|
|
poly = ia.Polygon([(9.0, 1.0), (11.0, 1.0)])
|
|
image_shape = (10, 10, 3)
|
|
factor = poly.compute_out_of_image_fraction(image_shape)
|
|
assert np.isclose(factor, 0.5, atol=1e-3)
|
|
|
|
def test_polygon_with_three_points_as_line(self):
|
|
poly = ia.Polygon([(9.0, 1.0), (10.0, 1.0), (11.0, 1.0)])
|
|
image_shape = (10, 10, 3)
|
|
factor = poly.compute_out_of_image_fraction(image_shape)
|
|
assert np.isclose(factor, 0.5, atol=1e-3)
|
|
|
|
def test_standard_polygon_not_ooi(self):
|
|
poly = ia.Polygon([(1.0, 1.0), (2.0, 1.0), (2.0, 2.0)])
|
|
image_shape = (10, 10, 3)
|
|
factor = poly.compute_out_of_image_fraction(image_shape)
|
|
assert np.isclose(factor, 0.0)
|
|
|
|
def test_standard_polygon_partially_ooi(self):
|
|
poly = ia.Polygon([(9.0, 1.0), (11.0, 1.0), (11.0, 3.0), (9.0, 3.0)])
|
|
image_shape = (10, 10, 3)
|
|
factor = poly.compute_out_of_image_fraction(image_shape)
|
|
assert np.isclose(factor, 0.5, atol=1e-3)
|
|
|
|
def test_standard_polygon_fully_ooi(self):
|
|
poly = ia.Polygon([(11.0, 1.0), (13.0, 1.0), (13.0, 3.0), (11.0, 3.0)])
|
|
image_shape = (10, 10, 3)
|
|
factor = poly.compute_out_of_image_fraction(image_shape)
|
|
assert np.isclose(factor, 1.0)
|
|
|
|
def test_zero_sized_image_axes(self):
|
|
poly = ia.Polygon([(1.0, 1.0), (2.0, 1.0), (2.0, 2.0)])
|
|
image_shape = (0, 0, 3)
|
|
factor = poly.compute_out_of_image_fraction(image_shape)
|
|
assert np.isclose(factor, 1.0)
|
|
|
|
|
|
class TestPolygon_is_fully_within_image(unittest.TestCase):
|
|
def test_barely_within_image__shape_as_3d_tuple(self):
|
|
poly = ia.Polygon([(0, 0), (0.999, 0), (0.999, 0.999), (0, 0.999)])
|
|
assert poly.is_fully_within_image((1, 1, 3))
|
|
|
|
def test_barely_within_image__shape_as_2d_tuple(self):
|
|
poly = ia.Polygon([(0, 0), (0.999, 0), (0.999, 0.999), (0, 0.999)])
|
|
assert poly.is_fully_within_image((1, 1))
|
|
|
|
def test_barely_within_image__shape_as_ndarray(self):
|
|
poly = ia.Polygon([(0, 0), (0.999, 0), (0.999, 0.999), (0, 0.999)])
|
|
assert poly.is_fully_within_image(
|
|
np.zeros((1, 1, 3), dtype=np.uint8)
|
|
)
|
|
|
|
def test_right_and_bottom_sides_overlap__shape_as_3d_tuple(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
assert not poly.is_fully_within_image((1, 1, 3))
|
|
|
|
def test_right_and_bottom_sides_overlap__shape_as_2d_tuple(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
assert not poly.is_fully_within_image((1, 1))
|
|
|
|
def test_right_and_bottom_sides_overlap__shape_as_ndarray(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
assert not poly.is_fully_within_image(
|
|
np.zeros((1, 1, 3), dtype=np.uint8)
|
|
)
|
|
|
|
def test_far_outside_of_image__shape_as_3d_tuple(self):
|
|
poly = ia.Polygon([(100, 100), (101, 100), (101, 101), (100, 101)])
|
|
assert not poly.is_fully_within_image((1, 1, 3))
|
|
|
|
def test_exterior_empty_fails(self):
|
|
poly = ia.Polygon([])
|
|
with self.assertRaises(Exception):
|
|
_ = poly.is_fully_within_image((1, 1, 3))
|
|
|
|
|
|
class TestPolygon_is_partly_within_image(unittest.TestCase):
|
|
def test_barely_within_image__shape_as_3d_tuple(self):
|
|
poly = ia.Polygon([(0, 0), (0.999, 0), (0.999, 0.999), (0, 0.999)])
|
|
assert poly.is_partly_within_image((1, 1, 3))
|
|
|
|
def test_barely_within_image__shape_as_2d_tuple(self):
|
|
poly = ia.Polygon([(0, 0), (0.999, 0), (0.999, 0.999), (0, 0.999)])
|
|
assert poly.is_partly_within_image((1, 1))
|
|
|
|
def test_barely_within_image__shape_as_ndarray(self):
|
|
poly = ia.Polygon([(0, 0), (0.999, 0), (0.999, 0.999), (0, 0.999)])
|
|
assert poly.is_partly_within_image(
|
|
np.zeros((1, 1, 3), dtype=np.uint8)
|
|
)
|
|
|
|
def test_right_and_bottom_sides_overlap__shape_as_3d_tuple(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
assert poly.is_partly_within_image((1, 1, 3))
|
|
|
|
def test_right_and_bottom_sides_overlap__shape_as_2d_tuple(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
assert poly.is_partly_within_image((1, 1))
|
|
|
|
def test_right_and_bottom_sides_overlap__shape_as_ndarray(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
assert poly.is_partly_within_image(np.zeros((1, 1, 3), dtype=np.uint8))
|
|
|
|
def test_far_outside_of_image__shape_as_3d_tuple(self):
|
|
poly = ia.Polygon([(100, 100), (101, 100), (101, 101), (100, 101)])
|
|
assert not poly.is_partly_within_image((1, 1, 3))
|
|
|
|
def test_far_outside_of_image__shape_as_2d_tuple(self):
|
|
poly = ia.Polygon([(100, 100), (101, 100), (101, 101), (100, 101)])
|
|
assert not poly.is_partly_within_image((1, 1))
|
|
|
|
def test_far_outside_of_image__shape_as_ndarray(self):
|
|
poly = ia.Polygon([(100, 100), (101, 100), (101, 101), (100, 101)])
|
|
assert not poly.is_partly_within_image(
|
|
np.zeros((1, 1, 3), dtype=np.uint8)
|
|
)
|
|
|
|
def test_exterior_empty_fails(self):
|
|
poly = ia.Polygon([])
|
|
with self.assertRaises(Exception):
|
|
_ = poly.is_partly_within_image((1, 1, 3))
|
|
|
|
|
|
class TestPolygon_is_out_of_image(unittest.TestCase):
|
|
def test_barely_within_image(self):
|
|
shapes = [(1, 1, 3), (1, 1), np.zeros((1, 1, 3), dtype=np.uint8)]
|
|
for shape in shapes:
|
|
shape_str = (
|
|
str(shape) if isinstance(shape, tuple) else str(shape.shape)
|
|
)
|
|
with self.subTest(shape=shape_str):
|
|
poly = ia.Polygon([(0, 0), (0.999, 0), (0.999, 0.999), (0, 0.999)])
|
|
is_ooi = poly.is_out_of_image
|
|
assert not is_ooi(shape, partly=False, fully=False)
|
|
assert not is_ooi(shape, partly=True, fully=False)
|
|
assert not is_ooi(shape, partly=False, fully=True)
|
|
assert not is_ooi(shape, partly=True, fully=True)
|
|
|
|
def test_right_and_bottom_sides_overlap__shape_as_ndarray(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
shape = np.zeros((1, 1, 3), dtype=np.uint8)
|
|
|
|
assert not poly.is_out_of_image(shape, partly=False, fully=False)
|
|
assert poly.is_out_of_image(shape, partly=True, fully=False)
|
|
assert not poly.is_out_of_image(shape, partly=False, fully=True)
|
|
assert poly.is_out_of_image(shape, partly=True, fully=True)
|
|
|
|
def test_far_outside_of_image__shape_as_3d_tuple(self):
|
|
poly = ia.Polygon([(100, 100), (101, 100), (101, 101), (100, 101)])
|
|
shape = (1, 1, 3)
|
|
|
|
assert not poly.is_out_of_image(shape, partly=False, fully=False)
|
|
assert not poly.is_out_of_image(shape, partly=True, fully=False)
|
|
assert poly.is_out_of_image(shape, partly=False, fully=True)
|
|
assert poly.is_out_of_image(shape, partly=True, fully=True)
|
|
|
|
def test_triangle_partially_outside_of_image(self):
|
|
poly = ia.Polygon([(8, 11), (11, 8), (11, 11)])
|
|
assert not poly.is_out_of_image((100, 100, 3), fully=True, partly=True)
|
|
assert not poly.is_out_of_image((10, 10, 3), fully=True, partly=False)
|
|
assert poly.is_out_of_image((10, 10, 3), fully=False, partly=True)
|
|
|
|
def test_rectangle_with_all_corners_outside_of_the_image(self):
|
|
poly = ia.Polygon([(-1.0, -1.0), (2.0, -1.0), (2.0, 2.0), (-1.0, 2.0)])
|
|
assert not poly.is_out_of_image((100, 100, 3), fully=True, partly=False)
|
|
assert poly.is_out_of_image((100, 100, 3), fully=False, partly=True)
|
|
assert not poly.is_out_of_image((1, 1, 3), fully=True, partly=False)
|
|
assert poly.is_out_of_image((1, 1, 3), fully=False, partly=True)
|
|
assert poly.is_out_of_image((1, 1, 3), fully=True, partly=True)
|
|
|
|
def test_polygon_with_two_points(self):
|
|
poly = ia.Polygon([(2.0, 2.0), (10.0, 2.0)])
|
|
assert not poly.is_out_of_image((100, 100, 3), fully=True, partly=False)
|
|
assert not poly.is_out_of_image((100, 100, 3), fully=False, partly=True)
|
|
assert not poly.is_out_of_image((3, 3, 3), fully=True, partly=False)
|
|
assert poly.is_out_of_image((3, 3, 3), fully=False, partly=True)
|
|
assert poly.is_out_of_image((1, 1, 3), fully=True, partly=False)
|
|
assert not poly.is_out_of_image((1, 1, 3), fully=False, partly=True)
|
|
|
|
def test_polygon_with_one_point(self):
|
|
poly = ia.Polygon([(2.0, 2.0)])
|
|
assert not poly.is_out_of_image((100, 100, 3), fully=True, partly=False)
|
|
assert not poly.is_out_of_image((100, 100, 3), fully=False, partly=True)
|
|
assert poly.is_out_of_image((1, 1, 3), fully=True, partly=False)
|
|
assert not poly.is_out_of_image((1, 1, 3), fully=False, partly=True)
|
|
|
|
def test_polygon_with_zero_points_fails(self):
|
|
poly = ia.Polygon([])
|
|
got_exception = False
|
|
try:
|
|
poly.is_out_of_image((1, 1, 3))
|
|
except Exception as exc:
|
|
assert (
|
|
"Cannot determine whether the polygon is inside the "
|
|
"image" in str(exc))
|
|
got_exception = True
|
|
assert got_exception
|
|
|
|
|
|
class TestPolygon_cut_out_of_image(unittest.TestCase):
|
|
@mock.patch("imgaug.augmentables.polys.Polygon.clip_out_of_image")
|
|
def test_warns_of_deprecation(self, mock_clip):
|
|
with warnings.catch_warnings(record=True) as caught_warnings:
|
|
warnings.simplefilter("always")
|
|
polygon = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
shape = (1, 1)
|
|
_ = polygon.cut_out_of_image(shape)
|
|
|
|
mock_clip.assert_called_once_with(shape)
|
|
assert "is deprecated" in str(caught_warnings[0].message)
|
|
|
|
|
|
class TestPolygon_clip_out_of_image(unittest.TestCase):
|
|
def test_polygon_inside_of_image(self):
|
|
# poly inside image
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)], label=None)
|
|
image = np.zeros((1, 1, 3), dtype=np.uint8)
|
|
multipoly_clipped = poly.clip_out_of_image(image)
|
|
assert isinstance(multipoly_clipped, list)
|
|
assert len(multipoly_clipped) == 1
|
|
assert multipoly_clipped[0].exterior_almost_equals(poly.exterior)
|
|
assert multipoly_clipped[0].label is None
|
|
|
|
def test_polygon_half_outside_of_image(self):
|
|
# square poly shifted by x=0.5, y=0.5 => half out of image
|
|
poly = ia.Polygon([(0.5, 0.5), (1.5, 0.5), (1.5, 1.5), (0.5, 1.5)],
|
|
label="test")
|
|
image = np.zeros((1, 1, 3), dtype=np.uint8)
|
|
multipoly_clipped = poly.clip_out_of_image(image)
|
|
assert isinstance(multipoly_clipped, list)
|
|
assert len(multipoly_clipped) == 1
|
|
assert multipoly_clipped[0].exterior_almost_equals(np.float32([
|
|
[0.5, 0.5],
|
|
[1.0, 0.5],
|
|
[1.0, 1.0],
|
|
[0.5, 1.0]
|
|
]))
|
|
assert multipoly_clipped[0].label == "test"
|
|
|
|
def test_single_edge_intersecting_with_image_edge(self):
|
|
# square poly with a single edge intersecting the image (issue #310)
|
|
poly = ia.Polygon([(-1.0, 0.0), (0.0, 0.0), (0.0, 1.0), (-1.0, 1.0)])
|
|
image = np.zeros((1, 1, 3), dtype=np.uint8)
|
|
multipoly_clipped = poly.clip_out_of_image(image)
|
|
assert isinstance(multipoly_clipped, list)
|
|
assert len(multipoly_clipped) == 0
|
|
|
|
def test_tiny_area_around_image_edge_intersecting(self):
|
|
# square poly with a tiny area on the left image edge intersecting with
|
|
# the image
|
|
offset = 1e-4
|
|
poly = ia.Polygon([(-1.0, 0.0), (0.0+offset, 0.0),
|
|
(0.0+offset, 1.0), (-1.0, 1.0)])
|
|
image = np.zeros((1, 1, 3), dtype=np.uint8)
|
|
multipoly_clipped = poly.clip_out_of_image(image)
|
|
assert isinstance(multipoly_clipped, list)
|
|
assert len(multipoly_clipped) == 1
|
|
assert multipoly_clipped[0].exterior_almost_equals(np.float32([
|
|
[0.0, 0.0],
|
|
[0.0+offset, 0.0],
|
|
[0.0+offset, 1.0],
|
|
[0.0, 1.0]
|
|
]))
|
|
|
|
def test_single_point_intersecting_with_image(self):
|
|
# square poly with a single point intersecting the image (issue #310)
|
|
poly = ia.Polygon([(-1.0, -1.0), (0.0, -1.0), (0.0, 0.0), (-1.0, 0.0)])
|
|
image = np.zeros((1, 1, 3), dtype=np.uint8)
|
|
multipoly_clipped = poly.clip_out_of_image(image)
|
|
assert isinstance(multipoly_clipped, list)
|
|
assert len(multipoly_clipped) == 0
|
|
|
|
def test_tiny_area_around_image_corner_point_intersecting_with_image(self):
|
|
# square poly with a tiny area around the top left image corner
|
|
# intersecting with the the image
|
|
offset = 1e-4
|
|
poly = ia.Polygon([(-1.0, -1.0), (0.0, -1.0),
|
|
(0.0+offset, 0.0+offset), (-1.0, 0.0)])
|
|
image = np.zeros((1, 1, 3), dtype=np.uint8)
|
|
multipoly_clipped = poly.clip_out_of_image(image)
|
|
assert isinstance(multipoly_clipped, list)
|
|
assert len(multipoly_clipped) == 1
|
|
assert multipoly_clipped[0].exterior_almost_equals(np.float32([
|
|
[0.0, 0.0],
|
|
[0.0+offset, 0.0],
|
|
[0.0+offset, 0.0+offset],
|
|
[0.0, 0.0+offset]
|
|
]))
|
|
|
|
def test_polygon_clipped_to_two_separate_polygons(self):
|
|
# non-square poly, with one rectangle on the left side of the image
|
|
# and one on the right side, both sides are connected by a thin strip
|
|
# below the image after clipping it should become two rectangles
|
|
poly = ia.Polygon([(-0.1, 0.0), (0.4, 0.0), (0.4, 1.1), (0.6, 1.1),
|
|
(0.6, 0.0), (1.1, 0.0), (1.1, 1.2), (-0.1, 1.2)],
|
|
label="test")
|
|
image = np.zeros((1, 1, 3), dtype=np.uint8)
|
|
multipoly_clipped = poly.clip_out_of_image(image)
|
|
assert isinstance(multipoly_clipped, list)
|
|
assert len(multipoly_clipped) == 2
|
|
assert multipoly_clipped[0].exterior_almost_equals(np.float32([
|
|
[0.0, 0.0],
|
|
[0.4, 0.0],
|
|
[0.4, 1.0],
|
|
[0.0, 1.0]
|
|
]))
|
|
assert multipoly_clipped[0].label == "test"
|
|
assert multipoly_clipped[1].exterior_almost_equals(np.float32([
|
|
[0.6, 0.0],
|
|
[1.0, 0.0],
|
|
[1.0, 1.0],
|
|
[0.6, 1.0]
|
|
]))
|
|
assert multipoly_clipped[0].label == "test"
|
|
|
|
def test_polygon_fully_outside_of_the_image(self):
|
|
# poly outside of image
|
|
poly = ia.Polygon([(10.0, 10.0), (11,.0, 10.0), (11.0, 11.0)])
|
|
multipoly_clipped = poly.clip_out_of_image((5, 5, 3))
|
|
assert isinstance(multipoly_clipped, list)
|
|
assert len(multipoly_clipped) == 0
|
|
|
|
def test_small_intersection_with_image_one_poly_point_inside_image(self):
|
|
# poly area partially inside image
|
|
# and one point is inside the image
|
|
poly = ia.Polygon([(50, 50), (110, 50), (110, 110), (50, 110)])
|
|
multipoly_clipped = poly.clip_out_of_image((100, 100, 3))
|
|
assert isinstance(multipoly_clipped, list)
|
|
assert len(multipoly_clipped) == 1
|
|
assert multipoly_clipped[0].exterior_almost_equals(np.float32([
|
|
[50, 50],
|
|
[100, 50],
|
|
[100, 100],
|
|
[50, 100]
|
|
]))
|
|
|
|
def test_small_intersection_with_image_no_poly_point_inside_image(self):
|
|
# poly area partially inside image,
|
|
# but not a single point is inside the image
|
|
poly = ia.Polygon([(100+0.5*100, 0),
|
|
(100+0.5*100, 100+0.5*100),
|
|
(0, 100+0.5*100)])
|
|
multipoly_clipped = poly.clip_out_of_image((100, 100, 3))
|
|
assert isinstance(multipoly_clipped, list)
|
|
assert len(multipoly_clipped) == 1
|
|
assert multipoly_clipped[0].exterior_almost_equals(np.float32([
|
|
[100, 0.5*100],
|
|
[100, 100],
|
|
[0.5*100, 100]
|
|
]))
|
|
|
|
def test_polygon_with_two_points_that_is_not_clipped(self):
|
|
# polygon with two points
|
|
poly = ia.Polygon([(2.0, 2.0), (10.0, 2.0)])
|
|
multipoly_clipped = poly.clip_out_of_image((100, 100, 3))
|
|
assert isinstance(multipoly_clipped, list)
|
|
assert len(multipoly_clipped) == 1
|
|
assert multipoly_clipped[0].exterior_almost_equals(np.float32([
|
|
[2.0, 2.0],
|
|
[10.0, 2.0]
|
|
]))
|
|
|
|
def test_polygon_with_two_points_that_is_clipped(self):
|
|
poly = ia.Polygon([(2.0, 2.0), (10.0, 2.0)])
|
|
multipoly_clipped = poly.clip_out_of_image((3, 3, 3))
|
|
assert isinstance(multipoly_clipped, list)
|
|
assert len(multipoly_clipped) == 1
|
|
assert multipoly_clipped[0].exterior_almost_equals(np.float32([
|
|
[2.0, 2.0],
|
|
[3.0, 2.0]
|
|
]), max_distance=1e-3)
|
|
|
|
def test_polygon_with_one_point_that_is_not_clipped(self):
|
|
# polygon with a single point
|
|
poly = ia.Polygon([(2.0, 2.0)])
|
|
multipoly_clipped = poly.clip_out_of_image((3, 3, 3))
|
|
assert isinstance(multipoly_clipped, list)
|
|
assert len(multipoly_clipped) == 1
|
|
assert multipoly_clipped[0].exterior_almost_equals(np.float32([
|
|
[2.0, 2.0]
|
|
]))
|
|
|
|
def test_polygon_with_one_point_that_is_clipped(self):
|
|
poly = ia.Polygon([(2.0, 2.0)])
|
|
multipoly_clipped = poly.clip_out_of_image((1, 1, 3))
|
|
assert isinstance(multipoly_clipped, list)
|
|
assert len(multipoly_clipped) == 0
|
|
|
|
|
|
class TestPolygon_shift_(unittest.TestCase):
|
|
@property
|
|
def _is_inplace(self):
|
|
return True
|
|
|
|
def _func(self, poly, *args, **kwargs):
|
|
def _func_impl():
|
|
return poly.shift_(*args, **kwargs)
|
|
|
|
return wrap_shift_deprecation(_func_impl, *args, **kwargs)
|
|
|
|
@property
|
|
def poly(self):
|
|
return ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)], label="test")
|
|
|
|
def test_shift_along_xy(self):
|
|
poly_shifted = self._func(self.poly, x=1, y=2)
|
|
assert np.allclose(poly_shifted.exterior, np.float32([
|
|
[0 + 1, 0 + 2],
|
|
[1 + 1, 0 + 2],
|
|
[1 + 1, 1 + 2],
|
|
[0 + 1, 1 + 2]
|
|
]))
|
|
assert poly_shifted.label == "test"
|
|
|
|
def test_inplaceness(self):
|
|
poly = self.poly
|
|
poly2 = self._func(poly, y=1)
|
|
|
|
if self._is_inplace:
|
|
assert poly is poly2
|
|
else:
|
|
assert poly is not poly2
|
|
|
|
|
|
class TestPolygon_shift(TestPolygon_shift_):
|
|
@property
|
|
def _is_inplace(self):
|
|
return False
|
|
|
|
def _func(self, poly, *args, **kwargs):
|
|
def _func_impl():
|
|
return poly.shift(*args, **kwargs)
|
|
|
|
return wrap_shift_deprecation(_func_impl, *args, **kwargs)
|
|
|
|
def test_shift_does_not_work_inplace(self):
|
|
poly = self.poly
|
|
poly_shifted = self._func(poly, top=1)
|
|
assert np.allclose(poly.exterior, np.float32([
|
|
[0, 0],
|
|
[1, 0],
|
|
[1, 1],
|
|
[0, 1]
|
|
]))
|
|
assert np.allclose(poly_shifted.exterior, np.float32([
|
|
[0, 1],
|
|
[1, 1],
|
|
[1, 2],
|
|
[0, 2]
|
|
]))
|
|
|
|
def test_shift_from_top(self):
|
|
for v in [1, 0, -1, 0.5]:
|
|
with self.subTest(top=v):
|
|
poly_shifted = self._func(self.poly, top=v)
|
|
assert np.allclose(poly_shifted.exterior, np.float32([
|
|
[0, 0 + v],
|
|
[1, 0 + v],
|
|
[1, 1 + v],
|
|
[0, 1 + v]
|
|
]))
|
|
assert poly_shifted.label == "test"
|
|
|
|
def test_shift_from_bottom(self):
|
|
for v in [1, 0, -1, 0.5]:
|
|
with self.subTest(bottom=v):
|
|
poly_shifted = self._func(self.poly, bottom=v)
|
|
assert np.allclose(poly_shifted.exterior, np.float32([
|
|
[0, 0 - v],
|
|
[1, 0 - v],
|
|
[1, 1 - v],
|
|
[0, 1 - v]
|
|
]))
|
|
assert poly_shifted.label == "test"
|
|
|
|
def test_shift_from_top_and_bottom(self):
|
|
for v in [1, 0, -1, 0.5]:
|
|
with self.subTest(top=v, bottom=-v):
|
|
poly_shifted = self._func(self.poly, top=v, bottom=-v)
|
|
assert np.allclose(poly_shifted.exterior, np.float32([
|
|
[0, 0 + 2*v],
|
|
[1, 0 + 2*v],
|
|
[1, 1 + 2*v],
|
|
[0, 1 + 2*v]
|
|
]))
|
|
assert poly_shifted.label == "test"
|
|
|
|
def test_shift_from_left(self):
|
|
for v in [1, 0, -1, 0.5]:
|
|
with self.subTest(left=v):
|
|
poly_shifted = self._func(self.poly, left=v)
|
|
assert np.allclose(poly_shifted.exterior, np.float32([
|
|
[0 + v, 0],
|
|
[1 + v, 0],
|
|
[1 + v, 1],
|
|
[0 + v, 1]
|
|
]))
|
|
assert poly_shifted.label == "test"
|
|
|
|
def test_shift_from_right(self):
|
|
for v in [1, 0, -1, 0.5]:
|
|
with self.subTest(right=v):
|
|
poly_shifted = self._func(self.poly, right=v)
|
|
assert np.allclose(poly_shifted.exterior, np.float32([
|
|
[0 - v, 0],
|
|
[1 - v, 0],
|
|
[1 - v, 1],
|
|
[0 - v, 1]
|
|
]))
|
|
assert poly_shifted.label == "test"
|
|
|
|
def test_shift_from_left_and_right(self):
|
|
for v in [1, 0, -1, 0.5]:
|
|
with self.subTest(left=v, right=-v):
|
|
poly_shifted = self._func(self.poly, left=v, right=-v)
|
|
assert np.allclose(poly_shifted.exterior, np.float32([
|
|
[0 + 2 * v, 0],
|
|
[1 + 2 * v, 0],
|
|
[1 + 2 * v, 1],
|
|
[0 + 2 * v, 1]
|
|
]))
|
|
assert poly_shifted.label == "test"
|
|
|
|
|
|
class TestPolygon_draw_on_image(unittest.TestCase):
|
|
@property
|
|
def image(self):
|
|
return np.tile(
|
|
np.arange(100).reshape((10, 10, 1)),
|
|
(1, 1, 3)
|
|
).astype(np.uint8)
|
|
|
|
def test_square_polygon(self):
|
|
# simple drawing of square
|
|
poly = ia.Polygon([(2, 2), (8, 2), (8, 8), (2, 8)])
|
|
image = self.image
|
|
image_poly = poly.draw_on_image(image,
|
|
color=[32, 128, 32],
|
|
color_face=[32, 128, 32],
|
|
color_lines=[0, 255, 0],
|
|
color_points=[0, 255, 0],
|
|
alpha=1.0,
|
|
alpha_face=1.0,
|
|
alpha_lines=1.0,
|
|
alpha_points=0.0,
|
|
raise_if_out_of_image=False)
|
|
assert image_poly.dtype.type == np.uint8
|
|
assert image_poly.shape == (10, 10, 3)
|
|
# draw did not change original image (copy=True)
|
|
assert np.sum(image) == 3 * np.sum(np.arange(100))
|
|
|
|
for c_idx, value in enumerate([0, 255, 0]):
|
|
# left boundary
|
|
assert np.all(image_poly[2:9, 2:3, c_idx]
|
|
== np.zeros((7, 1), dtype=np.uint8) + value)
|
|
# right boundary
|
|
assert np.all(image_poly[2:9, 8:9, c_idx]
|
|
== np.zeros((7, 1), dtype=np.uint8) + value)
|
|
# top boundary
|
|
assert np.all(image_poly[2:3, 2:9, c_idx]
|
|
== np.zeros((1, 7), dtype=np.uint8) + value)
|
|
# bottom boundary
|
|
assert np.all(image_poly[8:9, 2:9, c_idx]
|
|
== np.zeros((1, 7), dtype=np.uint8) + value)
|
|
expected = np.tile(
|
|
np.uint8([32, 128, 32]).reshape((1, 1, 3)),
|
|
(5, 5, 1)
|
|
)
|
|
assert np.all(image_poly[3:8, 3:8, :] == expected)
|
|
|
|
def test_square_polygon_use_no_color_subargs(self):
|
|
# simple drawing of square, use only "color" arg
|
|
poly = ia.Polygon([(2, 2), (8, 2), (8, 8), (2, 8)])
|
|
image = self.image
|
|
image_poly = poly.draw_on_image(image,
|
|
color=[0, 255, 0],
|
|
alpha=1.0,
|
|
alpha_face=1.0,
|
|
alpha_lines=1.0,
|
|
alpha_points=0.0,
|
|
raise_if_out_of_image=False)
|
|
assert image_poly.dtype.type == np.uint8
|
|
assert image_poly.shape == (10, 10, 3)
|
|
# draw did not change original image (copy=True)
|
|
assert np.sum(image) == 3 * np.sum(np.arange(100))
|
|
|
|
for c_idx, value in enumerate([0, 0.5*255, 0]):
|
|
value = int(np.round(value))
|
|
# left boundary
|
|
assert np.all(image_poly[2:9, 2:3, c_idx]
|
|
== np.zeros((7, 1), dtype=np.uint8) + value)
|
|
# right boundary
|
|
assert np.all(image_poly[2:9, 8:9, c_idx]
|
|
== np.zeros((7, 1), dtype=np.uint8) + value)
|
|
# top boundary
|
|
assert np.all(image_poly[2:3, 2:9, c_idx]
|
|
== np.zeros((1, 7), dtype=np.uint8) + value)
|
|
# bottom boundary
|
|
assert np.all(image_poly[8:9, 2:9, c_idx]
|
|
== np.zeros((1, 7), dtype=np.uint8) + value)
|
|
expected = np.tile(
|
|
np.uint8([0, 255, 0]).reshape((1, 1, 3)),
|
|
(5, 5, 1)
|
|
)
|
|
assert np.all(image_poly[3:8, 3:8, :] == expected)
|
|
|
|
def test_square_polygon_on_float32_image(self):
|
|
# simple drawing of square with float32 input
|
|
poly = ia.Polygon([(2, 2), (8, 2), (8, 8), (2, 8)])
|
|
image = self.image
|
|
image_poly = poly.draw_on_image(image.astype(np.float32),
|
|
color=[32, 128, 32],
|
|
color_face=[32, 128, 32],
|
|
color_lines=[0, 255, 0],
|
|
color_points=[0, 255, 0],
|
|
alpha=1.0,
|
|
alpha_face=1.0,
|
|
alpha_lines=1.0,
|
|
alpha_points=0.0,
|
|
raise_if_out_of_image=False)
|
|
assert image_poly.dtype.type == np.float32
|
|
assert image_poly.shape == (10, 10, 3)
|
|
for c_idx, value in enumerate([0, 255, 0]):
|
|
# left boundary
|
|
assert np.allclose(image_poly[2:9, 2:3, c_idx],
|
|
np.zeros((7, 1), dtype=np.float32) + value)
|
|
# right boundary
|
|
assert np.allclose(image_poly[2:9, 8:9, c_idx],
|
|
np.zeros((7, 1), dtype=np.float32) + value)
|
|
# top boundary
|
|
assert np.allclose(image_poly[2:3, 2:9, c_idx],
|
|
np.zeros((1, 7), dtype=np.float32) + value)
|
|
# bottom boundary
|
|
assert np.allclose(image_poly[8:9, 2:9, c_idx],
|
|
np.zeros((1, 7), dtype=np.float32) + value)
|
|
expected = np.tile(
|
|
np.float32([32, 128, 32]).reshape((1, 1, 3)),
|
|
(5, 5, 1)
|
|
)
|
|
assert np.allclose(image_poly[3:8, 3:8, :], expected)
|
|
|
|
def test_square_polygon_half_outside_of_image(self):
|
|
# drawing of poly that is half out of image
|
|
poly = ia.Polygon([(2, 2+5), (8, 2+5), (8, 8+5), (2, 8+5)])
|
|
image = self.image
|
|
image_poly = poly.draw_on_image(image,
|
|
color=[32, 128, 32],
|
|
color_face=[32, 128, 32],
|
|
color_lines=[0, 255, 0],
|
|
color_points=[0, 255, 0],
|
|
alpha=1.0,
|
|
alpha_face=1.0,
|
|
alpha_lines=1.0,
|
|
alpha_points=0.0,
|
|
raise_if_out_of_image=False)
|
|
assert image_poly.dtype.type == np.uint8
|
|
assert image_poly.shape == (10, 10, 3)
|
|
# draw did not change original image (copy=True)
|
|
assert np.sum(image) == 3 * np.sum(np.arange(100))
|
|
|
|
for c_idx, value in enumerate([0, 255, 0]):
|
|
# left boundary
|
|
assert np.all(image_poly[2+5:, 2:3, c_idx]
|
|
== np.zeros((3, 1), dtype=np.uint8) + value)
|
|
# right boundary
|
|
assert np.all(image_poly[2+5:, 8:9, c_idx]
|
|
== np.zeros((3, 1), dtype=np.uint8) + value)
|
|
# top boundary
|
|
assert np.all(image_poly[2+5:3+5, 2:9, c_idx]
|
|
== np.zeros((1, 7), dtype=np.uint8) + value)
|
|
expected = np.tile(
|
|
np.uint8([32, 128, 32]).reshape((1, 1, 3)),
|
|
(2, 5, 1)
|
|
)
|
|
assert np.all(image_poly[3+5:, 3:8, :] == expected)
|
|
|
|
def test_square_polygon_half_outside_of_image_with_raise_if_ooi(self):
|
|
# drawing of poly that is half out of image, with
|
|
# raise_if_out_of_image=True
|
|
poly = ia.Polygon([(2, 2+5), (8, 2+5), (8, 8+5), (0, 8+5)])
|
|
image = self.image
|
|
got_exception = False
|
|
try:
|
|
_ = poly.draw_on_image(image,
|
|
color=[32, 128, 32],
|
|
color_face=[32, 128, 32],
|
|
color_lines=[0, 255, 0],
|
|
color_points=[0, 255, 0],
|
|
alpha=1.0,
|
|
alpha_face=1.0,
|
|
alpha_lines=1.0,
|
|
alpha_points=0.0,
|
|
raise_if_out_of_image=True)
|
|
except Exception as exc:
|
|
assert "Cannot draw polygon" in str(exc)
|
|
got_exception = True
|
|
# only polygons fully outside of the image plane lead to exceptions
|
|
assert not got_exception
|
|
|
|
def test_polygon_fully_outside_of_image(self):
|
|
# drawing of poly that is fully out of image
|
|
poly = ia.Polygon([(100, 100), (100+10, 100), (100+10, 100+10),
|
|
(100, 100+10)])
|
|
image = self.image
|
|
image_poly = poly.draw_on_image(image,
|
|
color=[32, 128, 32],
|
|
color_face=[32, 128, 32],
|
|
color_lines=[0, 255, 0],
|
|
color_points=[0, 255, 0],
|
|
alpha=1.0,
|
|
alpha_face=1.0,
|
|
alpha_lines=1.0,
|
|
alpha_points=0.0,
|
|
raise_if_out_of_image=False)
|
|
assert np.array_equal(image_poly, image)
|
|
|
|
def test_polygon_fully_outside_of_image_with_raise_if_ooi(self):
|
|
# drawing of poly that is fully out of image,
|
|
# with raise_if_out_of_image=True
|
|
poly = ia.Polygon([(100, 100), (100+10, 100), (100+10, 100+10),
|
|
(100, 100+10)])
|
|
image = self.image
|
|
got_exception = False
|
|
try:
|
|
_ = poly.draw_on_image(image,
|
|
color=[32, 128, 32],
|
|
color_face=[32, 128, 32],
|
|
color_lines=[0, 255, 0],
|
|
color_points=[0, 255, 0],
|
|
alpha=1.0,
|
|
alpha_face=1.0,
|
|
alpha_lines=1.0,
|
|
alpha_points=0.0,
|
|
raise_if_out_of_image=True)
|
|
except Exception as exc:
|
|
assert "Cannot draw polygon" in str(exc)
|
|
got_exception = True
|
|
assert got_exception
|
|
|
|
def test_only_lines_visible(self):
|
|
# face+points invisible via alpha
|
|
poly = ia.Polygon([(2, 2), (8, 2), (8, 8), (2, 8)])
|
|
image = self.image
|
|
image_poly = poly.draw_on_image(image,
|
|
color=[32, 128, 32],
|
|
color_face=[32, 128, 32],
|
|
color_lines=[0, 255, 0],
|
|
color_points=[0, 255, 0],
|
|
alpha=1.0,
|
|
alpha_face=0.0,
|
|
alpha_lines=1.0,
|
|
alpha_points=0.0,
|
|
raise_if_out_of_image=False)
|
|
assert image_poly.dtype.type == np.uint8
|
|
assert image_poly.shape == (10, 10, 3)
|
|
# draw did not change original image (copy=True)
|
|
assert np.sum(image) == 3 * np.sum(np.arange(100))
|
|
for c_idx, value in enumerate([0, 255, 0]):
|
|
# left boundary
|
|
assert np.all(image_poly[2:9, 2:3, c_idx]
|
|
== np.zeros((7, 1), dtype=np.uint8) + value)
|
|
assert np.all(image_poly[3:8, 3:8, :] == image[3:8, 3:8, :])
|
|
|
|
def test_only_face_visible(self):
|
|
# boundary+points invisible via alpha
|
|
poly = ia.Polygon([(2, 2), (8, 2), (8, 8), (2, 8)])
|
|
image = self.image
|
|
image_poly = poly.draw_on_image(image,
|
|
color=[32, 128, 32],
|
|
color_face=[32, 128, 32],
|
|
color_lines=[0, 255, 0],
|
|
color_points=[0, 255, 0],
|
|
alpha=1.0,
|
|
alpha_face=1.0,
|
|
alpha_lines=0.0,
|
|
alpha_points=0.0,
|
|
raise_if_out_of_image=False)
|
|
assert image_poly.dtype.type == np.uint8
|
|
assert image_poly.shape == (10, 10, 3)
|
|
# draw did not change original image (copy=True)
|
|
assert np.sum(image) == 3 * np.sum(np.arange(100))
|
|
expected = np.tile(
|
|
np.uint8([32, 128, 32]).reshape((1, 1, 3)), (6, 6, 1)
|
|
)
|
|
assert np.all(image_poly[2:8, 2:8, :] == expected)
|
|
|
|
def test_alpha_is_080(self):
|
|
# alpha=0.8
|
|
poly = ia.Polygon([(2, 2), (8, 2), (8, 8), (2, 8)])
|
|
image = self.image
|
|
image_poly = poly.draw_on_image(image,
|
|
color=[32, 128, 32],
|
|
color_face=[32, 128, 32],
|
|
color_lines=[0, 255, 0],
|
|
color_points=[0, 255, 0],
|
|
alpha=0.8,
|
|
alpha_points=0.0,
|
|
raise_if_out_of_image=False)
|
|
assert image_poly.dtype.type == np.uint8
|
|
assert image_poly.shape == (10, 10, 3)
|
|
for c_idx, value in enumerate([0, 255, 0]):
|
|
expected = np.round(
|
|
(1-0.8)*image[2:9, 8:9, c_idx]
|
|
+ np.full((7, 1), 0.8*value, dtype=np.float32)
|
|
).astype(np.uint8)
|
|
|
|
# right boundary
|
|
assert np.all(image_poly[2:9, 8:9, c_idx] == expected)
|
|
expected = (0.8 * 0.5) * np.tile(
|
|
np.uint8([32, 128, 32]).reshape((1, 1, 3)), (5, 5, 1)
|
|
) + (1 - (0.8 * 0.5)) * image[3:8, 3:8, :]
|
|
assert np.all(image_poly[3:8, 3:8, :]
|
|
== np.round(expected).astype(np.uint8))
|
|
|
|
def test_face_and_lines_at_half_visibility(self):
|
|
# alpha of fill and perimeter 0.5
|
|
poly = ia.Polygon([(2, 2), (8, 2), (8, 8), (2, 8)])
|
|
image = self.image
|
|
image_poly = poly.draw_on_image(image,
|
|
color=[32, 128, 32],
|
|
color_face=[32, 128, 32],
|
|
color_lines=[0, 255, 0],
|
|
color_points=[0, 255, 0],
|
|
alpha=1.0,
|
|
alpha_face=0.5,
|
|
alpha_lines=0.5,
|
|
alpha_points=0.0,
|
|
raise_if_out_of_image=False)
|
|
assert image_poly.dtype.type == np.uint8
|
|
assert image_poly.shape == (10, 10, 3)
|
|
for c_idx, value in enumerate([0, 255, 0]):
|
|
expected = np.round(
|
|
0.5*image[2:9, 8:9, c_idx]
|
|
+ np.full((7, 1), 0.5*value, dtype=np.float32)
|
|
).astype(np.uint8)
|
|
|
|
# right boundary
|
|
assert np.all(image_poly[2:9, 8:9, c_idx] == expected)
|
|
|
|
expected = 0.5 * np.tile(
|
|
np.uint8([32, 128, 32]).reshape((1, 1, 3)), (5, 5, 1)
|
|
) + 0.5 * image[3:8, 3:8, :]
|
|
assert np.all(image_poly[3:8, 3:8, :]
|
|
== np.round(expected).astype(np.uint8))
|
|
|
|
# copy=False
|
|
# test deactivated as the function currently does not offer a copy
|
|
# argument
|
|
"""
|
|
image_cp = np.copy(image)
|
|
poly = ia.Polygon([(2, 2), (8, 2), (8, 8), (2, 8)])
|
|
image_poly = poly.draw_on_image(image_cp,
|
|
color_face=[32, 128, 32],
|
|
color_boundary=[0, 255, 0],
|
|
alpha_face=1.0,
|
|
alpha_boundary=1.0,
|
|
raise_if_out_of_image=False)
|
|
assert image_poly.dtype.type == np.uint8
|
|
assert image_poly.shape == (10, 10, 3)
|
|
assert np.all(image_cp == image_poly)
|
|
assert not np.all(image_cp == image)
|
|
for c_idx, value in enumerate([0, 255, 0]):
|
|
# left boundary
|
|
assert np.all(image_poly[2:9, 2:3, c_idx]
|
|
== np.zeros((6, 1, 3), dtype=np.uint8) + value)
|
|
# left boundary
|
|
assert np.all(image_cp[2:9, 2:3, c_idx]
|
|
== np.zeros((6, 1, 3), dtype=np.uint8) + value)
|
|
expected = np.tile(
|
|
np.uint8([32, 128, 32]).reshape((1, 1, 3)),
|
|
(5, 5, 1)
|
|
)
|
|
assert np.all(image_poly[3:8, 3:8, :] == expected)
|
|
assert np.all(image_cp[3:8, 3:8, :] == expected)
|
|
"""
|
|
|
|
|
|
class TestPolygon_extract_from_image(unittest.TestCase):
|
|
@property
|
|
def image(self):
|
|
return np.arange(20*20*2).reshape((20, 20, 2)).astype(np.int32)
|
|
|
|
def test_polygon_is_identical_with_image_shape(self):
|
|
# inside image and completely covers it
|
|
poly = ia.Polygon([(0, 0), (10, 0), (10, 10), (0, 10)])
|
|
subimage = poly.extract_from_image(self.image)
|
|
assert np.array_equal(subimage, self.image[0:10, 0:10, :])
|
|
|
|
def test_polygon_is_subpart_of_image(self):
|
|
# inside image, subpart of it (not all of the image has to be
|
|
# extracted)
|
|
poly = ia.Polygon([(1, 1), (9, 1), (9, 9), (1, 9)])
|
|
subimage = poly.extract_from_image(self.image)
|
|
assert np.array_equal(subimage, self.image[1:9, 1:9, :])
|
|
|
|
def test_polygon_fully_inside_image__no_rectangular_shape(self):
|
|
# inside image, two image areas that don't belong to the polygon but
|
|
# have to be extracted
|
|
poly = ia.Polygon([(0, 0), (10, 0), (10, 5), (20, 5),
|
|
(20, 20), (10, 20), (10, 5), (0, 5)])
|
|
subimage = poly.extract_from_image(self.image)
|
|
expected = np.copy(self.image)
|
|
expected[:5, 10:, :] = 0 # top right block
|
|
expected[5:, :10, :] = 0 # left bottom block
|
|
assert np.array_equal(subimage, expected)
|
|
|
|
def test_polygon_is_partially_outside_of_image(self):
|
|
# partially out of image
|
|
poly = ia.Polygon([(-5, 0), (5, 0), (5, 10), (-5, 10)])
|
|
subimage = poly.extract_from_image(self.image)
|
|
expected = np.zeros((10, 10, 2), dtype=np.int32)
|
|
expected[0:10, 5:10, :] = self.image[0:10, 0:5, :]
|
|
assert np.array_equal(subimage, expected)
|
|
|
|
def test_polygon_is_fully_outside_of_image(self):
|
|
# fully out of image
|
|
poly = ia.Polygon([(30, 0), (40, 0), (40, 10), (30, 10)])
|
|
subimage = poly.extract_from_image(self.image)
|
|
expected = np.zeros((10, 10, 2), dtype=np.int32)
|
|
assert np.array_equal(subimage, expected)
|
|
|
|
def test_polygon_coords_after_rounding_are_identical_with_img_shape(self):
|
|
# inside image, subpart of it
|
|
# float coordinates, rounded so that the whole image will be extracted
|
|
poly = ia.Polygon([(0.4, 0.4), (9.6, 0.4), (9.6, 9.6), (0.4, 9.6)])
|
|
subimage = poly.extract_from_image(self.image)
|
|
assert np.array_equal(subimage, self.image[0:10, 0:10, :])
|
|
|
|
def test_polygon_coords_after_rounding_are_subpart_of_image(self):
|
|
# inside image, subpart of it
|
|
# float coordinates, rounded so that x/y 0<=i<9 will be extracted
|
|
# (instead of 0<=i<10)
|
|
poly = ia.Polygon([(0.5, 0.5), (9.4, 0.5), (9.4, 9.4), (0.5, 9.4)])
|
|
subimage = poly.extract_from_image(self.image)
|
|
assert np.array_equal(subimage, self.image[0:9, 0:9, :])
|
|
|
|
def test_polygon_coords_after_rounding_are_subpart_of_image2(self):
|
|
# inside image, subpart of it
|
|
# float coordinates, rounded so that x/y 1<=i<9 will be extracted
|
|
# (instead of 0<=i<10)
|
|
poly = ia.Polygon([(0.51, 0.51), (9.4, 0.51), (9.4, 9.4), (0.51, 9.4)])
|
|
subimage = poly.extract_from_image(self.image)
|
|
assert np.array_equal(subimage, self.image[1:9, 1:9, :])
|
|
|
|
def test_polygon_without_area_fails(self):
|
|
# error for invalid polygons
|
|
got_exception = False
|
|
poly = ia.Polygon([(0.51, 0.51), (9.4, 0.51)])
|
|
try:
|
|
_ = poly.extract_from_image(self.image)
|
|
except Exception as exc:
|
|
assert "Polygon must be made up" in str(exc)
|
|
got_exception = True
|
|
assert got_exception
|
|
|
|
|
|
class TestPolygon_change_first_point_by_coords(unittest.TestCase):
|
|
def test_change_to_first_point_in_exterior(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
poly_reordered = poly.change_first_point_by_coords(x=0, y=0)
|
|
assert np.allclose(poly.exterior, poly_reordered.exterior)
|
|
|
|
def test_change_to_first_second_in_exterior(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
poly_reordered = poly.change_first_point_by_coords(x=1, y=0)
|
|
# make sure that it does not reorder inplace
|
|
assert np.allclose(poly.exterior, np.float32([[0, 0], [1, 0], [1, 1]]))
|
|
assert np.allclose(poly_reordered.exterior,
|
|
np.float32([[1, 0], [1, 1], [0, 0]]))
|
|
|
|
def test_change_to_third_point_in_exterior(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
poly_reordered = poly.change_first_point_by_coords(x=1, y=1)
|
|
assert np.allclose(poly_reordered.exterior,
|
|
np.float32([[1, 1], [0, 0], [1, 0]]))
|
|
|
|
def test_coords_slightly_off_from_target_point_limited_max_distance(self):
|
|
# inaccurate point, but close enough
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
poly_reordered = poly.change_first_point_by_coords(x=1.0, y=0.01,
|
|
max_distance=0.1)
|
|
assert np.allclose(poly_reordered.exterior,
|
|
np.float32([[1, 0], [1, 1], [0, 0]]))
|
|
|
|
def test_coords_slightly_off_from_target_point_infinite_max_distance(self):
|
|
# inaccurate point, but close enough (infinite max distance)
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
poly_reordered = poly.change_first_point_by_coords(x=1.0, y=0.01,
|
|
max_distance=None)
|
|
assert np.allclose(poly_reordered.exterior,
|
|
np.float32([[1, 0], [1, 1], [0, 0]]))
|
|
|
|
def test_closest_point_to_coords_exceeds_max_distance(self):
|
|
# point too far away
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
got_exception = False
|
|
try:
|
|
_ = poly.change_first_point_by_coords(x=1.0, y=0.01, max_distance=0.001)
|
|
except Exception as exc:
|
|
assert "Closest found point " in str(exc)
|
|
got_exception = True
|
|
assert got_exception
|
|
|
|
def test_polygon_with_two_points(self):
|
|
# reorder with two points
|
|
poly = ia.Polygon([(0, 0), (1, 0)])
|
|
poly_reordered = poly.change_first_point_by_coords(x=1, y=0)
|
|
assert np.allclose(poly_reordered.exterior,
|
|
np.float32([[1, 0], [0, 0]]))
|
|
|
|
def test_polygon_with_one_point(self):
|
|
# reorder with one point
|
|
poly = ia.Polygon([(0, 0)])
|
|
poly_reordered = poly.change_first_point_by_coords(x=0, y=0)
|
|
assert np.allclose(poly_reordered.exterior, np.float32([[0, 0]]))
|
|
|
|
def test_polygon_with_zero_points_fails(self):
|
|
# invalid polygon
|
|
got_exception = False
|
|
poly = ia.Polygon([])
|
|
try:
|
|
_ = poly.change_first_point_by_coords(x=0, y=0)
|
|
except Exception as exc:
|
|
assert "Cannot reorder polygon points" in str(exc)
|
|
got_exception = True
|
|
assert got_exception
|
|
|
|
|
|
class TestPolygon_change_first_point_by_index(unittest.TestCase):
|
|
def test_change_to_point_with_index_0(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
poly_reordered = poly.change_first_point_by_index(0)
|
|
assert np.allclose(poly.exterior, poly_reordered.exterior)
|
|
|
|
def test_change_to_point_with_index_1(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
poly_reordered = poly.change_first_point_by_index(1)
|
|
# make sure that it does not reorder inplace
|
|
assert np.allclose(poly.exterior,
|
|
np.float32([[0, 0], [1, 0], [1, 1]]))
|
|
assert np.allclose(poly_reordered.exterior,
|
|
np.float32([[1, 0], [1, 1], [0, 0]]))
|
|
|
|
def test_change_to_point_with_index_2(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
poly_reordered = poly.change_first_point_by_index(2)
|
|
assert np.allclose(poly_reordered.exterior,
|
|
np.float32([[1, 1], [0, 0], [1, 0]]))
|
|
|
|
def test_polygon_with_two_points(self):
|
|
# reorder with two points
|
|
poly = ia.Polygon([(0, 0), (1, 0)])
|
|
poly_reordered = poly.change_first_point_by_index(1)
|
|
assert np.allclose(poly_reordered.exterior,
|
|
np.float32([[1, 0], [0, 0]]))
|
|
|
|
def test_polygon_with_one_point(self):
|
|
# reorder with one point
|
|
poly = ia.Polygon([(0, 0)])
|
|
poly_reordered = poly.change_first_point_by_index(0)
|
|
assert np.allclose(poly_reordered.exterior, np.float32([[0, 0]]))
|
|
|
|
def test_polygon_with_zero_points_fails(self):
|
|
poly = ia.Polygon([])
|
|
got_exception = False
|
|
try:
|
|
_ = poly.change_first_point_by_index(0)
|
|
except AssertionError:
|
|
got_exception = True
|
|
assert got_exception
|
|
|
|
def test_index_beyond_max_index_fails(self):
|
|
# idx out of bounds
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
got_exception = False
|
|
try:
|
|
_ = poly.change_first_point_by_index(3)
|
|
except AssertionError:
|
|
got_exception = True
|
|
assert got_exception
|
|
|
|
def test_index_beyond_max_index_fails__single_point_polygon(self):
|
|
poly = ia.Polygon([(0, 0)])
|
|
got_exception = False
|
|
try:
|
|
_ = poly.change_first_point_by_index(1)
|
|
except AssertionError:
|
|
got_exception = True
|
|
assert got_exception
|
|
|
|
def test_index_below_zero_fails(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
got_exception = False
|
|
try:
|
|
_ = poly.change_first_point_by_index(-1)
|
|
except AssertionError:
|
|
got_exception = True
|
|
assert got_exception
|
|
|
|
|
|
class TestPolygon_subdivide_(unittest.TestCase):
|
|
@property
|
|
def _is_inplace(self):
|
|
return True
|
|
|
|
def _func(self, poly, points_per_edge):
|
|
return poly.subdivide_(points_per_edge)
|
|
|
|
def test_zero_points(self):
|
|
poly = ia.Polygon([])
|
|
poly_sub = self._func(poly, 1)
|
|
assert len(poly_sub.exterior) == 0
|
|
|
|
def test_one_point(self):
|
|
poly = ia.Polygon([(1, 1)])
|
|
poly_sub = self._func(poly, 1)
|
|
assert len(poly_sub.exterior) == 1
|
|
|
|
def test_two_points(self):
|
|
poly = ia.Polygon([(1, 2), (2, 4)])
|
|
poly_sub = self._func(poly, 1)
|
|
assert len(poly_sub.exterior) == 4
|
|
assert poly_sub.coords_almost_equals([
|
|
(1, 2),
|
|
(1.5, 3.0),
|
|
(2, 4),
|
|
(1.5, 3.0)
|
|
])
|
|
|
|
def test_three_points(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
poly_sub = self._func(poly, 1)
|
|
assert len(poly_sub.exterior) == 6
|
|
assert poly_sub.coords_almost_equals([
|
|
(0, 0),
|
|
(0.5, 0.0),
|
|
(1, 0),
|
|
(1, 0.5),
|
|
(1, 1),
|
|
(0.5, 0.5)
|
|
])
|
|
|
|
def test_three_points__n_points_0(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
poly_sub = self._func(poly, 0)
|
|
assert len(poly_sub.exterior) == 3
|
|
assert poly_sub.coords_almost_equals([
|
|
(0, 0),
|
|
(1, 0),
|
|
(1, 1),
|
|
])
|
|
|
|
def test_three_points__n_points_2(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
poly_sub = self._func(poly, 2)
|
|
assert len(poly_sub.exterior) == 3+2*3
|
|
assert poly_sub.coords_almost_equals([
|
|
(0, 0),
|
|
(1/3, 0),
|
|
(2/3, 0),
|
|
(1, 0),
|
|
(1, 1/3),
|
|
(1, 2/3),
|
|
(1, 1),
|
|
(2/3, 2/3),
|
|
(1/3, 1/3)
|
|
])
|
|
|
|
def test_label_none_is_preserved(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
poly_sub = self._func(poly, 1)
|
|
assert poly_sub.label is None
|
|
|
|
def test_label_str_is_preserved(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)], label="foo")
|
|
poly_sub = self._func(poly, 1)
|
|
assert poly_sub.label == "foo"
|
|
|
|
def test_inplaceness(self):
|
|
poly = ia.Polygon([(1, 2), (2, 4)])
|
|
poly2 = self._func(poly, 1)
|
|
|
|
if self._is_inplace:
|
|
assert poly is poly2
|
|
else:
|
|
assert poly is not poly2
|
|
|
|
|
|
class TestPolygon_subdivide(TestPolygon_subdivide_):
|
|
@property
|
|
def _is_inplace(self):
|
|
return False
|
|
|
|
def _func(self, poly, points_per_edge):
|
|
return poly.subdivide(points_per_edge)
|
|
|
|
|
|
class TestPolygon_to_shapely_line_string(unittest.TestCase):
|
|
def test_three_point_polygon(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
ls = poly.to_shapely_line_string()
|
|
assert np.allclose(ls.coords, np.float32([[0, 0], [1, 0], [1, 1]]))
|
|
|
|
def test_two_point_polygon(self):
|
|
# two point polygon
|
|
poly = ia.Polygon([(0, 0), (1, 0)])
|
|
ls = poly.to_shapely_line_string()
|
|
assert np.allclose(ls.coords, np.float32([[0, 0], [1, 0]]))
|
|
|
|
def test_one_point_polygon_fails(self):
|
|
# one point polygon
|
|
poly = ia.Polygon([(0, 0)])
|
|
got_exception = False
|
|
try:
|
|
_ = poly.to_shapely_line_string()
|
|
except Exception as exc:
|
|
assert (
|
|
"Conversion to shapely line string requires at least two "
|
|
"points" in str(exc))
|
|
got_exception = True
|
|
assert got_exception
|
|
|
|
def test_zero_point_polygon_fails(self):
|
|
# zero point polygon
|
|
poly = ia.Polygon([])
|
|
got_exception = False
|
|
try:
|
|
_ = poly.to_shapely_line_string()
|
|
except Exception as exc:
|
|
assert (
|
|
"Conversion to shapely line string requires at least two "
|
|
"points" in str(exc))
|
|
got_exception = True
|
|
assert got_exception
|
|
|
|
def test_closed_is_true(self):
|
|
# closed line string
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
ls = poly.to_shapely_line_string(closed=True)
|
|
assert np.allclose(ls.coords,
|
|
np.float32([[0, 0], [1, 0], [1, 1], [0, 0]]))
|
|
|
|
def test_interpolate_is_1(self):
|
|
# interpolation
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
ls = poly.to_shapely_line_string(interpolate=1)
|
|
assert np.allclose(
|
|
ls.coords,
|
|
np.float32([[0, 0], [0.5, 0], [1, 0], [1, 0.5],
|
|
[1, 1], [0.5, 0.5]]))
|
|
|
|
def test_interpolate_is_2(self):
|
|
# interpolation with 2 steps
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
ls = poly.to_shapely_line_string(interpolate=2)
|
|
assert np.allclose(ls.coords, np.float32([
|
|
[0, 0], [1/3, 0], [2/3, 0],
|
|
[1, 0], [1, 1/3], [1, 2/3],
|
|
[1, 1], [2/3, 2/3], [1/3, 1/3]
|
|
]))
|
|
|
|
def test_closed_is_true_and_interpolate_is_1(self):
|
|
# interpolation with closed=True
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
ls = poly.to_shapely_line_string(closed=True, interpolate=1)
|
|
assert np.allclose(
|
|
ls.coords,
|
|
np.float32([[0, 0], [0.5, 0], [1, 0], [1, 0.5], [1, 1],
|
|
[0.5, 0.5], [0, 0]]))
|
|
|
|
|
|
class TestPolygon_to_shapely_polygon(unittest.TestCase):
|
|
def test_square_polygon(self):
|
|
exterior = [(0, 0), (1, 0), (1, 1), (0, 1)]
|
|
poly = ia.Polygon(exterior)
|
|
poly_shapely = poly.to_shapely_polygon()
|
|
gen = zip(exterior, poly_shapely.exterior.coords)
|
|
for (x_exp, y_exp), (x_obs, y_obs) in gen:
|
|
assert np.isclose(x_obs, x_exp, rtol=0, atol=1e-8)
|
|
assert np.isclose(y_obs, y_exp, rtol=0, atol=1e-8)
|
|
|
|
|
|
class TestPolygon_to_bounding_box(unittest.TestCase):
|
|
def test_square_polygon(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
bb = poly.to_bounding_box()
|
|
assert 0 - 1e-8 < bb.x1 < 0 + 1e-8
|
|
assert 0 - 1e-8 < bb.y1 < 0 + 1e-8
|
|
assert 1 - 1e-8 < bb.x2 < 1 + 1e-8
|
|
assert 1 - 1e-8 < bb.y2 < 1 + 1e-8
|
|
|
|
def test_triangular_polygon(self):
|
|
poly = ia.Polygon([(0.5, 0), (1, 1), (0, 1)])
|
|
bb = poly.to_bounding_box()
|
|
assert 0 - 1e-8 < bb.x1 < 0 + 1e-8
|
|
assert 0 - 1e-8 < bb.y1 < 0 + 1e-8
|
|
assert 1 - 1e-8 < bb.x2 < 1 + 1e-8
|
|
assert 1 - 1e-8 < bb.y2 < 1 + 1e-8
|
|
|
|
def test_triangular_polygon2(self):
|
|
poly = ia.Polygon([(0.5, 0.5), (2, 0.1), (1, 1)])
|
|
bb = poly.to_bounding_box()
|
|
assert 0.5 - 1e-8 < bb.x1 < 0.5 + 1e-8
|
|
assert 0.1 - 1e-8 < bb.y1 < 0.1 + 1e-8
|
|
assert 2.0 - 1e-8 < bb.x2 < 2.0 + 1e-8
|
|
assert 1.0 - 1e-8 < bb.y2 < 1.0 + 1e-8
|
|
|
|
|
|
class TestPolygon_to_line_string(unittest.TestCase):
|
|
def test_polygon_with_zero_points(self):
|
|
poly = ia.Polygon([])
|
|
ls = poly.to_line_string(closed=False)
|
|
assert len(ls.coords) == 0
|
|
assert ls.label is None
|
|
|
|
def test_polygon_with_zero_points__closed_is_true(self):
|
|
poly = ia.Polygon([])
|
|
ls = poly.to_line_string(closed=True)
|
|
assert len(ls.coords) == 0
|
|
assert ls.label is None
|
|
|
|
def test_polygon_with_zero_points__label_set(self):
|
|
poly = ia.Polygon([], label="foo")
|
|
ls = poly.to_line_string(closed=False)
|
|
assert len(ls.coords) == 0
|
|
assert ls.label == "foo"
|
|
|
|
def test_polygon_with_one_point(self):
|
|
poly = ia.Polygon([(0, 0)])
|
|
ls = poly.to_line_string(closed=False)
|
|
assert len(ls.coords) == 1
|
|
assert ls.label is None
|
|
|
|
def test_polygon_with_one_point__closed_is_true(self):
|
|
poly = ia.Polygon([(0, 0)])
|
|
ls = poly.to_line_string(closed=True)
|
|
assert len(ls.coords) == 1
|
|
assert ls.coords_almost_equals([(0, 0)])
|
|
assert ls.label is None
|
|
|
|
def test_polygon_with_two_points(self):
|
|
poly = ia.Polygon([(0, 0), (1, 1)])
|
|
ls = poly.to_line_string(closed=False)
|
|
assert len(ls.coords) == 2
|
|
assert ls.coords_almost_equals([(0, 0), (1, 1)])
|
|
assert ls.label is None
|
|
|
|
def test_polygon_with_two_point__closed_is_true(self):
|
|
poly = ia.Polygon([(0, 0), (1, 1)])
|
|
ls = poly.to_line_string(closed=True)
|
|
assert len(ls.coords) == 3
|
|
assert ls.coords_almost_equals([(0, 0), (1, 1), (0, 0)])
|
|
assert ls.label is None
|
|
|
|
def test_polygon_with_two_points__label_is_set(self):
|
|
poly = ia.Polygon([(0, 0), (1, 1)], label="foo")
|
|
ls = poly.to_line_string()
|
|
assert len(ls.coords) == 3
|
|
assert ls.coords_almost_equals([(0, 0), (1, 1), (0, 0)])
|
|
assert ls.label == "foo"
|
|
|
|
def test_polygon_with_two_point__closed_is_true_label_is_set(self):
|
|
poly = ia.Polygon([(0, 0), (1, 1)], label="foo")
|
|
ls = poly.to_line_string(closed=True)
|
|
assert len(ls.coords) == 3
|
|
assert ls.coords_almost_equals([(0, 0), (1, 1), (0, 0)])
|
|
assert ls.label == "foo"
|
|
|
|
|
|
class TestPolygon_from_shapely(unittest.TestCase):
|
|
def test_square_polygon(self):
|
|
exterior = [(0, 0), (1, 0), (1, 1), (0, 1)]
|
|
poly_shapely = shapely.geometry.Polygon(exterior)
|
|
poly = ia.Polygon.from_shapely(poly_shapely)
|
|
|
|
# shapely messes up the point ordering, so we try to correct it here
|
|
start_idx = 0
|
|
for i, (x, y) in enumerate(poly.exterior):
|
|
dist = np.sqrt((exterior[0][0] - x) ** 2
|
|
+ (exterior[0][1] - x) ** 2)
|
|
if dist < 1e-4:
|
|
start_idx = i
|
|
break
|
|
poly = poly.change_first_point_by_index(start_idx)
|
|
|
|
for (x_exp, y_exp), (x_obs, y_obs) in zip(exterior, poly.exterior):
|
|
assert x_exp - 1e-8 < x_obs < x_exp + 1e-8
|
|
assert y_exp - 1e-8 < y_obs < y_exp + 1e-8
|
|
|
|
def test_polygon_with_zero_points(self):
|
|
poly_shapely = shapely.geometry.Polygon([])
|
|
poly = ia.Polygon.from_shapely(poly_shapely)
|
|
assert len(poly.exterior) == 0
|
|
|
|
|
|
class TestPolygon_copy(unittest.TestCase):
|
|
def test_square_polygon_with_label(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)], label="test")
|
|
poly_cp = poly.copy()
|
|
assert poly.exterior.dtype.type == poly_cp.exterior.dtype.type
|
|
assert poly.exterior.shape == poly_cp.exterior.shape
|
|
assert np.allclose(poly.exterior, poly_cp.exterior)
|
|
assert poly.label == poly_cp.label
|
|
|
|
|
|
class TestPolygon_deepcopy(unittest.TestCase):
|
|
def test_square_polygon_with_label(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)], label="test")
|
|
poly_cp = poly.deepcopy()
|
|
assert poly.exterior.dtype.type == poly_cp.exterior.dtype.type
|
|
assert poly.exterior.shape == poly_cp.exterior.shape
|
|
assert np.allclose(poly.exterior, poly_cp.exterior)
|
|
assert poly.label == poly_cp.label
|
|
|
|
def test_copy_is_not_shallow(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)], label="test")
|
|
poly_cp = poly.deepcopy()
|
|
poly_cp.exterior[0, 0] = 100.0
|
|
poly_cp.label = "test2"
|
|
assert poly.exterior.dtype.type == poly_cp.exterior.dtype.type
|
|
assert poly.exterior.shape == poly_cp.exterior.shape
|
|
assert not np.allclose(poly.exterior, poly_cp.exterior)
|
|
assert not poly.label == poly_cp.label
|
|
|
|
|
|
class TestPolygon___repr___and___str__(unittest.TestCase):
|
|
def test_with_int_coordinates_provided_to_init(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)], label="test")
|
|
expected = (
|
|
"Polygon(["
|
|
"(x=0.000, y=0.000), "
|
|
"(x=1.000, y=0.000), "
|
|
"(x=1.000, y=1.000), "
|
|
"(x=0.000, y=1.000)"
|
|
"] (4 points), label=test)"
|
|
)
|
|
assert poly.__repr__() == expected
|
|
assert poly.__str__() == expected
|
|
|
|
def test_with_float_coordinates_provided_to_init(self):
|
|
poly = ia.Polygon([(0, 0.5), (1.5, 0), (1, 1), (0, 1)], label="test")
|
|
expected = (
|
|
"Polygon(["
|
|
"(x=0.000, y=0.500), "
|
|
"(x=1.500, y=0.000), "
|
|
"(x=1.000, y=1.000), "
|
|
"(x=0.000, y=1.000)"
|
|
"] (4 points), label=test)"
|
|
)
|
|
assert poly.__repr__() == expected
|
|
assert poly.__str__() == expected
|
|
|
|
def test_label_is_none(self):
|
|
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)], label=None)
|
|
expected = (
|
|
"Polygon(["
|
|
"(x=0.000, y=0.000), "
|
|
"(x=1.000, y=0.000), "
|
|
"(x=1.000, y=1.000), "
|
|
"(x=0.000, y=1.000)"
|
|
"] (4 points), label=None)"
|
|
)
|
|
assert poly.__repr__() == expected
|
|
assert poly.__str__() == expected
|
|
|
|
def test_polygon_has_zero_points(self):
|
|
poly = ia.Polygon([], label="test")
|
|
expected = "Polygon([] (0 points), label=test)"
|
|
assert poly.__repr__() == expected
|
|
assert poly.__str__() == expected
|
|
|
|
|
|
class TestPolygon_coords_almost_equals(unittest.TestCase):
|
|
@mock.patch("imgaug.augmentables.polys.Polygon.exterior_almost_equals")
|
|
def test_calls_exterior_almost_equals(self, mock_eae):
|
|
mock_eae.return_value = "foo"
|
|
poly_a = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
poly_b = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
|
|
result = poly_a.coords_almost_equals(poly_b)
|
|
|
|
assert result == "foo"
|
|
mock_eae.assert_called_once_with(poly_b, max_distance=1e-4,
|
|
points_per_edge=8)
|
|
|
|
@mock.patch("imgaug.augmentables.polys.Polygon.exterior_almost_equals")
|
|
def test_calls_exterior_almost_equals__no_defaults(self, mock_eae):
|
|
mock_eae.return_value = "foo"
|
|
poly_a = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
poly_b = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
|
|
result = poly_a.coords_almost_equals(poly_b, max_distance=1,
|
|
points_per_edge=2)
|
|
|
|
assert result == "foo"
|
|
mock_eae.assert_called_once_with(poly_b, max_distance=1,
|
|
points_per_edge=2)
|
|
|
|
|
|
class TestPolygon_exterior_almost_equals(unittest.TestCase):
|
|
def test_exactly_same_exterior(self):
|
|
# exactly same exterior
|
|
poly_a = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
poly_b = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
assert poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_one_point_duplicated(self):
|
|
# one point duplicated
|
|
poly_a = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
poly_b = ia.Polygon([(0, 0), (1, 0), (1, 1), (1, 1), (0, 1)])
|
|
assert poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_several_points_added_without_changing_basic_shape(self):
|
|
# several points added without changing geometry
|
|
poly_a = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
poly_b = ia.Polygon([(0, 0), (0.5, 0), (1, 0), (1, 0.5), (1, 1), (0.5, 1), (0, 1), (0, 0.5)])
|
|
assert poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_different_order(self):
|
|
# different order
|
|
poly_a = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
poly_b = ia.Polygon([(0, 1), (1, 1), (1, 0), (0, 0)])
|
|
assert poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_tiny_shift_below_max_distance(self):
|
|
# tiny shift below tolerance
|
|
poly_a = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
poly_b = ia.Polygon([(0+1e-6, 0), (1+1e-6, 0), (1+1e-6, 1),
|
|
(0+1e-6, 1)])
|
|
assert poly_a.exterior_almost_equals(poly_b, max_distance=1e-3)
|
|
|
|
def test_tiny_shift_above_max_distance(self):
|
|
# tiny shift above tolerance
|
|
poly_a = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
poly_b = ia.Polygon([(0+1e-6, 0), (1+1e-6, 0), (1+1e-6, 1),
|
|
(0+1e-6, 1)])
|
|
assert not poly_a.exterior_almost_equals(poly_b, max_distance=1e-9)
|
|
|
|
def test_polygons_with_half_intersection(self):
|
|
# shifted polygon towards half overlap
|
|
poly_a = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
poly_b = ia.Polygon([(0.5, 0), (1.5, 0), (1.5, 1), (0.5, 1)])
|
|
assert not poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_polygons_without_any_intersection(self):
|
|
# shifted polygon towards no overlap at all
|
|
poly_a = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
poly_b = ia.Polygon([(100, 0), (101, 0), (101, 1), (100, 1)])
|
|
assert not poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_both_polygons_with_zero_points(self):
|
|
# both polygons without points
|
|
poly_a = ia.Polygon([])
|
|
poly_b = ia.Polygon([])
|
|
assert poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_both_polygons_with_one_point__both_identical(self):
|
|
# both polygons with one point
|
|
poly_a = ia.Polygon([(0, 0)])
|
|
poly_b = ia.Polygon([(0, 0)])
|
|
assert poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_both_polygons_with_zero_points__both_different(self):
|
|
poly_a = ia.Polygon([(0, 0)])
|
|
poly_b = ia.Polygon([(100, 100)])
|
|
assert not poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_both_polygons_with_zero_points__difference_below_max_dist(self):
|
|
poly_a = ia.Polygon([(0, 0)])
|
|
poly_b = ia.Polygon([(0+1e-6, 0)])
|
|
assert poly_a.exterior_almost_equals(poly_b, max_distance=1e-2)
|
|
|
|
def test_both_polygons_with_zero_points_difference_above_max_dist(self):
|
|
poly_a = ia.Polygon([(0, 0)])
|
|
poly_b = ia.Polygon([(0+1, 0)])
|
|
assert not poly_a.exterior_almost_equals(poly_b, max_distance=1e-2)
|
|
|
|
def test_both_polygons_with_two_points__identical(self):
|
|
# both polygons with two points
|
|
poly_a = ia.Polygon([(0, 0), (1, 0)])
|
|
poly_b = ia.Polygon([(0, 0), (1, 0)])
|
|
assert poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_both_polygons_with_two_points__identical_and_zero_area(self):
|
|
poly_a = ia.Polygon([(0, 0), (0, 0)])
|
|
poly_b = ia.Polygon([(0, 0), (0, 0)])
|
|
assert poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_both_polygons_with_two_points__second_point_different(self):
|
|
poly_a = ia.Polygon([(0, 0), (1, 0)])
|
|
poly_b = ia.Polygon([(0, 0), (2, 0)])
|
|
assert not poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_both_polygons_with_two_points__difference_below_max_dist(self):
|
|
poly_a = ia.Polygon([(0, 0), (1, 0)])
|
|
poly_b = ia.Polygon([(0+1e-6, 0), (1+1e-6, 0)])
|
|
assert poly_a.exterior_almost_equals(poly_b, max_distance=1e-2)
|
|
|
|
def test_both_polygons_with_three_points__identical(self):
|
|
# both polygons with three points
|
|
poly_a = ia.Polygon([(0, 0), (1, 0), (0.5, 1)])
|
|
poly_b = ia.Polygon([(0, 0), (1, 0), (0.5, 1)])
|
|
assert poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_both_polygons_with_three_points__one_point_differs(self):
|
|
poly_a = ia.Polygon([(0, 0), (1, 0), (0.5, 1)])
|
|
poly_b = ia.Polygon([(0, 0), (1, -1), (0.5, 1)])
|
|
assert not poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_both_polygons_with_three_points__difference_below_max_dist(self):
|
|
poly_a = ia.Polygon([(0, 0), (1, 0), (0.5, 1)])
|
|
poly_b = ia.Polygon([(0, 0), (1+1e-6, 0), (0.5, 1)])
|
|
assert poly_a.exterior_almost_equals(poly_b, max_distance=1e-2)
|
|
|
|
def test_one_polygon_zero_points_other_one_point(self):
|
|
# one polygon with zero points, other with one
|
|
poly_a = ia.Polygon([])
|
|
poly_b = ia.Polygon([(0, 0)])
|
|
assert not poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_one_polygon_one_point_other_zero_points(self):
|
|
poly_a = ia.Polygon([(0, 0)])
|
|
poly_b = ia.Polygon([])
|
|
assert not poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_one_polygon_one_point_other_two_points(self):
|
|
# one polygon with one point, other with two
|
|
poly_a = ia.Polygon([(-10, -20)])
|
|
poly_b = ia.Polygon([(0, 0), (1, 0)])
|
|
assert not poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_one_polygon_one_point_other_two_points_2(self):
|
|
poly_a = ia.Polygon([(0, 0)])
|
|
poly_b = ia.Polygon([(0, 0), (1, 0)])
|
|
assert not poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_one_polygon_two_points_other_one_point(self):
|
|
poly_a = ia.Polygon([(0, 0), (1, 0)])
|
|
poly_b = ia.Polygon([(0, 0)])
|
|
assert not poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_one_polygon_two_points_other_one_point__all_identical(self):
|
|
poly_a = ia.Polygon([(0, 0), (0, 0)])
|
|
poly_b = ia.Polygon([(0, 0)])
|
|
assert poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_one_polygon_one_point_other_two_points__all_identical(self):
|
|
poly_a = ia.Polygon([(0, 0)])
|
|
poly_b = ia.Polygon([(0, 0), (0, 0)])
|
|
assert poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_one_polygon_two_points_other_one_point__diff_below_max_dist(self):
|
|
poly_a = ia.Polygon([(0, 0), (0+1e-6, 0)])
|
|
poly_b = ia.Polygon([(0, 0)])
|
|
assert poly_a.exterior_almost_equals(poly_b, max_distance=1e-2)
|
|
|
|
def test_one_polygon_two_points_other_one_point__diff_above_max_dist(self):
|
|
poly_a = ia.Polygon([(0, 0), (0+1e-4, 0)])
|
|
poly_b = ia.Polygon([(0, 0)])
|
|
assert not poly_a.exterior_almost_equals(poly_b, max_distance=1e-9)
|
|
|
|
def test_one_polygon_one_point_other_three_points(self):
|
|
# one polygon with one point, other with three
|
|
poly_a = ia.Polygon([(0, 0)])
|
|
poly_b = ia.Polygon([(0, 0), (1, 0), (0.5, 1)])
|
|
assert not poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_one_polygon_three_points_other_one_point(self):
|
|
poly_a = ia.Polygon([(0, 0), (1, 0), (0.5, 1)])
|
|
poly_b = ia.Polygon([(0, 0)])
|
|
assert not poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_one_polygon_one_point_other_three_points__all_identical(self):
|
|
poly_a = ia.Polygon([(0, 0)])
|
|
poly_b = ia.Polygon([(0, 0), (0, 0), (0, 0)])
|
|
assert poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_one_polygon_one_point_other_three_points__one_point_differs(self):
|
|
poly_a = ia.Polygon([(0, 0)])
|
|
poly_b = ia.Polygon([(0, 0), (0, 0), (1, 0)])
|
|
assert not poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_one_polygon_one_point_other_three_points__one_point_differs2(self):
|
|
poly_a = ia.Polygon([(0, 0)])
|
|
poly_b = ia.Polygon([(0, 0), (1, 0), (0, 0)])
|
|
assert not poly_a.exterior_almost_equals(poly_b)
|
|
|
|
def test_one_polygon_one_point_other_three_points__dist_below_max(self):
|
|
poly_a = ia.Polygon([(0, 0)])
|
|
poly_b = ia.Polygon([(0, 0), (0+1e-6, 0), (0, 0+1e-6)])
|
|
assert poly_a.exterior_almost_equals(poly_b, max_distance=1e-2)
|
|
|
|
def test_one_polygon_one_point_other_three_points__dist_above_max(self):
|
|
poly_a = ia.Polygon([(0, 0)])
|
|
poly_b = ia.Polygon([(0, 0), (0+1e-4, 0), (0, 0+1e-4)])
|
|
assert not poly_a.exterior_almost_equals(poly_b, max_distance=1e-9)
|
|
|
|
|
|
class TestPolygon_almost_equals(unittest.TestCase):
|
|
def test_both_polygons_empty(self):
|
|
poly_a = ia.Polygon([])
|
|
poly_b = ia.Polygon([])
|
|
assert poly_a.almost_equals(poly_b)
|
|
|
|
def test_both_polygons_one_point(self):
|
|
poly_a = ia.Polygon([(0, 0)])
|
|
poly_b = ia.Polygon([(0, 0)])
|
|
assert poly_a.almost_equals(poly_b)
|
|
|
|
def test_one_polygon_one_point_other_two_points__all_identical(self):
|
|
poly_a = ia.Polygon([(0, 0)])
|
|
poly_b = ia.Polygon([(0, 0), (0, 0)])
|
|
assert poly_a.almost_equals(poly_b)
|
|
|
|
def test_one_polygon_one_point_other_three_points__all_identical(self):
|
|
poly_a = ia.Polygon([(0, 0)])
|
|
poly_b = ia.Polygon([(0, 0), (0, 0), (0, 0)])
|
|
assert poly_a.almost_equals(poly_b)
|
|
|
|
def test_one_polygon_one_point_other_two_points__diff_below_max_dist(self):
|
|
poly_a = ia.Polygon([(0, 0)])
|
|
poly_b = ia.Polygon([(0, 0), (0+1e-10, 0)])
|
|
assert poly_a.almost_equals(poly_b)
|
|
|
|
def test_both_polygons_one_point__first_with_label(self):
|
|
poly_a = ia.Polygon([(0, 0)], label="test")
|
|
poly_b = ia.Polygon([(0, 0)])
|
|
assert not poly_a.almost_equals(poly_b)
|
|
|
|
def test_both_polygons_one_point__second_with_label(self):
|
|
poly_a = ia.Polygon([(0, 0)])
|
|
poly_b = ia.Polygon([(0, 0)], label="test")
|
|
assert not poly_a.almost_equals(poly_b)
|
|
|
|
def test_both_polygons_one_point__both_with_label(self):
|
|
poly_a = ia.Polygon([(0, 0)], label="test")
|
|
poly_b = ia.Polygon([(0, 0)], label="test")
|
|
assert poly_a.almost_equals(poly_b)
|
|
|
|
def test_both_polygons_one_point__both_with_label_but_point_differs(self):
|
|
poly_a = ia.Polygon([(0, 0)], label="test")
|
|
poly_b = ia.Polygon([(1, 0)], label="test")
|
|
assert not poly_a.almost_equals(poly_b)
|
|
|
|
def test_both_polygons_one_point__same_point_but_labels_differ(self):
|
|
poly_a = ia.Polygon([(0, 0)], label="testA")
|
|
poly_b = ia.Polygon([(0, 0)], label="testB")
|
|
assert not poly_a.almost_equals(poly_b)
|
|
|
|
def test_both_polygons_three_points(self):
|
|
poly_a = ia.Polygon([(0, 0), (1, 0), (0.5, 1)])
|
|
poly_b = ia.Polygon([(0, 0), (1, 0), (0.5, 1)])
|
|
assert poly_a.almost_equals(poly_b)
|
|
|
|
def test_one_polygon_one_point_other_three_points(self):
|
|
poly_a = ia.Polygon([(0, 0)])
|
|
poly_b = ia.Polygon([(0, 0), (1, 0), (0.5, 1)])
|
|
assert not poly_a.almost_equals(poly_b)
|
|
|
|
|
|
class TestPolygon___getitem__(unittest.TestCase):
|
|
def test_with_three_points(self):
|
|
cba = ia.Polygon([(1, 2), (3, 4), (5, 5)])
|
|
assert np.allclose(cba[0], (1, 2))
|
|
assert np.allclose(cba[1], (3, 4))
|
|
assert np.allclose(cba[2], (5, 5))
|
|
|
|
def test_with_three_points_slice(self):
|
|
cba = ia.Polygon([(1, 2), (3, 4), (5, 5)])
|
|
assert np.allclose(cba[1:], [(3, 4), (5, 5)])
|
|
|
|
|
|
class TestPolygon___iter__(unittest.TestCase):
|
|
def test_with_three_points(self):
|
|
cba = ia.Polygon([(1, 2), (3, 4), (5, 5)])
|
|
for i, xy in enumerate(cba):
|
|
assert i in [0, 1, 2]
|
|
if i == 0:
|
|
assert np.allclose(xy, (1, 2))
|
|
elif i == 1:
|
|
assert np.allclose(xy, (3, 4))
|
|
elif i == 2:
|
|
assert np.allclose(xy, (5, 5))
|
|
assert i == 2
|
|
|
|
def test_with_zero_points(self):
|
|
cba = ia.Polygon([])
|
|
i = 0
|
|
for xy in cba:
|
|
i += 1
|
|
assert i == 0
|
|
|
|
|
|
# TODO add test for _convert_points_to_shapely_line_string
|
|
|
|
|
|
class TestPolygonsOnImage___init__(unittest.TestCase):
|
|
def test_with_one_polygon(self):
|
|
# standard case with one polygon
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])],
|
|
shape=(10, 10, 3)
|
|
)
|
|
assert len(poly_oi.polygons) == 1
|
|
assert np.allclose(
|
|
poly_oi.polygons[0].exterior,
|
|
[(0, 0), (1, 0), (1, 1), (0, 1)],
|
|
rtol=0, atol=1e-4)
|
|
assert poly_oi.shape == (10, 10, 3)
|
|
|
|
def test_with_multiple_polygons(self):
|
|
# standard case with multiple polygons
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
|
|
ia.Polygon([(0, 0), (1, 0), (1, 1)]),
|
|
ia.Polygon([(0.5, 0), (1, 0.5), (0.5, 1), (0, 0.5)])],
|
|
shape=(10, 10, 3)
|
|
)
|
|
assert len(poly_oi.polygons) == 3
|
|
assert np.allclose(
|
|
poly_oi.polygons[0].exterior,
|
|
[(0, 0), (1, 0), (1, 1), (0, 1)],
|
|
rtol=0, atol=1e-4)
|
|
assert np.allclose(
|
|
poly_oi.polygons[1].exterior,
|
|
[(0, 0), (1, 0), (1, 1)],
|
|
rtol=0, atol=1e-4)
|
|
assert np.allclose(
|
|
poly_oi.polygons[2].exterior,
|
|
[(0.5, 0), (1, 0.5), (0.5, 1), (0, 0.5)],
|
|
rtol=0, atol=1e-4)
|
|
assert poly_oi.shape == (10, 10, 3)
|
|
|
|
def test_with_zero_polygons(self):
|
|
# list of polygons is empty
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[],
|
|
shape=(10, 10, 3)
|
|
)
|
|
assert len(poly_oi.polygons) == 0
|
|
|
|
def test_with_invalid_polygon(self):
|
|
# invalid polygon
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(0, 0), (0.5, 0), (0.5, 1.5), (0, 1), (1, 1), (0, 1)])],
|
|
shape=(10, 10, 3)
|
|
)
|
|
assert len(poly_oi.polygons) == 1
|
|
assert np.allclose(
|
|
poly_oi.polygons[0].exterior,
|
|
[(0, 0), (0.5, 0), (0.5, 1.5), (0, 1), (1, 1), (0, 1)],
|
|
rtol=0, atol=1e-4)
|
|
|
|
def test_with_zero_polygons_and_shape_given_as_array(self):
|
|
# shape given as numpy array
|
|
with assertWarns(self, ia.DeprecationWarning):
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[],
|
|
shape=np.zeros((10, 10, 3), dtype=np.uint8)
|
|
)
|
|
assert poly_oi.shape == (10, 10, 3)
|
|
|
|
def test_with_zero_polygons_and_shape_given_as_2d_tuple(self):
|
|
# 2D shape
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[],
|
|
shape=(10, 11)
|
|
)
|
|
assert poly_oi.shape == (10, 11)
|
|
|
|
|
|
class TestPolygonsOnImage_items(unittest.TestCase):
|
|
def test_with_two_polygons(self):
|
|
poly1 = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
poly2 = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
psoi = ia.PolygonsOnImage(
|
|
[poly1, poly2],
|
|
shape=(10, 10, 3)
|
|
)
|
|
|
|
items = psoi.items
|
|
|
|
assert items == [poly1, poly2]
|
|
|
|
def test_items_empty(self):
|
|
psoi = ia.PolygonsOnImage([], shape=(40, 50, 3))
|
|
|
|
items = psoi.items
|
|
|
|
assert items == []
|
|
|
|
|
|
class TestPolygonsOnImage_items_setter(unittest.TestCase):
|
|
def test_with_list_of_polygons(self):
|
|
ps = [ia.Polygon([(0, 0), (1, 0), (1, 1)]),
|
|
ia.Polygon([(1, 1), (2, 1), (2, 2)])]
|
|
psoi = ia.PolygonsOnImage([], shape=(10, 20, 3))
|
|
psoi.items = ps
|
|
assert np.all([
|
|
(np.allclose(ps_i.coords, ps_j.coords))
|
|
for ps_i, ps_j
|
|
in zip(psoi.polygons, ps)
|
|
])
|
|
|
|
|
|
class TestPolygonsOnImage_empty(unittest.TestCase):
|
|
def test_with_multiple_polygons(self):
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
|
|
ia.Polygon([(0, 0), (1, 0), (1, 1)]),
|
|
ia.Polygon([(0.5, 0), (1, 0.5), (0.5, 1), (0, 0.5)])],
|
|
shape=(10, 10, 3)
|
|
)
|
|
assert poly_oi.empty is False
|
|
|
|
def test_with_zero_polygons(self):
|
|
# list of polygons is empty
|
|
poly_oi = ia.PolygonsOnImage([], shape=(10, 10, 3))
|
|
assert poly_oi.empty is True
|
|
|
|
|
|
class TestPolygonsOnImage_on_(unittest.TestCase):
|
|
@property
|
|
def _is_inplace(self):
|
|
return True
|
|
|
|
def _func(self, psoi, image):
|
|
return psoi.on_(image)
|
|
|
|
def test_new_shape_is_identical_to_old_shape(self):
|
|
# size unchanged
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
|
|
ia.Polygon([(0, 0), (1, 0), (1, 1)]),
|
|
ia.Polygon([(0.5, 0), (1, 0.5), (0.5, 1), (0, 0.5)])],
|
|
shape=(1, 1, 3)
|
|
)
|
|
poly_oi_proj = self._func(poly_oi, (1, 1, 3))
|
|
assert np.allclose(
|
|
poly_oi_proj.polygons[0].exterior,
|
|
[(0, 0), (1, 0), (1, 1), (0, 1)],
|
|
rtol=0, atol=1e-4)
|
|
assert np.allclose(
|
|
poly_oi_proj.polygons[1].exterior,
|
|
[(0, 0), (1, 0), (1, 1)],
|
|
rtol=0, atol=1e-4)
|
|
assert np.allclose(
|
|
poly_oi_proj.polygons[2].exterior,
|
|
[(0.5, 0), (1, 0.5), (0.5, 1), (0, 0.5)],
|
|
rtol=0, atol=1e-4)
|
|
assert poly_oi_proj.shape == (1, 1, 3)
|
|
|
|
def test_new_shape_is_10x_smaller_than_old_shape(self):
|
|
# 10x decrease in size
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(0, 0), (10, 0), (10, 10), (0, 10)]),
|
|
ia.Polygon([(0, 0), (10, 0), (10, 10)]),
|
|
ia.Polygon([(5, 0), (10, 5), (5, 10), (0, 5)])],
|
|
shape=(10, 10, 3)
|
|
)
|
|
poly_oi_proj = self._func(poly_oi, (1, 1, 3))
|
|
assert np.allclose(
|
|
poly_oi_proj.polygons[0].exterior,
|
|
[(0, 0), (1, 0), (1, 1), (0, 1)],
|
|
rtol=0, atol=1e-4)
|
|
assert np.allclose(
|
|
poly_oi_proj.polygons[1].exterior,
|
|
[(0, 0), (1, 0), (1, 1)],
|
|
rtol=0, atol=1e-4)
|
|
assert np.allclose(
|
|
poly_oi_proj.polygons[2].exterior,
|
|
[(0.5, 0), (1, 0.5), (0.5, 1), (0, 0.5)],
|
|
rtol=0, atol=1e-4)
|
|
assert poly_oi_proj.shape == (1, 1, 3)
|
|
|
|
def test_new_shape_is_2x_width_and_10x_height_of_old_shape(self):
|
|
# 2x increase in width, 10x decrease in height
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(0, 0), (50, 0), (50, 100), (0, 100)])],
|
|
shape=(100, 100, 3)
|
|
)
|
|
poly_oi_proj = self._func(poly_oi, (10, 200, 3))
|
|
assert np.allclose(
|
|
poly_oi_proj.polygons[0].exterior,
|
|
[(0, 0), (100, 0), (100, 10), (0, 10)],
|
|
rtol=0, atol=1e-4)
|
|
assert poly_oi_proj.shape == (10, 200, 3)
|
|
|
|
|
|
class TestPolygonsOnImage_on(TestPolygonsOnImage_on_):
|
|
@property
|
|
def _is_inplace(self):
|
|
return False
|
|
|
|
def _func(self, psoi, image):
|
|
return psoi.on(image)
|
|
|
|
|
|
class TestPolygonsOnImage_draw_on_image(unittest.TestCase):
|
|
def test_with_zero_polygons(self):
|
|
# no polygons, nothing changed
|
|
image = np.zeros((10, 10, 3), dtype=np.uint8)
|
|
|
|
poly_oi = ia.PolygonsOnImage([], shape=image.shape)
|
|
image_drawn = poly_oi.draw_on_image(image)
|
|
assert np.sum(image) == 0
|
|
assert np.sum(image_drawn) == 0
|
|
|
|
def test_with_two_polygons(self):
|
|
# draw two polygons
|
|
image = np.zeros((10, 10, 3), dtype=np.uint8)
|
|
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(1, 1), (9, 1), (9, 9), (1, 9)]),
|
|
ia.Polygon([(3, 3), (7, 3), (7, 7), (3, 7)])],
|
|
shape=image.shape)
|
|
image_expected = np.copy(image)
|
|
image_expected = poly_oi.polygons[0].draw_on_image(image_expected)
|
|
image_expected = poly_oi.polygons[1].draw_on_image(image_expected)
|
|
image_drawn = poly_oi.draw_on_image(image)
|
|
|
|
assert np.sum(image) == 0
|
|
assert np.sum(image_drawn) > 0
|
|
assert np.sum(image_expected) > 0
|
|
assert np.allclose(image_drawn, image_expected)
|
|
|
|
|
|
class TestPolygonsOnImage_remove_out_of_image_(unittest.TestCase):
|
|
@property
|
|
def _is_inplace(self):
|
|
return False
|
|
|
|
def _func(self, psoi, fully=True, partly=False):
|
|
return psoi.remove_out_of_image_(fully, partly)
|
|
|
|
def test_with_zero_polygons(self):
|
|
# no polygons, nothing to remove
|
|
poly_oi = ia.PolygonsOnImage([], shape=(10, 11, 3))
|
|
for fully, partly in [(False, False), (False, True),
|
|
(True, False), (True, True)]:
|
|
poly_oi_rm = self._func(poly_oi.deepcopy(),
|
|
fully=fully, partly=partly)
|
|
assert len(poly_oi_rm.polygons) == 0
|
|
assert poly_oi_rm.shape == (10, 11, 3)
|
|
|
|
def test_one_polygon_fully_inside_image(self):
|
|
# one polygon, fully inside the image
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(1, 1), (9, 1), (9, 9), (1, 9)])],
|
|
shape=(10, 11, 3))
|
|
for fully, partly in [(False, False), (False, True),
|
|
(True, False), (True, True)]:
|
|
poly_oi_rm = self._func(poly_oi.deepcopy(),
|
|
fully=fully, partly=partly)
|
|
assert len(poly_oi_rm.polygons) == 1
|
|
assert np.allclose(poly_oi_rm.polygons[0].exterior,
|
|
[(1, 1), (9, 1), (9, 9), (1, 9)],
|
|
rtol=0, atol=1e-4)
|
|
assert poly_oi_rm.shape == (10, 11, 3)
|
|
|
|
def test_one_poly_partially_ooi_one_fully_ooi(self):
|
|
# two polygons, one partly outside, one fully outside
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(1, 1), (11, 1), (11, 9), (1, 9)]),
|
|
ia.Polygon([(100, 100), (200, 100), (200, 200), (100, 200)])],
|
|
shape=(10, 10, 3))
|
|
|
|
poly_oi_rm = self._func(poly_oi.deepcopy(), fully=False, partly=False)
|
|
assert len(poly_oi.polygons) == 2
|
|
assert len(poly_oi_rm.polygons) == 2
|
|
assert np.allclose(poly_oi_rm.polygons[0].exterior,
|
|
[(1, 1), (11, 1), (11, 9), (1, 9)],
|
|
rtol=0, atol=1e-4)
|
|
assert np.allclose(poly_oi_rm.polygons[1].exterior,
|
|
[(100, 100), (200, 100), (200, 200), (100, 200)],
|
|
rtol=0, atol=1e-4)
|
|
assert poly_oi_rm.shape == (10, 10, 3)
|
|
|
|
poly_oi_rm = self._func(poly_oi.deepcopy(), fully=True, partly=False)
|
|
assert len(poly_oi.polygons) == 2
|
|
assert len(poly_oi_rm.polygons) == 1
|
|
assert np.allclose(poly_oi_rm.polygons[0].exterior,
|
|
[(1, 1), (11, 1), (11, 9), (1, 9)],
|
|
rtol=0, atol=1e-4)
|
|
assert poly_oi_rm.shape == (10, 10, 3)
|
|
|
|
poly_oi_rm = self._func(poly_oi.deepcopy(), fully=False, partly=True)
|
|
assert len(poly_oi.polygons) == 2
|
|
assert len(poly_oi_rm.polygons) == 1
|
|
assert np.allclose(poly_oi_rm.polygons[0].exterior,
|
|
[(100, 100), (200, 100), (200, 200), (100, 200)],
|
|
rtol=0, atol=1e-4)
|
|
assert poly_oi_rm.shape == (10, 10, 3)
|
|
|
|
poly_oi_rm = self._func(poly_oi.deepcopy(), fully=True, partly=True)
|
|
assert len(poly_oi.polygons) == 2
|
|
assert len(poly_oi_rm.polygons) == 0
|
|
assert poly_oi_rm.shape == (10, 10, 3)
|
|
|
|
def test_inplaceness(self):
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(1, 1), (11, 1), (11, 9), (1, 9)]),
|
|
ia.Polygon([(100, 100), (200, 100), (200, 200), (100, 200)])],
|
|
shape=(10, 10, 3))
|
|
|
|
poly_oi_rm = self._func(poly_oi.deepcopy(), fully=True, partly=False)
|
|
|
|
if self._is_inplace:
|
|
assert poly_oi_rm is poly_oi
|
|
else:
|
|
assert poly_oi_rm is not poly_oi
|
|
|
|
|
|
class TestPolygonsOnImage_remove_out_if_image(
|
|
TestPolygonsOnImage_remove_out_of_image_):
|
|
@property
|
|
def _is_inplace(self):
|
|
return False
|
|
|
|
def _func(self, psoi, fully=True, partly=False):
|
|
return psoi.remove_out_of_image(fully, partly)
|
|
|
|
|
|
class TestPolygonsOnImage_remove_out_of_image_fraction_(unittest.TestCase):
|
|
def test_three_polygons(self):
|
|
item1 = ia.Polygon([(5, 1), (9, 1), (9, 2), (5, 2)])
|
|
item2 = ia.Polygon([(5, 1), (15, 1), (15, 2), (5, 2)])
|
|
item3 = ia.Polygon([(15, 1), (25, 1), (25, 2), (15, 2)])
|
|
cbaoi = ia.PolygonsOnImage([item1, item2, item3],
|
|
shape=(10, 10, 3))
|
|
|
|
cbaoi_reduced = cbaoi.remove_out_of_image_fraction_(0.6)
|
|
|
|
assert len(cbaoi_reduced.items) == 2
|
|
assert cbaoi_reduced.items == [item1, item2]
|
|
assert cbaoi_reduced is cbaoi
|
|
|
|
|
|
class TestPolygonsOnImage_remove_out_of_image_fraction(unittest.TestCase):
|
|
def test_three_polygons(self):
|
|
item1 = ia.Polygon([(5, 1), (9, 1), (9, 2), (5, 2)])
|
|
item2 = ia.Polygon([(5, 1), (15, 1), (15, 2), (5, 2)])
|
|
item3 = ia.Polygon([(15, 1), (25, 1), (25, 2), (15, 2)])
|
|
cbaoi = ia.PolygonsOnImage([item1, item2, item3],
|
|
shape=(10, 10, 3))
|
|
|
|
cbaoi_reduced = cbaoi.remove_out_of_image_fraction(0.6)
|
|
|
|
assert len(cbaoi_reduced.items) == 2
|
|
assert cbaoi_reduced.items == [item1, item2]
|
|
assert cbaoi_reduced is not cbaoi
|
|
|
|
|
|
class TestPolygonsOnImage_clip_out_of_image_(unittest.TestCase):
|
|
# NOTE: clip_out_of_image() can change the order of points,
|
|
# hence we check here for each expected point whether it appears
|
|
# somewhere in the list of points
|
|
|
|
@property
|
|
def _is_inplace(self):
|
|
return True
|
|
|
|
def _func(self, psoi):
|
|
return psoi.clip_out_of_image_()
|
|
|
|
@classmethod
|
|
def _any_point_close(cls, points, point_search):
|
|
found = False
|
|
for point in points:
|
|
if np.allclose(point, point_search, atol=1e-4, rtol=0):
|
|
found = True
|
|
return found
|
|
|
|
def test_with_zero_polygons(self):
|
|
# no polygons
|
|
poly_oi = ia.PolygonsOnImage([], shape=(10, 11, 3))
|
|
poly_oi_clip = self._func(poly_oi)
|
|
assert len(poly_oi_clip.polygons) == 0
|
|
assert poly_oi_clip.shape == (10, 11, 3)
|
|
|
|
def test_with_one_polygon_fully_inside(self):
|
|
# one polygon, fully inside
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(1, 1), (8, 1), (8, 9), (1, 9)])],
|
|
shape=(10, 11, 3))
|
|
poly_oi_clip = self._func(poly_oi)
|
|
assert len(poly_oi_clip.polygons) == 1
|
|
for point_search in [(1, 1), (8, 1), (8, 9), (1, 9)]:
|
|
assert self._any_point_close(poly_oi_clip.polygons[0].exterior,
|
|
point_search)
|
|
assert poly_oi_clip.shape == (10, 11, 3)
|
|
|
|
def test_with_one_polygon_partially_ooi(self):
|
|
# one polygon, partially outside
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(1, 1), (15, 1), (15, 9), (1, 9)])],
|
|
shape=(10, 11, 3))
|
|
poly_oi_clip = self._func(poly_oi)
|
|
assert len(poly_oi_clip.polygons) == 1
|
|
for point_search in [(1, 1), (11, 1), (11, 9), (1, 9)]:
|
|
assert self._any_point_close(poly_oi_clip.polygons[0].exterior,
|
|
point_search)
|
|
assert poly_oi_clip.shape == (10, 11, 3)
|
|
|
|
def test_with_one_polygon_fully_ooi(self):
|
|
# one polygon, fully outside
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(100, 100), (200, 100), (200, 200), (100, 200)])],
|
|
shape=(10, 11, 3))
|
|
poly_oi_clip = self._func(poly_oi)
|
|
assert len(poly_oi_clip.polygons) == 0
|
|
assert poly_oi_clip.shape == (10, 11, 3)
|
|
|
|
def test_with_three_pols_one_not_ooi_one_partially_ooi_one_fully_ooi(self):
|
|
# three polygons, one fully inside, one partially outside,
|
|
# one fully outside
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(1, 1), (8, 1), (8, 9), (1, 9)]),
|
|
ia.Polygon([(1, 1), (15, 1), (15, 9), (1, 9)]),
|
|
ia.Polygon([(100, 100), (200, 100), (200, 200), (100, 200)])],
|
|
shape=(10, 11, 3))
|
|
poly_oi_clip = self._func(poly_oi)
|
|
assert len(poly_oi_clip.polygons) == 2
|
|
for point_search in [(1, 1), (8, 1), (8, 9), (1, 9)]:
|
|
assert self._any_point_close(poly_oi_clip.polygons[0].exterior,
|
|
point_search)
|
|
for point_search in [(1, 1), (11, 1), (11, 9), (1, 9)]:
|
|
assert self._any_point_close(poly_oi_clip.polygons[1].exterior,
|
|
point_search)
|
|
assert poly_oi_clip.shape == (10, 11, 3)
|
|
|
|
def test_inplaceness(self):
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(1, 1), (15, 1), (15, 9), (1, 9)])],
|
|
shape=(10, 11, 3))
|
|
|
|
poly_oi_clip = self._func(poly_oi)
|
|
|
|
if self._is_inplace:
|
|
assert poly_oi_clip is poly_oi
|
|
else:
|
|
assert poly_oi_clip is not poly_oi
|
|
|
|
|
|
class TestPolygonsOnImage_clip_out_of_image(
|
|
TestPolygonsOnImage_clip_out_of_image_):
|
|
@property
|
|
def _is_inplace(self):
|
|
return False
|
|
|
|
def _func(self, psoi):
|
|
return psoi.clip_out_of_image()
|
|
|
|
|
|
class TestPolygonsOnImage_shift_(unittest.TestCase):
|
|
@property
|
|
def _is_inplace(self):
|
|
return True
|
|
|
|
def _func(self, psoi, *args, **kwargs):
|
|
def _func_impl():
|
|
return psoi.shift_(*args, **kwargs)
|
|
|
|
if len(psoi.polygons) == 0:
|
|
return _func_impl()
|
|
return wrap_shift_deprecation(_func_impl, *args, **kwargs)
|
|
|
|
def test_with_three_polygons_along_xy(self):
|
|
# three polygons
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(1, 1), (8, 1), (8, 9), (1, 9)]),
|
|
ia.Polygon([(1, 1), (15, 1), (15, 9), (1, 9)]),
|
|
ia.Polygon([(100, 100), (200, 100), (200, 200), (100, 200)])],
|
|
shape=(10, 11, 3))
|
|
poly_oi_shifted = self._func(poly_oi, x=1, y=2)
|
|
assert len(poly_oi_shifted.polygons) == 3
|
|
assert np.allclose(poly_oi_shifted.polygons[0].exterior,
|
|
[(1+1, 1+2), (8+1, 1+2), (8+1, 9+2), (1+1, 9+2)],
|
|
rtol=0, atol=1e-4)
|
|
assert np.allclose(poly_oi_shifted.polygons[1].exterior,
|
|
[(1+1, 1+2), (15+1, 1+2), (15+1, 9+2), (1+1, 9+2)],
|
|
rtol=0, atol=1e-4)
|
|
assert np.allclose(poly_oi_shifted.polygons[2].exterior,
|
|
[(100+1, 100+2), (200+1, 100+2),
|
|
(200+1, 200+2), (100+1, 200+2)],
|
|
rtol=0, atol=1e-4)
|
|
assert poly_oi_shifted.shape == (10, 11, 3)
|
|
|
|
def test_inplaceness(self):
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(1, 1), (8, 1), (8, 9), (1, 9)]),
|
|
ia.Polygon([(1, 1), (15, 1), (15, 9), (1, 9)]),
|
|
ia.Polygon([(100, 100), (200, 100), (200, 200), (100, 200)])],
|
|
shape=(10, 11, 3))
|
|
|
|
poly_oi_shifted = self._func(poly_oi, x=1)
|
|
|
|
if self._is_inplace:
|
|
assert poly_oi_shifted is poly_oi
|
|
else:
|
|
assert poly_oi_shifted is not poly_oi
|
|
|
|
|
|
class TestPolygonsOnImage_shift(TestPolygonsOnImage_shift_):
|
|
@property
|
|
def _is_inplace(self):
|
|
return False
|
|
|
|
def _func(self, psoi, *args, **kwargs):
|
|
def _func_impl():
|
|
return psoi.shift(*args, **kwargs)
|
|
|
|
return wrap_shift_deprecation(_func_impl, *args, **kwargs)
|
|
|
|
def test_with_zero_polygons(self):
|
|
# no polygons
|
|
poly_oi = ia.PolygonsOnImage([], shape=(10, 11, 3))
|
|
poly_oi_shifted = self._func(poly_oi, top=3, right=0, bottom=1,
|
|
left=-3)
|
|
assert len(poly_oi_shifted.polygons) == 0
|
|
assert poly_oi_shifted.shape == (10, 11, 3)
|
|
|
|
def test_with_three_polygons(self):
|
|
# three polygons
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(1, 1), (8, 1), (8, 9), (1, 9)]),
|
|
ia.Polygon([(1, 1), (15, 1), (15, 9), (1, 9)]),
|
|
ia.Polygon([(100, 100), (200, 100), (200, 200), (100, 200)])],
|
|
shape=(10, 11, 3))
|
|
poly_oi_shifted = self._func(poly_oi, top=3, right=0, bottom=1,
|
|
left=-3)
|
|
assert len(poly_oi_shifted.polygons) == 3
|
|
assert np.allclose(poly_oi_shifted.polygons[0].exterior,
|
|
[(1-3, 1+2), (8-3, 1+2), (8-3, 9+2), (1-3, 9+2)],
|
|
rtol=0, atol=1e-4)
|
|
assert np.allclose(poly_oi_shifted.polygons[1].exterior,
|
|
[(1-3, 1+2), (15-3, 1+2), (15-3, 9+2), (1-3, 9+2)],
|
|
rtol=0, atol=1e-4)
|
|
assert np.allclose(poly_oi_shifted.polygons[2].exterior,
|
|
[(100-3, 100+2), (200-3, 100+2),
|
|
(200-3, 200+2), (100-3, 200+2)],
|
|
rtol=0, atol=1e-4)
|
|
assert poly_oi_shifted.shape == (10, 11, 3)
|
|
|
|
|
|
class TestPolygonsOnImage_subdivide_(unittest.TestCase):
|
|
@property
|
|
def _is_inplace(self):
|
|
return True
|
|
|
|
def _func(self, psoi, points_per_edge):
|
|
return psoi.subdivide_(points_per_edge)
|
|
|
|
def test_mocked(self):
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(1, 1), (8, 1), (8, 9), (1, 9)]),
|
|
ia.Polygon([(1, 1), (15, 1), (15, 9), (1, 9)])],
|
|
shape=(10, 11, 3))
|
|
mock_sub = mock.Mock()
|
|
mock_sub.return_value = "foo"
|
|
poly_oi.items[0].subdivide_ = mock_sub
|
|
poly_oi.items[1].subdivide_ = mock_sub
|
|
|
|
poly_oi_sub = self._func(poly_oi, 2)
|
|
|
|
assert mock_sub.call_count == 2
|
|
assert mock_sub.call_args_list[0][0][0] == 2
|
|
assert mock_sub.call_args_list[1][0][0] == 2
|
|
assert poly_oi_sub.items == ["foo", "foo"]
|
|
|
|
def test_with_zero_polygons(self):
|
|
poly_oi = ia.PolygonsOnImage([], shape=(10, 11, 3))
|
|
poly_oi_sub = self._func(poly_oi, 1)
|
|
assert len(poly_oi_sub.polygons) == 0
|
|
assert poly_oi_sub.shape == (10, 11, 3)
|
|
|
|
def test_with_one_polygon(self):
|
|
poly_oi = ia.PolygonsOnImage([ia.Polygon([(0, 0), (1, 0)])],
|
|
shape=(10, 11, 3))
|
|
poly_oi_sub = self._func(poly_oi, 1)
|
|
assert poly_oi_sub.shape == (10, 11, 3)
|
|
assert len(poly_oi_sub.items) == 1
|
|
assert poly_oi_sub.items[0].coords_almost_equals([
|
|
(0, 0),
|
|
(0.5, 0),
|
|
(1, 0),
|
|
(0.5, 0)
|
|
])
|
|
|
|
def test_inplaceness(self):
|
|
poly_oi = ia.PolygonsOnImage([ia.Polygon([(0, 0), (1, 0)])],
|
|
shape=(10, 11, 3))
|
|
|
|
poly_oi_sub = self._func(poly_oi, 1)
|
|
|
|
if self._is_inplace:
|
|
assert poly_oi_sub is poly_oi
|
|
else:
|
|
assert poly_oi_sub is not poly_oi
|
|
|
|
|
|
class TestPolygonsOnImage_subdivide(TestPolygonsOnImage_subdivide_):
|
|
@property
|
|
def _is_inplace(self):
|
|
return False
|
|
|
|
def _func(self, psoi, points_per_edge):
|
|
return psoi.subdivide(points_per_edge)
|
|
|
|
def test_mocked(self):
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(1, 1), (8, 1), (8, 9), (1, 9)]),
|
|
ia.Polygon([(1, 1), (15, 1), (15, 9), (1, 9)])],
|
|
shape=(10, 11, 3))
|
|
mock_sub = mock.Mock()
|
|
mock_sub.return_value = "foo"
|
|
poly_oi.items[0].subdivide_ = mock_sub
|
|
poly_oi.items[1].subdivide_ = mock_sub
|
|
|
|
poly_oi_sub = self._func(poly_oi, 2)
|
|
|
|
# When the PSOI is copied, each polygon is also copied. That leads
|
|
# to new Polygon instances that have no longer the subdivide_
|
|
# method overwritten by the mock. Hence, the mock is never actually
|
|
# called here.
|
|
assert mock_sub.call_count == 0
|
|
assert poly_oi_sub.items != ["foo", "foo"]
|
|
|
|
|
|
class TestPolygonsOnImage_to_xy_array(unittest.TestCase):
|
|
def test_filled_object(self):
|
|
psoi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(0, 0), (1, 0), (1, 1)]),
|
|
ia.Polygon([(10, 10), (20, 0), (20, 20)])],
|
|
shape=(2, 2, 3))
|
|
|
|
xy_out = psoi.to_xy_array()
|
|
|
|
expected = np.float32([
|
|
[0.0, 0.0],
|
|
[1.0, 0.0],
|
|
[1.0, 1.0],
|
|
[10.0, 10.0],
|
|
[20.0, 0.0],
|
|
[20.0, 20.0]
|
|
])
|
|
assert xy_out.shape == (6, 2)
|
|
assert np.allclose(xy_out, expected)
|
|
assert xy_out.dtype.name == "float32"
|
|
|
|
def test_empty_object(self):
|
|
psoi = ia.PolygonsOnImage(
|
|
[],
|
|
shape=(1, 2, 3))
|
|
|
|
xy_out = psoi.to_xy_array()
|
|
|
|
assert xy_out.shape == (0, 2)
|
|
assert xy_out.dtype.name == "float32"
|
|
|
|
|
|
class TestPolygonsOnImage_fill_from_xy_array_(unittest.TestCase):
|
|
def test_empty_array(self):
|
|
xy = np.zeros((0, 2), dtype=np.float32)
|
|
psoi = ia.PolygonsOnImage([], shape=(2, 2, 3))
|
|
|
|
psoi = psoi.fill_from_xy_array_(xy)
|
|
|
|
assert len(psoi.polygons) == 0
|
|
|
|
def test_empty_list(self):
|
|
xy = []
|
|
psoi = ia.PolygonsOnImage([], shape=(2, 2, 3))
|
|
|
|
psoi = psoi.fill_from_xy_array_(xy)
|
|
|
|
assert len(psoi.polygons) == 0
|
|
|
|
def test_array_with_two_coords(self):
|
|
xy = np.array(
|
|
[(100, 100),
|
|
(101, 100),
|
|
(101, 101),
|
|
(110, 110),
|
|
(120, 100),
|
|
(120, 120)], dtype=np.float32)
|
|
psoi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(0, 0), (1, 0), (1, 1)]),
|
|
ia.Polygon([(10, 10), (20, 0), (20, 20)])],
|
|
shape=(2, 2, 3))
|
|
|
|
psoi = psoi.fill_from_xy_array_(xy)
|
|
|
|
assert len(psoi.polygons) == 2
|
|
assert np.allclose(
|
|
psoi.polygons[0].coords,
|
|
[(100, 100), (101, 100), (101, 101)])
|
|
assert np.allclose(
|
|
psoi.polygons[1].coords,
|
|
[(110, 110), (120, 100), (120, 120)])
|
|
|
|
def test_list_with_two_coords(self):
|
|
xy = [(100, 100),
|
|
(101, 100),
|
|
(101, 101),
|
|
(110, 110),
|
|
(120, 100),
|
|
(120, 120)]
|
|
psoi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(0, 0), (1, 0), (1, 1)]),
|
|
ia.Polygon([(10, 10), (20, 0), (20, 20)])],
|
|
shape=(2, 2, 3))
|
|
|
|
psoi = psoi.fill_from_xy_array_(xy)
|
|
|
|
assert len(psoi.polygons) == 2
|
|
assert np.allclose(
|
|
psoi.polygons[0].coords,
|
|
[(100, 100), (101, 100), (101, 101)])
|
|
assert np.allclose(
|
|
psoi.polygons[1].coords,
|
|
[(110, 110), (120, 100), (120, 120)])
|
|
|
|
|
|
class TestPolygonsOnImage_to_keypoints_on_image(unittest.TestCase):
|
|
def test_filled_instance(self):
|
|
psoi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(0, 0), (1, 0), (1, 1)]),
|
|
ia.Polygon([(10, 10), (20, 0), (20, 20)])],
|
|
shape=(1, 2, 3))
|
|
|
|
kpsoi = psoi.to_keypoints_on_image()
|
|
|
|
assert len(kpsoi.keypoints) == 2*3
|
|
assert kpsoi.keypoints[0].x == 0
|
|
assert kpsoi.keypoints[0].y == 0
|
|
assert kpsoi.keypoints[1].x == 1
|
|
assert kpsoi.keypoints[1].y == 0
|
|
assert kpsoi.keypoints[2].x == 1
|
|
assert kpsoi.keypoints[2].y == 1
|
|
assert kpsoi.keypoints[3].x == 10
|
|
assert kpsoi.keypoints[3].y == 10
|
|
assert kpsoi.keypoints[4].x == 20
|
|
assert kpsoi.keypoints[4].y == 0
|
|
assert kpsoi.keypoints[5].x == 20
|
|
assert kpsoi.keypoints[5].y == 20
|
|
|
|
def test_empty_instance(self):
|
|
psoi = ia.PolygonsOnImage([], shape=(1, 2, 3))
|
|
|
|
kpsoi = psoi.to_keypoints_on_image()
|
|
|
|
assert len(kpsoi.keypoints) == 0
|
|
|
|
|
|
class TestPolygonsOnImage_invert_to_keypoints_on_image(unittest.TestCase):
|
|
def test_filled_instance(self):
|
|
psoi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(0, 0), (1, 0), (1, 1)]),
|
|
ia.Polygon([(10, 10), (20, 0), (20, 20)])],
|
|
shape=(1, 2, 3))
|
|
kpsoi = ia.KeypointsOnImage(
|
|
[ia.Keypoint(100, 100), ia.Keypoint(101, 100),
|
|
ia.Keypoint(101, 101),
|
|
ia.Keypoint(110, 110), ia.Keypoint(120, 100),
|
|
ia.Keypoint(120, 120)],
|
|
shape=(10, 20, 30))
|
|
|
|
psoi_inv = psoi.invert_to_keypoints_on_image_(kpsoi)
|
|
|
|
assert len(psoi_inv.polygons) == 2
|
|
assert psoi_inv.shape == (10, 20, 30)
|
|
assert np.allclose(
|
|
psoi.polygons[0].coords,
|
|
[(100, 100), (101, 100), (101, 101)])
|
|
assert np.allclose(
|
|
psoi.polygons[1].coords,
|
|
[(110, 110), (120, 100), (120, 120)])
|
|
|
|
def test_empty_instance(self):
|
|
psoi = ia.PolygonsOnImage([], shape=(1, 2, 3))
|
|
kpsoi = ia.KeypointsOnImage([], shape=(10, 20, 30))
|
|
|
|
psoi_inv = psoi.invert_to_keypoints_on_image_(kpsoi)
|
|
|
|
assert len(psoi_inv.polygons) == 0
|
|
assert psoi_inv.shape == (10, 20, 30)
|
|
|
|
|
|
class TestPolygonsOnImage_copy(unittest.TestCase):
|
|
def test_with_two_polygons(self):
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(1, 1), (8, 1), (8, 9), (1, 9)]),
|
|
ia.Polygon([(2, 2), (16, 2), (16, 10), (2, 10)])],
|
|
shape=(10, 11, 3))
|
|
poly_oi_copy = poly_oi.copy()
|
|
assert len(poly_oi_copy.polygons) == 2
|
|
assert np.allclose(poly_oi_copy.polygons[0].exterior,
|
|
[(1, 1), (8, 1), (8, 9), (1, 9)],
|
|
rtol=0, atol=1e-4)
|
|
assert np.allclose(poly_oi_copy.polygons[1].exterior,
|
|
[(2, 2), (16, 2), (16, 10), (2, 10)],
|
|
rtol=0, atol=1e-4)
|
|
|
|
poly_oi_copy.shape = (20, 30, 3)
|
|
assert poly_oi.shape == (10, 11, 3)
|
|
assert poly_oi_copy.shape == (20, 30, 3)
|
|
|
|
# make sure that changing the polygons only affects the copy
|
|
poly_oi_copy.polygons = [ia.Polygon([(0, 0), (1, 0), (1, 1)])]
|
|
assert np.allclose(poly_oi.polygons[0].exterior,
|
|
[(1, 1), (8, 1), (8, 9), (1, 9)],
|
|
rtol=0, atol=1e-4)
|
|
assert np.allclose(poly_oi_copy.polygons[0].exterior,
|
|
[(0, 0), (1, 0), (1, 1)],
|
|
rtol=0, atol=1e-4)
|
|
|
|
def test_polygons_parameter_set(self):
|
|
poly1 = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
poly2 = ia.Polygon([(0+1, 0), (1+1, 0), (1+1, 1)])
|
|
poly3 = ia.Polygon([(0+2, 0), (1+2, 0), (1+2, 1)])
|
|
psoi = ia.PolygonsOnImage([poly1, poly2], shape=(40, 50, 3))
|
|
|
|
psoi_copy = psoi.copy(polygons=[poly3])
|
|
|
|
assert psoi_copy is not psoi
|
|
assert psoi_copy.shape == (40, 50, 3)
|
|
assert psoi_copy.polygons == [poly3]
|
|
|
|
def test_shape_parameter_set(self):
|
|
poly1 = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
poly2 = ia.Polygon([(0+1, 0), (1+1, 0), (1+1, 1)])
|
|
psoi = ia.PolygonsOnImage([poly1, poly2], shape=(40, 50, 3))
|
|
|
|
psoi_copy = psoi.copy(shape=(40+1, 50+1, 3))
|
|
|
|
assert psoi_copy is not psoi
|
|
assert psoi_copy.shape == (40+1, 50+1, 3)
|
|
assert psoi_copy.polygons == [poly1, poly2]
|
|
|
|
|
|
class TestPolygonsOnImage_deepcopy(unittest.TestCase):
|
|
def test_with_two_polygons(self):
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(1, 1), (8, 1), (8, 9), (1, 9)]),
|
|
ia.Polygon([(2, 2), (16, 2), (16, 10), (2, 10)])],
|
|
shape=(10, 11, 3))
|
|
poly_oi_copy = poly_oi.deepcopy()
|
|
assert len(poly_oi_copy.polygons) == 2
|
|
assert np.allclose(poly_oi_copy.polygons[0].exterior,
|
|
[(1, 1), (8, 1), (8, 9), (1, 9)],
|
|
rtol=0, atol=1e-4)
|
|
assert np.allclose(poly_oi_copy.polygons[1].exterior,
|
|
[(2, 2), (16, 2), (16, 10), (2, 10)],
|
|
rtol=0, atol=1e-4)
|
|
|
|
poly_oi_copy.shape = (20, 30, 3)
|
|
assert poly_oi.shape == (10, 11, 3)
|
|
assert poly_oi_copy.shape == (20, 30, 3)
|
|
|
|
# make sure that changing the polygons only affects the copy
|
|
poly_oi_copy.polygons[0] = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
assert np.allclose(poly_oi.polygons[0].exterior,
|
|
[(1, 1), (8, 1), (8, 9), (1, 9)],
|
|
rtol=0, atol=1e-4)
|
|
assert np.allclose(poly_oi_copy.polygons[0].exterior,
|
|
[(0, 0), (1, 0), (1, 1)],
|
|
rtol=0, atol=1e-4)
|
|
|
|
# make sure that the arrays were also copied
|
|
poly_oi_copy.polygons[1].exterior[0][0] = 100
|
|
assert np.allclose(poly_oi.polygons[1].exterior,
|
|
[(2, 2), (16, 2), (16, 10), (2, 10)],
|
|
rtol=0, atol=1e-4)
|
|
assert np.allclose(poly_oi_copy.polygons[1].exterior,
|
|
[(100, 2), (16, 2), (16, 10), (2, 10)],
|
|
rtol=0, atol=1e-4)
|
|
|
|
def test_polygons_parameter_set(self):
|
|
poly1 = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
poly2 = ia.Polygon([(0+1, 0), (1+1, 0), (1+1, 1)])
|
|
poly3 = ia.Polygon([(0+2, 0), (1+2, 0), (1+2, 1)])
|
|
psoi = ia.PolygonsOnImage([poly1, poly2], shape=(40, 50, 3))
|
|
|
|
psoi_copy = psoi.deepcopy(polygons=[poly3])
|
|
|
|
assert psoi_copy is not psoi
|
|
assert psoi_copy.shape == (40, 50, 3)
|
|
assert len(psoi_copy.polygons) == 1
|
|
assert psoi_copy.polygons[0].coords_almost_equals(poly3)
|
|
|
|
def test_shape_parameter_set(self):
|
|
poly1 = ia.Polygon([(0, 0), (1, 0), (1, 1)])
|
|
poly2 = ia.Polygon([(0+1, 0), (1+1, 0), (1+1, 1)])
|
|
psoi = ia.PolygonsOnImage([poly1, poly2], shape=(40, 50, 3))
|
|
|
|
psoi_copy = psoi.deepcopy(shape=(40+1, 50+1, 3))
|
|
|
|
assert psoi_copy is not psoi
|
|
assert psoi_copy.shape == (40+1, 50+1, 3)
|
|
assert len(psoi_copy.polygons) == 2
|
|
assert psoi_copy.polygons[0].coords_almost_equals(poly1)
|
|
assert psoi_copy.polygons[1].coords_almost_equals(poly2)
|
|
|
|
|
|
class TestPolygonsOnImage___getitem__(unittest.TestCase):
|
|
def test_with_two_polygons(self):
|
|
cbas = [
|
|
ia.Polygon([(0, 0), (1, 0), (1, 1)]),
|
|
ia.Polygon([(1, 1), (2, 1), (2, 2)])
|
|
]
|
|
cbasoi = ia.PolygonsOnImage(cbas, shape=(3, 4, 3))
|
|
|
|
assert cbasoi[0] is cbas[0]
|
|
assert cbasoi[1] is cbas[1]
|
|
assert cbasoi[0:2] == cbas
|
|
|
|
|
|
class TestPolygonsOnImage___iter__(unittest.TestCase):
|
|
def test_with_two_polygons(self):
|
|
cbas = [ia.Polygon([(0, 0), (1, 0), (1, 1)]),
|
|
ia.Polygon([(1, 0), (2, 2), (1.5, 3)])]
|
|
cbasoi = ia.PolygonsOnImage(cbas, shape=(40, 50, 3))
|
|
|
|
for i, cba in enumerate(cbasoi):
|
|
assert cba is cbas[i]
|
|
|
|
def test_with_zero_polygons(self):
|
|
cbasoi = ia.PolygonsOnImage([], shape=(40, 50, 3))
|
|
i = 0
|
|
for _cba in cbasoi:
|
|
i += 1
|
|
assert i == 0
|
|
|
|
|
|
class TestPolygonsOnImage___len__(unittest.TestCase):
|
|
def test_with_two_polygons(self):
|
|
cbas = [ia.Polygon([(0, 0), (1, 0), (1, 1)]),
|
|
ia.Polygon([(1, 0), (2, 2), (1.5, 3)])]
|
|
cbasoi = ia.PolygonsOnImage(cbas, shape=(40, 50, 3))
|
|
assert len(cbasoi) == 2
|
|
|
|
|
|
class TestPolygonsOnImage___repr___and___str__(unittest.TestCase):
|
|
def test_with_zero_polygons(self):
|
|
poly_oi = ia.PolygonsOnImage([], shape=(10, 11, 3))
|
|
expected = "PolygonsOnImage([], shape=(10, 11, 3))"
|
|
assert poly_oi.__repr__() == expected
|
|
assert poly_oi.__str__() == expected
|
|
|
|
def test_with_two_polygons(self):
|
|
poly_oi = ia.PolygonsOnImage(
|
|
[ia.Polygon([(1, 1), (8, 1), (8, 9), (1, 9)]),
|
|
ia.Polygon([(2, 2), (16, 2), (16, 10), (2, 10)])],
|
|
shape=(10, 11, 3))
|
|
expected = (
|
|
"PolygonsOnImage(["
|
|
"Polygon([(x=1.000, y=1.000), (x=8.000, y=1.000), "
|
|
"(x=8.000, y=9.000), (x=1.000, y=9.000)] "
|
|
"(4 points), label=None), "
|
|
"Polygon([(x=2.000, y=2.000), (x=16.000, y=2.000), "
|
|
"(x=16.000, y=10.000), (x=2.000, y=10.000)] "
|
|
"(4 points), label=None)"
|
|
"], shape=(10, 11, 3))"
|
|
)
|
|
assert poly_oi.__repr__() == expected
|
|
assert poly_oi.__str__() == expected
|
|
|
|
|
|
class Test_ConcavePolygonRecoverer(unittest.TestCase):
|
|
def setUp(self):
|
|
reseed()
|
|
|
|
@classmethod
|
|
def _assert_points_are_identical(cls, observed, expected, atol=1e-8,
|
|
rtol=0):
|
|
assert len(observed) == len(expected)
|
|
for i, (ps_obs, ps_exp) in enumerate(zip(observed, expected)):
|
|
assert len(ps_obs) == len(ps_exp), "Failed at point %d" % (i,)
|
|
for p_obs, p_exp in zip(ps_obs, ps_exp):
|
|
assert len(p_obs) == 2
|
|
assert len(p_exp) == 2
|
|
assert np.allclose(p_obs, p_exp, atol=atol, rtol=rtol), (
|
|
"Unexpected coords at %d" % (i,))
|
|
|
|
# TODO split into multiple tests
|
|
def test_recover_from_fails_for_less_than_three_points(self):
|
|
old_polygon = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
cpr = _ConcavePolygonRecoverer()
|
|
with self.assertRaises(AssertionError):
|
|
_poly = cpr.recover_from([], old_polygon)
|
|
|
|
with self.assertRaises(AssertionError):
|
|
_poly = cpr.recover_from([(0, 0)], old_polygon)
|
|
|
|
with self.assertRaises(AssertionError):
|
|
_poly = cpr.recover_from([(0, 0), (1, 0)], old_polygon)
|
|
|
|
_poly = cpr.recover_from([(0, 0), (1, 0), (1, 1)], old_polygon)
|
|
|
|
def test_recover_from_concave_polygons(self):
|
|
cpr = _ConcavePolygonRecoverer()
|
|
|
|
polys = [
|
|
[(0, 0), (1, 0), (1, 1)],
|
|
[(0, 0), (1, 0), (1, 1), (0, 1)],
|
|
[(0, 0), (0.5, 0), (1, 0), (1, 0.5), (1, 1), (0.5, 1.0), (0, 1)],
|
|
]
|
|
|
|
for poly in polys:
|
|
with self.subTest(poly=str(poly)):
|
|
old_polygon = ia.Polygon(poly)
|
|
poly_concave = cpr.recover_from(poly, old_polygon)
|
|
assert poly_concave.is_valid
|
|
found = [False] * len(poly)
|
|
for i, point in enumerate(poly):
|
|
for point_ext in poly_concave.exterior:
|
|
dist = np.sqrt((point[0] - point_ext[0])**2
|
|
+ (point[1] - point_ext[1])**2)
|
|
if dist < 0.01:
|
|
found[i] = True
|
|
assert np.all(found)
|
|
|
|
def test_recover_from_line(self):
|
|
cpr = _ConcavePolygonRecoverer()
|
|
|
|
poly = [(0, 0), (1, 0), (2, 0)]
|
|
old_polygon = ia.Polygon(poly)
|
|
poly_concave = cpr.recover_from(poly, old_polygon)
|
|
assert poly_concave.is_valid
|
|
found = [False] * len(poly)
|
|
for i, point in enumerate(poly):
|
|
for point_ext in poly_concave.exterior:
|
|
dist = np.sqrt((point[0] - point_ext[0])**2
|
|
+ (point[1] - point_ext[1])**2)
|
|
if dist < 0.025:
|
|
found[i] = True
|
|
assert np.all(found)
|
|
|
|
def test_recover_from_polygon_with_duplicate_points(self):
|
|
cpr = _ConcavePolygonRecoverer()
|
|
|
|
poly = [(0, 0), (1, 0), (1, 0), (1, 1)]
|
|
old_polygon = ia.Polygon(poly)
|
|
poly_concave = cpr.recover_from(poly, old_polygon)
|
|
assert poly_concave.is_valid
|
|
found = [False] * len(poly)
|
|
for i, point in enumerate(poly):
|
|
for point_ext in poly_concave.exterior:
|
|
dist = np.sqrt((point[0] - point_ext[0])**2
|
|
+ (point[1] - point_ext[1])**2)
|
|
if dist < 0.01:
|
|
found[i] = True
|
|
assert np.all(found)
|
|
|
|
def test_recover_from_invalid_polygon(self):
|
|
cpr = _ConcavePolygonRecoverer()
|
|
poly = [(0, 0), (0.5, 0), (0.5, 1.2), (1, 0), (1, 1), (0, 1)]
|
|
old_polygon = ia.Polygon(poly)
|
|
poly_concave = cpr.recover_from(poly, old_polygon)
|
|
assert poly_concave.is_valid
|
|
found = [False] * len(poly)
|
|
for i, point in enumerate(poly):
|
|
for point_ext in poly_concave.exterior:
|
|
dist = np.sqrt(
|
|
(point[0] - point_ext[0])**2
|
|
+ (point[1] - point_ext[1])**2
|
|
)
|
|
if dist < 0.025:
|
|
found[i] = True
|
|
assert np.all(found)
|
|
|
|
def test_recover_from_random_polygons(self):
|
|
cpr = _ConcavePolygonRecoverer()
|
|
nb_iterations = 10
|
|
height, width = 10, 20
|
|
nb_points_matrix = np.random.randint(3, 30, size=(nb_iterations,))
|
|
for nb_points in nb_points_matrix:
|
|
points = np.random.random(size=(nb_points, 2))
|
|
points[:, 0] *= width
|
|
points[:, 1] *= height
|
|
# currently mainly used to copy the label, so not a significant
|
|
# issue that it is not concave
|
|
old_polygon = ia.Polygon(points)
|
|
poly_concave = cpr.recover_from(points, old_polygon)
|
|
assert poly_concave.is_valid
|
|
# test if all points are in BB around returned polygon
|
|
# would be better to directly call a polygon.contains(point) method
|
|
# but that does not yet exist
|
|
xx = poly_concave.exterior[:, 0]
|
|
yy = poly_concave.exterior[:, 1]
|
|
bb_x1, bb_x2 = min(xx), max(xx)
|
|
bb_y1, bb_y2 = min(yy), max(yy)
|
|
bb = ia.BoundingBox(
|
|
x1=bb_x1-1e-4, y1=bb_y1-1e-4,
|
|
x2=bb_x2+1e-4, y2=bb_y2+1e-4)
|
|
for point in points:
|
|
assert bb.contains(ia.Keypoint(x=point[0], y=point[1]))
|
|
|
|
def test__remove_consecutive_duplicate_points(self):
|
|
recoverer = _ConcavePolygonRecoverer()
|
|
|
|
points = [
|
|
[(0, 0), (1, 1)],
|
|
[(0.0, 0.5), (1.0, 1.0)],
|
|
np.float32([(0.0, 0.5), (1.0, 1.0)]),
|
|
[(0, 0), (0, 0)],
|
|
[(0, 0), (0, 0), (1, 0)],
|
|
[(0, 0), (1, 0), (1, 0)],
|
|
[(0, 0), (1, 0), (1, 0), (2, 0), (0, 0)]
|
|
]
|
|
expected = [
|
|
[(0, 0), (1, 1)],
|
|
[(0.0, 0.5), (1.0, 1.0)],
|
|
[(0.0, 0.5), (1.0, 1.0)],
|
|
[(0, 0)],
|
|
[(0, 0), (1, 0)],
|
|
[(0, 0), (1, 0)],
|
|
[(0, 0), (1, 0), (2, 0)]
|
|
]
|
|
|
|
for points_i, expected_i in zip(points, expected):
|
|
with self.subTest(points=points_i):
|
|
points_deduplicated = \
|
|
recoverer._remove_consecutive_duplicate_points(points_i)
|
|
assert np.allclose(points_deduplicated, expected_i)
|
|
|
|
# TODO split into multiple tests
|
|
def test__jitter_duplicate_points(self):
|
|
def _norm(a, b):
|
|
return np.linalg.norm(np.float32(a) - np.float32(b))
|
|
|
|
cpr = _ConcavePolygonRecoverer(threshold_duplicate_points=1e-4)
|
|
rng = iarandom.RNG(0)
|
|
|
|
points = [(0, 0), (1, 0), (1, 1), (0, 1)]
|
|
points_jittered = cpr._jitter_duplicate_points(points, rng.copy())
|
|
assert np.allclose(points, points_jittered, rtol=0, atol=1e-4)
|
|
|
|
points = [(0, 0), (1, 0), (0, 1)]
|
|
points_jittered = cpr._jitter_duplicate_points(points, rng.copy())
|
|
assert np.allclose(points, points_jittered, rtol=0, atol=1e-4)
|
|
|
|
points = [(0, 0), (0.01, 0), (0.01, 0.01), (0, 0.01)]
|
|
points_jittered = cpr._jitter_duplicate_points(points, rng.copy())
|
|
assert np.allclose(points, points_jittered, rtol=0, atol=1e-4)
|
|
|
|
points = [(0, 0), (1, 0), (1 + 1e-6, 0), (1, 1), (0, 1)]
|
|
points_jittered = cpr._jitter_duplicate_points(points, rng.copy())
|
|
assert np.allclose(
|
|
[point
|
|
for i, point
|
|
in enumerate(points_jittered)
|
|
if i in [0, 1, 3, 4]],
|
|
[(0, 0), (1, 0), (1, 1), (0, 1)],
|
|
rtol=0,
|
|
atol=1e-5
|
|
)
|
|
assert _norm([1, 0], points_jittered[2]) >= 1e-4
|
|
|
|
points = [(0, 0), (1, 0), (1, 1), (1 + 1e-6, 0), (0, 1)]
|
|
points_jittered = cpr._jitter_duplicate_points(points, rng.copy())
|
|
assert np.allclose(
|
|
[point
|
|
for i, point
|
|
in enumerate(points_jittered)
|
|
if i in [0, 1, 2, 4]],
|
|
[(0, 0), (1, 0), (1, 1), (0, 1)],
|
|
rtol=0,
|
|
atol=1e-5
|
|
)
|
|
assert _norm([1, 0], points_jittered[3]) >= 1e-4
|
|
|
|
points = [(0, 0), (1, 0), (1, 1), (0, 1), (1 + 1e-6, 0)]
|
|
points_jittered = cpr._jitter_duplicate_points(points, rng.copy())
|
|
assert np.allclose(
|
|
[point
|
|
for i, point
|
|
in enumerate(points_jittered)
|
|
if i in [0, 1, 2, 3]],
|
|
[(0, 0), (1, 0), (1, 1), (0, 1)],
|
|
rtol=0,
|
|
atol=1e-5
|
|
)
|
|
assert _norm([1, 0], points_jittered[4]) >= 1e-4
|
|
|
|
points = [(0, 0), (1, 0), (1 + 1e-6, 0), (1, 1), (1 + 1e-6, 0), (0, 1),
|
|
(1 + 1e-6, 0), (1 + 1e-6, 0 + 1e-6), (1 + 1e-6, 0 + 2e-6)]
|
|
points_jittered = cpr._jitter_duplicate_points(points, rng.copy())
|
|
assert np.allclose(
|
|
[point
|
|
for i, point
|
|
in enumerate(points_jittered)
|
|
if i in [0, 1, 3, 5]],
|
|
[(0, 0), (1, 0), (1, 1), (0, 1)],
|
|
rtol=0,
|
|
atol=1e-5
|
|
)
|
|
assert _norm([1, 0], points_jittered[2]) >= 1e-4
|
|
assert _norm([1, 0], points_jittered[4]) >= 1e-4
|
|
assert _norm([1, 0], points_jittered[6]) >= 1e-4
|
|
assert _norm([1, 0], points_jittered[7]) >= 1e-4
|
|
assert _norm([1, 0], points_jittered[8]) >= 1e-4
|
|
|
|
points = [(0, 0), (1, 0), (0 + 1e-6, 0 - 1e-6), (1 + 1e-6, 0), (1, 1),
|
|
(1 + 1e-6, 0), (0, 1), (1 + 1e-6, 0), (1 + 1e-6, 0 + 1e-6),
|
|
(1 + 1e-6, 0 + 2e-6)]
|
|
points_jittered = cpr._jitter_duplicate_points(points, rng.copy())
|
|
assert np.allclose(
|
|
[point
|
|
for i, point
|
|
in enumerate(points_jittered)
|
|
if i in [0, 1, 4, 6]],
|
|
[(0, 0), (1, 0), (1, 1), (0, 1)],
|
|
rtol=0,
|
|
atol=1e-5
|
|
)
|
|
assert _norm([0, 0], points_jittered[2]) >= 1e-4
|
|
assert _norm([1, 0], points_jittered[3]) >= 1e-4
|
|
assert _norm([1, 0], points_jittered[5]) >= 1e-4
|
|
assert _norm([1, 0], points_jittered[7]) >= 1e-4
|
|
assert _norm([1, 0], points_jittered[8]) >= 1e-4
|
|
assert _norm([1, 0], points_jittered[9]) >= 1e-4
|
|
|
|
# TODO split into multiple tests
|
|
def test__calculate_circumference(self):
|
|
points = [(0, 0), (1, 0), (1, 1), (0, 1)]
|
|
circ = _ConcavePolygonRecoverer._calculate_circumference(points)
|
|
assert np.allclose(circ, 4)
|
|
|
|
points = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
|
|
circ = _ConcavePolygonRecoverer._calculate_circumference(points)
|
|
assert np.allclose(circ, 4)
|
|
|
|
points = np.float32([(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)])
|
|
circ = _ConcavePolygonRecoverer._calculate_circumference(points)
|
|
assert np.allclose(circ, 4)
|
|
|
|
points = [(0, 0), (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0)]
|
|
circ = _ConcavePolygonRecoverer._calculate_circumference(points)
|
|
assert np.allclose(circ, 6)
|
|
|
|
# TODO split into multiple tests
|
|
def test__fit_best_valid_polygon(self):
|
|
def _assert_ids_match(observed, expected):
|
|
assert len(observed) == len(expected), (
|
|
"len mismatch: %d vs %d" % (len(observed), len(expected)))
|
|
|
|
max_count = 0
|
|
for i in range(len(observed)):
|
|
counter = 0
|
|
for j in range(i, i+len(expected)):
|
|
observed_item = observed[(i+j) % len(observed)]
|
|
expected_item = expected[j % len(expected)]
|
|
if observed_item == expected_item:
|
|
counter += 1
|
|
else:
|
|
break
|
|
|
|
max_count = max(max_count, counter)
|
|
|
|
assert max_count == len(expected), (
|
|
"count mismatch: %d vs %d" % (max_count, len(expected)))
|
|
|
|
cpr = _ConcavePolygonRecoverer()
|
|
rng = iarandom.RNG(0)
|
|
|
|
points = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
|
|
points_fit = cpr._fit_best_valid_polygon(
|
|
points, random_state=rng.copy())
|
|
# doing this without the list(.) wrappers fails on python2.7
|
|
assert list(points_fit) == list(sm.xrange(len(points)))
|
|
|
|
# square-like, but top line has one point in its center which's
|
|
# y-coordinate is below the bottom line
|
|
points = [(0.0, 0.0), (0.45, 0.0), (0.5, 1.5), (0.55, 0.0), (1.0, 0.0),
|
|
(1.0, 1.0), (0.0, 1.0)]
|
|
points_fit = cpr._fit_best_valid_polygon(
|
|
points, random_state=rng.copy())
|
|
_assert_ids_match(points_fit, [0, 1, 3, 4, 5, 2, 6])
|
|
assert ia.Polygon([points[idx] for idx in points_fit]).is_valid
|
|
|
|
# |--| |--|
|
|
# | | | |
|
|
# | | | |
|
|
# |--|--|--|
|
|
# | |
|
|
# ----
|
|
# the intersection points on the bottom line are not provided,
|
|
# hence the result is expected to have triangles at the bottom left
|
|
# and right
|
|
points = [(0.0, 0), (0.25, 0), (0.25, 1.25),
|
|
(0.75, 1.25), (0.75, 0), (1.0, 0),
|
|
(1.0, 1.0), (0.0, 1.0)]
|
|
points_fit = cpr._fit_best_valid_polygon(
|
|
points, random_state=rng.copy())
|
|
_assert_ids_match(points_fit, [0, 1, 4, 5, 6, 3, 2, 7])
|
|
poly_observed = ia.Polygon([points[idx] for idx in points_fit])
|
|
assert poly_observed.is_valid
|
|
|
|
# same as above, but intersection points at the bottom line are
|
|
# provided without oversampling, i.e. incorporating these points
|
|
# would lead to an invalid polygon
|
|
points = [(0.0, 0), (0.25, 0), (0.25, 1.0), (0.25, 1.25),
|
|
(0.75, 1.25), (0.75, 1.0), (0.75, 0), (1.0, 0),
|
|
(1.0, 1.0), (0.0, 1.0)]
|
|
points_fit = cpr._fit_best_valid_polygon(
|
|
points, random_state=rng.copy())
|
|
assert len(points_fit) >= len(points) - 2 # TODO add IoU check here
|
|
poly_observed = ia.Polygon([points[idx] for idx in points_fit])
|
|
assert poly_observed.is_valid
|
|
|
|
# TODO split into multiple tests
|
|
def test__fix_polygon_is_line(self):
|
|
cpr = _ConcavePolygonRecoverer()
|
|
rng = iarandom.RNG(0)
|
|
|
|
points = [(0, 0), (1, 0), (1, 1)]
|
|
points_fixed = cpr._fix_polygon_is_line(points, rng.copy())
|
|
assert np.allclose(points_fixed, points, atol=0, rtol=0)
|
|
|
|
points = [(0, 0), (1, 0), (2, 0)]
|
|
points_fixed = cpr._fix_polygon_is_line(points, rng.copy())
|
|
assert not np.allclose(points_fixed, points, atol=0, rtol=0)
|
|
assert not cpr._is_polygon_line(points_fixed)
|
|
assert np.allclose(points_fixed, points, rtol=0, atol=1e-2)
|
|
|
|
points = [(0, 0), (0, 1), (0, 2)]
|
|
points_fixed = cpr._fix_polygon_is_line(points, rng.copy())
|
|
assert not np.allclose(points_fixed, points, atol=0, rtol=0)
|
|
assert not cpr._is_polygon_line(points_fixed)
|
|
assert np.allclose(points_fixed, points, rtol=0, atol=1e-2)
|
|
|
|
points = [(0, 0), (1, 1), (2, 2)]
|
|
points_fixed = cpr._fix_polygon_is_line(points, rng.copy())
|
|
assert not np.allclose(points_fixed, points, atol=0, rtol=0)
|
|
assert not cpr._is_polygon_line(points_fixed)
|
|
assert np.allclose(points_fixed, points, rtol=0, atol=1e-2)
|
|
|
|
# TODO split into multiple tests
|
|
def test__is_polygon_line(self):
|
|
points = [(0, 0), (1, 0), (1, 1)]
|
|
assert not _ConcavePolygonRecoverer._is_polygon_line(points)
|
|
|
|
points = [(0, 0), (1, 0), (1, 1), (0, 1)]
|
|
assert not _ConcavePolygonRecoverer._is_polygon_line(points)
|
|
|
|
points = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
|
|
assert not _ConcavePolygonRecoverer._is_polygon_line(points)
|
|
|
|
points = np.float32([(0, 0), (1, 0), (1, 1), (0, 1)])
|
|
assert not _ConcavePolygonRecoverer._is_polygon_line(points)
|
|
|
|
points = [(0, 0), (1, 0)]
|
|
assert _ConcavePolygonRecoverer._is_polygon_line(points)
|
|
|
|
points = [(0, 0), (1, 0), (2, 0)]
|
|
assert _ConcavePolygonRecoverer._is_polygon_line(points)
|
|
|
|
points = [(0, 0), (1, 0), (1, 0)]
|
|
assert _ConcavePolygonRecoverer._is_polygon_line(points)
|
|
|
|
points = [(0, 0), (1, 0), (1, 0), (2, 0)]
|
|
assert _ConcavePolygonRecoverer._is_polygon_line(points)
|
|
|
|
points = [(0, 0), (1, 0), (1, 0), (2, 0), (0.5, 0)]
|
|
assert _ConcavePolygonRecoverer._is_polygon_line(points)
|
|
|
|
points = [(0, 0), (1, 0), (1, 0), (2, 0), (1, 1)]
|
|
assert not _ConcavePolygonRecoverer._is_polygon_line(points)
|
|
|
|
# TODO split into multiple tests
|
|
def test__generate_intersection_points(self):
|
|
cpr = _ConcavePolygonRecoverer()
|
|
|
|
# triangle
|
|
points = [(0.5, 0), (1, 1), (0, 1)]
|
|
points_inter = cpr._generate_intersection_points(
|
|
points, one_point_per_intersection=False)
|
|
assert points_inter == [[], [], []]
|
|
|
|
# rotated square
|
|
points = [(0.5, 0), (1, 0.5), (0.5, 1), (0, 0.5)]
|
|
points_inter = cpr._generate_intersection_points(
|
|
points, one_point_per_intersection=False)
|
|
assert points_inter == [[], [], [], []]
|
|
|
|
# square
|
|
points = [(0, 0), (1, 0), (1, 1), (0, 1)]
|
|
points_inter = cpr._generate_intersection_points(
|
|
points, one_point_per_intersection=False)
|
|
assert points_inter == [[], [], [], []]
|
|
|
|
# |--| |--|
|
|
# | |__| |
|
|
# | |
|
|
# |--------|
|
|
points = [(0.0, 0), (0.25, 0), (0.25, 0.25),
|
|
(0.75, 0.25), (0.75, 0), (1.0, 0),
|
|
(1.0, 1.0), (0.0, 1.0)]
|
|
points_inter = cpr._generate_intersection_points(
|
|
points, one_point_per_intersection=False)
|
|
assert points_inter == [[], [], [], [], [], [], [], []]
|
|
|
|
# same as above, but middle part goes much further down,
|
|
# crossing the bottom line
|
|
points = [(0.0, 0), (0.25, 0), (0.25, 1.25),
|
|
(0.75, 1.25), (0.75, 0), (1.0, 0),
|
|
(1.0, 1.0), (0.0, 1.0)]
|
|
points_inter = cpr._generate_intersection_points(
|
|
points, one_point_per_intersection=False)
|
|
self._assert_points_are_identical(
|
|
points_inter,
|
|
[[], [(0.25, 1.0)], [], [(0.75, 1.0)], [], [],
|
|
[(0.75, 1.0), (0.25, 1.0)], []])
|
|
|
|
# square-like structure with intersections in top right area
|
|
points = [(0, 0), (0.5, 0), (1.01, 0.5), (1.0, 0), (1, 1), (0, 1),
|
|
(0, 0)]
|
|
points_inter = cpr._generate_intersection_points(
|
|
points, one_point_per_intersection=False)
|
|
self._assert_points_are_identical(
|
|
points_inter,
|
|
[[], [(1.0, 0.4902)], [], [(1.0, 0.4902)], [], [], []],
|
|
atol=1e-2)
|
|
|
|
# same as above, but with a second intersection in bottom left
|
|
points = [(0, 0), (0.5, 0), (1.01, 0.5), (1.0, 0), (1, 1), (-0.25, 1),
|
|
(0, 1.25)]
|
|
points_inter = cpr._generate_intersection_points(
|
|
points, one_point_per_intersection=False)
|
|
self._assert_points_are_identical(
|
|
points_inter,
|
|
[[], [(1.0, 0.4902)], [], [(1.0, 0.4902)], [(0, 1.0)], [],
|
|
[(0, 1.0)]],
|
|
atol=1e-2)
|
|
|
|
# double triangle with point in center that is shared by both triangles
|
|
points = [(0, 0), (0.5, 0.5), (1.0, 0), (1.0, 1.0), (0.5, 0.5),
|
|
(0, 1.0)]
|
|
points_inter = cpr._generate_intersection_points(
|
|
points, one_point_per_intersection=False)
|
|
self._assert_points_are_identical(
|
|
points_inter,
|
|
[[], [], [], [], [], []])
|
|
|
|
# TODO split into multiple tests
|
|
def test__oversample_intersection_points(self):
|
|
cpr = _ConcavePolygonRecoverer()
|
|
cpr.oversampling = 0.1
|
|
|
|
points = [(0.0, 0.0), (1.0, 0.0)]
|
|
segment_add_points_sorted = [[(0.5, 0.0)], []]
|
|
points_oversampled = cpr._oversample_intersection_points(
|
|
points, segment_add_points_sorted)
|
|
self._assert_points_are_identical(
|
|
points_oversampled,
|
|
[[(0.45, 0.0), (0.5, 0.0), (0.55, 0.0)], []],
|
|
atol=1e-4
|
|
)
|
|
|
|
points = [(0.0, 0.0), (2.0, 0.0)]
|
|
segment_add_points_sorted = [[(0.5, 0.0)], []]
|
|
points_oversampled = cpr._oversample_intersection_points(
|
|
points, segment_add_points_sorted)
|
|
self._assert_points_are_identical(
|
|
points_oversampled,
|
|
[[(0.45, 0.0), (0.5, 0.0), (0.65, 0.0)], []],
|
|
atol=1e-4
|
|
)
|
|
|
|
points = [(0.0, 0.0), (1.0, 0.0)]
|
|
segment_add_points_sorted = [[(0.5, 0.0), (0.6, 0.0)], []]
|
|
points_oversampled = cpr._oversample_intersection_points(
|
|
points, segment_add_points_sorted)
|
|
self._assert_points_are_identical(
|
|
points_oversampled,
|
|
[[(0.45, 0.0), (0.5, 0.0), (0.51, 0.0), (0.59, 0.0), (0.6, 0.0),
|
|
(0.64, 0.0)], []],
|
|
atol=1e-4
|
|
)
|
|
|
|
points = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
|
|
segment_add_points_sorted = [[(0.5, 0.0)], [], [(0.8, 1.0)],
|
|
[(0.0, 0.7)]]
|
|
points_oversampled = cpr._oversample_intersection_points(
|
|
points, segment_add_points_sorted)
|
|
self._assert_points_are_identical(
|
|
points_oversampled,
|
|
[[(0.45, 0.0), (0.5, 0.0), (0.55, 0.0)],
|
|
[],
|
|
[(0.82, 1.0), (0.8, 1.0), (0.72, 1.0)],
|
|
[(0.0, 0.73), (0.0, 0.7), (0.0, 0.63)]],
|
|
atol=1e-4
|
|
)
|
|
|
|
# TODO split into multiple tests
|
|
def test__insert_intersection_points(self):
|
|
points = [(0, 0), (1, 0), (2, 0)]
|
|
segments_add_point_sorted = [[], [], []]
|
|
points_inserted = _ConcavePolygonRecoverer._insert_intersection_points(
|
|
points, segments_add_point_sorted)
|
|
assert points_inserted == points
|
|
|
|
segments_add_point_sorted = [[(0.5, 0)], [], []]
|
|
points_inserted = _ConcavePolygonRecoverer._insert_intersection_points(
|
|
points, segments_add_point_sorted)
|
|
assert points_inserted == [(0, 0), (0.5, 0), (1, 0), (2, 0)]
|
|
|
|
segments_add_point_sorted = [[(0.5, 0), (0.75, 0)], [], []]
|
|
points_inserted = _ConcavePolygonRecoverer._insert_intersection_points(
|
|
points, segments_add_point_sorted)
|
|
assert points_inserted == [(0, 0), (0.5, 0), (0.75, 0), (1, 0), (2, 0)]
|
|
|
|
segments_add_point_sorted = [[(0.5, 0)], [(1.5, 0)], []]
|
|
points_inserted = _ConcavePolygonRecoverer._insert_intersection_points(
|
|
points, segments_add_point_sorted)
|
|
assert points_inserted == [(0, 0), (0.5, 0), (1, 0), (1.5, 0), (2, 0)]
|
|
|
|
segments_add_point_sorted = [[(0.5, 0)], [(1.5, 0)], [(2.5, 0)]]
|
|
points_inserted = _ConcavePolygonRecoverer._insert_intersection_points(
|
|
points, segments_add_point_sorted)
|
|
assert points_inserted == [(0, 0), (0.5, 0), (1, 0), (1.5, 0), (2, 0),
|
|
(2.5, 0)]
|