import pytest

from lunavl.sdk.detectors.base import ImageForDetection
from lunavl.sdk.detectors.facedetector import FaceDetector
from lunavl.sdk.errors.errors import LunaVLError
from lunavl.sdk.errors.exceptions import LunaSDKException
from lunavl.sdk.faceengine.setting_provider import DetectorType
from lunavl.sdk.image_utils.geometry import Rect
from lunavl.sdk.image_utils.image import VLImage, ColorFormat
from tests.detect_test_class import (
    VLIMAGE_ONE_FACE,
    GOOD_AREA,
    VLIMAGE_SEVERAL_FACE,
    AREA_WITHOUT_FACE,
    OUTSIDE_AREA,
    VLIMAGE_SMALL,
    FaceDetectTestClass,
    BAD_IMAGE,
)
from tests.resources import ONE_FACE, MANY_FACES, NO_FACES
from tests.schemas import jsonValidator, REQUIRED_FACE_DETECTION, LANDMARKS5


class TestFaceDetector(FaceDetectTestClass):
    """
    Test of face detector.
    """

    #: Face detector with default detector type
    defaultDetector: FaceDetector

    @classmethod
    def setup_class(cls):
        super().setup_class()
        cls.defaultDetector = cls.faceEngine.createFaceDetector(DetectorType.FACE_DET_DEFAULT)

    def test_image_detection_with_transfer_option(self):
        """
        Test structure image for detection
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detect(images=[ImageForDetection(image=VLIMAGE_ONE_FACE, detectArea=GOOD_AREA)])
                self.assertFaceDetection(detection[0], VLIMAGE_ONE_FACE)
                assert 1 == len(detection)

    def test_check_landmarks_points(self):
        """
        Test validation landmarks points
        """
        detection = TestFaceDetector.defaultDetector.detectOne(image=VLIMAGE_ONE_FACE, detect68Landmarks=True)
        self.assertFaceDetection(detection, VLIMAGE_ONE_FACE)

        self.assertLandmarksPoints(detection.landmarks5.points)
        self.assertLandmarksPoints(detection.landmarks68.points)

    def test_landmarks_as_dict(self):
        """
        Test conversion landmarks to dictionary
        """
        currentLandmarks5 = TestFaceDetector.defaultDetector.detectOne(image=VLIMAGE_ONE_FACE).landmarks5.asDict()

        assert (
            jsonValidator(schema=LANDMARKS5).validate(currentLandmarks5) is None
        ), f"{currentLandmarks5} does not match with schema {LANDMARKS5}"

    def test_valid_bounding_box(self):
        """
        Test validate bounding box (rect and score)
        """
        detection = TestFaceDetector.defaultDetector.detectOne(image=VLIMAGE_ONE_FACE)
        self.assertBoundingBox(detection.boundingBox)
        detection = TestFaceDetector.defaultDetector.detect(images=[VLIMAGE_ONE_FACE])[0][0]
        self.assertBoundingBox(detection.boundingBox)

    def test_bounding_box_as_dict(self):
        """
        Test conversion bounding box to dictionary
        """
        boundingBox = TestFaceDetector.defaultDetector.detectOne(image=VLIMAGE_ONE_FACE).boundingBox.asDict()

        assert (
            jsonValidator(schema=REQUIRED_FACE_DETECTION).validate(boundingBox) is None
        ), f"{boundingBox} does not match with schema {REQUIRED_FACE_DETECTION}"

    def test_face_detection_as_dict(self):
        """
        Test conversion result face detection to dictionary
        """
        for case in self.landmarksCases:
            with self.subTest(landmarks5=case.detect5Landmarks, landmarks68=case.detect68Landmarks):
                detectAsDict = TestFaceDetector.defaultDetector.detectOne(
                    image=VLIMAGE_ONE_FACE,
                    detect5Landmarks=case.detect5Landmarks,
                    detect68Landmarks=case.detect68Landmarks,
                ).asDict()
                assert (
                    jsonValidator(schema=REQUIRED_FACE_DETECTION).validate(detectAsDict) is None
                ), f"{detectAsDict} does not match with schema {REQUIRED_FACE_DETECTION}"

    def test_detection_with_default_detector_type(self):
        """
        Test face detection with default detector type
        """
        for detectionFunction in ("detect", "detectOne"):
            with self.subTest(detectionFunction=detectionFunction):
                if detectionFunction == "detectOne":
                    detection = TestFaceDetector.defaultDetector.detectOne(image=VLIMAGE_ONE_FACE)
                else:
                    detection = TestFaceDetector.defaultDetector.detect(images=[VLIMAGE_ONE_FACE])[0]
                self.assertFaceDetection(detection, VLIMAGE_ONE_FACE)

    def test_detect_one_using_different_type_detector(self):
        """
        Test detection of one face using different type of detector
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detectOne(image=VLIMAGE_ONE_FACE)
                self.assertFaceDetection(detection, VLIMAGE_ONE_FACE)

    def test_batch_detect_using_different_type_detector(self):
        """
        Test batch detection using different type of detector
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detect(images=[VLIMAGE_ONE_FACE])[0]
                self.assertFaceDetection(detection, VLIMAGE_ONE_FACE)

    def test_batch_detect_with_success_and_error(self):
        """
        Test batch detection with success and error using FACE_DET_V3 (there is no error with other detector)
        """
        with pytest.raises(LunaSDKException) as exceptionInfo:
            self.detectors[2].detect(images=[VLIMAGE_ONE_FACE, BAD_IMAGE])
        self.assertLunaVlError(exceptionInfo, LunaVLError.BatchedInternalError)
        assert len(exceptionInfo.value.context) == 2, "Expect two errors in exception context"
        self.assertReceivedAndRawExpectedErrors(exceptionInfo.value.context[0], LunaVLError.Ok)
        self.assertReceivedAndRawExpectedErrors(exceptionInfo.value.context[1], LunaVLError.Internal)

    def test_detect_one_with_image_of_several_faces(self):
        """
        Test detection of one face with image of several faces
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detectOne(image=VLIMAGE_SEVERAL_FACE)
                self.assertFaceDetection(detection, VLIMAGE_SEVERAL_FACE)

    def test_detect_one_with_image_without_faces(self):
        """
        Test detection of one face with image without faces
        """
        imageWithoutFace = VLImage.load(filename=NO_FACES)
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detectOne(image=imageWithoutFace)
                assert detection is None, detection

    def test_batch_detect_with_image_without_faces(self):
        """
        Test batch face detection with image without faces
        """
        imageWithoutFace = VLImage.load(filename=NO_FACES)
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detect(images=[imageWithoutFace])
                assert 0 == len(detection[0])

    def test_detect_one_by_area_without_face(self):
        """
        Test detection of one face by area without face
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detectOne(image=VLIMAGE_ONE_FACE, detectArea=AREA_WITHOUT_FACE)
                assert detection is None, detection

    def test_detect_one_by_area_with_face(self):
        """
        Test detection of one face by area with face
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detectOne(image=VLIMAGE_ONE_FACE, detectArea=GOOD_AREA)
                self.assertFaceDetection(detection, VLIMAGE_ONE_FACE)

    def test_batch_detect_with_image_of_several_faces(self):
        """
        Test batch face detection with image of several faces
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detect(images=[VLIMAGE_SEVERAL_FACE])
                self.assertFaceDetection(detection[0], VLIMAGE_SEVERAL_FACE)
                assert 1 == len(detection)
                assert 5 == len(detection[0])

    def test_batch_detect_of_multiple_images(self):
        """
        Test batch detection of multiple images
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detect(images=[VLIMAGE_SEVERAL_FACE, VLIMAGE_ONE_FACE])
                self.assertFaceDetection(detection[0], VLIMAGE_SEVERAL_FACE)
                self.assertFaceDetection(detection[1], VLIMAGE_ONE_FACE)
                assert 2 == len(detection)
                assert 5 == len(detection[0])
                assert 1 == len(detection[1])

    def test_get_landmarks_for_detect_one(self):
        """
        Test get and check landmark instances for detection of one face
        """
        for case in self.landmarksCases:
            with self.subTest(landmarks5=case.detect5Landmarks, landmarks68=case.detect68Landmarks):
                for detector in self.detectors:
                    with self.subTest(detectorType=detector.detectorType):
                        detection = detector.detectOne(
                            image=VLIMAGE_ONE_FACE,
                            detect68Landmarks=case.detect68Landmarks,
                            detect5Landmarks=case.detect5Landmarks,
                        )
                        self.assertDetectionLandmarks(
                            detection=detection, landmarks5=case.detect5Landmarks, landmarks68=case.detect68Landmarks
                        )

    def test_get_landmarks_for_batch_detect(self):
        """
        Test get and check landmark instances for batch detect
        """
        for case in self.landmarksCases:
            with self.subTest(landmarks5=case.detect5Landmarks, landmarks68=case.detect68Landmarks):
                for detector in self.detectors:
                    with self.subTest(detectorType=detector.detectorType):
                        detection = detector.detect(
                            images=[VLIMAGE_ONE_FACE],
                            detect68Landmarks=case.detect68Landmarks,
                            detect5Landmarks=case.detect5Landmarks,
                        )[0][0]
                        self.assertDetectionLandmarks(
                            detection=detection, landmarks5=case.detect5Landmarks, landmarks68=case.detect68Landmarks
                        )

    def test_batch_detect_limit(self):
        """
        Test checking detection limit for an image
        """
        imageWithManyFaces = VLImage.load(filename=MANY_FACES)
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detect(images=[imageWithManyFaces])[0]
                assert 5 == len(detection)

                detection = detector.detect(images=[imageWithManyFaces], limit=20)[0]
                if detector.detectorType.name == "FACE_DET_V3":
                    assert 20 == len(detection)
                else:
                    assert 19 == len(detection)

    @pytest.mark.skip("core bug: Fatal error")
    def test_detect_limit_bad_param(self):
        """
        Test batch detection with negative limit number
        """
        imageWithManyFaces = VLImage.load(filename=MANY_FACES)
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detector.detect(images=[ImageForDetection(image=imageWithManyFaces, detectArea=GOOD_AREA)], limit=-1)

    def test_detect_one_invalid_image_format(self):
        """
        Test invalid image format detection
        """
        imageWithOneFaces = VLImage.load(filename=ONE_FACE, colorFormat=ColorFormat.B8G8R8)
        errorDetail = "Bad image format for detection, format: B8G8R8, image: one_face.jpg"
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                with pytest.raises(LunaSDKException) as exceptionInfo:
                    detector.detectOne(image=imageWithOneFaces)
                self.assertLunaVlError(exceptionInfo, LunaVLError.InvalidImageFormat.format(details=errorDetail))

    def test_batch_detect_invalid_image_format(self):
        """
        Test invalid image format detection
        """
        colorToImageMap = self.getColorToImageMap()
        allowedColorsForDetection = {ColorFormat.R8G8B8}
        for colorFormat in set(colorToImageMap) - allowedColorsForDetection:
            colorImage = colorToImageMap[colorFormat]
            errorDetail = f"Bad image format for detection, format: {colorFormat.value}, image: {colorImage.filename}"
            for detector in self.detectors:
                with self.subTest(detectorType=detector.detectorType, colorFormat=colorFormat.name):
                    with pytest.raises(LunaSDKException) as exceptionInfo:
                        detector.detect(images=[colorImage])
                    self.assertLunaVlError(exceptionInfo, LunaVLError.InvalidImageFormat.format(details=errorDetail))

    def test_batch_detect_by_area_without_face(self):
        """
        Test batch face detection by area without face
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detect(
                    images=[ImageForDetection(image=VLIMAGE_ONE_FACE, detectArea=AREA_WITHOUT_FACE)]
                )
                assert 1 == len(detection)
                assert 0 == len(detection[0])

    def test_batch_detect_by_area_with_face(self):
        """
        Test batch face detection by area with face
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detect(images=[ImageForDetection(image=VLIMAGE_ONE_FACE, detectArea=GOOD_AREA)])
                assert 1 == len(detection[0])
                self.assertFaceDetection(detection[0], VLIMAGE_ONE_FACE)

    def test_bad_area_detection(self):
        """
        Test detection of one face in area outside image
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                with pytest.raises(LunaSDKException) as exceptionInfo:
                    detector.detectOne(image=VLIMAGE_ONE_FACE, detectArea=OUTSIDE_AREA)
                self.assertLunaVlError(exceptionInfo, LunaVLError.InvalidRect)

    def test_batch_detect_in_area_outside_image(self):
        """
        Test batch detection in area outside image
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                with pytest.raises(LunaSDKException) as exceptionInfo:
                    detector.detect(images=[ImageForDetection(image=VLIMAGE_ONE_FACE, detectArea=OUTSIDE_AREA)])
                self.assertLunaVlError(exceptionInfo, LunaVLError.BatchedInternalError)
                assert len(exceptionInfo.value.context) == 1, "Expect one error in exception context"
                self.assertReceivedAndRawExpectedErrors(exceptionInfo.value.context[0], LunaVLError.InvalidRect)

    @pytest.mark.skip("unstable")
    def test_excessive_image_list_detection(self):
        """
        Test excessive image list detection
        """
        with pytest.raises(LunaSDKException) as exceptionInfo:
            TestFaceDetector.defaultDetector.detect(images=[VLIMAGE_ONE_FACE] * 40)
        self.assertLunaVlError(exceptionInfo, LunaVLError.HighMemoryUsage)

    def test_detect_one_invalid_rectangle(self):
        """
        Test detection of one face with an invalid rect
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                with pytest.raises(LunaSDKException) as exceptionInfo:
                    detector.detectOne(image=VLIMAGE_ONE_FACE, detectArea=Rect())
                self.assertLunaVlError(exceptionInfo, LunaVLError.InvalidRect)

    def test_batch_detect_invalid_rectangle(self):
        """
        Test batch face detection with an invalid rect
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                with pytest.raises(LunaSDKException) as exceptionInfo:
                    detector.detect(images=[ImageForDetection(image=VLIMAGE_ONE_FACE, detectArea=Rect())])
                self.assertLunaVlError(exceptionInfo, LunaVLError.BatchedInternalError)
                assert len(exceptionInfo.value.context) == 1, "Expect one error in exception context"
                self.assertReceivedAndRawExpectedErrors(exceptionInfo.value.context[0], LunaVLError.InvalidRect)

    def test_match_detection_one_image(self):
        """
        Test match of values at different detections (detectOne and detect) with one image
        """
        for image in (VLIMAGE_ONE_FACE, VLIMAGE_SMALL):
            for detector in self.detectors:
                with self.subTest(detectorType=detector.detectorType):
                    detectOne = detector.detectOne(image=image, detect68Landmarks=True)
                    batchDetect = detector.detect(images=[image] * 3, detect68Landmarks=True)
                    for detection in batchDetect:
                        for face in detection:
                            assert face.boundingBox.asDict() == detectOne.boundingBox.asDict()
                            assert face.landmarks5.asDict() == detectOne.landmarks5.asDict()
                            assert face.landmarks68.asDict() == detectOne.landmarks68.asDict()

import pytest

from lunavl.sdk.detectors.base import ImageForDetection
from lunavl.sdk.detectors.facedetector import FaceDetector
from lunavl.sdk.errors.errors import LunaVLError
from lunavl.sdk.errors.exceptions import LunaSDKException
from lunavl.sdk.faceengine.setting_provider import DetectorType
from lunavl.sdk.image_utils.geometry import Rect
from lunavl.sdk.image_utils.image import VLImage, ColorFormat
from tests.detect_test_class import (
    VLIMAGE_ONE_FACE,
    GOOD_AREA,
    VLIMAGE_SEVERAL_FACE,
    AREA_WITHOUT_FACE,
    OUTSIDE_AREA,
    VLIMAGE_SMALL,
    FaceDetectTestClass,
    BAD_IMAGE,
)
from tests.resources import ONE_FACE, MANY_FACES, NO_FACES
from tests.schemas import jsonValidator, REQUIRED_FACE_DETECTION, LANDMARKS5


class TestFaceDetector(FaceDetectTestClass):
    """
    Test of face detector.
    """

    #: Face detector with default detector type
    defaultDetector: FaceDetector

    @classmethod
    def setup_class(cls):
        super().setup_class()
        cls.defaultDetector = cls.faceEngine.createFaceDetector(DetectorType.FACE_DET_DEFAULT)

    def test_image_detection_with_transfer_option(self):
        """
        Test structure image for detection
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detect(images=[ImageForDetection(image=VLIMAGE_ONE_FACE, detectArea=GOOD_AREA)])
                self.assertFaceDetection(detection[0], VLIMAGE_ONE_FACE)
                assert 1 == len(detection)

    def test_check_landmarks_points(self):
        """
        Test validation landmarks points
        """
        detection = TestFaceDetector.defaultDetector.detectOne(image=VLIMAGE_ONE_FACE, detect68Landmarks=True)
        self.assertFaceDetection(detection, VLIMAGE_ONE_FACE)

        self.assertLandmarksPoints(detection.landmarks5.points)
        self.assertLandmarksPoints(detection.landmarks68.points)

    def test_landmarks_as_dict(self):
        """
        Test conversion landmarks to dictionary
        """
        currentLandmarks5 = TestFaceDetector.defaultDetector.detectOne(image=VLIMAGE_ONE_FACE).landmarks5.asDict()

        assert (
            jsonValidator(schema=LANDMARKS5).validate(currentLandmarks5) is None
        ), f"{currentLandmarks5} does not match with schema {LANDMARKS5}"

    def test_valid_bounding_box(self):
        """
        Test validate bounding box (rect and score)
        """
        detection = TestFaceDetector.defaultDetector.detectOne(image=VLIMAGE_ONE_FACE)
        self.assertBoundingBox(detection.boundingBox)
        detection = TestFaceDetector.defaultDetector.detect(images=[VLIMAGE_ONE_FACE])[0][0]
        self.assertBoundingBox(detection.boundingBox)

    def test_bounding_box_as_dict(self):
        """
        Test conversion bounding box to dictionary
        """
        boundingBox = TestFaceDetector.defaultDetector.detectOne(image=VLIMAGE_ONE_FACE).boundingBox.asDict()

        assert (
            jsonValidator(schema=REQUIRED_FACE_DETECTION).validate(boundingBox) is None
        ), f"{boundingBox} does not match with schema {REQUIRED_FACE_DETECTION}"

    def test_face_detection_as_dict(self):
        """
        Test conversion result face detection to dictionary
        """
        for case in self.landmarksCases:
            with self.subTest(landmarks5=case.detect5Landmarks, landmarks68=case.detect68Landmarks):
                detectAsDict = TestFaceDetector.defaultDetector.detectOne(
                    image=VLIMAGE_ONE_FACE,
                    detect5Landmarks=case.detect5Landmarks,
                    detect68Landmarks=case.detect68Landmarks,
                ).asDict()
                assert (
                    jsonValidator(schema=REQUIRED_FACE_DETECTION).validate(detectAsDict) is None
                ), f"{detectAsDict} does not match with schema {REQUIRED_FACE_DETECTION}"

    def test_detection_with_default_detector_type(self):
        """
        Test face detection with default detector type
        """
        for detectionFunction in ("detect", "detectOne"):
            with self.subTest(detectionFunction=detectionFunction):
                if detectionFunction == "detectOne":
                    detection = TestFaceDetector.defaultDetector.detectOne(image=VLIMAGE_ONE_FACE)
                else:
                    detection = TestFaceDetector.defaultDetector.detect(images=[VLIMAGE_ONE_FACE])[0]
                self.assertFaceDetection(detection, VLIMAGE_ONE_FACE)

    def test_detect_one_using_different_type_detector(self):
        """
        Test detection of one face using different type of detector
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detectOne(image=VLIMAGE_ONE_FACE)
                self.assertFaceDetection(detection, VLIMAGE_ONE_FACE)

    def test_batch_detect_using_different_type_detector(self):
        """
        Test batch detection using different type of detector
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detect(images=[VLIMAGE_ONE_FACE])[0]
                self.assertFaceDetection(detection, VLIMAGE_ONE_FACE)

    def test_batch_detect_with_success_and_error(self):
        """
        Test batch detection with success and error using FACE_DET_V3 (there is no error with other detector)
        """
        with pytest.raises(LunaSDKException) as exceptionInfo:
            self.detectors[2].detect(images=[VLIMAGE_ONE_FACE, BAD_IMAGE])
        self.assertLunaVlError(exceptionInfo, LunaVLError.BatchedInternalError)
        assert len(exceptionInfo.value.context) == 2, "Expect two errors in exception context"
        self.assertReceivedAndRawExpectedErrors(exceptionInfo.value.context[0], LunaVLError.Ok)
        self.assertReceivedAndRawExpectedErrors(exceptionInfo.value.context[1], LunaVLError.Internal)

    def test_detect_one_with_image_of_several_faces(self):
        """
        Test detection of one face with image of several faces
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detectOne(image=VLIMAGE_SEVERAL_FACE)
                self.assertFaceDetection(detection, VLIMAGE_SEVERAL_FACE)

    def test_detect_one_with_image_without_faces(self):
        """
        Test detection of one face with image without faces
        """
        imageWithoutFace = VLImage.load(filename=NO_FACES)
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detectOne(image=imageWithoutFace)
                assert detection is None, detection

    def test_batch_detect_with_image_without_faces(self):
        """
        Test batch face detection with image without faces
        """
        imageWithoutFace = VLImage.load(filename=NO_FACES)
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detect(images=[imageWithoutFace])
                assert 0 == len(detection[0])

    def test_detect_one_by_area_without_face(self):
        """
        Test detection of one face by area without face
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detectOne(image=VLIMAGE_ONE_FACE, detectArea=AREA_WITHOUT_FACE)
                assert detection is None, detection

    def test_detect_one_by_area_with_face(self):
        """
        Test detection of one face by area with face
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detectOne(image=VLIMAGE_ONE_FACE, detectArea=GOOD_AREA)
                self.assertFaceDetection(detection, VLIMAGE_ONE_FACE)

    def test_batch_detect_with_image_of_several_faces(self):
        """
        Test batch face detection with image of several faces
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detect(images=[VLIMAGE_SEVERAL_FACE])
                self.assertFaceDetection(detection[0], VLIMAGE_SEVERAL_FACE)
                assert 1 == len(detection)
                assert 5 == len(detection[0])

    def test_batch_detect_of_multiple_images(self):
        """
        Test batch detection of multiple images
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detect(images=[VLIMAGE_SEVERAL_FACE, VLIMAGE_ONE_FACE])
                self.assertFaceDetection(detection[0], VLIMAGE_SEVERAL_FACE)
                self.assertFaceDetection(detection[1], VLIMAGE_ONE_FACE)
                assert 2 == len(detection)
                assert 5 == len(detection[0])
                assert 1 == len(detection[1])

    def test_get_landmarks_for_detect_one(self):
        """
        Test get and check landmark instances for detection of one face
        """
        for case in self.landmarksCases:
            with self.subTest(landmarks5=case.detect5Landmarks, landmarks68=case.detect68Landmarks):
                for detector in self.detectors:
                    with self.subTest(detectorType=detector.detectorType):
                        detection = detector.detectOne(
                            image=VLIMAGE_ONE_FACE,
                            detect68Landmarks=case.detect68Landmarks,
                            detect5Landmarks=case.detect5Landmarks,
                        )
                        self.assertDetectionLandmarks(
                            detection=detection, landmarks5=case.detect5Landmarks, landmarks68=case.detect68Landmarks
                        )

    def test_get_landmarks_for_batch_detect(self):
        """
        Test get and check landmark instances for batch detect
        """
        for case in self.landmarksCases:
            with self.subTest(landmarks5=case.detect5Landmarks, landmarks68=case.detect68Landmarks):
                for detector in self.detectors:
                    with self.subTest(detectorType=detector.detectorType):
                        detection = detector.detect(
                            images=[VLIMAGE_ONE_FACE],
                            detect68Landmarks=case.detect68Landmarks,
                            detect5Landmarks=case.detect5Landmarks,
                        )[0][0]
                        self.assertDetectionLandmarks(
                            detection=detection, landmarks5=case.detect5Landmarks, landmarks68=case.detect68Landmarks
                        )

    def test_batch_detect_limit(self):
        """
        Test checking detection limit for an image
        """
        imageWithManyFaces = VLImage.load(filename=MANY_FACES)
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detect(images=[imageWithManyFaces])[0]
                assert 5 == len(detection)

                detection = detector.detect(images=[imageWithManyFaces], limit=20)[0]
                if detector.detectorType.name == "FACE_DET_V3":
                    assert 20 == len(detection)
                else:
                    assert 19 == len(detection)

    @pytest.mark.skip("core bug: Fatal error")
    def test_detect_limit_bad_param(self):
        """
        Test batch detection with negative limit number
        """
        imageWithManyFaces = VLImage.load(filename=MANY_FACES)
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detector.detect(images=[ImageForDetection(image=imageWithManyFaces, detectArea=GOOD_AREA)], limit=-1)

    def test_detect_one_invalid_image_format(self):
        """
        Test invalid image format detection
        """
        imageWithOneFaces = VLImage.load(filename=ONE_FACE, colorFormat=ColorFormat.B8G8R8)
        errorDetail = "Bad image format for detection, format: B8G8R8, image: one_face.jpg"
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                with pytest.raises(LunaSDKException) as exceptionInfo:
                    detector.detectOne(image=imageWithOneFaces)
                self.assertLunaVlError(exceptionInfo, LunaVLError.InvalidImageFormat.format(details=errorDetail))

    def test_batch_detect_invalid_image_format(self):
        """
        Test invalid image format detection
        """
        colorToImageMap = self.getColorToImageMap()
        allowedColorsForDetection = {ColorFormat.R8G8B8}
        for colorFormat in set(colorToImageMap) - allowedColorsForDetection:
            colorImage = colorToImageMap[colorFormat]
            errorDetail = f"Bad image format for detection, format: {colorFormat.value}, image: {colorImage.filename}"
            for detector in self.detectors:
                with self.subTest(detectorType=detector.detectorType, colorFormat=colorFormat.name):
                    with pytest.raises(LunaSDKException) as exceptionInfo:
                        detector.detect(images=[colorImage])
                    self.assertLunaVlError(exceptionInfo, LunaVLError.InvalidImageFormat.format(details=errorDetail))

    def test_batch_detect_by_area_without_face(self):
        """
        Test batch face detection by area without face
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detect(
                    images=[ImageForDetection(image=VLIMAGE_ONE_FACE, detectArea=AREA_WITHOUT_FACE)]
                )
                assert 1 == len(detection)
                assert 0 == len(detection[0])

    def test_batch_detect_by_area_with_face(self):
        """
        Test batch face detection by area with face
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                detection = detector.detect(images=[ImageForDetection(image=VLIMAGE_ONE_FACE, detectArea=GOOD_AREA)])
                assert 1 == len(detection[0])
                self.assertFaceDetection(detection[0], VLIMAGE_ONE_FACE)

    def test_bad_area_detection(self):
        """
        Test detection of one face in area outside image
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                with pytest.raises(LunaSDKException) as exceptionInfo:
                    detector.detectOne(image=VLIMAGE_ONE_FACE, detectArea=OUTSIDE_AREA)
                self.assertLunaVlError(exceptionInfo, LunaVLError.InvalidRect)

    def test_batch_detect_in_area_outside_image(self):
        """
        Test batch detection in area outside image
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                with pytest.raises(LunaSDKException) as exceptionInfo:
                    detector.detect(images=[ImageForDetection(image=VLIMAGE_ONE_FACE, detectArea=OUTSIDE_AREA)])
                self.assertLunaVlError(exceptionInfo, LunaVLError.BatchedInternalError)
                assert len(exceptionInfo.value.context) == 1, "Expect one error in exception context"
                self.assertReceivedAndRawExpectedErrors(exceptionInfo.value.context[0], LunaVLError.InvalidRect)

    @pytest.mark.skip("unstable")
    def test_excessive_image_list_detection(self):
        """
        Test excessive image list detection
        """
        with pytest.raises(LunaSDKException) as exceptionInfo:
            TestFaceDetector.defaultDetector.detect(images=[VLIMAGE_ONE_FACE] * 40)
        self.assertLunaVlError(exceptionInfo, LunaVLError.HighMemoryUsage)

    def test_detect_one_invalid_rectangle(self):
        """
        Test detection of one face with an invalid rect
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                with pytest.raises(LunaSDKException) as exceptionInfo:
                    detector.detectOne(image=VLIMAGE_ONE_FACE, detectArea=Rect())
                self.assertLunaVlError(exceptionInfo, LunaVLError.InvalidRect)

    def test_batch_detect_invalid_rectangle(self):
        """
        Test batch face detection with an invalid rect
        """
        for detector in self.detectors:
            with self.subTest(detectorType=detector.detectorType):
                with pytest.raises(LunaSDKException) as exceptionInfo:
                    detector.detect(images=[ImageForDetection(image=VLIMAGE_ONE_FACE, detectArea=Rect())])
                self.assertLunaVlError(exceptionInfo, LunaVLError.BatchedInternalError)
                assert len(exceptionInfo.value.context) == 1, "Expect one error in exception context"
                self.assertReceivedAndRawExpectedErrors(exceptionInfo.value.context[0], LunaVLError.InvalidRect)

    def test_match_detection_one_image(self):
        """
        Test match of values at different detections (detectOne and detect) with one image
        """
        for image in (VLIMAGE_ONE_FACE, VLIMAGE_SMALL):
            for detector in self.detectors:
                with self.subTest(detectorType=detector.detectorType):
                    detectOne = detector.detectOne(image=image, detect68Landmarks=True)
                    batchDetect = detector.detect(images=[image] * 3, detect68Landmarks=True)
                    for detection in batchDetect:
                        for face in detection:
                            assert face.boundingBox.asDict() == detectOne.boundingBox.asDict()
                            assert face.landmarks5.asDict() == detectOne.landmarks5.asDict()
                            assert face.landmarks68.asDict() == detectOne.landmarks68.asDict()

Test of face detector. #: Face detector with default detector type Test structure image for detection Test validation landmarks points Test conversion landmarks to dictionary Test validate bounding box (rect and score) Test conversion bounding box to dictionary Test conversion result face detection to dictionary Test face detection with default detector type Test detection of one face using different type of detector Test batch detection using different type of detector Test batch detection with success and error using FACE_DET_V3 (there is no error with other detector) Test detection of one face with image of several faces Test detection of one face with image without faces Test batch face detection with image without faces Test detection of one face by area without face Test detection of one face by area with face Test batch face detection with image of several faces Test batch detection of multiple images Test get and check landmark instances for detection of one face Test get and check landmark instances for batch detect Test checking detection limit for an image Test batch detection with negative limit number Test invalid image format detection Test invalid image format detection Test batch face detection by area without face Test batch face detection by area with face Test detection of one face in area outside image Test batch detection in area outside image Test excessive image list detection Test detection of one face with an invalid rect Test batch face detection with an invalid rect Test match of values at different detections (detectOne and detect) with one image