def test_occlude_segments(self):
        """
        Tests :func:`fatf.utils.data.occlusion.Occlusion.occlude_segments`.
        """
        occlusion = fudo.Occlusion(ARRAY_IMAGE_3D, SEGMENTS)

        msg = ('Segments subset must be either '
               'an integer or a list of integers.')
        with pytest.raises(TypeError) as exin:
            occlusion.occlude_segments('list')
        assert str(exin.value) == msg

        msg = ('The segment id 0 does not correspond to any of '
               'the known segments ([1, 2]).')
        with pytest.raises(ValueError) as exin:
            occlusion.occlude_segments(segments_subset=0)
        assert str(exin.value) == msg

        msg = 'The list of segments has duplicates.'
        with pytest.raises(ValueError) as exin:
            occlusion.occlude_segments(segments_subset=[1, 2, 1])
        assert str(exin.value) == msg

        msg = 'The segment id 1 is not an integer.'
        with pytest.raises(TypeError) as exin:
            occlusion.occlude_segments(segments_subset=[1, 2, '1'])
        assert str(exin.value) == msg

        msg = ('The segment id 4 does not correspond to any of '
               'the known segments ([1, 2]).')
        with pytest.raises(ValueError) as exin:
            occlusion.occlude_segments(segments_subset=[2, 4, 1])
        assert str(exin.value) == msg

        msg = ('The width, height or number of channels of the input '
               'image does not agree with the same parameters of the '
               'original image.')
        with pytest.raises(IncorrectShapeError) as exin:
            occlusion.occlude_segments([],
                                       image=np.ones(shape=(4, 4), dtype=int))
        assert str(exin.value) == msg

        # No image
        ocl = occlusion.occlude_segments(segments_subset=[])
        assert np.array_equal(ocl, ARRAY_IMAGE_3D)

        # External image with external colour
        img_ = np.ones(shape=(2, 2, 3), dtype=np.uint8)
        img_[0, 0, 0] = 42
        img_[1, 1, 1] = 42
        img_[0, 1, 2] = 42
        ocl_ = np.zeros(shape=(2, 2, 3), dtype=np.uint8)
        ocl_[1, 1] = (1, 42, 1)

        ocl = occlusion.occlude_segments([1], image=img_, colour='black')
        assert np.array_equal(ocl, ocl_)
        ocl = occlusion.occlude_segments(1, image=img_, colour='black')
        assert np.array_equal(ocl, ocl_)
    def test_randomise_patch(self):
        """
        Tests :func:`fatf.utils.data.occlusion.Occlusion._randomise_patch`.
        """
        fatf.setup_random_seed()
        mask_ = np.array([[1, 0], [0, 1]], dtype=bool)

        # Colour
        occlusion = fudo.Occlusion(ARRAY_IMAGE_3D, SEGMENTS)
        assert np.array_equal(
            occlusion._randomise_patch(mask_),
            np.array([[125, 114, 71], [52, 44, 216]], dtype=np.uint8))
        # ..check the default
        assert np.array_equal(
            occlusion._colouring_strategy(ONES),
            occlusion._generate_colouring_strategy('mean')(ONES))

        # Grayscale
        occlusion = fudo.Occlusion(ARRAY_IMAGE_2D, SEGMENTS)
        assert np.array_equal(
            occlusion._randomise_patch(mask_),
            np.array([119, 13], dtype=np.uint8))
        # ..check the default
        assert np.array_equal(
            occlusion._colouring_strategy(ONES),
            occlusion._generate_colouring_strategy('mean')(ONES))

        # Black-and-white
        occlusion = fudo.Occlusion(
            np.array([[0, 255], [255, 0]], dtype=np.uint8), SEGMENTS)
        assert np.array_equal(
            occlusion._randomise_patch(mask_),
            np.array([0, 255], dtype=np.uint8))
        # ..check the default
        assert np.array_equal(
            occlusion._colouring_strategy(ONES),
            occlusion._generate_colouring_strategy('black')(ONES))
    def test_colouring_strategy(self):
        """
        Tests ``colouring_strategy`` getters and setter for the
        :class:`fatf.utils.data.occlusion.Occlusion` class.
        """
        occlusion = fudo.Occlusion(ARRAY_IMAGE_3D, SEGMENTS)

        assert occlusion._colouring_strategy == occlusion.colouring_strategy
        assert np.array_equal(
            occlusion.colouring_strategy(ONES),
            occlusion._generate_colouring_strategy('mean')(ONES))

        occlusion.colouring_strategy = 'black'
        assert occlusion._colouring_strategy == occlusion.colouring_strategy
        assert np.array_equal(
            occlusion.colouring_strategy(ONES),
            occlusion._generate_colouring_strategy('black')(ONES))

        occlusion.set_colouring_strategy('white')
        assert occlusion._colouring_strategy == occlusion.colouring_strategy
        assert np.array_equal(
            occlusion.colouring_strategy(ONES),
            occlusion._generate_colouring_strategy('white')(ONES))
    def __init__(self,
                 image: np.ndarray,
                 predictive_model: object,
                 as_probabilistic: bool = True,
                 class_names: Optional[Union[List[str], List[int]]] = None,
                 segmentation_mask: Optional[np.ndarray] = None,
                 segments_merge_list: Union[None, List[int],
                                            List[List[int]]] = None,
                 ratio: float = 0.2,
                 kernel_size: float = 4,
                 max_dist: float = 200,
                 colour: Optional[Union[str, int, RGBcolour]] = None):
        """Constructs a bLIMEy LIME image explainer."""
        # pylint: disable=too-many-arguments,too-many-locals,too-many-branches
        # pylint: disable=too-many-statements
        # The image and the segmentation mask in numpy representation
        self.image = image.copy()
        if segmentation_mask is None:
            self.segmentation_mask = self.image.copy()
        else:
            self.segmentation_mask = segmentation_mask.copy()

        if not isinstance(as_probabilistic, bool):
            raise TypeError(
                'The as_probabilistic parameter must be a boolean.')
        self.as_probabilistic = as_probabilistic

        if self.as_probabilistic:
            is_functional = fatf_validation.check_model_functionality(
                predictive_model, True, False)
            if not is_functional:
                raise IncompatibleModelError(
                    'With as_probabilistic set to True the predictive model '
                    'needs to be capable of outputting probabilities via '
                    'a *predict_proba* method, which takes exactly one '
                    'required parameter -- data to be predicted -- and '
                    'outputs a 2-dimensional array with probabilities.')
        else:
            is_functional = fatf_validation.check_model_functionality(
                predictive_model, False, False)
            if not is_functional:
                raise IncompatibleModelError(
                    'With as_probabilistic set to False the predictive model '
                    'needs to be capable of outputting (class) predictions '
                    'via a *predict* method, which takes exactly one required '
                    'parameter -- data to be predicted -- and outputs a '
                    '1-dimensional array with (class) predictions.')
        self.predictive_model = predictive_model

        if self.as_probabilistic:
            predictive_function = \
                self.predictive_model.predict_proba  # type: ignore
            image_prediction = predictive_function([self.image])[0]
            classes_number = image_prediction.shape[0]
            image_prediction = int(np.argmax(image_prediction))
        else:
            predictive_function = self.predictive_model.predict  # type: ignore
            classes_number = None
            image_prediction = predictive_function([self.image])[0]
        self.predictive_function = predictive_function
        self.image_prediction = image_prediction
        self.classes_number = classes_number

        if class_names is not None:
            if isinstance(class_names, list):
                if not class_names:
                    raise ValueError('The class_names list cannot be empty.')
                if len(class_names) != len(set(class_names)):
                    raise ValueError('The class_names list contains '
                                     'duplicated entries.')
                _chosen_type = type(class_names[0])
                if _chosen_type is int or _chosen_type is str:
                    _chosen_error = False
                    for class_name in class_names:
                        if not isinstance(class_name, _chosen_type):
                            _chosen_error = True
                            break
                else:
                    _chosen_error = True
                    class_name = class_names[0]
                if _chosen_error:
                    raise TypeError('All elements of the class_names '
                                    'list must be strings or integers; '
                                    '*{}* is not.'.format(class_name))
                if self.classes_number is None:
                    self.classes_number = len(class_names)
                else:
                    if self.classes_number != len(class_names):
                        raise RuntimeError('The number of class names does '
                                           'not correspond to the shape of '
                                           'the model predictions.')
            else:
                raise TypeError('The class_names parameter must be a Python '
                                'list or None.')
        self.class_names = class_names

        logger.debug('Building segmentation.')
        self.segmenter = fatf_segmentation.QuickShift(
            self.image,
            segmentation_mask=self.segmentation_mask,
            ratio=ratio,
            kernel_size=kernel_size,
            max_dist=max_dist)
        if segments_merge_list is not None:
            self.segmenter.merge_segments(segments_merge_list, inplace=True)

        logger.debug('Building occlusion.')
        self.occluder = fatf_occlusion.Occlusion(self.image,
                                                 self.segmenter.segments,
                                                 colour=colour)

        # Placeholder to memorise the last data sample for training surrogates
        self.surrogate_data_sample = None  # type: Union[None, np.ndarray]
        self.surrogate_data_predictions = None  # type: Union[None, np.ndarray]
        self.similarities = None  # type: Union[None, np.ndarray]
    def test_occlude_segments_vectorised(self):
        """
        Tests :func:`fatf.utils.data.occlusion.Occlusion.\
occlude_segments_vectorised`.
        """
        occlusion = fudo.Occlusion(ARRAY_IMAGE_3D, SEGMENTS)

        msg = ('The width, height or number of channels of the input '
               'image does not agree with the same parameters of the '
               'original image.')
        with pytest.raises(IncorrectShapeError) as exin:
            occlusion.occlude_segments_vectorised(
                None, image=np.ones(shape=(4, 4), dtype=int))
        assert str(exin.value) == msg

        err = ('The vector representation of segments should be a 1- or '
               '2-dimensional numpy array.')
        with pytest.raises(IncorrectShapeError) as exin:
            occlusion.occlude_segments_vectorised(np.array([[[1, 2, 3]]]))
        assert str(exin.value) == err

        err = ('The vector representation of segments cannot be '
               'a structured numpy array.')
        with pytest.raises(TypeError) as exin:
            occlusion.occlude_segments_vectorised(ARRAY_STRUCT)
        assert str(exin.value) == err

        err = ('The vector representation of segments should be '
               'a numerical numpy array.')
        with pytest.raises(TypeError) as exin:
            occlusion.occlude_segments_vectorised(np.array(['1', '2']))
        assert str(exin.value) == err

        err = ('The number of elements (3) in the vector representation of '
               'segments should correspond to the unique number of segments '
               '(2).')
        with pytest.raises(IncorrectShapeError) as exin:
            occlusion.occlude_segments_vectorised(np.array([1, 2, 3]))
        assert str(exin.value) == err

        err = ('The number of columns (3) in the vector representation '
               'of segments should correspond to the unique number of '
               'segments (2).')
        with pytest.raises(IncorrectShapeError) as exin:
            occlusion.occlude_segments_vectorised(np.array([[1, 2, 3]]))
        assert str(exin.value) == err

        err = ('The vector representation of segments should be binary '
               'numpy array.')
        with pytest.raises(TypeError) as exin:
            occlusion.occlude_segments_vectorised(np.array([[1, 2]]))
        assert str(exin.value) == err

        # 1-D mask
        ocl = occlusion.occlude_segments_vectorised(np.array([1, 1]))
        assert np.array_equal(ocl, ARRAY_IMAGE_3D)
        ocl = occlusion.occlude_segments_vectorised(
            np.array([1, 0]), colour='black')
        ocl_ = np.ones(shape=(2, 2, 3), dtype=np.uint8)
        ocl_[1, 1] = (0, 0, 0)
        assert np.array_equal(ocl, ocl_)

        # 1-D mask -- colour
        ocl = occlusion.occlude_segments_vectorised(
            np.array([1.0, 0.0]), colour='black')
        assert np.array_equal(ocl, ocl_)

        # 2-D mask -- colour
        ocl = occlusion.occlude_segments_vectorised(
            np.array([[1.0, 0.0], [1, 0]]), colour='black')
        assert np.array_equal(ocl, np.array([ocl_, ocl_]))

        # 2-D mask -- external image
        ocl = occlusion.occlude_segments_vectorised(
            np.array([[1.0, 0.0], [1, 0]]), image=ocl_, colour='black')
        assert np.array_equal(ocl, np.array([ocl_, ocl_]))
    def test_occlusion_class_init(self, caplog):
        """
        Tests :class:`fatf.utils.data.occlusion.Occlusion` class init.
        """
        log_1 = 'Assuming a black-and-white image.'
        log_2 = 'Rescale 0/1 black-and-white image to 0/255.'

        assert len(caplog.records) == 0
        err = ('Black-and-white images must use 0 as '
               'black and 1 or 255 as white.')
        with pytest.raises(RuntimeError) as exin:
            fudo.Occlusion(
                np.array([[2, 255], [255, 2]], dtype=int),
                np.ones(shape=(2, 2), dtype=int))
        assert str(exin.value) == err
        assert len(caplog.records) == 1
        assert caplog.records[0].levelname == 'INFO'
        assert caplog.records[0].getMessage() == log_1

        assert len(caplog.records) == 1
        with pytest.raises(RuntimeError) as exin:
            fudo.Occlusion(
                np.array([[2, 1], [1, 2]], dtype=int),
                np.ones(shape=(2, 2), dtype=int))
        assert str(exin.value) == err
        assert len(caplog.records) == 3
        assert caplog.records[1].levelname == 'INFO'
        assert caplog.records[1].getMessage() == log_1
        assert caplog.records[2].levelname == 'INFO'
        assert caplog.records[2].getMessage() == log_2

        # Colour image
        wrn_msg = 'The segmentation has only **one** segment.'
        with pytest.warns(UserWarning) as warning:
            occlusion = fudo.Occlusion(ARRAY_IMAGE_3D, ONES)
        assert len(warning) == 1
        assert str(warning[0].message) == wrn_msg
        #
        assert np.array_equal(occlusion.image, ARRAY_IMAGE_3D)
        assert np.array_equal(occlusion.segments, ONES)
        assert occlusion.is_rgb
        assert not occlusion.is_bnw
        assert np.array_equal(occlusion.unique_segments, [1])
        assert occlusion.segments_number == 1
        assert np.array_equal(
            occlusion._colouring_strategy(ONES),
            occlusion._generate_colouring_strategy('mean')(ONES))
        assert np.array_equal(
            occlusion.colouring_strategy(ONES),
            occlusion._generate_colouring_strategy('mean')(ONES))

        # Grayscale image
        occlusion = fudo.Occlusion(ARRAY_IMAGE_2D, SEGMENTS, 'white')
        assert np.array_equal(occlusion.image, ARRAY_IMAGE_2D)
        assert np.array_equal(occlusion.segments, SEGMENTS)
        assert not occlusion.is_rgb
        assert not occlusion.is_bnw
        assert np.array_equal(occlusion.unique_segments, [1, 2])
        assert occlusion.segments_number == 2
        assert np.array_equal(
            occlusion._colouring_strategy(ONES),
            occlusion._generate_colouring_strategy('white')(ONES))
        assert np.array_equal(
            occlusion.colouring_strategy(ONES),
            occlusion._generate_colouring_strategy('white')(ONES))

        # Black-and-white image
        assert len(caplog.records) == 3
        occlusion = fudo.Occlusion(ARRAY_IMAGE_BNW1, SEGMENTS)
        assert len(caplog.records) == 5
        assert caplog.records[3].levelname == 'INFO'
        assert caplog.records[3].getMessage() == log_1
        assert caplog.records[4].levelname == 'INFO'
        assert caplog.records[4].getMessage() == log_2
        #
        assert np.array_equal(occlusion.image,
                              np.array([[0, 255], [255, 0]], dtype=np.uint8))
        assert np.array_equal(occlusion.segments, SEGMENTS)
        assert not occlusion.is_rgb
        assert occlusion.is_bnw
        assert np.array_equal(occlusion.unique_segments, [1, 2])
        assert occlusion.segments_number == 2
        assert np.array_equal(
            occlusion._colouring_strategy(ONES),
            occlusion._generate_colouring_strategy('black')(ONES))
        assert np.array_equal(
            occlusion.colouring_strategy(ONES),
            occlusion._generate_colouring_strategy('black')(ONES))
    def test_generate_colouring_strategy(self):
        """
        Tests :func:`fatf.utils.data.occlusion.Occlusion.\
_generate_colouring_strategy`.
        """
        occlusion = fudo.Occlusion(ARRAY_IMAGE_3D, SEGMENTS)

        # Errors
        msg = ('The colour can either be a string specifier; or '
               'an RGB thriplet for RGB images and an integer '
               'for or grayscale and black-and-white images.')
        with pytest.raises(TypeError) as exin:
            occlusion._generate_colouring_strategy(['list'])
        assert str(exin.value) == msg

        # int for colour
        with pytest.raises(TypeError) as exin:
            occlusion._generate_colouring_strategy(33)
        assert str(exin.value) == msg

        # tuple for grayscale/black-and-white
        occlusion = fudo.Occlusion(ARRAY_IMAGE_2D, SEGMENTS)
        with pytest.raises(TypeError) as exin:
            occlusion._generate_colouring_strategy((4, 2, 0))
        assert str(exin.value) == msg
        with pytest.raises(TypeError) as exin:
            occlusion._generate_colouring_strategy(2.0)
        assert str(exin.value) == msg

        # Colour
        occlusion = fudo.Occlusion(ARRAY_IMAGE_3D, SEGMENTS)

        # string
        msg = ('Unknown colouring strategy name: colour.\n'
               "Choose one of the following: ['black', 'blue', 'green', "
               "'mean', 'pink', 'random', 'random-patch', 'randomise', "
               "'randomise-patch', 'red', 'white'].")
        with pytest.raises(ValueError) as exin:
            occlusion._generate_colouring_strategy('colour')
        assert str(exin.value) == msg
        # functional -- mean
        clr = occlusion._generate_colouring_strategy(None)(ONES)
        assert np.array_equal(clr, np.ones(shape=(2, 2, 2, 3), dtype=np.uint8))
        clr = occlusion._generate_colouring_strategy('mean')(ONES)
        assert np.array_equal(clr, np.ones(shape=(2, 2, 2, 3), dtype=np.uint8))

        one_ = np.zeros(shape=(2, 2), dtype=bool)
        one_[1, 1] = True
        fatf.setup_random_seed()
        # functional -- random
        clr = occlusion._generate_colouring_strategy('random')(ONES)
        assert np.array_equal(clr, (57, 12, 140))
        # functional -- random-patch
        clr = occlusion._generate_colouring_strategy('random-patch')(one_)
        assert np.array_equal(clr, np.array([[16, 15, 47]], dtype=np.uint8))
        # functional -- randomise
        clr = occlusion._generate_colouring_strategy('randomise')(one_)
        assert np.array_equal(clr, (101, 214, 112))
        # functional -- randomise-patch
        clr = occlusion._generate_colouring_strategy('randomise-patch')(one_)
        assert np.array_equal(clr, np.array([[81, 216, 174]], dtype=np.uint8))
        # functional -- black
        clr = occlusion._generate_colouring_strategy('black')(one_)
        assert np.array_equal(clr, (0, 0, 0))
        # functional -- white
        clr = occlusion._generate_colouring_strategy('white')(one_)
        assert np.array_equal(clr, (255, 255, 255))
        # functional -- red
        clr = occlusion._generate_colouring_strategy('red')(one_)
        assert np.array_equal(clr, (255, 0, 0))
        # functional -- green
        clr = occlusion._generate_colouring_strategy('green')(one_)
        assert np.array_equal(clr, (0, 255, 0))
        # functional -- blue
        clr = occlusion._generate_colouring_strategy('blue')(one_)
        assert np.array_equal(clr, (0, 0, 255))
        # functional -- pink
        clr = occlusion._generate_colouring_strategy('pink')(one_)
        assert np.array_equal(clr, (255, 192, 203))

        # tuple
        clr = occlusion._generate_colouring_strategy((42, 24, 242))(one_)
        assert np.array_equal(clr, (42, 24, 242))

        # Grayscale
        occlusion = fudo.Occlusion(ARRAY_IMAGE_2D, SEGMENTS)
        # int
        msg = ('Unknown colouring strategy name: colour.\n'
               "Choose one of the following: ['black', 'mean', 'random', "
               "'random-patch', 'randomise', 'randomise-patch', 'white'].")
        with pytest.raises(ValueError) as exin:
            occlusion._generate_colouring_strategy('colour')
        assert str(exin.value) == msg

        msg = ('The colour should be an integer between '
               '0 and 255 for grayscale images.')
        with pytest.raises(ValueError) as exin:
            occlusion._generate_colouring_strategy(-1)
        assert str(exin.value) == msg
        with pytest.raises(ValueError) as exin:
            occlusion._generate_colouring_strategy(256)
        assert str(exin.value) == msg

        clr = occlusion._generate_colouring_strategy(42)(one_)
        assert clr == 42

        # string
        clr = occlusion._generate_colouring_strategy(None)(ONES)
        assert np.array_equal(
            clr,
            np.array([[[85, 2], [85, 2]], [[85, 2], [85, 2]]], dtype=np.uint8))
        clr = occlusion._generate_colouring_strategy('mean')(ONES)
        assert np.array_equal(
            clr,
            np.array([[[85, 2], [85, 2]], [[85, 2], [85, 2]]], dtype=np.uint8))

        fatf.setup_random_seed()
        # functional -- random
        clr = occlusion._generate_colouring_strategy('random')(ONES)
        assert clr == 57
        # functional -- random-patch
        clr = occlusion._generate_colouring_strategy('random-patch')(one_)
        assert np.array_equal(clr, np.array([125], dtype=np.uint8))
        # functional -- randomise
        clr = occlusion._generate_colouring_strategy('randomise')(one_)
        assert clr == 71
        # functional -- randomise-patch
        clr = occlusion._generate_colouring_strategy('randomise-patch')(one_)
        assert np.array_equal(clr, np.array([44], dtype=np.uint8))
        # functional -- black
        clr = occlusion._generate_colouring_strategy('black')(one_)
        assert clr == 0
        # functional -- white
        clr = occlusion._generate_colouring_strategy('white')(one_)
        assert clr == 255

        # Black-and-white
        occlusion = fudo.Occlusion(
            np.array([[0, 255], [0, 255]], dtype=np.uint8), SEGMENTS)

        # int
        msg = ('The colour should be 0 for black, or 1 or 255 for '
               'white for black-and-white images.')
        with pytest.raises(ValueError) as exin:
            occlusion._generate_colouring_strategy(42)
        assert str(exin.value) == msg

        clr = occlusion._generate_colouring_strategy(0)(one_)
        assert clr == 0
        clr = occlusion._generate_colouring_strategy(1)(one_)
        assert clr == 255
        clr = occlusion._generate_colouring_strategy(255)(one_)
        assert clr == 255

        # string
        msg = 'Mean occlusion is not supported for black-and-white images.'
        with pytest.raises(RuntimeError) as exin:
            occlusion._generate_colouring_strategy(None)
        assert str(exin.value) == msg
        with pytest.raises(RuntimeError) as exin:
            occlusion._generate_colouring_strategy('mean')
        assert str(exin.value) == msg

        fatf.setup_random_seed()
        # functional -- random
        clr = occlusion._generate_colouring_strategy('random')(ONES)
        assert clr == 0
        # functional -- random-patch
        clr = occlusion._generate_colouring_strategy('random-patch')(one_)
        assert np.array_equal(clr, np.array([0], dtype=np.uint8))
        # functional -- randomise
        clr = occlusion._generate_colouring_strategy('randomise')(one_)
        assert clr == 0
        # functional -- randomise-patch
        clr = occlusion._generate_colouring_strategy('randomise-patch')(one_)
        assert np.array_equal(clr, np.array([0], dtype=np.uint8))
        # functional -- black
        clr = occlusion._generate_colouring_strategy('black')(one_)
        assert clr == 0
        # functional -- white
        clr = occlusion._generate_colouring_strategy('white')(one_)
        assert clr == 255