class BaseImageAnnotator(Screen):
    # four view modes for the image ...
    ViewModeRawData = 0
    ViewModeInvertedData = 1
    ViewModeGrayData = 2
    ViewModeRawNoData = 3
    ViewModeInvertedNoData = 4
    ViewModeGrayNoData = 5

    def __init__(self, title, size):
        Screen.__init__(self, title, size)

        self.base_rgb_image = None
        self.base_inv_image = None
        self.base_gray_image = None

        self.view_mode = BaseImageAnnotator.ViewModeRawData
        self.view_scale = 1.0

        self.view_overlay_opacity = 1.0

        self.container_view_buttons = None
        self.lbl_zoom = None
        self.btn_zoom_reduce = None
        self.btn_zoom_increase = None
        self.btn_zoom_zero = None

        self.lbl_view_data = None
        self.btn_view_raw_data = None
        self.btn_view_inv_data = None
        self.btn_view_gray_data = None

        self.lbl_view_clear = None
        self.btn_view_raw_clear = None
        self.btn_view_inv_clear = None
        self.btn_view_gray_clear = None

        self.container_images = None
        self.canvas_select = None
        self.canvas_display = None
        self.img_main = None

    def create_image_annotator_controls(self, container_top, container_width,
                                        general_background, text_color,
                                        button_text_color, button_back_color):
        # View panel with view control buttons
        self.container_view_buttons = ScreenContainer(
            "container_view_buttons", (container_width, 160),
            back_color=general_background)
        self.container_view_buttons.position = (
            self.width - self.container_view_buttons.width - 10, container_top)
        self.elements.append(self.container_view_buttons)

        button_2_width = 130
        button_2_left = int(container_width * 0.25) - button_2_width / 2
        button_2_right = int(container_width * 0.75) - button_2_width / 2

        # zoom ....
        self.lbl_zoom = ScreenLabel("lbl_zoom", "Zoom: 100%", 21, 290, 1)
        self.lbl_zoom.position = (5, 5)
        self.lbl_zoom.set_background(general_background)
        self.lbl_zoom.set_color(text_color)
        self.container_view_buttons.append(self.lbl_zoom)

        self.btn_zoom_reduce = ScreenButton("btn_zoom_reduce", "[ - ]", 21, 90)
        self.btn_zoom_reduce.set_colors(button_text_color, button_back_color)
        self.btn_zoom_reduce.position = (10, self.lbl_zoom.get_bottom() + 10)
        self.btn_zoom_reduce.click_callback = self.btn_zoom_reduce_click
        self.container_view_buttons.append(self.btn_zoom_reduce)

        self.btn_zoom_increase = ScreenButton("btn_zoom_increase", "[ + ]", 21,
                                              90)
        self.btn_zoom_increase.set_colors(button_text_color, button_back_color)
        self.btn_zoom_increase.position = (self.container_view_buttons.width -
                                           self.btn_zoom_increase.width - 10,
                                           self.lbl_zoom.get_bottom() + 10)
        self.btn_zoom_increase.click_callback = self.btn_zoom_increase_click
        self.container_view_buttons.append(self.btn_zoom_increase)

        self.btn_zoom_zero = ScreenButton("btn_zoom_zero", "100%", 21, 90)
        self.btn_zoom_zero.set_colors(button_text_color, button_back_color)
        self.btn_zoom_zero.position = (
            (self.container_view_buttons.width - self.btn_zoom_zero.width) / 2,
            self.lbl_zoom.get_bottom() + 10)
        self.btn_zoom_zero.click_callback = self.btn_zoom_zero_click
        self.container_view_buttons.append(self.btn_zoom_zero)

        # ---
        # Data views

        button_4_width = 65
        button_4_gap = int((container_width - 20 - button_4_width * 4) / 3)
        button_4_left_1 = 10
        button_4_left_2 = 10 + (button_4_width + button_4_gap)
        button_4_left_3 = 10 + (button_4_width + button_4_gap) * 2
        button_4_left_4 = 10 + (button_4_width + button_4_gap) * 3

        self.lbl_view_data = ScreenLabel("lbl_view_data", "Data", 18,
                                         button_4_width)
        self.lbl_view_data.position = (button_4_left_1,
                                       self.btn_zoom_zero.get_bottom() + 10)
        self.lbl_view_data.set_background(general_background)
        self.lbl_view_data.set_color(text_color)
        self.container_view_buttons.append(self.lbl_view_data)

        self.btn_view_raw_data = ScreenButton("btn_view_raw_data", "RGB", 18,
                                              button_4_width)
        self.btn_view_raw_data.set_colors(button_text_color, button_back_color)
        self.btn_view_raw_data.position = (button_4_left_2,
                                           self.btn_zoom_zero.get_bottom() +
                                           10)
        self.btn_view_raw_data.click_callback = self.btn_view_raw_data_click
        self.container_view_buttons.append(self.btn_view_raw_data)

        self.btn_view_inv_data = ScreenButton("btn_view_inv_data", "INV", 18,
                                              button_4_width)
        self.btn_view_inv_data.set_colors(button_text_color, button_back_color)
        self.btn_view_inv_data.position = (button_4_left_3,
                                           self.btn_zoom_zero.get_bottom() +
                                           10)
        self.btn_view_inv_data.click_callback = self.btn_view_inv_data_click
        self.container_view_buttons.append(self.btn_view_inv_data)

        self.btn_view_gray_data = ScreenButton("btn_view_gray", "Gray", 18,
                                               button_4_width)
        self.btn_view_gray_data.set_colors(button_text_color,
                                           button_back_color)
        self.btn_view_gray_data.position = (button_4_left_4,
                                            self.btn_zoom_zero.get_bottom() +
                                            10)
        self.btn_view_gray_data.click_callback = self.btn_view_gray_data_click
        self.container_view_buttons.append(self.btn_view_gray_data)

        # ------
        # clear views ...

        self.lbl_view_clear = ScreenLabel("lbl_view_clear", "Clear", 18,
                                          button_4_width)
        self.lbl_view_clear.position = (button_4_left_1,
                                        self.btn_view_raw_data.get_bottom() +
                                        10)
        self.lbl_view_clear.set_background(general_background)
        self.lbl_view_clear.set_color(text_color)
        self.container_view_buttons.append(self.lbl_view_clear)

        self.btn_view_raw_clear = ScreenButton("btn_view_raw_clear", "RGB", 21,
                                               button_4_width)
        self.btn_view_raw_clear.set_colors(button_text_color,
                                           button_back_color)
        self.btn_view_raw_clear.position = (
            button_4_left_2, self.btn_view_raw_data.get_bottom() + 10)
        self.btn_view_raw_clear.click_callback = self.btn_view_raw_clear_click
        self.container_view_buttons.append(self.btn_view_raw_clear)

        self.btn_view_inv_clear = ScreenButton("btn_view_inv_clear", "INV", 21,
                                               button_4_width)
        self.btn_view_inv_clear.set_colors(button_text_color,
                                           button_back_color)
        self.btn_view_inv_clear.position = (
            button_4_left_3, self.btn_view_raw_data.get_bottom() + 10)
        self.btn_view_inv_clear.click_callback = self.btn_view_inv_clear_click
        self.container_view_buttons.append(self.btn_view_inv_clear)

        self.btn_view_gray_clear = ScreenButton("btn_view_gray_clear", "Gray",
                                                21, button_4_width)
        self.btn_view_gray_clear.set_colors(button_text_color,
                                            button_back_color)
        self.btn_view_gray_clear.position = (
            button_4_left_4, self.btn_view_raw_data.get_bottom() + 10)
        self.btn_view_gray_clear.click_callback = self.btn_view_gray_clear_click
        self.container_view_buttons.append(self.btn_view_gray_clear)

        image_width = self.width - self.container_view_buttons.width - 30
        image_height = self.height - container_top - 10
        self.container_images = ScreenContainer("container_images",
                                                (image_width, image_height),
                                                back_color=(0, 0, 0))
        self.container_images.position = (10, container_top)
        self.elements.append(self.container_images)

        # ... image objects ...
        tempo_blank = np.zeros((50, 50, 3), np.uint8)
        tempo_blank[:, :, :] = 255
        self.img_main = ScreenImage("img_main", tempo_blank, 0, 0, True,
                                    cv2.INTER_NEAREST)
        self.img_main.position = (0, 0)
        self.img_main.mouse_button_down_callback = self.img_main_mouse_button_down
        self.img_main.double_click_callback = self.img_main_mouse_double_click
        self.container_images.append(self.img_main)

        self.canvas_select = ScreenCanvas("canvas_select", 100, 100)
        self.canvas_select.position = (0, 0)
        self.canvas_select.locked = True
        self.canvas_select.object_edited_callback = self.canvas_select_object_edited
        # self.canvas_select.object_selected_callback = self.canvas_select_selection_changed
        self.container_images.append(self.canvas_select)

        # default selection objects
        # 1) A polygon ....
        base_points = np.array([[10, 10], [20, 10], [20, 20], [10, 20]],
                               dtype=np.float64)
        self.canvas_select.add_polygon_element("selection_polygon",
                                               base_points)
        self.canvas_select.elements["selection_polygon"].visible = False
        # 2) A rectangle ....
        self.canvas_select.add_rectangle_element("selection_rectangle", 10, 10,
                                                 10, 10)
        self.canvas_select.elements["selection_rectangle"].visible = False

        self.canvas_display = ScreenCanvas("canvas_display", 100, 100)
        self.canvas_display.position = (0, 0)
        self.canvas_display.locked = True
        self.canvas_display.object_edited_callback = self.canvas_display_object_edited
        self.container_images.append(self.canvas_display)

    def copy_view(self, other_window):
        # buggy ... need to fix some canvas sizing issues ....
        """
        self.view_scale = other_window.view_scale
        self.view_mode = other_window.view_mode
        self.update_current_view(True)

        self.container_images.v_scroll.active = other_window.container_images.v_scroll.active
        self.container_images.v_scroll.value = other_window.container_images.v_scroll.value
        self.container_images.h_scroll.active = other_window.container_images.h_scroll.active
        self.container_images.h_scroll.value = other_window.container_images.h_scroll.value
        """
        pass

    def btn_zoom_reduce_click(self, button):
        if self.view_scale <= 1.0:
            # reduce in quarters ...
            self.update_view_scale(self.view_scale - 0.25)
        else:
            # reduce in halves ....
            self.update_view_scale(self.view_scale - 0.50)

    def btn_zoom_increase_click(self, button):
        if self.view_scale < 1.0:
            # increase in quarters ...
            self.update_view_scale(self.view_scale + 0.25)
        else:
            # increase in halves ...
            self.update_view_scale(self.view_scale + 0.50)

    def btn_zoom_zero_click(self, button):
        self.update_view_scale(1.0)

    def btn_view_raw_data_click(self, button):
        self.view_mode = BaseImageAnnotator.ViewModeRawData
        self.update_current_view()

    def btn_view_inv_data_click(self, button):
        self.view_mode = BaseImageAnnotator.ViewModeInvertedData
        self.update_current_view()

    def btn_view_gray_data_click(self, button):
        self.view_mode = BaseImageAnnotator.ViewModeGrayData
        self.update_current_view()

    def btn_view_raw_clear_click(self, button):
        self.view_mode = BaseImageAnnotator.ViewModeRawNoData
        self.update_current_view()

    def btn_view_inv_clear_click(self, button):
        self.view_mode = BaseImageAnnotator.ViewModeInvertedNoData
        self.update_current_view()

    def btn_view_gray_clear_click(self, button):
        self.view_mode = BaseImageAnnotator.ViewModeGrayNoData
        self.update_current_view()

    def update_view_scale(self, new_scale):
        prev_scale = self.view_scale

        if 0.25 <= new_scale <= 4.0:
            self.view_scale = new_scale
        else:
            return

        # keep previous offsets ...
        scroll_offset_y = self.container_images.v_scroll.value if self.container_images.v_scroll.active else 0
        scroll_offset_x = self.container_images.h_scroll.value if self.container_images.h_scroll.active else 0

        prev_center_y = scroll_offset_y + self.container_images.height / 2
        prev_center_x = scroll_offset_x + self.container_images.width / 2

        # compute new scroll bar offsets
        scale_factor = (new_scale / prev_scale)
        new_off_y = prev_center_y * scale_factor - self.container_images.height / 2
        new_off_x = prev_center_x * scale_factor - self.container_images.width / 2

        # update view ....
        self.update_current_view(True)

        # set offsets
        if self.container_images.v_scroll.active and 0 <= new_off_y <= self.container_images.v_scroll.max:
            self.container_images.v_scroll.value = new_off_y
        if self.container_images.h_scroll.active and 0 <= new_off_x <= self.container_images.h_scroll.max:
            self.container_images.h_scroll.value = new_off_x

        # re-scale objects from both canvas
        # ... Canvas for Selection
        # ....... selection polygon  ...
        selection_polygon = self.canvas_select.elements["selection_polygon"]
        selection_polygon.points *= scale_factor
        # ....... selection rectangle ...
        selection_rectangle = self.canvas_select.elements[
            "selection_rectangle"]
        selection_rectangle.x *= scale_factor
        selection_rectangle.y *= scale_factor
        selection_rectangle.w *= scale_factor
        selection_rectangle.h *= scale_factor
        # ... Canvas for display ...
        for polygon_name in self.canvas_display.elements:
            display_polygon = self.canvas_display.elements[polygon_name]
            display_polygon.points *= scale_factor

        # update scale text ...
        self.lbl_zoom.set_text("Zoom: " +
                               str(int(round(self.view_scale * 100, 0))) + "%")

    def update_current_view(self, resized=False):
        if self.view_mode in [
                BaseImageAnnotator.ViewModeGrayData,
                BaseImageAnnotator.ViewModeGrayNoData
        ]:
            # gray scale mode
            base_image = self.base_gray_image
        elif self.view_mode in [
                BaseImageAnnotator.ViewModeInvertedData,
                BaseImageAnnotator.ViewModeInvertedNoData
        ]:
            if self.base_inv_image is None:
                self.base_inv_image = 255 - self.base_rgb_image
            base_image = self.base_inv_image
        else:
            base_image = self.base_rgb_image

        h, w, c = base_image.shape

        modified_image = base_image.copy()

        if self.view_mode in [
                BaseImageAnnotator.ViewModeRawData,
                BaseImageAnnotator.ViewModeInvertedData,
                BaseImageAnnotator.ViewModeGrayData
        ]:
            self.canvas_display.visible = True

            # This function must be implemented by the child class
            self.custom_view_update(modified_image)
        else:
            self.canvas_display.visible = False

        # finally, resize ...
        modified_image = cv2.resize(
            modified_image,
            (int(w * self.view_scale), int(h * self.view_scale)),
            interpolation=cv2.INTER_NEAREST)

        if self.view_overlay_opacity < 1.0:
            resized_base = cv2.resize(
                base_image,
                (int(w * self.view_scale), int(h * self.view_scale)),
                interpolation=cv2.INTER_NEAREST)

            # add transparency effect ...
            modified_image = resized_base.astype(
                np.float64) * 0.5 + modified_image.astype(np.float64) * 0.5
            modified_image = modified_image.astype(np.uint8)

        # update canvas size ....
        self.canvas_select.height, self.canvas_select.width, _ = modified_image.shape
        self.canvas_display.height, self.canvas_display.width, _ = modified_image.shape

        # replace/update image
        self.img_main.set_image(modified_image, 0, 0, True, cv2.INTER_NEAREST)
        if resized:
            self.container_images.recalculate_size()

    def img_main_mouse_button_down(self, img, pos, button):
        pass

    def img_main_mouse_double_click(self, element, pos, button):
        pass

    def canvas_select_object_edited(self, canvas, element_name):
        pass

    def canvas_display_object_edited(self, canvas, element_name):
        pass
class ChartLegendAnnotator(Screen):
    ModeNavigate = 0
    ModeRectangleSelect = 1
    ModeRectangleEdit = 2
    ModeConfirmExit = 3

    ViewModeRawData = 0
    ViewModeGrayData = 1
    ViewModeRawNoData = 2
    ViewModeGrayNoData = 3

    def __init__(self, size, panel_image, panel_info, parent_screen):
        Screen.__init__(self, "Chart Legend Ground Truth Annotation Interface",
                        size)

        self.panel_image = panel_image
        self.panel_gray = np.zeros(self.panel_image.shape,
                                   self.panel_image.dtype)
        self.panel_gray[:, :, 0] = cv2.cvtColor(self.panel_image,
                                                cv2.COLOR_RGB2GRAY)
        self.panel_gray[:, :, 1] = self.panel_gray[:, :, 0].copy()
        self.panel_gray[:, :, 2] = self.panel_gray[:, :, 0].copy()

        self.panel_info = panel_info

        if self.panel_info.legend is None:
            # create a new legend info
            self.legend = LegendInfo(
                self.panel_info.get_all_text(TextInfo.TypeLegendLabel))
            self.data_changed = True
        else:
            # make a copy
            self.legend = LegendInfo.Copy(self.panel_info.legend)
            self.data_changed = False

        self.parent_screen = parent_screen

        self.general_background = (80, 70, 150)
        self.text_color = (255, 255, 255)

        self.elements.back_color = self.general_background

        self.edition_mode = None
        self.tempo_text_id = None
        self.view_mode = ChartLegendAnnotator.ViewModeRawData
        self.view_scale = 1.0

        self.label_title = None

        self.container_view_buttons = None
        self.lbl_zoom = None
        self.btn_zoom_reduce = None
        self.btn_zoom_increase = None
        self.btn_zoom_zero = None

        self.btn_view_raw_data = None
        self.btn_view_gray_data = None
        self.btn_view_raw_clear = None
        self.btn_view_gray_clear = None

        self.container_confirm_buttons = None
        self.lbl_confirm_message = None
        self.btn_confirm_cancel = None
        self.btn_confirm_accept = None

        self.container_legend_options = None
        self.lbl_legend_title = None
        self.lbx_legend_list = None
        self.btn_legend_edit = None
        self.btn_legend_delete = None
        self.btn_return_accept = None
        self.btn_return_cancel = None

        self.container_images = None
        self.canvas_select = None
        self.canvas_display = None
        self.img_main = None

        self.create_controllers()

        # get the view ...
        self.update_current_view(True)

    def create_controllers(self):
        # add elements....
        button_text_color = (35, 50, 20)
        button_back_color = (228, 228, 228)

        # Main Title
        self.label_title = ScreenLabel(
            "title", "Chart Image Annotation Tool - Legend Markers Annotation",
            28)
        self.label_title.background = self.general_background
        self.label_title.position = (int(
            (self.width - self.label_title.width) / 2), 20)
        self.label_title.set_color(self.text_color)
        self.elements.append(self.label_title)

        container_top = 10 + self.label_title.get_bottom()
        container_width = 300

        button_width = 190
        button_left = (container_width - button_width) / 2

        button_2_width = 130
        button_2_left = int(container_width * 0.25) - button_2_width / 2
        button_2_right = int(container_width * 0.75) - button_2_width / 2

        # ===========================
        # View Options Panel

        # View panel with view control buttons
        self.container_view_buttons = ScreenContainer(
            "container_view_buttons", (container_width, 160),
            back_color=self.general_background)
        self.container_view_buttons.position = (
            self.width - self.container_view_buttons.width - 10, container_top)
        self.elements.append(self.container_view_buttons)

        # zoom ....
        self.lbl_zoom = ScreenLabel("lbl_zoom", "Zoom: 100%", 21, 290, 1)
        self.lbl_zoom.position = (5, 5)
        self.lbl_zoom.set_background(self.general_background)
        self.lbl_zoom.set_color(self.text_color)
        self.container_view_buttons.append(self.lbl_zoom)

        self.btn_zoom_reduce = ScreenButton("btn_zoom_reduce", "[ - ]", 21, 90)
        self.btn_zoom_reduce.set_colors(button_text_color, button_back_color)
        self.btn_zoom_reduce.position = (10, self.lbl_zoom.get_bottom() + 10)
        self.btn_zoom_reduce.click_callback = self.btn_zoom_reduce_click
        self.container_view_buttons.append(self.btn_zoom_reduce)

        self.btn_zoom_increase = ScreenButton("btn_zoom_increase", "[ + ]", 21,
                                              90)
        self.btn_zoom_increase.set_colors(button_text_color, button_back_color)
        self.btn_zoom_increase.position = (self.container_view_buttons.width -
                                           self.btn_zoom_increase.width - 10,
                                           self.lbl_zoom.get_bottom() + 10)
        self.btn_zoom_increase.click_callback = self.btn_zoom_increase_click
        self.container_view_buttons.append(self.btn_zoom_increase)

        self.btn_zoom_zero = ScreenButton("btn_zoom_zero", "100%", 21, 90)
        self.btn_zoom_zero.set_colors(button_text_color, button_back_color)
        self.btn_zoom_zero.position = (
            (self.container_view_buttons.width - self.btn_zoom_zero.width) / 2,
            self.lbl_zoom.get_bottom() + 10)
        self.btn_zoom_zero.click_callback = self.btn_zoom_zero_click
        self.container_view_buttons.append(self.btn_zoom_zero)

        self.btn_view_raw_data = ScreenButton("btn_view_raw_data", "RGB Data",
                                              21, button_2_width)
        self.btn_view_raw_data.set_colors(button_text_color, button_back_color)
        self.btn_view_raw_data.position = (button_2_left,
                                           self.btn_zoom_zero.get_bottom() +
                                           10)
        self.btn_view_raw_data.click_callback = self.btn_view_raw_data_click
        self.container_view_buttons.append(self.btn_view_raw_data)

        self.btn_view_gray_data = ScreenButton("btn_view_gray", "Gray Data",
                                               21, button_2_width)
        self.btn_view_gray_data.set_colors(button_text_color,
                                           button_back_color)
        self.btn_view_gray_data.position = (button_2_right,
                                            self.btn_zoom_zero.get_bottom() +
                                            10)
        self.btn_view_gray_data.click_callback = self.btn_view_gray_data_click
        self.container_view_buttons.append(self.btn_view_gray_data)

        self.btn_view_raw_clear = ScreenButton("btn_view_raw_clear",
                                               "RGB Clear", 21, button_2_width)
        self.btn_view_raw_clear.set_colors(button_text_color,
                                           button_back_color)
        self.btn_view_raw_clear.position = (
            button_2_left, self.btn_view_raw_data.get_bottom() + 10)
        self.btn_view_raw_clear.click_callback = self.btn_view_raw_clear_click
        self.container_view_buttons.append(self.btn_view_raw_clear)

        self.btn_view_gray_clear = ScreenButton("btn_view_gray_clear",
                                                "Gray Clear", 21,
                                                button_2_width)
        self.btn_view_gray_clear.set_colors(button_text_color,
                                            button_back_color)
        self.btn_view_gray_clear.position = (
            button_2_right, self.btn_view_raw_data.get_bottom() + 10)
        self.btn_view_gray_clear.click_callback = self.btn_view_gray_clear_click
        self.container_view_buttons.append(self.btn_view_gray_clear)

        # ===========================
        # confirmation panel
        self.container_confirm_buttons = ScreenContainer(
            "container_confirm_buttons", (container_width, 70),
            back_color=self.general_background)
        self.container_confirm_buttons.position = (
            self.width - self.container_confirm_buttons.width - 10,
            self.container_view_buttons.get_bottom() + 20)
        self.elements.append(self.container_confirm_buttons)
        self.container_confirm_buttons.visible = False

        self.lbl_confirm_message = ScreenLabel(
            "lbl_confirm_message", "Confirmation message goes here?", 21, 290,
            1)
        self.lbl_confirm_message.position = (5, 5)
        self.lbl_confirm_message.set_background(self.general_background)
        self.lbl_confirm_message.set_color(self.text_color)
        self.container_confirm_buttons.append(self.lbl_confirm_message)

        self.btn_confirm_cancel = ScreenButton("btn_confirm_cancel", "Cancel",
                                               21, 130)
        self.btn_confirm_cancel.set_colors(button_text_color,
                                           button_back_color)
        self.btn_confirm_cancel.position = (
            10, self.lbl_confirm_message.get_bottom() + 10)
        self.btn_confirm_cancel.click_callback = self.btn_confirm_cancel_click
        self.container_confirm_buttons.append(self.btn_confirm_cancel)

        self.btn_confirm_accept = ScreenButton("btn_confirm_accept", "Accept",
                                               21, 130)
        self.btn_confirm_accept.set_colors(button_text_color,
                                           button_back_color)
        self.btn_confirm_accept.position = (
            self.container_confirm_buttons.width -
            self.btn_confirm_accept.width - 10,
            self.lbl_confirm_message.get_bottom() + 10)
        self.btn_confirm_accept.click_callback = self.btn_confirm_accept_click
        self.container_confirm_buttons.append(self.btn_confirm_accept)

        # ======================================
        # legend options
        darker_background = (55, 45, 100)
        self.container_legend_options = ScreenContainer(
            "container_legend_options", (container_width, 550),
            back_color=darker_background)
        self.container_legend_options.position = (
            self.container_view_buttons.get_left(),
            self.container_view_buttons.get_bottom() + 20)
        self.elements.append(self.container_legend_options)

        self.lbl_legend_title = ScreenLabel("lbl_legend_title",
                                            "Legend Marker Annotation", 21,
                                            290, 1)
        self.lbl_legend_title.position = (5, 5)
        self.lbl_legend_title.set_background(darker_background)
        self.lbl_legend_title.set_color(self.text_color)
        self.container_legend_options.append(self.lbl_legend_title)

        self.lbx_legend_list = ScreenTextlist("lbx_legend_list",
                                              (container_width - 20, 350),
                                              18,
                                              back_color=(255, 255, 255),
                                              option_color=(0, 0, 0),
                                              selected_back=(120, 80, 50),
                                              selected_color=(255, 255, 255))
        self.lbx_legend_list.position = (10,
                                         self.lbl_legend_title.get_bottom() +
                                         10)
        self.lbx_legend_list.selected_value_change_callback = self.lbx_legend_list_changed
        self.container_legend_options.append(self.lbx_legend_list)

        self.btn_legend_edit = ScreenButton("btn_legend_edit", "Edit", 21,
                                            button_2_width)
        self.btn_legend_edit.set_colors(button_text_color, button_back_color)
        self.btn_legend_edit.position = (button_2_left,
                                         self.lbx_legend_list.get_bottom() +
                                         10)
        self.btn_legend_edit.click_callback = self.btn_legend_edit_click
        self.container_legend_options.append(self.btn_legend_edit)

        self.btn_legend_delete = ScreenButton("btn_legend_delete", "Delete",
                                              21, button_2_width)
        self.btn_legend_delete.set_colors(button_text_color, button_back_color)
        self.btn_legend_delete.position = (button_2_right,
                                           self.lbx_legend_list.get_bottom() +
                                           10)
        self.btn_legend_delete.click_callback = self.btn_legend_delete_click
        self.container_legend_options.append(self.btn_legend_delete)

        self.btn_return_accept = ScreenButton("btn_return_accept", "Accept",
                                              21, button_2_width)
        return_top = self.container_legend_options.height - self.btn_return_accept.height - 10
        self.btn_return_accept.set_colors(button_text_color, button_back_color)
        self.btn_return_accept.position = (button_2_left, return_top)
        self.btn_return_accept.click_callback = self.btn_return_accept_click
        self.container_legend_options.append(self.btn_return_accept)

        self.btn_return_cancel = ScreenButton("btn_return_cancel", "Cancel",
                                              21, button_2_width)
        self.btn_return_cancel.set_colors(button_text_color, button_back_color)
        self.btn_return_cancel.position = (button_2_right, return_top)
        self.btn_return_cancel.click_callback = self.btn_return_cancel_click
        self.container_legend_options.append(self.btn_return_cancel)

        # ======================================
        # visuals
        # ===========================
        # Image

        image_width = self.width - self.container_view_buttons.width - 30
        image_height = self.height - container_top - 10
        self.container_images = ScreenContainer("container_images",
                                                (image_width, image_height),
                                                back_color=(0, 0, 0))
        self.container_images.position = (10, container_top)
        self.elements.append(self.container_images)

        # ... image objects ...
        tempo_blank = np.zeros((50, 50, 3), np.uint8)
        tempo_blank[:, :, :] = 255
        self.img_main = ScreenImage("img_main", tempo_blank, 0, 0, True,
                                    cv2.INTER_NEAREST)
        self.img_main.position = (0, 0)
        self.img_main.mouse_button_down_callback = self.img_mouse_down
        self.container_images.append(self.img_main)

        self.canvas_select = ScreenCanvas("canvas_select", 100, 100)
        self.canvas_select.position = (0, 0)
        self.canvas_select.locked = True
        # self.canvas_select.object_edited_callback = self.canvas_object_edited
        # self.canvas_select.object_selected_callback = self.canvas_selection_changed
        self.container_images.append(self.canvas_select)

        base_points = np.array([[10, 10], [20, 10], [20, 20], [10, 20]],
                               dtype=np.float64)
        self.canvas_select.add_polygon_element("selection_polygon",
                                               base_points)
        self.canvas_select.elements["selection_polygon"].visible = False

        self.canvas_display = ScreenCanvas("canvas_display", 100, 100)
        self.canvas_display.position = (0, 0)
        self.canvas_display.locked = True
        self.container_images.append(self.canvas_display)

        self.add_text_regions()

        self.set_editor_mode(ChartLegendAnnotator.ModeNavigate)

    def get_color_display_info(self, text_id):
        main_color = self.canvas_display.colors[text_id % len(
            self.canvas_display.colors)]
        sel_color = self.canvas_display.sel_colors[text_id % len(
            self.canvas_display.sel_colors)]

        return main_color, sel_color

    def get_text_display_info(self, text):
        return "{0:d} - {1:s}".format(text.id, text.value)

    def add_text_regions(self):
        # populate the list-box using existing text regions (if any)
        for text in self.legend.text_labels:
            text_desc = self.get_text_display_info(text)
            main_color, sel_color = self.get_color_display_info(text.id)

            self.lbx_legend_list.add_option(str(text.id), text_desc)

            self.canvas_display.add_polygon_element(
                str(text.id), text.position_polygon.copy(), main_color,
                sel_color)

            if self.legend.marker_per_label[text.id] is not None:
                marker_pos = self.legend.marker_per_label[text.id].copy()
                self.canvas_display.add_polygon_element(
                    str(text.id) + "-mark", marker_pos, main_color, sel_color)

    def btn_zoom_reduce_click(self, button):
        self.update_view_scale(self.view_scale - 0.25)

    def btn_zoom_increase_click(self, button):
        self.update_view_scale(self.view_scale + 0.25)

    def btn_zoom_zero_click(self, button):
        self.update_view_scale(1.0)

    def btn_view_raw_data_click(self, button):
        self.view_mode = ChartLegendAnnotator.ViewModeRawData
        self.update_current_view()

    def btn_view_gray_data_click(self, button):
        self.view_mode = ChartLegendAnnotator.ViewModeGrayData
        self.update_current_view()

    def btn_view_raw_clear_click(self, button):
        self.view_mode = ChartLegendAnnotator.ViewModeRawNoData
        self.update_current_view()

    def btn_view_gray_clear_click(self, button):
        self.view_mode = ChartLegendAnnotator.ViewModeGrayNoData
        self.update_current_view()

    def update_current_view(self, resized=False):
        if self.view_mode in [
                ChartLegendAnnotator.ViewModeGrayData,
                ChartLegendAnnotator.ViewModeGrayNoData
        ]:
            # gray scale mode
            base_image = self.panel_gray
        else:
            base_image = self.panel_image

        h, w, c = base_image.shape

        modified_image = base_image.copy()

        if self.view_mode in [
                ChartLegendAnnotator.ViewModeRawData,
                ChartLegendAnnotator.ViewModeGrayData
        ]:
            # TODO: show here any relevant annotations on the modified image ...
            # (for example, draw the polygons)
            self.canvas_display.visible = True
        else:
            self.canvas_display.visible = False

        # finally, resize ...
        modified_image = cv2.resize(
            modified_image,
            (int(w * self.view_scale), int(h * self.view_scale)),
            interpolation=cv2.INTER_NEAREST)

        # update canvas size ....
        self.canvas_select.height, self.canvas_select.width, _ = modified_image.shape
        self.canvas_display.height, self.canvas_display.width, _ = modified_image.shape

        # replace/update image
        self.img_main.set_image(modified_image, 0, 0, True, cv2.INTER_NEAREST)
        if resized:
            self.container_images.recalculate_size()

    def update_view_scale(self, new_scale):
        prev_scale = self.view_scale

        if 0.25 <= new_scale <= 4.0:
            self.view_scale = new_scale
        else:
            return

        # keep previous offsets ...
        scroll_offset_y = self.container_images.v_scroll.value if self.container_images.v_scroll.active else 0
        scroll_offset_x = self.container_images.h_scroll.value if self.container_images.h_scroll.active else 0

        prev_center_y = scroll_offset_y + self.container_images.height / 2
        prev_center_x = scroll_offset_x + self.container_images.width / 2

        # compute new scroll bar offsets
        scale_factor = (new_scale / prev_scale)
        new_off_y = prev_center_y * scale_factor - self.container_images.height / 2
        new_off_x = prev_center_x * scale_factor - self.container_images.width / 2

        # update view ....
        self.update_current_view(True)

        # set offsets
        if self.container_images.v_scroll.active and 0 <= new_off_y <= self.container_images.v_scroll.max:
            self.container_images.v_scroll.value = new_off_y
        if self.container_images.h_scroll.active and 0 <= new_off_x <= self.container_images.h_scroll.max:
            self.container_images.h_scroll.value = new_off_x

        # re-scale objects from both canvas
        # ... selection ...
        selection_polygon = self.canvas_select.elements["selection_polygon"]
        selection_polygon.points *= scale_factor
        # ... display ...
        for polygon_name in self.canvas_display.elements:
            display_polygon = self.canvas_display.elements[polygon_name]
            display_polygon.points *= scale_factor

        # update scale text ...
        self.lbl_zoom.set_text("Zoom: " +
                               str(int(round(self.view_scale * 100, 0))) + "%")

    def set_editor_mode(self, new_mode):
        self.edition_mode = new_mode

        # Navigation mode ...
        self.container_legend_options.visible = (
            self.edition_mode == ChartLegendAnnotator.ModeNavigate)

        # Confirm panel and buttons  ...
        self.container_confirm_buttons.visible = self.edition_mode in [
            ChartLegendAnnotator.ModeRectangleSelect,
            ChartLegendAnnotator.ModeRectangleEdit,
            ChartLegendAnnotator.ModeConfirmExit
        ]

        if self.edition_mode == ChartLegendAnnotator.ModeRectangleSelect:
            self.lbl_confirm_message.set_text("Select Legend Mark Location")
        elif self.edition_mode == ChartLegendAnnotator.ModeRectangleEdit:
            self.lbl_confirm_message.set_text("Editing Legend Mark Location")
        elif self.edition_mode == ChartLegendAnnotator.ModeConfirmExit:
            self.lbl_confirm_message.set_text("Discard Changes to Legend?")

        # Do not show accept at these steps (they can be implicitly accepted, but need explicit cancel button only)
        self.btn_confirm_accept.visible = self.edition_mode != ChartLegendAnnotator.ModeRectangleSelect

        if new_mode in [ChartLegendAnnotator.ModeRectangleEdit]:
            # show polygon
            self.canvas_select.locked = False
            self.canvas_select.elements["selection_polygon"].visible = True
        else:
            # for every other mode
            self.canvas_select.locked = True
            self.canvas_select.elements["selection_polygon"].visible = False

    def btn_confirm_cancel_click(self, button):
        if self.edition_mode in [
                ChartLegendAnnotator.ModeRectangleEdit,
                ChartLegendAnnotator.ModeRectangleSelect,
                ChartLegendAnnotator.ModeConfirmExit
        ]:

            if self.edition_mode == ChartLegendAnnotator.ModeRectangleEdit:
                polygon_name = str(self.tempo_text_id) + "-mark"

                if polygon_name in self.canvas_display.elements:
                    self.canvas_display.elements[polygon_name].visible = True

            # return to navigation
            self.set_editor_mode(ChartLegendAnnotator.ModeNavigate)
        else:
            print(self.edition_mode)
            raise Exception("Not Implemented")

    def btn_confirm_accept_click(self, button):
        if self.edition_mode == ChartLegendAnnotator.ModeConfirmExit:
            print("-> Changes made to Legend Annotations were lost")
            self.return_screen = self.parent_screen
        elif self.edition_mode == ChartLegendAnnotator.ModeRectangleEdit:
            # get polygon from GUI
            raw_polygon = self.canvas_select.elements[
                "selection_polygon"].points.copy()
            mark_polygon = raw_polygon / self.view_scale
            # update on GUI ...
            polygon_name = str(self.tempo_text_id) + "-mark"
            if self.legend.marker_per_label[self.tempo_text_id] is None:
                # add to the display canvas ...
                main_color, sel_color = self.get_color_display_info(
                    self.tempo_text_id)
                self.canvas_display.add_polygon_element(
                    polygon_name, raw_polygon, main_color, sel_color)
            else:
                # update the display canvas ...
                self.canvas_display.update_polygon_element(
                    polygon_name, raw_polygon, True)

            # update on DATA
            self.legend.marker_per_label[
                self.tempo_text_id] = mark_polygon.copy()
            self.data_changed = True

            # return ...
            self.set_editor_mode(ChartLegendAnnotator.ModeNavigate)
        else:
            raise Exception("Not Implemented")

    def get_next_axis_aligned_box(self, click_x, click_y):

        last_known_polygon = None
        for text_id in self.legend.marker_per_label:
            if self.legend.marker_per_label[text_id] is not None:
                last_known_polygon = self.legend.marker_per_label[text_id]
                break

        if last_known_polygon is None:
            # default small rectangle
            rect_w, rect_h = 40, 20
        else:
            # axis aligned container rectangle from last bbox
            min_x = last_known_polygon[:, 0].min()
            max_x = last_known_polygon[:, 0].max()
            min_y = last_known_polygon[:, 1].min()
            max_y = last_known_polygon[:, 1].max()

            rect_w = (max_x - min_x) * self.view_scale
            rect_h = (max_y - min_y) * self.view_scale

        points = np.array([[click_x, click_y], [click_x + rect_w, click_y],
                           [click_x + rect_w, click_y + rect_h],
                           [click_x, click_y + rect_h]])

        return points

    def img_mouse_down(self, img_object, pos, button):
        if button == 1:
            if self.edition_mode == ChartLegendAnnotator.ModeRectangleSelect:
                click_x, click_y = pos
                points = self.get_next_axis_aligned_box(click_x, click_y)
                self.canvas_select.elements["selection_polygon"].update(points)

                self.set_editor_mode(ChartLegendAnnotator.ModeRectangleEdit)

    def lbx_legend_list_changed(self, new_value, old_value):
        self.canvas_display.change_selected_element(new_value)

    def btn_legend_edit_click(self, button):
        if self.lbx_legend_list.selected_option_value is None:
            print("Must select a legend label from the list")
            return

        # check if new or edit
        self.tempo_text_id = int(self.lbx_legend_list.selected_option_value)
        if self.legend.marker_per_label[self.tempo_text_id] is None:
            # new ...
            self.set_editor_mode(ChartLegendAnnotator.ModeRectangleSelect)
        else:
            # edit existing ...
            # ... copy points to selection canvas ...
            polygon = self.legend.marker_per_label[
                self.tempo_text_id].copy() * self.view_scale
            self.canvas_select.update_polygon_element("selection_polygon",
                                                      polygon, True)

            self.canvas_display.elements[str(self.tempo_text_id) +
                                         "-mark"].visible = False

            self.set_editor_mode(ChartLegendAnnotator.ModeRectangleEdit)

    def btn_legend_delete_click(self, button):
        if self.lbx_legend_list.selected_option_value is None:
            print("Must select a legend label from the list")
            return

        self.tempo_text_id = int(self.lbx_legend_list.selected_option_value)

        # delete from GUI ...
        if self.legend.marker_per_label[self.tempo_text_id] is not None:
            polygon_name = str(self.tempo_text_id) + "-mark"
            self.canvas_display.remove_element(polygon_name)

        # delete from Data ...
        self.legend.marker_per_label[self.tempo_text_id] = None

        self.data_changed = True

    def btn_return_accept_click(self, button):
        if self.data_changed:
            # overwrite existing legend data ...
            self.panel_info.legend = LegendInfo.Copy(self.legend)
            self.parent_screen.subtool_completed(True)

        # return
        self.return_screen = self.parent_screen

    def btn_return_cancel_click(self, button):
        if self.data_changed:
            self.set_editor_mode(ChartLegendAnnotator.ModeConfirmExit)
        else:
            # simply return
            self.return_screen = self.parent_screen
class LineChartAnnotator(Screen):
    ModeNavigate = 0
    ModeNumberEdit = 1
    ModeLineSelect = 2
    ModeLineEdit = 3
    ModePointAdd = 4
    ModePointEdit = 5
    ModeConfirmNumberOverwrite = 6
    ModeConfirmExit = 7

    ViewModeRawData = 0
    ViewModeGrayData = 1
    ViewModeRawNoData = 2
    ViewModeGrayNoData = 3

    DoubleClickMaxPointDistance = 5

    def __init__(self, size, panel_image, panel_info, parent_screen):
        Screen.__init__(self, "Line Chart Ground Truth Annotation Interface",
                        size)

        self.panel_image = panel_image
        self.panel_gray = np.zeros(self.panel_image.shape,
                                   self.panel_image.dtype)
        self.panel_gray[:, :, 0] = cv2.cvtColor(self.panel_image,
                                                cv2.COLOR_RGB2GRAY)
        self.panel_gray[:, :, 1] = self.panel_gray[:, :, 0].copy()
        self.panel_gray[:, :, 2] = self.panel_gray[:, :, 0].copy()

        self.panel_info = panel_info

        if self.panel_info.data is None:
            # create default Line chart data ...
            self.data = LineData.CreateDefault(self.panel_info)
            self.data_changed = True
        else:
            # make a copy ...
            self.data = LineData.Copy(self.panel_info.data)
            self.data_changed = False

        self.parent_screen = parent_screen

        self.general_background = (150, 190, 20)
        self.text_color = (255, 255, 255)

        self.elements.back_color = self.general_background
        self.edition_mode = None

        self.tempo_line_index = None
        self.tempo_point_index = None
        self.tempo_line_values = None

        self.view_mode = LineChartAnnotator.ViewModeRawData
        self.view_scale = 1.0

        self.label_title = None

        self.container_view_buttons = None
        self.lbl_zoom = None
        self.btn_zoom_reduce = None
        self.btn_zoom_increase = None
        self.btn_zoom_zero = None

        self.btn_view_raw_data = None
        self.btn_view_gray_data = None
        self.btn_view_raw_clear = None
        self.btn_view_gray_clear = None

        self.container_confirm_buttons = None
        self.lbl_confirm_message = None
        self.btn_confirm_cancel = None
        self.btn_confirm_accept = None

        self.container_annotation_buttons = None
        self.lbl_edit_title = None
        self.btn_edit_number = None
        self.btn_edit_data = None
        self.btn_return_accept = None
        self.btn_return_cancel = None

        self.container_number_buttons = None
        self.lbl_number_title = None
        self.lbl_number_series_title = None
        self.lbx_number_series_values = None
        self.btn_number_series_add = None
        self.btn_number_series_remove = None
        self.btn_number_return = None

        self.container_data_buttons = None
        self.lbl_data_title = None
        self.lbx_data_series_values = None
        self.btn_data_series_edit = None
        self.btn_data_return = None

        self.container_line_buttons = None
        self.lbl_line_title = None
        self.lbl_line_name = None
        self.lbx_line_points = None
        self.btn_line_point_edit = None
        self.btn_line_point_delete = None
        self.btn_line_point_add = None
        self.btn_line_return_accept = None
        self.btn_line_return_cancel = None

        self.container_images = None
        self.canvas_display = None
        self.img_main = None

        self.create_controllers()

        # get the view ...
        self.update_current_view(True)

    def create_controllers(self):
        # add elements....
        button_text_color = (35, 50, 20)
        button_back_color = (228, 228, 228)

        # Main Title
        self.label_title = ScreenLabel(
            "title",
            "Chart Image Annotation Tool - Line Chart Data Annotation", 28)
        self.label_title.background = self.general_background
        self.label_title.position = (int(
            (self.width - self.label_title.width) / 2), 20)
        self.label_title.set_color(self.text_color)
        self.elements.append(self.label_title)

        container_top = 10 + self.label_title.get_bottom()
        container_width = 300

        button_width = 190
        button_left = (container_width - button_width) / 2

        button_2_width = 130
        button_2_left = int(container_width * 0.25) - button_2_width / 2
        button_2_right = int(container_width * 0.75) - button_2_width / 2

        # ===========================
        # View Options Panel

        # View panel with view control buttons
        self.container_view_buttons = ScreenContainer(
            "container_view_buttons", (container_width, 160),
            back_color=self.general_background)
        self.container_view_buttons.position = (
            self.width - self.container_view_buttons.width - 10, container_top)
        self.elements.append(self.container_view_buttons)

        # zoom ....
        self.lbl_zoom = ScreenLabel("lbl_zoom", "Zoom: 100%", 21, 290, 1)
        self.lbl_zoom.position = (5, 5)
        self.lbl_zoom.set_background(self.general_background)
        self.lbl_zoom.set_color(self.text_color)
        self.container_view_buttons.append(self.lbl_zoom)

        self.btn_zoom_reduce = ScreenButton("btn_zoom_reduce", "[ - ]", 21, 90)
        self.btn_zoom_reduce.set_colors(button_text_color, button_back_color)
        self.btn_zoom_reduce.position = (10, self.lbl_zoom.get_bottom() + 10)
        self.btn_zoom_reduce.click_callback = self.btn_zoom_reduce_click
        self.container_view_buttons.append(self.btn_zoom_reduce)

        self.btn_zoom_increase = ScreenButton("btn_zoom_increase", "[ + ]", 21,
                                              90)
        self.btn_zoom_increase.set_colors(button_text_color, button_back_color)
        self.btn_zoom_increase.position = (self.container_view_buttons.width -
                                           self.btn_zoom_increase.width - 10,
                                           self.lbl_zoom.get_bottom() + 10)
        self.btn_zoom_increase.click_callback = self.btn_zoom_increase_click
        self.container_view_buttons.append(self.btn_zoom_increase)

        self.btn_zoom_zero = ScreenButton("btn_zoom_zero", "100%", 21, 90)
        self.btn_zoom_zero.set_colors(button_text_color, button_back_color)
        self.btn_zoom_zero.position = (
            (self.container_view_buttons.width - self.btn_zoom_zero.width) / 2,
            self.lbl_zoom.get_bottom() + 10)
        self.btn_zoom_zero.click_callback = self.btn_zoom_zero_click
        self.container_view_buttons.append(self.btn_zoom_zero)

        self.btn_view_raw_data = ScreenButton("btn_view_raw_data", "RGB Data",
                                              21, button_2_width)
        self.btn_view_raw_data.set_colors(button_text_color, button_back_color)
        self.btn_view_raw_data.position = (button_2_left,
                                           self.btn_zoom_zero.get_bottom() +
                                           10)
        self.btn_view_raw_data.click_callback = self.btn_view_raw_data_click
        self.container_view_buttons.append(self.btn_view_raw_data)

        self.btn_view_gray_data = ScreenButton("btn_view_gray", "Gray Data",
                                               21, button_2_width)
        self.btn_view_gray_data.set_colors(button_text_color,
                                           button_back_color)
        self.btn_view_gray_data.position = (button_2_right,
                                            self.btn_zoom_zero.get_bottom() +
                                            10)
        self.btn_view_gray_data.click_callback = self.btn_view_gray_data_click
        self.container_view_buttons.append(self.btn_view_gray_data)

        self.btn_view_raw_clear = ScreenButton("btn_view_raw_clear",
                                               "RGB Clear", 21, button_2_width)
        self.btn_view_raw_clear.set_colors(button_text_color,
                                           button_back_color)
        self.btn_view_raw_clear.position = (
            button_2_left, self.btn_view_raw_data.get_bottom() + 10)
        self.btn_view_raw_clear.click_callback = self.btn_view_raw_clear_click
        self.container_view_buttons.append(self.btn_view_raw_clear)

        self.btn_view_gray_clear = ScreenButton("btn_view_gray_clear",
                                                "Gray Clear", 21,
                                                button_2_width)
        self.btn_view_gray_clear.set_colors(button_text_color,
                                            button_back_color)
        self.btn_view_gray_clear.position = (
            button_2_right, self.btn_view_raw_data.get_bottom() + 10)
        self.btn_view_gray_clear.click_callback = self.btn_view_gray_clear_click
        self.container_view_buttons.append(self.btn_view_gray_clear)

        # ===========================
        # confirmation panel
        self.container_confirm_buttons = ScreenContainer(
            "container_confirm_buttons", (container_width, 70),
            back_color=self.general_background)
        self.container_confirm_buttons.position = (
            self.width - self.container_confirm_buttons.width - 10,
            self.container_view_buttons.get_bottom() + 20)
        self.elements.append(self.container_confirm_buttons)
        self.container_confirm_buttons.visible = False

        self.lbl_confirm_message = ScreenLabel(
            "lbl_confirm_message", "Confirmation message goes here?", 21, 290,
            1)
        self.lbl_confirm_message.position = (5, 5)
        self.lbl_confirm_message.set_background(self.general_background)
        self.lbl_confirm_message.set_color(self.text_color)
        self.container_confirm_buttons.append(self.lbl_confirm_message)

        self.btn_confirm_cancel = ScreenButton("btn_confirm_cancel", "Cancel",
                                               21, 130)
        self.btn_confirm_cancel.set_colors(button_text_color,
                                           button_back_color)
        self.btn_confirm_cancel.position = (
            10, self.lbl_confirm_message.get_bottom() + 10)
        self.btn_confirm_cancel.click_callback = self.btn_confirm_cancel_click
        self.container_confirm_buttons.append(self.btn_confirm_cancel)

        self.btn_confirm_accept = ScreenButton("btn_confirm_accept", "Accept",
                                               21, 130)
        self.btn_confirm_accept.set_colors(button_text_color,
                                           button_back_color)
        self.btn_confirm_accept.position = (
            self.container_confirm_buttons.width -
            self.btn_confirm_accept.width - 10,
            self.lbl_confirm_message.get_bottom() + 10)
        self.btn_confirm_accept.click_callback = self.btn_confirm_accept_click
        self.container_confirm_buttons.append(self.btn_confirm_accept)

        # ==============================
        # main annotation options
        darker_background = (100, 130, 15)

        self.container_annotation_buttons = ScreenContainer(
            "container_annotation_buttons", (container_width, 180),
            back_color=darker_background)
        self.container_annotation_buttons.position = (
            self.container_view_buttons.get_left(),
            self.container_view_buttons.get_bottom() + 20)
        self.elements.append(self.container_annotation_buttons)

        self.lbl_edit_title = ScreenLabel("lbl_edit_title",
                                          "Line Chart Annotation Options", 21,
                                          290, 1)
        self.lbl_edit_title.position = (5, 5)
        self.lbl_edit_title.set_background(darker_background)
        self.lbl_edit_title.set_color(self.text_color)
        self.container_annotation_buttons.append(self.lbl_edit_title)

        self.btn_edit_number = ScreenButton("btn_edit_number",
                                            "Edit Number of Lines", 21,
                                            button_width)
        self.btn_edit_number.set_colors(button_text_color, button_back_color)
        self.btn_edit_number.position = (button_left,
                                         self.lbl_edit_title.get_bottom() + 10)
        self.btn_edit_number.click_callback = self.btn_edit_number_click
        self.container_annotation_buttons.append(self.btn_edit_number)

        self.btn_edit_data = ScreenButton("btn_edit_data", "Edit Line Data",
                                          21, button_width)
        self.btn_edit_data.set_colors(button_text_color, button_back_color)
        self.btn_edit_data.position = (button_left,
                                       self.btn_edit_number.get_bottom() + 10)
        self.btn_edit_data.click_callback = self.btn_edit_data_click
        self.container_annotation_buttons.append(self.btn_edit_data)

        self.btn_return_accept = ScreenButton("btn_return_accept", "Accept",
                                              21, button_2_width)
        return_top = self.container_annotation_buttons.height - self.btn_return_accept.height - 10
        self.btn_return_accept.set_colors(button_text_color, button_back_color)
        self.btn_return_accept.position = (button_2_left, return_top)
        self.btn_return_accept.click_callback = self.btn_return_accept_click
        self.container_annotation_buttons.append(self.btn_return_accept)

        self.btn_return_cancel = ScreenButton("btn_return_cancel", "Cancel",
                                              21, button_2_width)
        self.btn_return_cancel.set_colors(button_text_color, button_back_color)
        self.btn_return_cancel.position = (button_2_right, return_top)
        self.btn_return_cancel.click_callback = self.btn_return_cancel_click
        self.container_annotation_buttons.append(self.btn_return_cancel)

        # ==================================
        # - options to define the total number of lines in the chart ....
        self.container_number_buttons = ScreenContainer(
            "container_number_buttons", (container_width, 530),
            back_color=darker_background)
        self.container_number_buttons.position = (
            self.container_view_buttons.get_left(),
            self.container_view_buttons.get_bottom() + 15)
        self.elements.append(self.container_number_buttons)

        self.lbl_number_title = ScreenLabel("lbl_number_title ",
                                            "Lines in Chart: [0]", 25, 290, 1)
        self.lbl_number_title.position = (5, 5)
        self.lbl_number_title.set_background(darker_background)
        self.lbl_number_title.set_color(self.text_color)
        self.container_number_buttons.append(self.lbl_number_title)

        self.lbl_number_series_title = ScreenLabel("lbl_number_series_title",
                                                   "Data Series", 21, 290, 1)
        self.lbl_number_series_title.position = (
            5, self.lbl_number_title.get_bottom() + 10)
        self.lbl_number_series_title.set_background(darker_background)
        self.lbl_number_series_title.set_color(self.text_color)
        self.container_number_buttons.append(self.lbl_number_series_title)

        self.lbx_number_series_values = ScreenTextlist(
            "lbx_number_series_values", (container_width - 20, 290),
            18,
            back_color=(255, 255, 255),
            option_color=(0, 0, 0),
            selected_back=(120, 80, 50),
            selected_color=(255, 255, 255))
        self.lbx_number_series_values.position = (
            10, self.lbl_number_series_title.get_bottom() + 10)
        # self.lbx_number_categories_values.selected_value_change_callback = self.lbx_number_categories_values_changed
        self.container_number_buttons.append(self.lbx_number_series_values)

        self.btn_number_series_add = ScreenButton("btn_number_series_add",
                                                  "Add", 21, button_2_width)
        self.btn_number_series_add.set_colors(button_text_color,
                                              button_back_color)
        self.btn_number_series_add.position = (
            button_2_left, self.lbx_number_series_values.get_bottom() + 10)
        self.btn_number_series_add.click_callback = self.btn_number_series_add_click
        self.container_number_buttons.append(self.btn_number_series_add)

        self.btn_number_series_remove = ScreenButton(
            "btn_number_series_remove", "Remove", 21, button_2_width)
        self.btn_number_series_remove.set_colors(button_text_color,
                                                 button_back_color)
        self.btn_number_series_remove.position = (
            button_2_right, self.lbx_number_series_values.get_bottom() + 10)
        self.btn_number_series_remove.click_callback = self.btn_number_series_remove_click
        self.container_number_buttons.append(self.btn_number_series_remove)

        self.btn_number_return = ScreenButton("btn_number_return", "Return",
                                              21, button_width)
        self.btn_number_return.set_colors(button_text_color, button_back_color)
        self.btn_number_return.position = (
            button_left, self.btn_number_series_add.get_bottom() + 15)
        self.btn_number_return.click_callback = self.btn_number_return_click
        self.container_number_buttons.append(self.btn_number_return)
        self.container_number_buttons.visible = False

        # ==============================
        # data annotation options
        self.container_data_buttons = ScreenContainer(
            "container_data_buttons", (container_width, 380),
            back_color=darker_background)
        self.container_data_buttons.position = (
            self.container_view_buttons.get_left(),
            self.container_view_buttons.get_bottom() + 20)
        self.elements.append(self.container_data_buttons)

        self.lbl_data_title = ScreenLabel("lbl_data_title ", "Lines in Chart",
                                          25, 290, 1)
        self.lbl_data_title.position = (5, 5)
        self.lbl_data_title.set_background(darker_background)
        self.lbl_data_title.set_color(self.text_color)
        self.container_data_buttons.append(self.lbl_data_title)

        self.lbx_data_series_values = ScreenTextlist(
            "lbx_data_series_values", (container_width - 20, 210),
            18,
            back_color=(255, 255, 255),
            option_color=(0, 0, 0),
            selected_back=(120, 80, 50),
            selected_color=(255, 255, 255))
        self.lbx_data_series_values.position = (
            10, self.lbl_data_title.get_bottom() + 20)
        self.container_data_buttons.append(self.lbx_data_series_values)

        self.btn_data_series_edit = ScreenButton("btn_data_series_edit",
                                                 "Edit Points", 21,
                                                 button_width)
        self.btn_data_series_edit.set_colors(button_text_color,
                                             button_back_color)
        self.btn_data_series_edit.position = (
            button_left, self.lbx_data_series_values.get_bottom() + 10)
        self.btn_data_series_edit.click_callback = self.btn_data_series_edit_click
        self.container_data_buttons.append(self.btn_data_series_edit)

        self.btn_data_return = ScreenButton("btn_data_return", "Return", 21,
                                            button_width)
        self.btn_data_return.set_colors(button_text_color, button_back_color)
        self.btn_data_return.position = (
            button_left, self.btn_data_series_edit.get_bottom() + 20)
        self.btn_data_return.click_callback = self.btn_data_return_click
        self.container_data_buttons.append(self.btn_data_return)

        self.container_data_buttons.visible = False

        # ==============================
        # line annotation options
        self.container_line_buttons = ScreenContainer(
            "container_line_buttons", (container_width, 450),
            back_color=darker_background)
        self.container_line_buttons.position = (
            self.container_view_buttons.get_left(),
            self.container_view_buttons.get_bottom() + 20)
        self.elements.append(self.container_line_buttons)

        self.lbl_line_title = ScreenLabel("lbl_line_title", "Points in Line",
                                          25, 290, 1)
        self.lbl_line_title.position = (5, 5)
        self.lbl_line_title.set_background(darker_background)
        self.lbl_line_title.set_color(self.text_color)
        self.container_line_buttons.append(self.lbl_line_title)

        self.lbl_line_name = ScreenLabel("lbl_line_name", "[Data series]", 18,
                                         290, 1)
        self.lbl_line_name.position = (5,
                                       self.lbl_line_title.get_bottom() + 20)
        self.lbl_line_name.set_background(darker_background)
        self.lbl_line_name.set_color(self.text_color)
        self.container_line_buttons.append(self.lbl_line_name)

        self.lbx_line_points = ScreenTextlist("lbx_line_points",
                                              (container_width - 20, 210),
                                              18,
                                              back_color=(255, 255, 255),
                                              option_color=(0, 0, 0),
                                              selected_back=(120, 80, 50),
                                              selected_color=(255, 255, 255))
        self.lbx_line_points.position = (10,
                                         self.lbl_line_name.get_bottom() + 30)
        self.lbx_line_points.selected_value_change_callback = self.lbx_line_points_value_changed
        self.container_line_buttons.append(self.lbx_line_points)

        self.btn_line_point_edit = ScreenButton("btn_line_point_edit",
                                                "Edit Point", 21,
                                                button_2_width)
        self.btn_line_point_edit.set_colors(button_text_color,
                                            button_back_color)
        self.btn_line_point_edit.position = (
            button_2_left, self.lbx_line_points.get_bottom() + 10)
        self.btn_line_point_edit.click_callback = self.btn_line_point_edit_click
        self.container_line_buttons.append(self.btn_line_point_edit)

        self.btn_line_point_delete = ScreenButton("btn_line_point_delete",
                                                  "Remove Point", 21,
                                                  button_2_width)
        self.btn_line_point_delete.set_colors(button_text_color,
                                              button_back_color)
        self.btn_line_point_delete.position = (
            button_2_right, self.lbx_line_points.get_bottom() + 10)
        self.btn_line_point_delete.click_callback = self.btn_line_point_delete_click
        self.container_line_buttons.append(self.btn_line_point_delete)

        self.btn_line_point_add = ScreenButton("btn_line_point_add",
                                               "Add Points", 21, button_width)
        self.btn_line_point_add.set_colors(button_text_color,
                                           button_back_color)
        self.btn_line_point_add.position = (
            button_left, self.btn_line_point_edit.get_bottom() + 10)
        self.btn_line_point_add.click_callback = self.btn_line_point_add_click
        self.container_line_buttons.append(self.btn_line_point_add)

        self.btn_line_return_accept = ScreenButton("btn_line_return_accept",
                                                   "Accept", 21,
                                                   button_2_width)
        self.btn_line_return_accept.set_colors(button_text_color,
                                               button_back_color)
        self.btn_line_return_accept.position = (
            button_2_left, self.btn_line_point_add.get_bottom() + 20)
        self.btn_line_return_accept.click_callback = self.btn_line_return_accept_click
        self.container_line_buttons.append(self.btn_line_return_accept)

        self.btn_line_return_cancel = ScreenButton("btn_line_return_cancel",
                                                   "Cancel", 21,
                                                   button_2_width)
        self.btn_line_return_cancel.set_colors(button_text_color,
                                               button_back_color)
        self.btn_line_return_cancel.position = (
            button_2_right, self.btn_line_point_add.get_bottom() + 20)
        self.btn_line_return_cancel.click_callback = self.btn_line_return_cancel_click
        self.container_line_buttons.append(self.btn_line_return_cancel)

        self.container_line_buttons.visible = False

        # ======================================
        # visuals
        # ===========================
        # Image

        image_width = self.width - self.container_view_buttons.width - 30
        image_height = self.height - container_top - 10
        self.container_images = ScreenContainer("container_images",
                                                (image_width, image_height),
                                                back_color=(0, 0, 0))
        self.container_images.position = (10, container_top)
        self.elements.append(self.container_images)

        # ... image objects ...
        tempo_blank = np.zeros((50, 50, 3), np.uint8)
        tempo_blank[:, :, :] = 255
        self.img_main = ScreenImage("img_main", tempo_blank, 0, 0, True,
                                    cv2.INTER_NEAREST)
        self.img_main.position = (0, 0)
        self.img_main.mouse_button_down_callback = self.img_mouse_down
        self.img_main.double_click_callback = self.img_mouse_double_click
        self.container_images.append(self.img_main)

        self.canvas_display = ScreenCanvas("canvas_display", 100, 100)
        self.canvas_display.position = (0, 0)
        self.canvas_display.locked = True
        self.container_images.append(self.canvas_display)

        self.prepare_number_controls()

        self.set_editor_mode(LineChartAnnotator.ModeNavigate)

    def btn_zoom_reduce_click(self, button):
        self.update_view_scale(self.view_scale - 0.25)

    def btn_zoom_increase_click(self, button):
        self.update_view_scale(self.view_scale + 0.25)

    def btn_zoom_zero_click(self, button):
        self.update_view_scale(1.0)

    def btn_view_raw_data_click(self, button):
        self.view_mode = LineChartAnnotator.ViewModeRawData
        self.update_current_view()

    def btn_view_gray_data_click(self, button):
        self.view_mode = LineChartAnnotator.ViewModeGrayData
        self.update_current_view()

    def btn_view_raw_clear_click(self, button):
        self.view_mode = LineChartAnnotator.ViewModeRawNoData
        self.update_current_view()

    def btn_view_gray_clear_click(self, button):
        self.view_mode = LineChartAnnotator.ViewModeGrayNoData
        self.update_current_view()

    def update_view_scale(self, new_scale):
        prev_scale = self.view_scale

        if 0.25 <= new_scale <= 4.0:
            self.view_scale = new_scale
        else:
            return

        # keep previous offsets ...
        scroll_offset_y = self.container_images.v_scroll.value if self.container_images.v_scroll.active else 0
        scroll_offset_x = self.container_images.h_scroll.value if self.container_images.h_scroll.active else 0

        prev_center_y = scroll_offset_y + self.container_images.height / 2
        prev_center_x = scroll_offset_x + self.container_images.width / 2

        # compute new scroll box offsets
        scale_factor = (new_scale / prev_scale)
        new_off_y = prev_center_y * scale_factor - self.container_images.height / 2
        new_off_x = prev_center_x * scale_factor - self.container_images.width / 2

        # update view ....
        self.update_current_view(True)

        # set offsets
        if self.container_images.v_scroll.active and 0 <= new_off_y <= self.container_images.v_scroll.max:
            self.container_images.v_scroll.value = new_off_y
        if self.container_images.h_scroll.active and 0 <= new_off_x <= self.container_images.h_scroll.max:
            self.container_images.h_scroll.value = new_off_x

        # re-scale objects from both canvas
        # ... display ...
        for polygon_name in self.canvas_display.elements:
            display_polygon = self.canvas_display.elements[polygon_name]
            display_polygon.points *= scale_factor

        # update scale text ...
        self.lbl_zoom.set_text("Zoom: " +
                               str(int(round(self.view_scale * 100, 0))) + "%")

    def update_current_view(self, resized=False):
        if self.view_mode in [
                LineChartAnnotator.ViewModeGrayData,
                LineChartAnnotator.ViewModeGrayNoData
        ]:
            # gray scale mode
            base_image = self.panel_gray
        else:
            base_image = self.panel_image

        h, w, c = base_image.shape

        modified_image = base_image.copy()

        if self.view_mode in [
                LineChartAnnotator.ViewModeRawData,
                LineChartAnnotator.ViewModeGrayData
        ]:
            # (for example, draw the polygons)
            self.canvas_display.visible = True

            x1, y1, x2, y2 = self.panel_info.axes.bounding_box
            x1 = int(x1)
            y1 = int(y1)
            x2 = int(x2)
            y2 = int(y2)

            # axes lines
            cv2.line(modified_image, (x1, y1), (x1, y2), (0, 255, 0),
                     thickness=1)  # y = green
            cv2.line(modified_image, (x1, y2), (x2, y2), (0, 0, 255),
                     thickness=1)  # x = blue
            # close the data area rectangle ?
            # cv2.line(modified_image, (x2, y1), (x2, y2), (0, 128, 0), thickness=1)
            # cv2.line(modified_image, (x1, y1), (x2, y1), (0, 0, 128), thickness=1)

            # check which lines will be drawn ...
            if self.edition_mode in [
                    LineChartAnnotator.ModeLineEdit,
                    LineChartAnnotator.ModePointAdd,
                    LineChartAnnotator.ModePointEdit
            ]:
                # Only draw the line being edited ... based on its temporary changes ...
                lines_to_drawing = [self.tempo_line_values]
            else:
                # draw everything ...
                lines_to_drawing = self.data.lines

            # for each line to drawn ...
            for idx, line_values in enumerate(lines_to_drawing):
                line_color = self.canvas_display.colors[idx % len(
                    self.canvas_display.colors)]

                all_transformed_points = []
                for p_idx in range(len(line_values.points)):
                    # transform current point from relative space to absolute pixel space
                    c_x, c_y = line_values.points[p_idx]
                    c_x += x1
                    c_y = y2 - c_y
                    current_point = (int(round(c_x)), int(round(c_y)))

                    all_transformed_points.append(current_point)

                    # Draw the points as small circles ...
                    if (self.edition_mode == LineChartAnnotator.ModeLineEdit
                            and self.lbx_line_points.selected_option_value
                            is not None
                            and int(self.lbx_line_points.selected_option_value)
                            == p_idx):
                        # empty large circle for selected option
                        cv2.circle(modified_image,
                                   current_point,
                                   5,
                                   line_color,
                                   thickness=2)
                    else:
                        # filled small circle
                        cv2.circle(modified_image,
                                   current_point,
                                   3,
                                   line_color,
                                   thickness=-1)

                # Draw the line ...
                all_transformed_points = np.array(
                    all_transformed_points).astype(np.int32)
                modified_image = cv2.polylines(modified_image,
                                               [all_transformed_points], False,
                                               line_color)

        else:
            self.canvas_display.visible = False

        # finally, resize ...
        modified_image = cv2.resize(
            modified_image,
            (int(w * self.view_scale), int(h * self.view_scale)),
            interpolation=cv2.INTER_NEAREST)

        # update canvas size ....
        self.canvas_display.height, self.canvas_display.width, _ = modified_image.shape

        # replace/update image
        self.img_main.set_image(modified_image, 0, 0, True, cv2.INTER_NEAREST)
        if resized:
            self.container_images.recalculate_size()

    def btn_confirm_cancel_click(self, button):
        if self.edition_mode in [LineChartAnnotator.ModeConfirmExit]:
            # return to navigation
            self.set_editor_mode(LineChartAnnotator.ModeNavigate)

        elif self.edition_mode in [
                LineChartAnnotator.ModePointAdd,
                LineChartAnnotator.ModePointEdit
        ]:
            # return to line edition mode ...
            self.update_points_list()
            self.set_editor_mode(LineChartAnnotator.ModeLineEdit)
        else:
            print(self.edition_mode)
            raise Exception("Not Implemented")

    def btn_confirm_accept_click(self, button):
        if self.edition_mode == LineChartAnnotator.ModeConfirmExit:
            print("-> Changes made to Line Data Annotations were lost")
            self.return_screen = self.parent_screen
        else:
            raise Exception("Not Implemented")

    def btn_edit_number_click(self, button):
        self.set_editor_mode(LineChartAnnotator.ModeNumberEdit)
        self.update_current_view()

    def btn_edit_data_click(self, button):
        self.fill_data_series_list(self.lbx_data_series_values)
        self.set_editor_mode(LineChartAnnotator.ModeLineSelect)
        self.update_current_view()

    def btn_return_accept_click(self, button):
        if self.data_changed:
            # overwrite existing Line data ...
            self.panel_info.data = LineData.Copy(self.data)
            self.parent_screen.subtool_completed(True)

        # return
        self.return_screen = self.parent_screen

    def btn_return_cancel_click(self, button):
        if self.data_changed:
            self.set_editor_mode(LineChartAnnotator.ModeConfirmExit)
        else:
            # simply return
            self.return_screen = self.parent_screen

    def numbers_update_GUI(self, data_series_changed):
        n_lines = self.data.total_lines()
        self.lbl_number_title.set_text("Lines in Chart: {0:d}".format(n_lines))

        if data_series_changed:
            self.fill_data_series_list(self.lbx_number_series_values)

        self.update_current_view()

    def btn_number_series_add_click(self, button):
        # add ... using general default values (based on axis info)
        p1, p2 = self.get_default_line_values()
        self.data.add_data_series(default_points=[p1, p2])

        self.data_changed = True

        # update GUI
        self.numbers_update_GUI(True)

    def btn_number_series_remove_click(self, button):
        if self.lbx_number_series_values.selected_option_value is None:
            print("Must select a data series")
            return

        option_idx = int(self.lbx_number_series_values.selected_option_value)

        # remove ...
        self.data.remove_data_series(option_idx)

        self.data_changed = True

        # update GUI
        self.numbers_update_GUI(True)

    def get_default_line_values(self):
        a_x1, a_y1, a_x2, a_y2 = self.panel_info.axes.bounding_box
        a_x1 = int(a_x1)
        a_y1 = int(a_y1)
        a_x2 = int(a_x2)
        a_y2 = int(a_y2)

        axis_range = a_y2 - a_y1
        axis_domain = a_x2 - a_x1

        line_y = 0.5 * axis_range

        return (0, line_y), (axis_domain, line_y)

    def btn_number_return_click(self, button):
        self.set_editor_mode(LineChartAnnotator.ModeNavigate)
        self.update_current_view()

    def from_pos_to_rel_click(self, pos):
        # click pos ...
        click_x, click_y = pos
        click_x /= self.view_scale
        click_y /= self.view_scale

        x1, y1, x2, y2 = self.panel_info.axes.bounding_box
        x1 = int(x1)
        y2 = int(y2)

        rel_x = click_x - x1
        rel_y = y2 - click_y

        return rel_x, rel_y

    def img_mouse_down(self, img_object, pos, button):
        if button == 1:
            if self.edition_mode in [
                    LineChartAnnotator.ModePointAdd,
                    LineChartAnnotator.ModePointEdit
            ]:
                # click pos ...
                rel_x, rel_y = self.from_pos_to_rel_click(pos)

                if self.edition_mode == LineChartAnnotator.ModePointEdit:
                    # set new position for point being edited ...
                    self.tempo_line_values.set_point(self.tempo_point_index,
                                                     rel_x, rel_y)
                    # go back to previous mode
                    self.update_points_list()
                    self.set_editor_mode(LineChartAnnotator.ModeLineEdit)
                elif self.edition_mode == LineChartAnnotator.ModePointAdd:
                    # Add the new point
                    self.tempo_line_values.add_point(rel_x, rel_y,
                                                     LineValues.InsertByXValue)
                    # .. and stay on current state until cancel is pressed.

                self.update_current_view()

    def prepare_number_controls(self):
        n_lines = self.data.total_lines()
        self.lbl_number_title.set_text("Lines in Chart: {0:d}".format(n_lines))

        self.fill_data_series_list(self.lbx_number_series_values)

    def fill_data_series_list(self, text_list):
        text_list.clear_options()
        for idx, current_text in enumerate(self.data.data_series):
            if current_text is None:
                display_value = str(idx + 1)
            else:
                display_value = "{0:d}: {1:s}".format(idx + 1,
                                                      current_text.value)

            text_list.add_option(str(idx), display_value)

    def set_editor_mode(self, new_mode):
        self.edition_mode = new_mode

        # Navigation mode ...
        self.container_annotation_buttons.visible = (
            self.edition_mode == LineChartAnnotator.ModeNavigate)

        # edit modes ...
        self.container_number_buttons.visible = (
            self.edition_mode == LineChartAnnotator.ModeNumberEdit)
        self.container_data_buttons.visible = (
            self.edition_mode == LineChartAnnotator.ModeLineSelect)
        self.container_line_buttons.visible = (
            self.edition_mode == LineChartAnnotator.ModeLineEdit)

        # Confirm panel and buttons  ...
        self.container_confirm_buttons.visible = self.edition_mode in [
            LineChartAnnotator.ModeConfirmNumberOverwrite,
            LineChartAnnotator.ModePointAdd, LineChartAnnotator.ModePointEdit,
            LineChartAnnotator.ModeConfirmExit
        ]

        if self.edition_mode == LineChartAnnotator.ModeConfirmNumberOverwrite:
            self.lbl_confirm_message.set_text("Discard Existing Line Data?")
        elif self.edition_mode == LineChartAnnotator.ModePointAdd:
            self.lbl_confirm_message.set_text("Click on New Point")
        elif self.edition_mode == LineChartAnnotator.ModePointEdit:
            self.lbl_confirm_message.set_text("Click on New Position")
        elif self.edition_mode == LineChartAnnotator.ModeConfirmExit:
            self.lbl_confirm_message.set_text("Discard Changes to Line Data?")

        # Do not show accept at these steps (they can be implicitly accepted, but need explicit cancel button only)
        self.btn_confirm_accept.visible = self.edition_mode not in [
            LineChartAnnotator.ModePointAdd, LineChartAnnotator.ModePointEdit
        ]

    def btn_data_series_edit_click(self, button):
        if self.lbx_data_series_values.selected_option_value is None:
            print("Must select a data series")
            return

        option_idx = int(self.lbx_data_series_values.selected_option_value)

        # prepare temporals
        self.tempo_line_index = option_idx
        self.tempo_line_values = LineValues.Copy(self.data.lines[option_idx])

        # series name ...
        display = self.lbx_data_series_values.option_display[
            self.lbx_data_series_values.selected_option_value]
        self.lbl_line_name.set_text(display)

        # ... list of points ...
        self.update_points_list()

        self.set_editor_mode(LineChartAnnotator.ModeLineEdit)
        self.update_current_view(False)

    def btn_data_return_click(self, button):
        self.set_editor_mode(LineChartAnnotator.ModeNavigate)

    def update_points_list(self):
        self.lbx_line_points.clear_options()
        for idx, (p_x, p_y) in enumerate(self.tempo_line_values.points):
            display_value = "{0:d}: ({1:.1f}, {2:.1f})".format(
                idx + 1, p_x, p_y)

            self.lbx_line_points.add_option(str(idx), display_value)

    def btn_line_point_edit_click(self, button):
        if self.lbx_line_points.selected_option_value is None:
            print("Must select a data point from list")
            return

        self.tempo_point_index = int(
            self.lbx_line_points.selected_option_value)
        self.set_editor_mode(LineChartAnnotator.ModePointEdit)

    def delete_tempo_line_point(self, del_idx):
        if self.tempo_line_values.remove_point(del_idx):
            # update GUI
            self.update_points_list()
            self.update_current_view()

    def btn_line_point_delete_click(self, button):
        if self.lbx_line_points.selected_option_value is None:
            print("Must select a data point from list")
            return

        # try delete
        del_idx = int(self.lbx_line_points.selected_option_value)
        self.delete_tempo_line_point(del_idx)

    def btn_line_point_add_click(self, button):
        self.set_editor_mode(LineChartAnnotator.ModePointAdd)

    def btn_line_return_accept_click(self, button):
        self.data.lines[self.tempo_line_index] = LineValues.Copy(
            self.tempo_line_values)
        self.data_changed = True

        self.set_editor_mode(LineChartAnnotator.ModeLineSelect)
        self.update_current_view()

    def btn_line_return_cancel_click(self, button):
        self.set_editor_mode(LineChartAnnotator.ModeLineSelect)
        self.update_current_view(False)

    def lbx_line_points_value_changed(self, new_value, old_value):
        self.update_current_view(False)

    def img_mouse_double_click(self, img, position, button):
        if self.edition_mode == LineChartAnnotator.ModeLineEdit:
            # click relative position
            rel_x, rel_y = self.from_pos_to_rel_click(position)

            # find closest point ...
            distance, point_idx = self.tempo_line_values.closest_point(
                rel_x, rel_y)

            if button == 1:
                # left click
                if point_idx is not None and distance < LineChartAnnotator.DoubleClickMaxPointDistance:
                    # ... edit...
                    self.tempo_point_index = point_idx
                    self.set_editor_mode(LineChartAnnotator.ModePointEdit)
                else:
                    # ... add point ...
                    self.tempo_line_values.add_point(rel_x, rel_y,
                                                     LineValues.InsertByXValue)
                    # update GUI
                    self.update_points_list()

                self.update_current_view(False)
            else:
                # right click ... delete ...
                if distance < LineChartAnnotator.DoubleClickMaxPointDistance:
                    self.delete_tempo_line_point(point_idx)
Esempio n. 4
0
class ChartTextAnnotator(Screen):
    ModeNavigate = 0
    ModeAddingTextSelect = 1
    ModeAddingTextEdit = 2
    ModeEditingText = 3
    ModeConfirmDeleteText = 4
    ModeConfirmExit = 5
    ModeConfirmOverwrite = 6

    ViewModeRawData = 0
    ViewModeGrayData = 1
    ViewModeRawNoData = 2
    ViewModeGrayNoData = 3

    RotationAuto = 0
    Rotation0 = 1
    Rotation90 = 2
    Rotation180 = 3
    Rotation270 = 4

    def __init__(self, size, panel_image, panel_info, parent_screen,
                 admin_mode):
        Screen.__init__(self, "Chart Text Ground Truth Annotation Interface",
                        size)

        self.panel_image = panel_image
        self.panel_gray = np.zeros(self.panel_image.shape,
                                   self.panel_image.dtype)
        self.panel_gray[:, :, 0] = cv2.cvtColor(self.panel_image,
                                                cv2.COLOR_RGB2GRAY)
        self.panel_gray[:, :, 1] = self.panel_gray[:, :, 0].copy()
        self.panel_gray[:, :, 2] = self.panel_gray[:, :, 0].copy()

        self.panel_info = panel_info

        # working copy ...
        self.text_regions = [
            TextInfo.Copy(text_info) for text_info in self.panel_info.text
        ]

        self.parent_screen = parent_screen
        self.admin_mode = admin_mode

        self.general_background = (150, 100, 60)
        self.text_color = (255, 255, 255)
        self.canvas_colors = {
            TextInfo.TypeChartTitle: (255, 0, 0),
            TextInfo.TypeAxisTitle: (0, 255, 0),
            TextInfo.TypeTickLabel: (0, 0, 255),
            TextInfo.TypeLegendTitle: (255, 255, 0),
            TextInfo.TypeLegendLabel: (255, 0, 255),
            TextInfo.TypeValueLabel: (0, 255, 255),
            TextInfo.TypeOther: (128, 0, 0),
        }
        self.canvas_sel_colors = {
            TextInfo.TypeChartTitle: (128, 0, 0),
            TextInfo.TypeAxisTitle: (0, 128, 0),
            TextInfo.TypeTickLabel: (0, 0, 128),
            TextInfo.TypeLegendTitle: (128, 128, 0),
            TextInfo.TypeLegendLabel: (128, 0, 128),
            TextInfo.TypeValueLabel: (0, 128, 128),
            TextInfo.TypeOther: (64, 0, 0),
        }

        self.elements.back_color = self.general_background

        self.edition_mode = None
        self.view_mode = ChartTextAnnotator.ViewModeRawData
        self.view_scale = 1.0

        self.data_changed = False
        self.tempo_edit_text = None

        self.label_title = None

        self.container_view_buttons = None
        self.lbl_zoom = None
        self.btn_zoom_reduce = None
        self.btn_zoom_increase = None
        self.btn_zoom_zero = None

        self.btn_view_raw_data = None
        self.btn_view_gray_data = None
        self.btn_view_raw_clear = None
        self.btn_view_gray_clear = None

        self.container_confirm_buttons = None
        self.lbl_confirm_message = None
        self.btn_confirm_cancel = None
        self.btn_confirm_accept = None

        self.container_text_options = None
        self.lbl_text_title = None
        self.lbx_text_list = None
        self.btn_text_add = None
        self.btn_text_edit = None
        self.btn_text_delete = None
        self.btn_return_accept = None
        self.btn_return_cancel = None

        self.container_edit_options = None
        self.lbl_edit_title = None
        self.lbx_edit_type = None
        self.lbl_edit_text = None
        self.btn_edit_OCR_0 = None
        self.btn_edit_OCR_180 = None
        self.btn_edit_OCR_any = None
        self.btn_edit_OCR_270 = None
        self.btn_edit_OCR_90 = None
        self.txt_edit_text = None
        self.btn_edit_cancel = None
        self.btn_edit_accept = None

        self.container_images = None
        self.canvas_select = None
        self.canvas_display = None
        self.img_main = None

        self.create_controllers()

        # get the view ...
        self.update_current_view(True)

    def create_controllers(self):
        # add elements....
        button_text_color = (35, 50, 20)
        button_back_color = (228, 228, 228)

        # Main Title
        self.label_title = ScreenLabel(
            "title", "Chart Image Annotation Tool - Panel Text Annotation", 28)
        self.label_title.background = self.general_background
        self.label_title.position = (int(
            (self.width - self.label_title.width) / 2), 20)
        self.label_title.set_color(self.text_color)
        self.elements.append(self.label_title)

        container_top = 10 + self.label_title.get_bottom()
        container_width = 300

        button_width = 190
        button_left = (container_width - button_width) / 2

        button_2_width = 130
        button_2_left = int(container_width * 0.25) - button_2_width / 2
        button_2_right = int(container_width * 0.75) - button_2_width / 2

        # ===========================
        # View Options Panel

        # View panel with view control buttons
        self.container_view_buttons = ScreenContainer(
            "container_view_buttons", (container_width, 160),
            back_color=self.general_background)
        self.container_view_buttons.position = (
            self.width - self.container_view_buttons.width - 10, container_top)
        self.elements.append(self.container_view_buttons)

        # zoom ....
        self.lbl_zoom = ScreenLabel("lbl_zoom", "Zoom: 100%", 21, 290, 1)
        self.lbl_zoom.position = (5, 5)
        self.lbl_zoom.set_background(self.general_background)
        self.lbl_zoom.set_color(self.text_color)
        self.container_view_buttons.append(self.lbl_zoom)

        self.btn_zoom_reduce = ScreenButton("btn_zoom_reduce", "[ - ]", 21, 90)
        self.btn_zoom_reduce.set_colors(button_text_color, button_back_color)
        self.btn_zoom_reduce.position = (10, self.lbl_zoom.get_bottom() + 10)
        self.btn_zoom_reduce.click_callback = self.btn_zoom_reduce_click
        self.container_view_buttons.append(self.btn_zoom_reduce)

        self.btn_zoom_increase = ScreenButton("btn_zoom_increase", "[ + ]", 21,
                                              90)
        self.btn_zoom_increase.set_colors(button_text_color, button_back_color)
        self.btn_zoom_increase.position = (self.container_view_buttons.width -
                                           self.btn_zoom_increase.width - 10,
                                           self.lbl_zoom.get_bottom() + 10)
        self.btn_zoom_increase.click_callback = self.btn_zoom_increase_click
        self.container_view_buttons.append(self.btn_zoom_increase)

        self.btn_zoom_zero = ScreenButton("btn_zoom_zero", "100%", 21, 90)
        self.btn_zoom_zero.set_colors(button_text_color, button_back_color)
        self.btn_zoom_zero.position = (
            (self.container_view_buttons.width - self.btn_zoom_zero.width) / 2,
            self.lbl_zoom.get_bottom() + 10)
        self.btn_zoom_zero.click_callback = self.btn_zoom_zero_click
        self.container_view_buttons.append(self.btn_zoom_zero)

        self.btn_view_raw_data = ScreenButton("btn_view_raw_data", "RGB Data",
                                              21, button_2_width)
        self.btn_view_raw_data.set_colors(button_text_color, button_back_color)
        self.btn_view_raw_data.position = (button_2_left,
                                           self.btn_zoom_zero.get_bottom() +
                                           10)
        self.btn_view_raw_data.click_callback = self.btn_view_raw_data_click
        self.container_view_buttons.append(self.btn_view_raw_data)

        self.btn_view_gray_data = ScreenButton("btn_view_gray", "Gray Data",
                                               21, button_2_width)
        self.btn_view_gray_data.set_colors(button_text_color,
                                           button_back_color)
        self.btn_view_gray_data.position = (button_2_right,
                                            self.btn_zoom_zero.get_bottom() +
                                            10)
        self.btn_view_gray_data.click_callback = self.btn_view_gray_data_click
        self.container_view_buttons.append(self.btn_view_gray_data)

        self.btn_view_raw_clear = ScreenButton("btn_view_raw_clear",
                                               "RGB Clear", 21, button_2_width)
        self.btn_view_raw_clear.set_colors(button_text_color,
                                           button_back_color)
        self.btn_view_raw_clear.position = (
            button_2_left, self.btn_view_raw_data.get_bottom() + 10)
        self.btn_view_raw_clear.click_callback = self.btn_view_raw_clear_click
        self.container_view_buttons.append(self.btn_view_raw_clear)

        self.btn_view_gray_clear = ScreenButton("btn_view_gray_clear",
                                                "Gray Clear", 21,
                                                button_2_width)
        self.btn_view_gray_clear.set_colors(button_text_color,
                                            button_back_color)
        self.btn_view_gray_clear.position = (
            button_2_right, self.btn_view_raw_data.get_bottom() + 10)
        self.btn_view_gray_clear.click_callback = self.btn_view_gray_clear_click
        self.container_view_buttons.append(self.btn_view_gray_clear)

        # ===========================
        # confirmation panel
        self.container_confirm_buttons = ScreenContainer(
            "container_confirm_buttons", (container_width, 70),
            back_color=self.general_background)
        self.container_confirm_buttons.position = (
            self.width - self.container_confirm_buttons.width - 10,
            self.container_view_buttons.get_bottom() + 20)
        self.elements.append(self.container_confirm_buttons)
        self.container_confirm_buttons.visible = False

        self.lbl_confirm_message = ScreenLabel(
            "lbl_confirm_message", "Confirmation message goes here?", 21, 290,
            1)
        self.lbl_confirm_message.position = (5, 5)
        self.lbl_confirm_message.set_background(self.general_background)
        self.lbl_confirm_message.set_color(self.text_color)
        self.container_confirm_buttons.append(self.lbl_confirm_message)

        self.btn_confirm_cancel = ScreenButton("btn_confirm_cancel", "Cancel",
                                               21, 130)
        self.btn_confirm_cancel.set_colors(button_text_color,
                                           button_back_color)
        self.btn_confirm_cancel.position = (
            10, self.lbl_confirm_message.get_bottom() + 10)
        self.btn_confirm_cancel.click_callback = self.btn_confirm_cancel_click
        self.container_confirm_buttons.append(self.btn_confirm_cancel)

        self.btn_confirm_accept = ScreenButton("btn_confirm_accept", "Accept",
                                               21, 130)
        self.btn_confirm_accept.set_colors(button_text_color,
                                           button_back_color)
        self.btn_confirm_accept.position = (
            self.container_confirm_buttons.width -
            self.btn_confirm_accept.width - 10,
            self.lbl_confirm_message.get_bottom() + 10)
        self.btn_confirm_accept.click_callback = self.btn_confirm_accept_click
        self.container_confirm_buttons.append(self.btn_confirm_accept)

        # ===========================
        # general edition options
        darker_background = (100, 66, 30)
        self.container_text_options = ScreenContainer(
            "container_text_options", (container_width, 550),
            back_color=darker_background)
        self.container_text_options.position = (
            self.container_view_buttons.get_left(),
            self.container_view_buttons.get_bottom() + 20)
        self.elements.append(self.container_text_options)

        self.lbl_text_title = ScreenLabel("lbl_text_title",
                                          "Options for Text Annotation", 21,
                                          290, 1)
        self.lbl_text_title.position = (5, 5)
        self.lbl_text_title.set_background(darker_background)
        self.lbl_text_title.set_color(self.text_color)
        self.container_text_options.append(self.lbl_text_title)

        self.lbx_text_list = ScreenTextlist("lbx_text_list",
                                            (container_width - 20, 350),
                                            18,
                                            back_color=(255, 255, 255),
                                            option_color=(0, 0, 0),
                                            selected_back=(120, 80, 50),
                                            selected_color=(255, 255, 255))
        self.lbx_text_list.position = (10,
                                       self.lbl_text_title.get_bottom() + 10)
        self.lbx_text_list.selected_value_change_callback = self.lbx_text_list_option_changed
        self.container_text_options.append(self.lbx_text_list)

        self.btn_text_add = ScreenButton("btn_text_add", "Add", 21, 90)
        self.btn_text_add.set_colors(button_text_color, button_back_color)
        self.btn_text_add.position = (10, self.lbx_text_list.get_bottom() + 10)
        self.btn_text_add.click_callback = self.btn_text_add_click
        self.container_text_options.append(self.btn_text_add)

        self.btn_text_edit = ScreenButton("btn_text_edit", "Edit", 21, 90)
        self.btn_text_edit.set_colors(button_text_color, button_back_color)
        self.btn_text_edit.position = (
            (self.container_text_options.width - self.btn_text_edit.width) / 2,
            self.lbx_text_list.get_bottom() + 10)
        self.btn_text_edit.click_callback = self.btn_text_edit_click
        self.container_text_options.append(self.btn_text_edit)

        self.btn_text_delete = ScreenButton("btn_text_delete", "Delete", 21,
                                            90)
        self.btn_text_delete.set_colors(button_text_color, button_back_color)
        self.btn_text_delete.position = (self.container_text_options.width -
                                         self.btn_text_delete.width - 10,
                                         self.lbx_text_list.get_bottom() + 10)
        self.btn_text_delete.click_callback = self.btn_text_delete_click
        self.container_text_options.append(self.btn_text_delete)

        self.btn_return_accept = ScreenButton("btn_return_accept", "Accept",
                                              21, button_2_width)
        self.btn_return_accept.set_colors(button_text_color, button_back_color)
        self.btn_return_accept.position = (button_2_left,
                                           self.container_text_options.height -
                                           self.btn_return_accept.height - 10)
        self.btn_return_accept.click_callback = self.btn_return_accept_click
        self.container_text_options.append(self.btn_return_accept)

        self.btn_return_cancel = ScreenButton("btn_return_cancel", "Cancel",
                                              21, button_2_width)
        self.btn_return_cancel.set_colors(button_text_color, button_back_color)
        self.btn_return_cancel.position = (button_2_right,
                                           self.container_text_options.height -
                                           self.btn_return_cancel.height - 10)
        self.btn_return_cancel.click_callback = self.btn_return_cancel_click
        self.container_text_options.append(self.btn_return_cancel)

        # =============================================================
        # options for editing a given text

        self.container_edit_options = ScreenContainer(
            "container_edit_options", (container_width, 550),
            back_color=darker_background)
        self.container_edit_options.position = (
            self.container_view_buttons.get_left(),
            self.container_view_buttons.get_bottom() + 20)
        self.elements.append(self.container_edit_options)

        self.lbl_edit_title = ScreenLabel("lbl_edit_title", "Editing Text", 21,
                                          290, 1)
        self.lbl_edit_title.position = (5, 5)
        self.lbl_edit_title.set_background(darker_background)
        self.lbl_edit_title.set_color(self.text_color)
        self.container_edit_options.append(self.lbl_edit_title)

        self.lbx_edit_type = ScreenTextlist("lbx_edit_type",
                                            (container_width - 20, 280),
                                            18,
                                            back_color=(255, 255, 255),
                                            option_color=(0, 0, 0),
                                            selected_back=(120, 80, 50),
                                            selected_color=(255, 255, 255))
        self.lbx_edit_type.position = (10,
                                       self.lbl_edit_title.get_bottom() + 10)
        self.container_edit_options.append(self.lbx_edit_type)
        self.add_text_types()

        self.lbl_edit_text = ScreenLabel("lbl_edit_text", "Transcription", 21,
                                         button_width, 1)
        self.lbl_edit_text.position = (button_left,
                                       self.lbx_edit_type.get_bottom() + 20)
        self.lbl_edit_text.set_background(darker_background)
        self.lbl_edit_text.set_color(self.text_color)
        self.container_edit_options.append(self.lbl_edit_text)

        self.btn_edit_OCR_0 = ScreenButton("btn_edit_OCR_0", "OCR R+0", 21,
                                           button_2_width)
        self.btn_edit_OCR_0.set_colors(button_text_color, button_back_color)
        self.btn_edit_OCR_0.position = (button_2_left,
                                        self.lbl_edit_text.get_bottom() + 10)
        self.btn_edit_OCR_0.click_callback = self.btn_edit_OCR_0_click
        self.container_edit_options.append(self.btn_edit_OCR_0)

        self.btn_edit_OCR_any = ScreenButton("btn_edit_OCR_any", "OCR R+ANY",
                                             21, button_2_width)
        self.btn_edit_OCR_any.set_colors(button_text_color, button_back_color)
        self.btn_edit_OCR_any.position = (button_2_right,
                                          self.lbl_edit_text.get_bottom() + 10)
        self.btn_edit_OCR_any.click_callback = self.btn_edit_OCR_any_click
        self.container_edit_options.append(self.btn_edit_OCR_any)
        """
        self.btn_edit_OCR_180 = ScreenButton("btn_edit_OCR_180", "OCR R+180", 21, button_2_width)
        self.btn_edit_OCR_180.set_colors(button_text_color, button_back_color)
        self.btn_edit_OCR_180.position = (button_2_right, self.lbl_edit_text.get_bottom() + 10)
        self.btn_edit_OCR_180.click_callback = self.btn_edit_OCR_180_click
        self.container_edit_options.append(self.btn_edit_OCR_180)
        """

        self.btn_edit_OCR_270 = ScreenButton("btn_edit_OCR_270", "OCR R-90",
                                             21, button_2_width)
        self.btn_edit_OCR_270.set_colors(button_text_color, button_back_color)
        self.btn_edit_OCR_270.position = (button_2_left,
                                          self.btn_edit_OCR_0.get_bottom() +
                                          10)
        self.btn_edit_OCR_270.click_callback = self.btn_edit_OCR_270_click
        self.container_edit_options.append(self.btn_edit_OCR_270)

        self.btn_edit_OCR_90 = ScreenButton("btn_edit_OCR_90", "OCR R+90", 21,
                                            button_2_width)
        self.btn_edit_OCR_90.set_colors(button_text_color, button_back_color)
        self.btn_edit_OCR_90.position = (button_2_right,
                                         self.btn_edit_OCR_0.get_bottom() + 10)
        self.btn_edit_OCR_90.click_callback = self.btn_edit_OCR_90_click
        self.container_edit_options.append(self.btn_edit_OCR_90)

        self.txt_edit_text = ScreenTextbox("txt_edit_text",
                                           "",
                                           24,
                                           container_width - 20,
                                           text_color=(255, 255, 255),
                                           back_color=(0, 0, 0))
        self.txt_edit_text.position = (5,
                                       self.btn_edit_OCR_270.get_bottom() + 10)
        self.txt_edit_text.capture_EOL = True  # allow multi-line elements
        self.container_edit_options.append(self.txt_edit_text)

        self.btn_edit_cancel = ScreenButton("btn_edit_cancel", "Cancel", 21,
                                            button_2_width)
        self.btn_edit_cancel.set_colors(button_text_color, button_back_color)
        self.btn_edit_cancel.position = (button_2_right,
                                         self.container_edit_options.height -
                                         self.btn_edit_cancel.height - 10)
        self.btn_edit_cancel.click_callback = self.btn_edit_cancel_click
        self.container_edit_options.append(self.btn_edit_cancel)

        self.btn_edit_accept = ScreenButton("btn_return_accept", "Accept", 21,
                                            button_2_width)
        self.btn_edit_accept.set_colors(button_text_color, button_back_color)
        self.btn_edit_accept.position = (button_2_left,
                                         self.container_edit_options.height -
                                         self.btn_edit_accept.height - 10)
        self.btn_edit_accept.click_callback = self.btn_edit_accept_click
        self.container_edit_options.append(self.btn_edit_accept)
        self.container_edit_options.visible = False

        # ===========================
        # Image

        image_width = self.width - self.container_view_buttons.width - 30
        image_height = self.height - container_top - 10
        self.container_images = ScreenContainer("container_images",
                                                (image_width, image_height),
                                                back_color=(0, 0, 0))
        self.container_images.position = (10, container_top)
        self.elements.append(self.container_images)

        # ... image objects ...
        tempo_blank = np.zeros((50, 50, 3), np.uint8)
        tempo_blank[:, :, :] = 255
        self.img_main = ScreenImage("img_main", tempo_blank, 0, 0, True,
                                    cv2.INTER_NEAREST)
        self.img_main.position = (0, 0)
        self.img_main.double_click_callback = self.img_mouse_double_click
        self.img_main.mouse_button_down_callback = self.img_mouse_down
        self.container_images.append(self.img_main)

        self.canvas_select = ScreenCanvas("canvas_select", 100, 100)
        self.canvas_select.position = (0, 0)
        self.canvas_select.locked = True
        # self.canvas_select.object_edited_callback = self.canvas_object_edited
        # self.canvas_select.object_selected_callback = self.canvas_selection_changed
        self.container_images.append(self.canvas_select)

        base_points = np.array([[10, 10], [20, 10], [20, 20], [10, 20]],
                               dtype=np.float64)
        self.canvas_select.add_polygon_element("selection_polygon",
                                               base_points)
        self.canvas_select.elements["selection_polygon"].visible = False

        self.canvas_display = ScreenCanvas("canvas_display", 100, 100)
        self.canvas_display.position = (0, 0)
        self.canvas_display.locked = True
        self.container_images.append(self.canvas_display)

        self.add_text_regions()

        self.set_editor_mode(ChartTextAnnotator.ModeNavigate)

    def add_text_types(self):
        self.lbx_edit_type.add_option(str(TextInfo.TypeChartTitle),
                                      "Chart Title")
        self.lbx_edit_type.add_option(str(TextInfo.TypeAxisTitle),
                                      "Axis Title")
        self.lbx_edit_type.add_option(str(TextInfo.TypeTickLabel),
                                      "Tick Label")
        self.lbx_edit_type.add_option(str(TextInfo.TypeLegendTitle),
                                      "Legend Title")
        self.lbx_edit_type.add_option(str(TextInfo.TypeLegendLabel),
                                      "Legend Label")
        self.lbx_edit_type.add_option(str(TextInfo.TypeValueLabel),
                                      "Value Label")
        self.lbx_edit_type.add_option(str(TextInfo.TypeOther), "Other")

    def get_text_display(self, text):
        return "{0:d} - {1:s}: {2:s}".format(text.id,
                                             text.get_type_description(),
                                             text.value)

    def add_text_regions(self):
        # populate the list-box using existing text regions (if any)
        for text in self.text_regions:
            self.lbx_text_list.add_option(str(text.id),
                                          self.get_text_display(text))

            self.canvas_display.add_polygon_element(
                str(text.id), text.position_polygon.copy(),
                self.canvas_colors[text.type],
                self.canvas_sel_colors[text.type])

    def btn_zoom_reduce_click(self, button):
        self.update_view_scale(self.view_scale - 0.25)

    def btn_zoom_increase_click(self, button):
        self.update_view_scale(self.view_scale + 0.25)

    def btn_zoom_zero_click(self, button):
        self.update_view_scale(1.0)

    def btn_view_raw_data_click(self, button):
        self.view_mode = ChartTextAnnotator.ViewModeRawData
        self.update_current_view()

    def btn_view_gray_data_click(self, button):
        self.view_mode = ChartTextAnnotator.ViewModeGrayData
        self.update_current_view()

    def btn_view_raw_clear_click(self, button):
        self.view_mode = ChartTextAnnotator.ViewModeRawNoData
        self.update_current_view()

    def btn_view_gray_clear_click(self, button):
        self.view_mode = ChartTextAnnotator.ViewModeGrayNoData
        self.update_current_view()

    def update_current_view(self, resized=False):
        if self.view_mode in [
                ChartTextAnnotator.ViewModeGrayData,
                ChartTextAnnotator.ViewModeGrayNoData
        ]:
            # gray scale mode
            base_image = self.panel_gray
        else:
            base_image = self.panel_image

        h, w, c = base_image.shape

        modified_image = base_image.copy()

        if self.view_mode in [
                ChartTextAnnotator.ViewModeRawData,
                ChartTextAnnotator.ViewModeGrayData
        ]:
            # TODO: show here any relevant annotations on the modified image ...
            # (for example, draw the polygons)
            self.canvas_display.visible = True
        else:
            self.canvas_display.visible = False

        # finally, resize ...
        modified_image = cv2.resize(
            modified_image,
            (int(w * self.view_scale), int(h * self.view_scale)),
            interpolation=cv2.INTER_NEAREST)

        # update canvas size ....
        self.canvas_select.height, self.canvas_select.width, _ = modified_image.shape
        self.canvas_display.height, self.canvas_display.width, _ = modified_image.shape

        # replace/update image
        self.img_main.set_image(modified_image, 0, 0, True, cv2.INTER_NEAREST)
        if resized:
            self.container_images.recalculate_size()

    def update_view_scale(self, new_scale):
        prev_scale = self.view_scale

        if 0.25 <= new_scale <= 4.0:
            self.view_scale = new_scale
        else:
            return

        # keep previous offsets ...
        scroll_offset_y = self.container_images.v_scroll.value if self.container_images.v_scroll.active else 0
        scroll_offset_x = self.container_images.h_scroll.value if self.container_images.h_scroll.active else 0

        prev_center_y = scroll_offset_y + self.container_images.height / 2
        prev_center_x = scroll_offset_x + self.container_images.width / 2

        # compute new scroll bar offsets
        scale_factor = (new_scale / prev_scale)
        new_off_y = prev_center_y * scale_factor - self.container_images.height / 2
        new_off_x = prev_center_x * scale_factor - self.container_images.width / 2

        # update view ....
        self.update_current_view(True)

        # set offsets
        if self.container_images.v_scroll.active and 0 <= new_off_y <= self.container_images.v_scroll.max:
            self.container_images.v_scroll.value = new_off_y
        if self.container_images.h_scroll.active and 0 <= new_off_x <= self.container_images.h_scroll.max:
            self.container_images.h_scroll.value = new_off_x

        # re-scale objects from both canvas
        # ... selection ...
        selection_polygon = self.canvas_select.elements["selection_polygon"]
        selection_polygon.points *= scale_factor
        # ... display ...
        for polygon_name in self.canvas_display.elements:
            display_polygon = self.canvas_display.elements[polygon_name]
            display_polygon.points *= scale_factor

        # update scale text ...
        self.lbl_zoom.set_text("Zoom: " +
                               str(int(round(self.view_scale * 100, 0))) + "%")

    def set_editor_mode(self, new_mode):
        self.edition_mode = new_mode

        # Navigation mode ...
        self.container_text_options.visible = (
            self.edition_mode == ChartTextAnnotator.ModeNavigate)

        # Edit panels ...
        self.container_edit_options.visible = self.edition_mode in [
            ChartTextAnnotator.ModeAddingTextEdit,
            ChartTextAnnotator.ModeEditingText
        ]

        # Confirm panel and buttons  ...
        self.container_confirm_buttons.visible = self.edition_mode in [
            ChartTextAnnotator.ModeAddingTextSelect,
            ChartTextAnnotator.ModeConfirmDeleteText,
            ChartTextAnnotator.ModeConfirmExit,
            ChartTextAnnotator.ModeConfirmOverwrite
        ]

        if self.edition_mode == ChartTextAnnotator.ModeAddingTextSelect:
            self.lbl_confirm_message.set_text("Select Text Location")
        elif self.edition_mode == ChartTextAnnotator.ModeAddingTextEdit:
            self.lbl_confirm_message.set_text("Adding Text Region")
        elif self.edition_mode == ChartTextAnnotator.ModeConfirmDeleteText:
            self.lbl_confirm_message.set_text("Delete Text Region?")
        elif self.edition_mode == ChartTextAnnotator.ModeConfirmExit:
            self.lbl_confirm_message.set_text("Discard Changes to Text?")
        elif self.edition_mode == ChartTextAnnotator.ModeConfirmOverwrite:
            self.lbl_confirm_message.set_text(
                "Discarding Legend/Axis data, Proceed?")

        # Do not show accept at these steps (they can be implicitly accepted, but need explicit cancel button only)
        self.btn_confirm_accept.visible = self.edition_mode != ChartTextAnnotator.ModeAddingTextSelect

        if new_mode in [
                ChartTextAnnotator.ModeAddingTextEdit,
                ChartTextAnnotator.ModeEditingText
        ]:
            # show polygon
            self.canvas_select.locked = False
            self.canvas_select.elements["selection_polygon"].visible = True
        else:
            # for every other mode
            self.canvas_select.locked = True
            self.canvas_select.elements["selection_polygon"].visible = False

        if new_mode == ChartTextAnnotator.ModeAddingTextEdit:
            # prepare empty input ...
            self.lbx_text_list.change_option_selected(None)
            self.txt_edit_text.updateText("")

    def btn_confirm_accept_click(self, button):
        if self.edition_mode == ChartTextAnnotator.ModeConfirmExit:
            print("-> Changes made to Text Annotations were lost")
            self.return_screen = self.parent_screen
        elif self.edition_mode == ChartTextAnnotator.ModeConfirmDeleteText:
            # delete tempo region ...
            # remove from text regions
            self.text_regions.remove(self.tempo_edit_text)

            # remove from the GUI
            self.lbx_text_list.remove_option(str(self.tempo_edit_text.id))
            self.canvas_display.remove_element(str(self.tempo_edit_text.id))

            self.data_changed = True

            # return  ...
            self.set_editor_mode(ChartTextAnnotator.ModeNavigate)
        elif self.edition_mode == ChartTextAnnotator.ModeConfirmOverwrite:
            # overwrite text data ...
            if self.admin_mode:
                overwrite = input(
                    "Discard Axis and/or Legend Info (y/n)? ").lower() in [
                        "y", "yes", "1", "true"
                    ]
            else:
                # for all other users, existing data will be discarded to ensure consistency
                overwrite = True

            self.panel_info.overwrite_text(self.text_regions, overwrite)
            self.parent_screen.subtool_completed(True)
            # return
            self.return_screen = self.parent_screen
        else:
            raise Exception("Not Implemented")

    def btn_confirm_cancel_click(self, button):
        if self.edition_mode in [
                ChartTextAnnotator.ModeAddingTextSelect,
                ChartTextAnnotator.ModeConfirmDeleteText,
                ChartTextAnnotator.ModeConfirmExit,
                ChartTextAnnotator.ModeConfirmOverwrite
        ]:
            # return to navigation
            self.set_editor_mode(ChartTextAnnotator.ModeNavigate)
        else:
            print(self.edition_mode)
            raise Exception("Not Implemented")

    def get_next_polygon(self, click_x, click_y):

        if len(
                self.text_regions
        ) > 0 and self.text_regions[-1].axis_aligned_rectangle_ratio() < 0.8:
            # simply copy as a polygon centered on the click point

            # copy and scale to current view ...
            points = self.text_regions[-1].position_polygon.copy(
            ) * self.view_scale

            # find scaled center ... compute and apply translation to get a polygon aligned with the click point
            old_cx = points[:, 0].mean()
            old_cy = points[:, 1].mean()

            delta_x = click_x - old_cx
            delta_y = click_y - old_cy

            points[:, 0] += delta_x
            points[:, 1] += delta_y
        else:
            if len(self.text_regions) == 0:
                # default small rectangle
                rect_w, rect_h = 40, 20
            else:
                # axis aligned container rectangle from last bbox
                min_x, min_y, max_x, max_y = self.text_regions[
                    -1].get_axis_aligned_rectangle()

                rect_w = (max_x - min_x) * self.view_scale
                rect_h = (max_y - min_y) * self.view_scale

            points = np.array([[click_x, click_y], [click_x + rect_w, click_y],
                               [click_x + rect_w, click_y + rect_h],
                               [click_x, click_y + rect_h]])

        return points

    def img_mouse_double_click(self, element, pos, button):
        if button == 1:
            # double left click ...
            if self.edition_mode == ChartTextAnnotator.ModeNavigate:
                click_x, click_y = pos
                points = self.get_next_polygon(click_x, click_y)
                self.canvas_select.elements["selection_polygon"].update(points)

                self.set_editor_mode(ChartTextAnnotator.ModeAddingTextEdit)

    def btn_text_add_click(self, button):
        self.set_editor_mode(ChartTextAnnotator.ModeAddingTextSelect)

    def btn_text_edit_click(self, button):
        if self.lbx_text_list.selected_option_value is None:
            print("Must select an option to edit")
            return

        for text in self.text_regions:
            if text.id == int(self.lbx_text_list.selected_option_value):
                # option found
                # copy
                self.tempo_edit_text = text

                # ... copy points to selection canvas ...
                polygon = self.tempo_edit_text.position_polygon.copy(
                ) * self.view_scale
                self.canvas_select.update_polygon_element(
                    "selection_polygon", polygon, True)
                # ... copy type to list of types
                self.lbx_edit_type.change_option_selected(
                    str(self.tempo_edit_text.type))
                # ... copy transcript to textbox
                self.txt_edit_text.updateText(self.tempo_edit_text.value)

                self.canvas_display.elements[str(
                    self.tempo_edit_text.id)].visible = False

                self.set_editor_mode(ChartTextAnnotator.ModeEditingText)
                break

    def btn_text_delete_click(self, button):
        if self.lbx_text_list.selected_option_value is None:
            print("Must select an option to edit")
            return

        for text in self.text_regions:
            if text.id == int(self.lbx_text_list.selected_option_value):
                # option found
                # copy
                self.tempo_edit_text = text

                self.set_editor_mode(ChartTextAnnotator.ModeConfirmDeleteText)
                break

    def btn_return_accept_click(self, button):
        if self.data_changed:
            if self.panel_info.legend is not None or self.panel_info.axes is not None:
                # confirm if return ... might overwrite existing data
                self.set_editor_mode(ChartTextAnnotator.ModeConfirmOverwrite)
            else:
                # overwrite text data ... both legend  and axes are None
                self.panel_info.overwrite_text(self.text_regions, False)
                # return
                self.return_screen = self.parent_screen
        else:
            # Nothing changed just return
            self.return_screen = self.parent_screen

    def btn_return_cancel_click(self, button):
        if self.data_changed:
            # confirm if return ...
            self.set_editor_mode(ChartTextAnnotator.ModeConfirmExit)
        else:
            # just return
            self.return_screen = self.parent_screen

    def find_next_id(self):
        if len(self.text_regions) == 0:
            return 0
        else:
            return max([text.id for text in self.text_regions]) + 1

    def btn_edit_accept_click(self, button):
        if self.lbx_edit_type.selected_option_value is None:
            print("-> Must Select text type")
            return

        if len(self.txt_edit_text.text.strip()) == 0:
            print("-> Must Provide Transcription")
            return

        text_polygon = self.canvas_select.elements[
            "selection_polygon"].points / self.view_scale
        text_type = int(self.lbx_edit_type.selected_option_value)
        text_value = self.txt_edit_text.text.strip()

        if self.edition_mode == ChartTextAnnotator.ModeAddingTextEdit:
            # new text will be added
            next_id = self.find_next_id()
            text = TextInfo(next_id, text_polygon, text_type, text_value)
            self.text_regions.append(text)
            # add to the GUI
            display = self.get_text_display(text)
            self.lbx_text_list.add_option(str(next_id), display)
            self.canvas_display.add_polygon_element(
                str(text.id),
                text.position_polygon.copy() * self.view_scale,
                self.canvas_colors[text.type],
                self.canvas_sel_colors[text.type])
            self.data_changed = True
            # return  ...
            self.set_editor_mode(ChartTextAnnotator.ModeNavigate)
        elif self.edition_mode == ChartTextAnnotator.ModeEditingText:
            # existing text should be over-written

            self.tempo_edit_text.position_polygon = text_polygon
            self.tempo_edit_text.type = text_type
            self.tempo_edit_text.value = text_value

            key_str = str(self.tempo_edit_text.id)
            display = self.get_text_display(self.tempo_edit_text)

            # update list
            self.lbx_text_list.update_option_display(key_str, display)
            # update and make canvas representation of the text region visible again ..
            canvas_points = self.tempo_edit_text.position_polygon.copy(
            ) * self.view_scale
            self.canvas_display.update_polygon_element(key_str, canvas_points,
                                                       True)
            self.canvas_display.update_custom_colors(
                key_str, self.canvas_colors[self.tempo_edit_text.type],
                self.canvas_sel_colors[self.tempo_edit_text.type])

            # remove temporal reference
            self.tempo_edit_text = None

            self.data_changed = True
            # return  ...
            self.set_editor_mode(ChartTextAnnotator.ModeNavigate)

    def btn_edit_cancel_click(self, button):
        if self.edition_mode == ChartTextAnnotator.ModeEditingText:
            # make the selected element visible again (no changes)
            self.canvas_display.elements[str(
                self.tempo_edit_text.id)].visible = True

        # return ....
        self.set_editor_mode(ChartTextAnnotator.ModeNavigate)

    def img_mouse_down(self, img_object, pos, button):
        if button == 1:
            if self.edition_mode == ChartTextAnnotator.ModeAddingTextSelect:
                click_x, click_y = pos
                points = self.get_next_polygon(click_x, click_y)
                self.canvas_select.elements["selection_polygon"].update(points)

                self.set_editor_mode(ChartTextAnnotator.ModeAddingTextEdit)

    def lbx_text_list_option_changed(self, new_value, old_value):
        self.canvas_display.change_selected_element(new_value)

    def btn_edit_OCR_0_click(self, button):
        self.apply_OCR(ChartTextAnnotator.Rotation0)
        # self.apply_OCR(ChartTextAnnotator.RotationAuto)

    def btn_edit_OCR_any_click(self, button):
        self.apply_OCR(ChartTextAnnotator.RotationAuto)

    def btn_edit_OCR_90_click(self, button):
        self.apply_OCR(ChartTextAnnotator.Rotation90)

    def btn_edit_OCR_270_click(self, button):
        self.apply_OCR(ChartTextAnnotator.Rotation270)

    def btn_edit_OCR_180_click(self, button):
        self.apply_OCR(ChartTextAnnotator.Rotation180)

    def order_points(self, pts):
        # sort the points based on their x-coordinates
        xSorted = pts[np.argsort(pts[:, 0]), :]

        # grab the left-most and right-most points from the sorted
        # x-roodinate points
        leftMost = xSorted[:2, :]
        rightMost = xSorted[2:, :]

        # now, sort the left-most coordinates according to their
        # y-coordinates so we can grab the top-left and bottom-left
        # points, respectively
        leftMost = leftMost[np.argsort(leftMost[:, 1]), :]
        (tl, bl) = leftMost

        # now that we have the top-left coordinate, use it as an
        # anchor to calculate the Euclidean distance between the
        # top-left and right-most points; by the Pythagorean
        # theorem, the point with the largest distance will be
        # our bottom-right point
        D = dist.cdist(tl[np.newaxis], rightMost, "euclidean")[0]
        (br, tr) = rightMost[np.argsort(D)[::-1], :]

        # return the coordinates in top-left, top-right,
        # bottom-right, and bottom-left order
        return np.asarray([tl, tr, br, bl], dtype=pts.dtype)

    def crop_rotated_rectangle(self, img, coords):
        # find rotated rectangle
        rect = cv2.minAreaRect(coords.reshape(4, 1, 2).astype(np.float32))
        rbox = self.order_points(cv2.boxPoints(rect))
        # get width and height of the detected rectangle
        # output of minAreaRect is unreliable for already axis aligned rectangles!!
        width = np.linalg.norm(
            [rbox[0, 0] - rbox[1, 0], rbox[0, 1] - rbox[1, 1]])
        height = np.linalg.norm(
            [rbox[0, 0] - rbox[-1, 0], rbox[0, 1] - rbox[-1, 1]])
        src_pts = rbox.astype(np.float32)
        # coordinate of the points in box points after the rectangle has been straightened
        # this step needs order_points to be called on src
        dst_pts = np.array(
            [[0, 0], [width - 1, 0], [width - 1, height - 1], [0, height - 1]],
            dtype="float32")
        # the perspective transformation matrix
        M = cv2.getPerspectiveTransform(src_pts, dst_pts)
        # directly warp the rotated rectangle to get the straightened rectangle
        warped = cv2.warpPerspective(img, M, (width, height), None,
                                     cv2.INTER_LINEAR, cv2.BORDER_CONSTANT,
                                     (255, 255, 255))
        return warped

    def crop_axis_aligned_rectangle(self, img, text_polygon, rotation):
        panel_h, panel_w, _ = img.shape

        # get polygon bounding box ...
        x1 = max(0, int(text_polygon[:, 0].min()))
        y1 = max(0, int(text_polygon[:, 1].min()))

        x2 = min(panel_w, int(text_polygon[:, 0].max() + 1))
        y2 = min(panel_h, int(text_polygon[:, 1].max() + 1))

        # get image first ..
        text_img = img[y1:y2, x1:x2]
        h, w, _ = text_img.shape

        # apply rotation ... (if any)
        if rotation == ChartTextAnnotator.Rotation90:
            M = cv2.getRotationMatrix2D((0, 0), -90, 1)
            M[0, 2] += (h - 1)
            text_img = cv2.warpAffine(text_img, M, (h, w))

        elif rotation == ChartTextAnnotator.Rotation180:
            M = cv2.getRotationMatrix2D((0, 0), -180, 1)
            M[0, 2] += (w - 1)
            M[1, 2] += (h - 1)
            text_img = cv2.warpAffine(text_img, M, (w, h))

        elif rotation == ChartTextAnnotator.Rotation270:
            M = cv2.getRotationMatrix2D((0, 0), 90, 1)
            M[1, 2] += (w - 1)
            text_img = cv2.warpAffine(text_img, M, (h, w))

        return text_img

    def apply_OCR(self, rotation):
        if OCR is None:
            print(
                "PyTesseract not found, please install to enable this function"
            )
            return

        text_polygon = self.canvas_select.elements[
            "selection_polygon"].points / self.view_scale
        print(text_polygon)

        if rotation == ChartTextAnnotator.RotationAuto:
            text_polygon = self.order_points(text_polygon)
            text_img = self.crop_rotated_rectangle(self.panel_image,
                                                   text_polygon)
        else:
            text_img = self.crop_axis_aligned_rectangle(
                self.panel_image, text_polygon, rotation)

        result = OCR.image_to_string(text_img, config='--psm 6')
        self.txt_edit_text.updateText(result)

        #if result.strip() == "":
        cv2.imwrite("TEMPO_CHECK_OCR_2.png", text_img)

        print("-> Tesseract Result: " + result)