from __future__ import print_function, division, absolute_import

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

from imgaug import augmenters as iaa
from imgaug import parameters as iap
from imgaug import dtypes as iadt
from imgaug import random as iarandom
from imgaug.testutils import reseed, runtest_pickleable_uint8_img


# TODO add tests for EdgeDetect
# TODO add tests for DirectedEdgeDetect


class Test_convolve(unittest.TestCase):
    def test_1x1_identity_matrix_2d_image(self):
        image = np.array([
            [0, 10, 20],
            [30, 40, 50]
        ], dtype=np.uint8)
        matrix = np.float32([
            [1.0]
        ])

        image_aug = iaa.convolve(image, matrix)

        assert image_aug is not image
        assert image_aug.dtype.name == "uint8"
        assert image_aug.shape == (2, 3)
        assert np.array_equal(image_aug, image)


class Test_convolve_(unittest.TestCase):
    def test_1x1_identity_matrix_2d_image_small_image_sizes(self):
        for height in np.arange(16):
            for width in np.arange(16):
                shapes = [
                    (height, width),
                    (height, width, 1),
                    (height, width, 3)
                ]

                for shape in shapes:
                    with self.subTest(shape=shape):
                        image = np.mod(
                            np.arange(int(np.prod(shape))).reshape(shape),
                            255
                        ).astype(np.uint8)
                        matrix = np.float32([
                            [1.0]
                        ])

                        image_aug = iaa.convolve_(np.copy(image), matrix)

                        assert image_aug.dtype.name == "uint8"
                        assert image_aug.shape == shape
                        assert np.array_equal(image_aug, image)

    def test_1x1_identity_matrix_2d_image(self):
        image = np.array([
            [0, 10, 20],
            [30, 40, 50]
        ], dtype=np.uint8)
        matrix = np.float32([
            [1.0]
        ])

        image_aug = iaa.convolve_(np.copy(image), matrix)

        assert image_aug.dtype.name == "uint8"
        assert image_aug.shape == (2, 3)
        assert np.array_equal(image_aug, image)

    def test_2x2_identity_matrix_2d_image(self):
        image = np.array([
            [0, 10, 20, 30],
            [40, 50, 60, 70]
        ], dtype=np.uint8)
        matrix = np.float32([
            [0.0, 0.0],
            [0.0, 1.0]
        ])

        image_aug = iaa.convolve_(np.copy(image), matrix)

        assert image_aug.dtype.name == "uint8"
        assert image_aug.shape == (2, 4)
        assert np.array_equal(image_aug, image)

    def test_3x3_identity_matrix_2d_image(self):
        image = np.array([
            [0, 10, 20],
            [30, 40, 50]
        ], dtype=np.uint8)
        matrix = np.float32([
            [0.0, 0.0, 0.0],
            [0.0, 1.0, 0.0],
            [0.0, 0.0, 0.0]
        ])

        image_aug = iaa.convolve_(np.copy(image), matrix)

        assert image_aug.dtype.name == "uint8"
        assert image_aug.shape == (2, 3)
        assert np.array_equal(image_aug, image)

    def test_single_matrix_2d_image(self):
        image = np.array([
            [0, 10, 20],
            [30, 40, 50],
            [60, 70, 80]
        ], dtype=np.uint8)
        matrix = np.float32([
            [0.0, 1.0, 0.0],
            [0.0, 1.0, 0.0],
            [0.0, 0.0, 0.0]
        ])
        expected = np.array([
            [0+30, 10+40, 20+50],
            [30+0, 40+10, 50+20],
            [60+30, 70+40, 80+50]
        ], dtype=np.float32)

        image_aug = iaa.convolve_(np.copy(image), matrix)

        assert image_aug.dtype.name == "uint8"
        assert image_aug.shape == (3, 3)
        assert np.array_equal(image_aug, expected)

    def test_single_matrix_3d_image(self):
        image = np.array([
            [0, 10, 20],
            [30, 40, 50]
        ], dtype=np.uint8)
        image = np.tile(image[:, :, np.newaxis], (1, 1, 2))
        matrix = np.float32([
            [0.0, 0.0, 0.0],
            [0.0, 2.0, 0.0],
            [0.0, 0.0, 0.0]
        ])

        image_aug = iaa.convolve_(np.copy(image), matrix)

        assert image_aug.dtype.name == "uint8"
        assert image_aug.shape == (2, 3, 2)
        assert np.array_equal(image_aug, 2*image)

    def test_matrix_is_list_of_arrays(self):
        image = np.array([
            [0, 10, 20],
            [30, 40, 50]
        ], dtype=np.uint8)
        image = np.tile(image[:, :, np.newaxis], (1, 1, 2))
        matrices = [
            np.float32([
                [0.0, 0.0, 0.0],
                [0.0, 1.0, 0.0],
                [0.0, 0.0, 0.0]
            ]),
            np.float32([
                [0.0, 0.0, 0.0],
                [0.0, 2.0, 0.0],
                [0.0, 0.0, 0.0]
            ])
        ]

        image_aug = iaa.convolve_(np.copy(image), matrices)

        assert image_aug.dtype.name == "uint8"
        assert image_aug.shape == (2, 3, 2)
        assert np.array_equal(image_aug[:, :, 0], image[:, :, 0])
        assert np.array_equal(image_aug[:, :, 1], 2*image[:, :, 1])

    def test_matrix_is_list_containing_none(self):
        image = np.array([
            [0, 10, 20],
            [30, 40, 50]
        ], dtype=np.uint8)
        image = np.tile(image[:, :, np.newaxis], (1, 1, 2))
        matrices = [
            None,
            np.float32([
                [0.0, 0.0, 0.0],
                [0.0, 2.0, 0.0],
                [0.0, 0.0, 0.0]
            ])
        ]

        image_aug = iaa.convolve_(np.copy(image), matrices)

        assert image_aug.dtype.name == "uint8"
        assert image_aug.shape == (2, 3, 2)
        assert np.array_equal(image_aug[:, :, 0], image[:, :, 0])
        assert np.array_equal(image_aug[:, :, 1], 2*image[:, :, 1])

    def test_matrix_is_list_containing_only_none(self):
        image = np.array([
            [0, 10, 20],
            [30, 40, 50]
        ], dtype=np.uint8)
        image = np.tile(image[:, :, np.newaxis], (1, 1, 2))
        matrices = [
            None,
            None
        ]

        image_aug = iaa.convolve_(np.copy(image), matrices)

        assert image_aug.dtype.name == "uint8"
        assert image_aug.shape == (2, 3, 2)
        assert np.array_equal(image_aug[:, :, 0], image[:, :, 0])
        assert np.array_equal(image_aug[:, :, 1], image[:, :, 1])

    def test_unusual_channel_numbers(self):
        for nb_channels in [1, 2, 3, 4, 5, 10, 512, 513]:
            with self.subTest(nb_channels=nb_channels):
                image = np.array([
                    [0, 10, 20],
                    [30, 40, 50]
                ], dtype=np.uint8)
                image = image[:, :, np.newaxis]
                image = np.tile(image, (1, 1, nb_channels))
                matrix = np.float32([
                    [2.0]
                ])

                image_aug = iaa.convolve_(np.copy(image), matrix)

                assert image_aug.dtype.name == "uint8"
                assert image_aug.shape == (2, 3, nb_channels)
                assert np.array_equal(image_aug, 2*image)

    def test_zero_sized_axes(self):
        shapes = [
            (0, 0),
            (0, 1),
            (1, 0),
            (0, 1, 0),
            (1, 0, 0),
            (0, 1, 1),
            (1, 0, 1)
        ]

        for shape in shapes:
            with self.subTest(shape=shape):
                image = np.zeros(shape, dtype=np.uint8)
                matrix = np.float32([
                    [2.0]
                ])

                image_aug = iaa.convolve_(np.copy(image), matrix)

                assert image_aug.shape == image.shape

    def test_view_heightwise(self):
        image = np.array([
            [0, 10, 20],
            [30, 40, 50]
        ], dtype=np.uint8)
        image_view = np.copy(image)[:2, :]
        assert image_view.flags["OWNDATA"] is False
        matrix = np.float32([
            [2.0]
        ])

        image_aug = iaa.convolve_(image_view, matrix)

        assert image_aug.dtype.name == "uint8"
        assert image_aug.shape == (2, 3)
        assert np.array_equal(image_aug, 2*image)

    def test_view_channelwise_1_channel(self):
        image = np.array([
            [0, 10, 20],
            [30, 40, 50]
        ], dtype=np.uint8)
        image = np.tile(image[:, :, np.newaxis], (1, 1, 3))
        image[:, :, 0] += 0
        image[:, :, 1] += 1
        image[:, :, 2] += 2
        image_view = np.copy(image)[:, :, [False, True, False]]
        assert image_view.flags["OWNDATA"] is False
        assert image_view.base.shape == (1, 2, 3)
        matrix = np.float32([
            [2.0]
        ])

        image_aug = iaa.convolve_(image_view, matrix)

        assert image_aug.dtype.name == "uint8"
        assert image_aug.shape == (2, 3, 1)
        assert np.array_equal(image_aug, 2*image[:, :, 1:2])

    def test_view_channelwise_4_channels(self):
        image = np.array([
            [0, 10, 20],
            [30, 40, 50]
        ], dtype=np.uint8)
        image = np.tile(image[:, :, np.newaxis], (1, 1, 6))
        image[:, :, 0] += 0
        image[:, :, 1] += 1
        image[:, :, 2] += 2
        mask = [False, True, True, True, True, False]
        image_view = np.copy(image)[:, :, mask]
        assert image_view.flags["OWNDATA"] is False
        assert image_view.base.shape == (4, 2, 3)
        matrix = np.float32([
            [2.0]
        ])

        image_aug = iaa.convolve_(image_view, matrix)

        assert image_aug.dtype.name == "uint8"
        assert image_aug.shape == (2, 3, 4)
        assert np.array_equal(image_aug, 2*image[:, :, mask])

    def test_noncontiguous(self):
        image = np.array([
            [0, 10, 20],
            [30, 40, 50]
        ], dtype=np.uint8)
        image_nonc = np.array(image, dtype=np.uint8, order="F")
        assert image_nonc.flags["C_CONTIGUOUS"] is False
        matrix = np.float32([
            [2.0]
        ])

        image_aug = iaa.convolve_(image_nonc, matrix)

        assert image_aug.dtype.name == "uint8"
        assert image_aug.shape == (2, 3)
        assert np.array_equal(image_aug, 2*image)


# TODO add test for keypoints once their handling was improved in Convolve
class TestConvolve(unittest.TestCase):
    def setUp(self):
        reseed()

    @property
    def img(self):
        return np.array([
            [1, 2, 3],
            [4, 5, 6],
            [7, 8, 9]
        ], dtype=np.uint8)

    def test_matrix_is_none(self):
        aug = iaa.Convolve(matrix=None)
        observed = aug.augment_image(self.img)
        assert np.array_equal(observed, self.img)

    def test_matrix_is_lambda_none(self):
        def _matrix_generator(_img, _nb_channels, _random_state):
            return [None]

        aug = iaa.Convolve(matrix=_matrix_generator)
        observed = aug.augment_image(self.img)
        assert np.array_equal(observed, self.img)

    def test_matrix_is_1x1_identity(self):
        # matrix is [[1]]
        aug = iaa.Convolve(matrix=np.float32([[1]]))
        observed = aug.augment_image(self.img)
        assert np.array_equal(observed, self.img)

    def test_matrix_is_lambda_1x1_identity(self):
        def _matrix_generator(_img, _nb_channels, _random_state):
            return np.float32([[1]])

        aug = iaa.Convolve(matrix=_matrix_generator)
        observed = aug.augment_image(self.img)
        assert np.array_equal(observed, self.img)

    def test_matrix_is_3x3_identity(self):
        m = np.float32([
            [0, 0, 0],
            [0, 1, 0],
            [0, 0, 0]
        ])
        aug = iaa.Convolve(matrix=m)
        observed = aug.augment_image(self.img)
        assert np.array_equal(observed, self.img)

    def test_matrix_is_lambda_3x3_identity(self):
        def _matrix_generator(_img, _nb_channels, _random_state):
            return np.float32([
                [0, 0, 0],
                [0, 1, 0],
                [0, 0, 0]
            ])
        aug = iaa.Convolve(matrix=_matrix_generator)
        observed = aug.augment_image(self.img)
        assert np.array_equal(observed, self.img)

    def test_matrix_is_3x3_two_in_center(self):
        m = np.float32([
            [0, 0, 0],
            [0, 2, 0],
            [0, 0, 0]
        ])
        aug = iaa.Convolve(matrix=m)
        observed = aug.augment_image(self.img)
        assert np.array_equal(observed, 2*self.img)

    def test_matrix_is_lambda_3x3_two_in_center(self):
        def _matrix_generator(_img, _nb_channels, _random_state):
            return np.float32([
                [0, 0, 0],
                [0, 2, 0],
                [0, 0, 0]
            ])

        aug = iaa.Convolve(matrix=_matrix_generator)
        observed = aug.augment_image(self.img)
        assert np.array_equal(observed, 2*self.img)

    def test_matrix_is_3x3_two_in_center_3_channels(self):
        m = np.float32([
            [0, 0, 0],
            [0, 2, 0],
            [0, 0, 0]
        ])
        aug = iaa.Convolve(matrix=m)
        img3 = np.tile(self.img[..., np.newaxis], (1, 1, 3))  # 3 channels
        observed = aug.augment_image(img3)
        assert np.array_equal(observed, 2*img3)

    def test_matrix_is_lambda_3x3_two_in_center_3_channels(self):
        def _matrix_generator(_img, _nb_channels, _random_state):
            return np.float32([
                [0, 0, 0],
                [0, 2, 0],
                [0, 0, 0]
            ])

        aug = iaa.Convolve(matrix=_matrix_generator)
        img3 = np.tile(self.img[..., np.newaxis], (1, 1, 3))  # 3 channels
        observed = aug.augment_image(img3)
        assert np.array_equal(observed, 2*img3)

    def test_matrix_is_3x3_with_multiple_nonzero_values(self):
        m = np.float32([
            [0, -1, 0],
            [0, 10, 0],
            [0, 0, 0]
        ])
        expected = np.uint8([
            [10*1+(-1)*4, 10*2+(-1)*5, 10*3+(-1)*6],
            [10*4+(-1)*1, 10*5+(-1)*2, 10*6+(-1)*3],
            [10*7+(-1)*4, 10*8+(-1)*5, 10*9+(-1)*6]
        ])

        aug = iaa.Convolve(matrix=m)
        observed = aug.augment_image(self.img)
        assert np.array_equal(observed, expected)

    def test_matrix_is_lambda_3x3_with_multiple_nonzero_values(self):
        def _matrix_generator(_img, _nb_channels, _random_state):
            return np.float32([
                [0, -1, 0],
                [0, 10, 0],
                [0, 0, 0]
            ])

        expected = np.uint8([
            [10*1+(-1)*4, 10*2+(-1)*5, 10*3+(-1)*6],
            [10*4+(-1)*1, 10*5+(-1)*2, 10*6+(-1)*3],
            [10*7+(-1)*4, 10*8+(-1)*5, 10*9+(-1)*6]
        ])

        aug = iaa.Convolve(matrix=_matrix_generator)
        observed = aug.augment_image(self.img)
        assert np.array_equal(observed, expected)

    def test_lambda_with_changing_matrices(self):
        # changing matrices when using callable
        def _matrix_generator(_img, _nb_channels, random_state):
            return np.float32([[
                iarandom.polyfill_integers(random_state, 0, 5)
            ]])

        expected = []
        for i in sm.xrange(5):
            expected.append(self.img * i)

        aug = iaa.Convolve(matrix=_matrix_generator)
        seen = [False] * 5
        for _ in sm.xrange(200):
            observed = aug.augment_image(self.img)
            found = False
            for i, expected_i in enumerate(expected):
                if np.array_equal(observed, expected_i):
                    seen[i] = True
                    found = True
                    break
            assert found
            if all(seen):
                break
        assert np.all(seen)

    def test_matrix_has_bad_datatype(self):
        # don't use assertRaisesRegex, because it doesnt exist in 2.7
        got_exception = False
        try:
            _aug = iaa.Convolve(matrix=False)
        except Exception as exc:
            assert "Expected " in str(exc)
            got_exception = True
        assert got_exception

    def test_zero_sized_axes(self):
        shapes = [
            (0, 0),
            (0, 1),
            (1, 0),
            (0, 1, 0),
            (1, 0, 0),
            (0, 1, 1),
            (1, 0, 1)
        ]

        for shape in shapes:
            with self.subTest(shape=shape):
                image = np.zeros(shape, dtype=np.uint8)
                aug = iaa.Convolve(matrix=np.float32([[1]]))

                image_aug = aug(image=image)

                assert image_aug.shape == image.shape

    def test_get_parameters(self):
        matrix = np.int32([[1]])
        aug = iaa.Convolve(matrix=matrix)
        params = aug.get_parameters()
        assert np.array_equal(params[0], matrix)
        assert params[1] == "constant"

    def test_other_dtypes_bool_identity_matrix(self):
        identity_matrix = np.int64([[1]])
        aug = iaa.Convolve(matrix=identity_matrix)

        image = np.zeros((3, 3), dtype=bool)
        image[1, 1] = True
        image_aug = aug.augment_image(image)
        assert image.dtype.type == np.bool_
        assert np.all(image_aug == image)

    def test_other_dtypes_uint_int_identity_matrix(self):
        identity_matrix = np.int64([[1]])
        aug = iaa.Convolve(matrix=identity_matrix)

        for dtype in [np.uint8, np.uint16, np.int8, np.int16]:
            image = np.zeros((3, 3), dtype=dtype)
            image[1, 1] = 100
            image_aug = aug.augment_image(image)
            assert image.dtype.type == dtype
            assert np.all(image_aug == image)

    def test_other_dtypes_float_identity_matrix(self):
        identity_matrix = np.int64([[1]])
        aug = iaa.Convolve(matrix=identity_matrix)

        for dtype in [np.float16, np.float32, np.float64]:
            image = np.zeros((3, 3), dtype=dtype)
            image[1, 1] = 100.0
            image_aug = aug.augment_image(image)
            assert image.dtype.type == dtype
            assert np.allclose(image_aug, image)

    def test_other_dtypes_bool_non_identity_matrix_with_small_values(self):
        matrix = np.float64([
            [0, 0.6, 0],
            [0, 0.4, 0],
            [0,   0, 0]
        ])
        aug = iaa.Convolve(matrix=matrix)

        image = np.zeros((3, 3), dtype=bool)
        image[1, 1] = True
        image[2, 1] = True
        expected = np.zeros((3, 3), dtype=bool)
        expected[0, 1] = True
        expected[2, 1] = True
        image_aug = aug.augment_image(image)
        assert image.dtype.type == np.bool_
        assert np.all(image_aug == expected)

    def test_other_dtypes_uint_int_non_identity_matrix_with_small_values(self):
        matrix = np.float64([
            [0, 0.5, 0],
            [0, 0.5, 0],
            [0,   0, 0]
        ])
        aug = iaa.Convolve(matrix=matrix)

        for dtype in [np.uint8, np.uint16, np.int8, np.int16]:
            image = np.zeros((3, 3), dtype=dtype)
            image[1, 1] = 100
            image[2, 1] = 100
            image_aug = aug.augment_image(image)

            expected = np.zeros((3, 3), dtype=dtype)
            expected[0, 1] = int(np.round(100 * 0.5))
            expected[1, 1] = int(np.round(100 * 0.5))
            expected[2, 1] = int(np.round(100 * 0.5 + 100 * 0.5))

            diff = np.abs(
                image_aug.astype(np.int64)
                - expected.astype(np.int64))
            assert image_aug.dtype.type == dtype
            assert np.max(diff) <= 2

    def test_other_dtypes_float_non_identity_matrix_with_small_values(self):
        matrix = np.float64([
            [0, 0.5, 0],
            [0, 0.5, 0],
            [0,   0, 0]
        ])
        aug = iaa.Convolve(matrix=matrix)

        for dtype in [np.float16, np.float32, np.float64]:
            image = np.zeros((3, 3), dtype=dtype)
            image[1, 1] = 100.0
            image[2, 1] = 100.0
            image_aug = aug.augment_image(image)

            expected = np.zeros((3, 3), dtype=dtype)
            expected[0, 1] = 100 * 0.5
            expected[1, 1] = 100 * 0.5
            expected[2, 1] = 100 * 0.5 + 100 * 0.5

            diff = np.abs(
                image_aug.astype(np.float64) - expected.astype(np.float64)
            )
            assert image_aug.dtype.type == dtype
            assert np.max(diff) < 1.0

    def test_other_dtypes_uint_int_non_identity_matrix_with_large_values(self):
        matrix = np.float64([
            [0, 0.5, 0],
            [0, 0.5, 0],
            [0,   0, 0]
        ])
        aug = iaa.Convolve(matrix=matrix)

        for dtype in [np.uint8, np.uint16, np.int8, np.int16]:
            _min_value, center_value, max_value = \
                iadt.get_value_range_of_dtype(dtype)

            value = int(center_value + 0.4 * max_value)

            image = np.zeros((3, 3), dtype=dtype)
            image[1, 1] = value
            image[2, 1] = value
            image_aug = aug.augment_image(image)

            expected = np.zeros((3, 3), dtype=dtype)
            expected[0, 1] = int(np.round(value * 0.5))
            expected[1, 1] = int(np.round(value * 0.5))
            expected[2, 1] = int(np.round(value * 0.5 + value * 0.5))

            diff = np.abs(
                image_aug.astype(np.int64)
                - expected.astype(np.int64))
            assert image_aug.dtype.type == dtype
            assert np.max(diff) <= 2

    def test_other_dtypes_float_non_identity_matrix_with_large_values(self):
        matrix = np.float64([
            [0, 0.5, 0],
            [0, 0.5, 0],
            [0,   0, 0]
        ])
        aug = iaa.Convolve(matrix=matrix)

        for dtype, value in zip([np.float16, np.float32, np.float64],
                                [5000, 1000*1000, 1000*1000*1000]):
            image = np.zeros((3, 3), dtype=dtype)
            image[1, 1] = value
            image[2, 1] = value
            image_aug = aug.augment_image(image)

            expected = np.zeros((3, 3), dtype=dtype)
            expected[0, 1] = value * 0.5
            expected[1, 1] = value * 0.5
            expected[2, 1] = value * 0.5 + value * 0.5

            diff = np.abs(
                image_aug.astype(np.float64) - expected.astype(np.float64))
            assert image_aug.dtype.type == dtype
            assert np.max(diff) < 1.0

    def test_failure_on_invalid_dtypes(self):
        # don't use assertRaisesRegex, because it doesnt exist in 2.7
        identity_matrix = np.int64([[1]])
        aug = iaa.Convolve(matrix=identity_matrix)
        for dt in [np.uint32, np.uint64, np.int32, np.int64]:
            got_exception = False
            try:
                _ = aug.augment_image(np.zeros((1, 1), dtype=dt))
            except Exception as exc:
                assert "forbidden dtype" in str(exc)
                got_exception = True
            assert got_exception

    def test_pickleable__identity_matrix(self):
        identity_matrix = np.int64([[1]])
        aug = iaa.Convolve(identity_matrix, seed=1)
        runtest_pickleable_uint8_img(aug, iterations=20)

    def test_pickleable__callback_function(self):
        aug = iaa.Convolve(_convolve_pickleable_matrix_generator,
                           seed=1)
        runtest_pickleable_uint8_img(aug, iterations=20)


def _convolve_pickleable_matrix_generator(_img, _nb_channels, random_state):
    return np.float32([[random_state.integers(0, 5)]])


class TestSharpen(unittest.TestCase):
    def setUp(self):
        reseed()

    @classmethod
    def _compute_sharpened_base_img(cls, lightness, m):
        img = np.zeros((3, 3), dtype=np.float32)
        k = 1
        # note that cv2 uses reflection padding by default
        img[0, 0] = (
            (m[1, 1] + lightness)/k * 10
            + 4 * (m[0, 0]/k) * 10
            + 4 * (m[2, 2]/k) * 20
        )
        img[0, 2] = img[0, 0]
        img[2, 0] = img[0, 0]
        img[2, 2] = img[0, 0]
        img[0, 1] = (
            (m[1, 1] + lightness)/k * 10
            + 6 * (m[0, 1]/k) * 10
            + 2 * (m[2, 2]/k) * 20
        )
        img[1, 0] = img[0, 1]
        img[1, 2] = img[0, 1]
        img[2, 1] = img[0, 1]
        img[1, 1] = (
            (m[1, 1] + lightness)/k * 20
            + 8 * (m[0, 1]/k) * 10
        )

        img = np.clip(img, 0, 255).astype(np.uint8)

        return img

    @property
    def base_img(self):
        base_img = [[10, 10, 10],
                    [10, 20, 10],
                    [10, 10, 10]]
        base_img = np.uint8(base_img)
        return base_img

    @property
    def base_img_sharpened(self):
        return self._compute_sharpened_base_img(1, self.m)

    @property
    def m(self):
        return np.array([[-1, -1, -1],
                         [-1, 8, -1],
                         [-1, -1, -1]], dtype=np.float32)

    @property
    def m_noop(self):
        return np.array([[0, 0, 0],
                         [0, 1, 0],
                         [0, 0, 0]], dtype=np.float32)

    def test_alpha_zero(self):
        aug = iaa.Sharpen(alpha=0, lightness=1)
        observed = aug.augment_image(self.base_img)
        expected = self.base_img
        assert np.allclose(observed, expected)

    def test_alpha_one(self):
        aug = iaa.Sharpen(alpha=1.0, lightness=1)
        observed = aug.augment_image(self.base_img)
        expected = self.base_img_sharpened
        assert np.allclose(observed, expected)

    def test_alpha_050(self):
        aug = iaa.Sharpen(alpha=0.5, lightness=1)
        observed = aug.augment_image(self.base_img)
        expected = self._compute_sharpened_base_img(
            0.5*1, 0.5 * self.m_noop + 0.5 * self.m)
        assert np.allclose(observed, expected.astype(np.uint8))

    def test_alpha_075(self):
        aug = iaa.Sharpen(alpha=0.75, lightness=1)
        observed = aug.augment_image(self.base_img)
        expected = self._compute_sharpened_base_img(
            0.75*1, 0.25 * self.m_noop + 0.75 * self.m)
        assert np.allclose(observed, expected)

    def test_alpha_is_stochastic_parameter(self):
        aug = iaa.Sharpen(alpha=iap.Choice([0.5, 1.0]), lightness=1)
        observed = aug.augment_image(self.base_img)
        expected1 = self._compute_sharpened_base_img(
            0.5*1, 0.5 * self.m_noop + 0.5 * self.m)
        expected2 = self._compute_sharpened_base_img(
            1.0*1, 0.0 * self.m_noop + 1.0 * self.m)
        assert (
            np.allclose(observed, expected1)
            or np.allclose(observed, expected2)
        )

    def test_failure_if_alpha_has_bad_datatype(self):
        # don't use assertRaisesRegex, because it doesnt exist in 2.7
        got_exception = False
        try:
            _ = iaa.Sharpen(alpha="test", lightness=1)
        except Exception as exc:
            assert "Expected " in str(exc)
            got_exception = True
        assert got_exception

    def test_alpha_1_lightness_2(self):
        aug = iaa.Sharpen(alpha=1.0, lightness=2)
        observed = aug.augment_image(self.base_img)
        expected = self._compute_sharpened_base_img(1.0*2, self.m)
        assert np.allclose(observed, expected)

    def test_alpha_1_lightness_3(self):
        aug = iaa.Sharpen(alpha=1.0, lightness=3)
        observed = aug.augment_image(self.base_img)
        expected = self._compute_sharpened_base_img(1.0*3, self.m)
        assert np.allclose(observed, expected)

    def test_alpha_1_lightness_is_stochastic_parameter(self):
        aug = iaa.Sharpen(alpha=1.0, lightness=iap.Choice([1.0, 1.5]))
        observed = aug.augment_image(self.base_img)
        expected1 = self._compute_sharpened_base_img(1.0*1.0, self.m)
        expected2 = self._compute_sharpened_base_img(1.0*1.5, self.m)
        assert (
            np.allclose(observed, expected1)
            or np.allclose(observed, expected2)
        )

    def test_failure_if_lightness_has_bad_datatype(self):
        # don't use assertRaisesRegex, because it doesnt exist in 2.7
        got_exception = False
        try:
            _ = iaa.Sharpen(alpha=1.0, lightness="test")
        except Exception as exc:
            assert "Expected " in str(exc)
            got_exception = True
        assert got_exception

    # this part doesnt really work so far due to nonlinearities resulting
    # from clipping to uint8
    """
    # alpha range
    aug = iaa.Sharpen(alpha=(0.0, 1.0), lightness=1)
    base_img = np.copy(base_img)
    base_img_sharpened_min = _compute_sharpened_base_img(
        0.0*1, 1.0 * m_noop + 0.0 * m)
    base_img_sharpened_max = _compute_sharpened_base_img(
        1.0*1, 0.0 * m_noop + 1.0 * m)
    #distance_max = np.average(
        np.abs(
            base_img_sharpened.astype(np.float32)
            - base_img.astype(np.float32)
        )
    )
    distance_max = np.average(
        np.abs(
            base_img_sharpened_max
            - base_img_sharpened_min
        )
    )
    nb_iterations = 250
    distances = []
    for _ in sm.xrange(nb_iterations):
        observed = aug.augment_image(base_img)
        distance = np.average(
            np.abs(
                observed.astype(np.float32)
                - base_img_sharpened_max.astype(np.float32)
            )
        ) / distance_max
        distances.append(distance)

    print(distances)
    print(min(distances), np.average(distances), max(distances))
    assert 0 - 1e-4 < min(distances) < 0.1
    assert 0.4 < np.average(distances) < 0.6
    assert 0.9 < max(distances) < 1.0 + 1e-4

    nb_bins = 5
    hist, _ = np.histogram(distances, bins=nb_bins, range=(0.0, 1.0),
                           density=False)
    density_expected = 1.0/nb_bins
    density_tolerance = 0.05
    for nb_samples in hist:
        density = nb_samples / nb_iterations
        assert (
            density_expected - density_tolerance
            < density
            < density_expected + density_tolerance)

    # lightness range
    aug = iaa.Sharpen(alpha=1.0, lightness=(0.5, 2.0))
    base_img = np.copy(base_img)
    base_img_sharpened = _compute_sharpened_base_img(1.0*2.0, m)
    distance_max = np.average(
        np.abs(
            base_img_sharpened.astype(np.int32)
            - base_img.astype(np.int32)
        )
    )
    nb_iterations = 250
    distances = []
    for _ in sm.xrange(nb_iterations):
        observed = aug.augment_image(base_img)
        distance = np.average(
            np.abs(
                observed.astype(np.int32)
                - base_img.astype(np.int32)
            )
        ) / distance_max
        distances.append(distance)

    assert 0 - 1e-4 < min(distances) < 0.1
    assert 0.4 < np.average(distances) < 0.6
    assert 0.9 < max(distances) < 1.0 + 1e-4

    nb_bins = 5
    hist, _ = np.histogram(distances, bins=nb_bins, range=(0.0, 1.0),
                           density=False)
    density_expected = 1.0/nb_bins
    density_tolerance = 0.05
    for nb_samples in hist:
        density = nb_samples / nb_iterations
        assert (
            density_expected - density_tolerance
            < density
            < density_expected + density_tolerance)
    """

    def test_pickleable(self):
        aug = iaa.Sharpen(alpha=(0.0, 1.0), lightness=(1, 3), seed=1)
        runtest_pickleable_uint8_img(aug, iterations=20)


class TestEmboss(unittest.TestCase):
    def setUp(self):
        reseed()

    @classmethod
    def _compute_embossed_base_img(cls, img, alpha, strength):
        img = np.copy(img)
        base_img_embossed = np.zeros((3, 3), dtype=np.float32)

        m = np.float32([[-1, 0, 0],
                        [0, 1, 0],
                        [0, 0, 1]])
        strength_matrix = strength * np.float32([
            [-1, -1, 0],
            [-1, 0, 1],
            [0, 1, 1]
        ])
        ms = m + strength_matrix

        for i in range(base_img_embossed.shape[0]):
            for j in range(base_img_embossed.shape[1]):
                for u in range(ms.shape[0]):
                    for v in range(ms.shape[1]):
                        weight = ms[u, v]
                        inputs_i = abs(i + (u - (ms.shape[0]-1)//2))
                        inputs_j = abs(j + (v - (ms.shape[1]-1)//2))
                        if inputs_i >= img.shape[0]:
                            diff = inputs_i - (img.shape[0]-1)
                            inputs_i = img.shape[0] - 1 - diff
                        if inputs_j >= img.shape[1]:
                            diff = inputs_j - (img.shape[1]-1)
                            inputs_j = img.shape[1] - 1 - diff
                        inputs = img[inputs_i, inputs_j]
                        base_img_embossed[i, j] += inputs * weight

        return np.clip(
            (1-alpha) * img
            + alpha * base_img_embossed,
            0,
            255
        ).astype(np.uint8)

    @classmethod
    def _allclose(cls, a, b):
        return np.max(
            a.astype(np.float32)
            - b.astype(np.float32)
        ) <= 2.1

    @property
    def base_img(self):
        return np.array([[10, 10, 10],
                         [10, 20, 10],
                         [10, 10, 15]], dtype=np.uint8)

    def test_alpha_0_strength_1(self):
        aug = iaa.Emboss(alpha=0, strength=1)
        observed = aug.augment_image(self.base_img)
        expected = self.base_img
        assert self._allclose(observed, expected)

    def test_alpha_1_strength_1(self):
        aug = iaa.Emboss(alpha=1.0, strength=1)
        observed = aug.augment_image(self.base_img)
        expected = self._compute_embossed_base_img(
            self.base_img, alpha=1.0, strength=1)
        assert self._allclose(observed, expected)

    def test_alpha_050_strength_1(self):
        aug = iaa.Emboss(alpha=0.5, strength=1)
        observed = aug.augment_image(self.base_img)
        expected = self._compute_embossed_base_img(
            self.base_img, alpha=0.5, strength=1)
        assert self._allclose(observed, expected.astype(np.uint8))

    def test_alpha_075_strength_1(self):
        aug = iaa.Emboss(alpha=0.75, strength=1)
        observed = aug.augment_image(self.base_img)
        expected = self._compute_embossed_base_img(
            self.base_img, alpha=0.75, strength=1)
        assert self._allclose(observed, expected)

    def test_alpha_stochastic_parameter_strength_1(self):
        aug = iaa.Emboss(alpha=iap.Choice([0.5, 1.0]), strength=1)
        observed = aug.augment_image(self.base_img)
        expected1 = self._compute_embossed_base_img(
            self.base_img, alpha=0.5, strength=1)
        expected2 = self._compute_embossed_base_img(
            self.base_img, alpha=1.0, strength=1)
        assert (
            self._allclose(observed, expected1)
            or self._allclose(observed, expected2)
        )

    def test_failure_on_invalid_datatype_for_alpha(self):
        # don't use assertRaisesRegex, because it doesnt exist in 2.7
        got_exception = False
        try:
            _ = iaa.Emboss(alpha="test", strength=1)
        except Exception as exc:
            assert "Expected " in str(exc)
            got_exception = True
        assert got_exception

    def test_alpha_1_strength_2(self):
        aug = iaa.Emboss(alpha=1.0, strength=2)
        observed = aug.augment_image(self.base_img)
        expected = self._compute_embossed_base_img(
            self.base_img, alpha=1.0, strength=2)
        assert self._allclose(observed, expected)

    def test_alpha_1_strength_3(self):
        aug = iaa.Emboss(alpha=1.0, strength=3)
        observed = aug.augment_image(self.base_img)
        expected = self._compute_embossed_base_img(
            self.base_img, alpha=1.0, strength=3)
        assert self._allclose(observed, expected)

    def test_alpha_1_strength_6(self):
        aug = iaa.Emboss(alpha=1.0, strength=6)
        observed = aug.augment_image(self.base_img)
        expected = self._compute_embossed_base_img(
            self.base_img, alpha=1.0, strength=6)
        assert self._allclose(observed, expected)

    def test_alpha_1_strength_stochastic_parameter(self):
        aug = iaa.Emboss(alpha=1.0, strength=iap.Choice([1.0, 2.5]))
        observed = aug.augment_image(self.base_img)
        expected1 = self._compute_embossed_base_img(
            self.base_img, alpha=1.0, strength=1.0)
        expected2 = self._compute_embossed_base_img(
            self.base_img, alpha=1.0, strength=2.5)
        assert (
            self._allclose(observed, expected1)
            or self._allclose(observed, expected2)
        )

    def test_failure_on_invalid_datatype_for_strength(self):
        # don't use assertRaisesRegex, because it doesnt exist in 2.7
        got_exception = False
        try:
            _ = iaa.Emboss(alpha=1.0, strength="test")
        except Exception as exc:
            assert "Expected " in str(exc)
            got_exception = True
        assert got_exception

    def test_pickleable(self):
        aug = iaa.Emboss(alpha=(0.0, 1.0), strength=(1, 3), seed=1)
        runtest_pickleable_uint8_img(aug, iterations=20)
