def test_plot_detections_top_side(self):
        plt = Plotter(num_classes=20)

        test_detections = [
            {
                'coordinates': [0, 0, 250, 400],
                'confidence': 0.998,
                'class_id': 16,
                'label': 'contramano'
            },
        ]

        test_image_path = 'tests/data/test.jpg'
        image = cv2.imread(test_image_path)

        drawn_image = plt.plot_detections(image, test_detections)

        assert drawn_image.shape == image.shape
    def test_plot_detections_bgr(self):
        plt = Plotter(num_classes=20, bgr=True)

        test_detections = [{
            'coordinates': [200, 100, 300, 200],
            'confidence': 0.998,
            'class_id': 16,
            'label': 'contramano'
        }, {
            'coordinates': [300, 800, 400, 950],
            'confidence': 0.783,
            'class_id': 13,
            'label': 'cocoa'
        }]

        test_image_path = 'tests/data/test.jpg'
        image = cv2.imread(test_image_path)

        drawn_image = plt.plot_detections(image, test_detections)

        assert drawn_image.shape == image.shape
    def test_save_plot_detections(self):
        plt = Plotter(num_classes=20)

        test_detections = [{
            'coordinates': [200, 100, 300, 200],
            'confidence': 0.998,
            'class_id': 16,
            'label': 'contramano'
        }, {
            'coordinates': [300, 800, 400, 950],
            'confidence': 0.783,
            'class_id': 13,
            'label': 'cocoa'
        }]

        test_image_path = 'tests/data/test.jpg'
        image = cv2.imread(test_image_path)

        output_filename = './tests/data/test_output.jpg'
        drawn_image = plt.plot_detections(image,
                                          test_detections,
                                          output=output_filename)

        assert os.path.exists(output_filename) == True
    def test_plot_detections_right_side(self):
        plt = Plotter(num_classes=20)

        test_detections = [{
            'coordinates': [450, 400, 500, 500],
            'confidence':
            0.998,
            'class_id':
            16,
            'label':
            'test-long-long-long-long-long-long-long-long-long-long-label'
        }, {
            'coordinates': [300, 800, 400, 950],
            'confidence': 0.783,
            'class_id': 13,
            'label': 'cocoa'
        }]

        test_image_path = 'tests/data/test.jpg'
        image = cv2.imread(test_image_path)

        drawn_image = plt.plot_detections(image, test_detections)

        assert drawn_image.shape == image.shape
class Detector:
    def __init__(self, detection_pipeline, classes_images_path,
                 classes_sound_path):
        self.__detection_pipeline = detection_pipeline
        self.__plotter = Plotter(num_classes=20, bgr=True)
        self.__logger = Logger()
        self.__cfd = CrossFrameDetector(num_classes=20,
                                        frame_history_count=5,
                                        frames_threshold=2)

        self.__classes_sound_path = classes_sound_path
        self.__classes_images = self.__load_classes_images(classes_images_path)

        self.__notification_queue = []

    def detect_image(self,
                     image,
                     output=None,
                     show_confidence=False,
                     return_image=False):

        detections = self.__detection_pipeline.detect_objects_in_image(image)

        if output:
            filename, extension = os.path.splitext(output)

            if extension == '.json':
                self.__logger.save_detections_to_json(detections, output)
            elif extension == '.txt':
                h, w, _ = image.shape
                self.__logger.save_detections_to_yolo(detections, (w, h),
                                                      output)
            elif extension == '.csv':
                self.__logger.save_detections_to_csv(detections, output)
            elif extension == '.jpg':
                detections_image = self.__plotter.plot_detections(
                    image, detections, draw_confidence=show_confidence)
                cv2.imwrite(output, detections_image)
            else:
                raise FormatNotSupportedException(
                    'Output extension must be .json .txt .csv or .jpg')

        if return_image:
            detections_image = self.__plotter.plot_detections(
                image, detections, draw_confidence=show_confidence)
            return detections_image, detections

        return detections

    def detect_video_feed(self,
                          video_feed,
                          show_output=False,
                          sound_notifications=False,
                          output=None,
                          output_csv=None,
                          show_confidence=False,
                          skip_frames=False):

        cap = cv2.VideoCapture(video_feed)

        width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
        frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
        fps = int(cap.get(cv2.CAP_PROP_FPS))

        if skip_frames:
            cap.release()

        if output:
            fourcc = cv2.VideoWriter_fourcc(*'XVID')
            out = cv2.VideoWriter(output, fourcc, fps, (width, height))

        if output_csv:
            csv_file = open(output_csv, 'w')
            titles = ['frame_id', 'class_id', 'x1', 'y1', 'x2', 'y2']
            csv_file.write(', '.join(titles) + '\n')

        if show_output:
            # from screeninfo import get_monitors
            # monitor = get_monitors()[0]
            # screen_w, screen_h, = monitor.width, monitor.height
            screen_w, screen_h, = 1920, 1080

            window_name = 'Detector'
            cv2.namedWindow(window_name, cv2.WND_PROP_FULLSCREEN)
            cv2.moveWindow(window_name, screen_w - 1, screen_h - 1)
            cv2.setWindowProperty(window_name, cv2.WND_PROP_FULLSCREEN,
                                  cv2.WINDOW_FULLSCREEN)

            if sound_notifications:
                sound_thread = threading.Thread(
                    target=self.__notification_thread, args=())
                sound_thread.daemon = True
                self.sound_notifications_lock = True
                sound_thread.start()

        frame_id = 0

        while (True):
            # Capture frame-by-frame
            if skip_frames:
                cap = cv2.VideoCapture(video_feed)

            ret, frame = cap.read()
            frame_id += 1

            if skip_frames:
                cap.release()

            if not ret:
                break

            print('\r>> Processing frame Nº {}'.format(frame_id), end='')

            detections = self.__detection_pipeline.detect_objects_in_image(
                frame)

            if output_csv:
                for detection in detections:
                    class_id = detection['class_id']
                    coordinates = detection['coordinates']

                    line = ', '.join(
                        [str(x) for x in [frame_id, class_id, *coordinates]])
                    csv_file.write(line + '\n')

            self.__cfd.register_detections(detections)

            frame = self.__plotter.plot_detections(
                frame, detections, draw_confidence=show_confidence)

            all_cfd_detections, current_cfd_detections = self.__cfd.get_detections(
            )

            if sound_notifications:
                for detection_id in current_cfd_detections:
                    self.__notification_queue.append(detection_id)

            cfd_frame = self.__draw_cfd_detections(frame, all_cfd_detections)

            # Display the resulting frame
            if show_output:
                cv2.imshow(window_name, frame)

            if output:
                out.write(frame)

            if cv2.waitKey(1) & 0xFF == ord('q'):
                break

        if output_csv:
            csv_file.close()

        if output:
            out.release()

        cap.release()
        cv2.destroyAllWindows()
        cv2.waitKey(1)
        cv2.waitKey(1)
        cv2.waitKey(1)
        cv2.waitKey(1)

        self.__cfd.reset()

        if sound_notifications:
            self.sound_notifications_lock = False

        return True

    def __notification_thread(self):
        while self.sound_notifications_lock:
            if len(self.__notification_queue) > 0:
                class_id = self.__notification_queue.pop()

                notification_path = os.path.join(self.__classes_sound_path,
                                                 'notification.mp3')
                notification_sound = AudioSegment.from_mp3(notification_path)

                play(notification_sound)

                class_sound_path = os.path.join(self.__classes_sound_path,
                                                str(class_id) + '.mp3')
                class_name_sound = AudioSegment.from_mp3(class_sound_path)

                play(class_name_sound)

    def __load_classes_images(self, path):
        classes_images = {}

        images_list = glob.glob(os.path.join(path, '*.jpg'))

        for image_path in images_list:
            class_id = int(os.path.basename(image_path)[:-4])
            image = cv2.imread(image_path)
            image = cv2.copyMakeBorder(image,
                                       10,
                                       10,
                                       10,
                                       10,
                                       cv2.BORDER_CONSTANT,
                                       value=[0, 0, 0])

            classes_images[class_id] = image

        return classes_images

    def __draw_cfd_detections(self, frame, cfd_detections):
        cfd_detection_count = len(cfd_detections)

        if cfd_detection_count > 0:
            frame_height, frame_width, _ = frame.shape
            class_image_size = int(frame_height / 4)

            aux = np.zeros(shape=(class_image_size,
                                  class_image_size * cfd_detection_count, 3),
                           dtype='uint8')

            start_h, start_w = 0, 0

            for class_id in cfd_detections:
                img = self.__classes_images[class_id]
                img = cv2.resize(img, (class_image_size, class_image_size))

                end_w = start_w + img.shape[1]
                aux[start_h:img.shape[0], start_w:end_w] = img
                start_w = start_w + img.shape[1]

            frame[frame_height - class_image_size:, :class_image_size *
                  cfd_detection_count] = aux

        return frame