Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
362 changes: 195 additions & 167 deletions test/test_transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,173 +381,6 @@ def test_linear_transformation(self):
# Checking if LinearTransformation can be printed as string
whitening.__repr__()

def test_affine(self):
input_img = np.zeros((40, 40, 3), dtype=np.uint8)
cnt = [20, 20]
for pt in [(16, 16), (20, 16), (20, 20)]:
for i in range(-5, 5):
for j in range(-5, 5):
input_img[pt[0] + i, pt[1] + j, :] = [255, 155, 55]

with self.assertRaises(TypeError, msg="Argument translate should be a sequence"):
F.affine(input_img, 10, translate=0, scale=1, shear=1)

pil_img = F.to_pil_image(input_img)

def _to_3x3_inv(inv_result_matrix):
result_matrix = np.zeros((3, 3))
result_matrix[:2, :] = np.array(inv_result_matrix).reshape((2, 3))
result_matrix[2, 2] = 1
return np.linalg.inv(result_matrix)

def _test_transformation(a, t, s, sh):
a_rad = math.radians(a)
s_rad = [math.radians(sh_) for sh_ in sh]
cx, cy = cnt
tx, ty = t
sx, sy = s_rad
rot = a_rad

# 1) Check transformation matrix:
C = np.array([[1, 0, cx],
[0, 1, cy],
[0, 0, 1]])
T = np.array([[1, 0, tx],
[0, 1, ty],
[0, 0, 1]])
Cinv = np.linalg.inv(C)

RS = np.array(
[[s * math.cos(rot), -s * math.sin(rot), 0],
[s * math.sin(rot), s * math.cos(rot), 0],
[0, 0, 1]])

SHx = np.array([[1, -math.tan(sx), 0],
[0, 1, 0],
[0, 0, 1]])

SHy = np.array([[1, 0, 0],
[-math.tan(sy), 1, 0],
[0, 0, 1]])

RSS = np.matmul(RS, np.matmul(SHy, SHx))

true_matrix = np.matmul(T, np.matmul(C, np.matmul(RSS, Cinv)))

result_matrix = _to_3x3_inv(F._get_inverse_affine_matrix(center=cnt, angle=a,
translate=t, scale=s, shear=sh))
self.assertLess(np.sum(np.abs(true_matrix - result_matrix)), 1e-10)
# 2) Perform inverse mapping:
true_result = np.zeros((40, 40, 3), dtype=np.uint8)
inv_true_matrix = np.linalg.inv(true_matrix)
for y in range(true_result.shape[0]):
for x in range(true_result.shape[1]):
# Same as for PIL:
# https://github.com/python-pillow/Pillow/blob/71f8ec6a0cfc1008076a023c0756542539d057ab/
# src/libImaging/Geometry.c#L1060
input_pt = np.array([x + 0.5, y + 0.5, 1.0])
res = np.floor(np.dot(inv_true_matrix, input_pt)).astype(np.int)
_x, _y = res[:2]
if 0 <= _x < input_img.shape[1] and 0 <= _y < input_img.shape[0]:
true_result[y, x, :] = input_img[_y, _x, :]

result = F.affine(pil_img, angle=a, translate=t, scale=s, shear=sh)
self.assertEqual(result.size, pil_img.size)
# Compute number of different pixels:
np_result = np.array(result)
n_diff_pixels = np.sum(np_result != true_result) / 3
# Accept 3 wrong pixels
self.assertLess(n_diff_pixels, 3,
"a={}, t={}, s={}, sh={}\n".format(a, t, s, sh) +
"n diff pixels={}\n".format(n_diff_pixels))

# Test rotation
a = 45
_test_transformation(a=a, t=(0, 0), s=1.0, sh=(0.0, 0.0))

# Test translation
t = [10, 15]
_test_transformation(a=0.0, t=t, s=1.0, sh=(0.0, 0.0))

# Test scale
s = 1.2
_test_transformation(a=0.0, t=(0.0, 0.0), s=s, sh=(0.0, 0.0))

# Test shear
sh = [45.0, 25.0]
_test_transformation(a=0.0, t=(0.0, 0.0), s=1.0, sh=sh)

# Test rotation, scale, translation, shear
for a in range(-90, 90, 36):
for t1 in range(-10, 10, 5):
for s in [0.77, 1.0, 1.27]:
for sh in range(-15, 15, 5):
_test_transformation(a=a, t=(t1, t1), s=s, sh=(sh, sh))

def test_random_affine(self):

with self.assertRaises(ValueError):
transforms.RandomAffine(-0.7)
transforms.RandomAffine([-0.7])
transforms.RandomAffine([-0.7, 0, 0.7])

transforms.RandomAffine([-90, 90], translate=2.0)
transforms.RandomAffine([-90, 90], translate=[-1.0, 1.0])
transforms.RandomAffine([-90, 90], translate=[-1.0, 0.0, 1.0])

transforms.RandomAffine([-90, 90], translate=[0.2, 0.2], scale=[0.0])
transforms.RandomAffine([-90, 90], translate=[0.2, 0.2], scale=[-1.0, 1.0])
transforms.RandomAffine([-90, 90], translate=[0.2, 0.2], scale=[0.5, -0.5])
transforms.RandomAffine([-90, 90], translate=[0.2, 0.2], scale=[0.5, 3.0, -0.5])

transforms.RandomAffine([-90, 90], translate=[0.2, 0.2], scale=[0.5, 0.5], shear=-7)
transforms.RandomAffine([-90, 90], translate=[0.2, 0.2], scale=[0.5, 0.5], shear=[-10])
transforms.RandomAffine([-90, 90], translate=[0.2, 0.2], scale=[0.5, 0.5], shear=[-10, 0, 10])
transforms.RandomAffine([-90, 90], translate=[0.2, 0.2], scale=[0.5, 0.5], shear=[-10, 0, 10, 0, 10])

# assert fill being either a Sequence or a Number
with self.assertRaises(TypeError):
transforms.RandomAffine(0, fill={})

t = transforms.RandomAffine(0, fill=None)
self.assertTrue(t.fill == 0)

x = np.zeros((100, 100, 3), dtype=np.uint8)
img = F.to_pil_image(x)

t = transforms.RandomAffine(10, translate=[0.5, 0.3], scale=[0.7, 1.3], shear=[-10, 10, 20, 40])
for _ in range(100):
angle, translations, scale, shear = t.get_params(t.degrees, t.translate, t.scale, t.shear,
img_size=img.size)
self.assertTrue(-10 < angle < 10)
self.assertTrue(-img.size[0] * 0.5 <= translations[0] <= img.size[0] * 0.5,
"{} vs {}".format(translations[0], img.size[0] * 0.5))
self.assertTrue(-img.size[1] * 0.5 <= translations[1] <= img.size[1] * 0.5,
"{} vs {}".format(translations[1], img.size[1] * 0.5))
self.assertTrue(0.7 < scale < 1.3)
self.assertTrue(-10 < shear[0] < 10)
self.assertTrue(-20 < shear[1] < 40)

# Checking if RandomAffine can be printed as string
t.__repr__()

t = transforms.RandomAffine(10, interpolation=transforms.InterpolationMode.BILINEAR)
self.assertIn("bilinear", t.__repr__())

# assert deprecation warning and non-BC
with self.assertWarnsRegex(UserWarning, r"Argument resample is deprecated and will be removed"):
t = transforms.RandomAffine(10, resample=2)
self.assertEqual(t.interpolation, transforms.InterpolationMode.BILINEAR)

with self.assertWarnsRegex(UserWarning, r"Argument fillcolor is deprecated and will be removed"):
t = transforms.RandomAffine(10, fillcolor=10)
self.assertEqual(t.fill, 10)

# assert changed type warning
with self.assertWarnsRegex(UserWarning, r"Argument interpolation should be of type InterpolationMode"):
t = transforms.RandomAffine(10, interpolation=2)
self.assertEqual(t.interpolation, transforms.InterpolationMode.BILINEAR)

def test_autoaugment(self):
for policy in transforms.AutoAugmentPolicy:
for fill in [None, 85, (128, 128, 128)]:
Expand Down Expand Up @@ -2090,5 +1923,200 @@ def test_normalize_3d_tensor():
torch.testing.assert_close(target, result2)


class TestAffine:

@pytest.fixture(scope='class')
def input_img(self):
input_img = np.zeros((40, 40, 3), dtype=np.uint8)
for pt in [(16, 16), (20, 16), (20, 20)]:
for i in range(-5, 5):
for j in range(-5, 5):
input_img[pt[0] + i, pt[1] + j, :] = [255, 155, 55]
return input_img

def test_affine_translate_seq(self, input_img):
with pytest.raises(TypeError, match=r"Argument translate should be a sequence"):
F.affine(input_img, 10, translate=0, scale=1, shear=1)

@pytest.fixture(scope='class')
def pil_image(self, input_img):
return F.to_pil_image(input_img)

def _to_3x3_inv(self, inv_result_matrix):
result_matrix = np.zeros((3, 3))
result_matrix[:2, :] = np.array(inv_result_matrix).reshape((2, 3))
result_matrix[2, 2] = 1
return np.linalg.inv(result_matrix)

def _test_transformation(self, angle, translate, scale, shear, pil_image, input_img):

a_rad = math.radians(angle)
s_rad = [math.radians(sh_) for sh_ in shear]
cnt = [20, 20]
cx, cy = cnt
tx, ty = translate
sx, sy = s_rad
rot = a_rad

# 1) Check transformation matrix:
C = np.array([[1, 0, cx],
[0, 1, cy],
[0, 0, 1]])
T = np.array([[1, 0, tx],
[0, 1, ty],
[0, 0, 1]])
Cinv = np.linalg.inv(C)

RS = np.array(
[[scale * math.cos(rot), -scale * math.sin(rot), 0],
[scale * math.sin(rot), scale * math.cos(rot), 0],
[0, 0, 1]])

SHx = np.array([[1, -math.tan(sx), 0],
[0, 1, 0],
[0, 0, 1]])

SHy = np.array([[1, 0, 0],
[-math.tan(sy), 1, 0],
[0, 0, 1]])

RSS = np.matmul(RS, np.matmul(SHy, SHx))

true_matrix = np.matmul(T, np.matmul(C, np.matmul(RSS, Cinv)))

result_matrix = self._to_3x3_inv(F._get_inverse_affine_matrix(center=cnt, angle=angle,
translate=translate, scale=scale, shear=shear))
assert np.sum(np.abs(true_matrix - result_matrix)) < 1e-10
# 2) Perform inverse mapping:
true_result = np.zeros((40, 40, 3), dtype=np.uint8)
inv_true_matrix = np.linalg.inv(true_matrix)
for y in range(true_result.shape[0]):
for x in range(true_result.shape[1]):
# Same as for PIL:
# https://github.com/python-pillow/Pillow/blob/71f8ec6a0cfc1008076a023c0756542539d057ab/
# src/libImaging/Geometry.c#L1060
input_pt = np.array([x + 0.5, y + 0.5, 1.0])
res = np.floor(np.dot(inv_true_matrix, input_pt)).astype(np.int)
_x, _y = res[:2]
if 0 <= _x < input_img.shape[1] and 0 <= _y < input_img.shape[0]:
true_result[y, x, :] = input_img[_y, _x, :]

result = F.affine(pil_image, angle=angle, translate=translate, scale=scale, shear=shear)
assert result.size == pil_image.size
# Compute number of different pixels:
np_result = np.array(result)
n_diff_pixels = np.sum(np_result != true_result) / 3
# Accept 3 wrong pixels
error_msg = ("angle={}, translate={}, scale={}, shear={}\n".format(angle, translate, scale, shear) +
"n diff pixels={}\n".format(n_diff_pixels))
assert n_diff_pixels < 3, error_msg

def test_transformation_discrete(self, pil_image, input_img):
# Test rotation
angle = 45
self._test_transformation(angle=angle, translate=(0, 0), scale=1.0,
shear=(0.0, 0.0), pil_image=pil_image, input_img=input_img)

# Test translation
translate = [10, 15]
self._test_transformation(angle=0.0, translate=translate, scale=1.0,
shear=(0.0, 0.0), pil_image=pil_image, input_img=input_img)

# Test scale
scale = 1.2
self._test_transformation(angle=0.0, translate=(0.0, 0.0), scale=scale,
shear=(0.0, 0.0), pil_image=pil_image, input_img=input_img)

# Test shear
shear = [45.0, 25.0]
self._test_transformation(angle=0.0, translate=(0.0, 0.0), scale=1.0,
shear=shear, pil_image=pil_image, input_img=input_img)

@pytest.mark.parametrize("angle", range(-90, 90, 36))
@pytest.mark.parametrize("translate", range(-10, 10, 5))
@pytest.mark.parametrize("scale", [0.77, 1.0, 1.27])
@pytest.mark.parametrize("shear", range(-15, 15, 5))
def test_transformation_range(self, angle, translate, scale, shear, pil_image, input_img):
self._test_transformation(angle=angle, translate=(translate, translate), scale=scale,
shear=(shear, shear), pil_image=pil_image, input_img=input_img)


def test_random_affine():

with pytest.raises(ValueError):
transforms.RandomAffine(-0.7)
with pytest.raises(ValueError):
transforms.RandomAffine([-0.7])
with pytest.raises(ValueError):
transforms.RandomAffine([-0.7, 0, 0.7])
with pytest.raises(TypeError):
transforms.RandomAffine([-90, 90], translate=2.0)
with pytest.raises(ValueError):
transforms.RandomAffine([-90, 90], translate=[-1.0, 1.0])
with pytest.raises(ValueError):
transforms.RandomAffine([-90, 90], translate=[-1.0, 0.0, 1.0])

with pytest.raises(ValueError):
transforms.RandomAffine([-90, 90], translate=[0.2, 0.2], scale=[0.0])
with pytest.raises(ValueError):
transforms.RandomAffine([-90, 90], translate=[0.2, 0.2], scale=[-1.0, 1.0])
with pytest.raises(ValueError):
transforms.RandomAffine([-90, 90], translate=[0.2, 0.2], scale=[0.5, -0.5])
with pytest.raises(ValueError):
transforms.RandomAffine([-90, 90], translate=[0.2, 0.2], scale=[0.5, 3.0, -0.5])

with pytest.raises(ValueError):
transforms.RandomAffine([-90, 90], translate=[0.2, 0.2], scale=[0.5, 0.5], shear=-7)
with pytest.raises(ValueError):
transforms.RandomAffine([-90, 90], translate=[0.2, 0.2], scale=[0.5, 0.5], shear=[-10])
with pytest.raises(ValueError):
transforms.RandomAffine([-90, 90], translate=[0.2, 0.2], scale=[0.5, 0.5], shear=[-10, 0, 10])
with pytest.raises(ValueError):
transforms.RandomAffine([-90, 90], translate=[0.2, 0.2], scale=[0.5, 0.5], shear=[-10, 0, 10, 0, 10])

# assert fill being either a Sequence or a Number
with pytest.raises(TypeError):
transforms.RandomAffine(0, fill={})

t = transforms.RandomAffine(0, fill=None)
assert t.fill == 0

x = np.zeros((100, 100, 3), dtype=np.uint8)
img = F.to_pil_image(x)

t = transforms.RandomAffine(10, translate=[0.5, 0.3], scale=[0.7, 1.3], shear=[-10, 10, 20, 40])
for _ in range(100):
angle, translations, scale, shear = t.get_params(t.degrees, t.translate, t.scale, t.shear,
img_size=img.size)
assert -10 < angle < 10
assert -img.size[0] * 0.5 <= translations[0] <= img.size[0] * 0.5, ("{} vs {}"
.format(translations[0], img.size[0] * 0.5))
assert -img.size[1] * 0.5 <= translations[1] <= img.size[1] * 0.5, ("{} vs {}"
.format(translations[1], img.size[1] * 0.5))
assert 0.7 < scale < 1.3
assert -10 < shear[0] < 10
assert -20 < shear[1] < 40

# Checking if RandomAffine can be printed as string
t.__repr__()

t = transforms.RandomAffine(10, interpolation=transforms.InterpolationMode.BILINEAR)
assert "bilinear" in t.__repr__()

# assert deprecation warning and non-BC
with pytest.warns(UserWarning, match=r"Argument resample is deprecated and will be removed"):
t = transforms.RandomAffine(10, resample=2)
assert t.interpolation == transforms.InterpolationMode.BILINEAR

with pytest.warns(UserWarning, match=r"Argument fillcolor is deprecated and will be removed"):
t = transforms.RandomAffine(10, fillcolor=10)
assert t.fill == 10

# assert changed type warning
with pytest.warns(UserWarning, match=r"Argument interpolation should be of type InterpolationMode"):
t = transforms.RandomAffine(10, interpolation=2)
assert t.interpolation == transforms.InterpolationMode.BILINEAR


if __name__ == '__main__':
unittest.main()