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
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)
class ChartMainAnnotator(Screen): def __init__(self, size, chart_dir, annotation_dir, admin_mode): Screen.__init__(self, "Chart Ground Truth Annotation Interface", size) # load the chart directory info ... self.chart_image_list = ImageInfo.ListChartDirectory(chart_dir, "") print("A total of {0:d} chart images were found".format( len(self.chart_image_list))) self.general_background = (20, 50, 85) self.text_color = (255, 255, 255) self.admin_mode = admin_mode # about the grid .... self.tb_grid_cols = 6 self.tb_grid_rows = 4 self.tb_width = 160 self.tb_height = 120 self.tb_outer_margin = 16 self.tb_inner_margin = 4 self.tb_label_height = 14 # this is approx self.tb_progress_h = 16 self.tb_progress_border = 4 self.tb_col_width = (self.tb_outer_margin + self.tb_width) self.tb_row_height = (self.tb_outer_margin + self.tb_inner_margin * 2 + self.tb_progress_h + self.tb_label_height + self.tb_height) self.elements.back_color = self.general_background self.chart_dir = chart_dir self.annotation_dir = annotation_dir # define here all graphic object references .... # ...for thumbnails .... self.container_thumbnails = None self.thumbnails_images = None self.thumbnails_status = None self.thumbnails_labels = None self.paginator = None # ... right side image options ... self.lbl_page_descriptor = None self.container_image_info = None self.img_display = None self.img_title = None self.btn_label_image = None self.btn_next_image = None self.btn_prev_image = None # ... general ... self.btn_exit = None # ... create them!!! ..... self.create_controllers() # ... load current page data .... self.current_page = None self.selected_element = 0 self.load_page(None, 0) def create_controllers(self): button_text_color = (35, 50, 20) button_back_color = (228, 228, 228) # thumbnail grid parameters tb_empty = np.zeros((3, 4, 3), dtype=np.uint8) default_status = self.gen_status_image([0] * 6, self.tb_width, self.tb_progress_h, self.tb_progress_border) # create the thumbnail container .... tb_contained_width = self.tb_col_width * self.tb_grid_cols + self.tb_outer_margin tb_contained_height = self.tb_row_height * self.tb_grid_rows + self.tb_outer_margin self.container_thumbnails = ScreenContainer( "container_nav_buttons", (tb_contained_width, tb_contained_height), back_color=(15, 30, 50)) self.container_thumbnails.position = (10, 10) self.elements.append(self.container_thumbnails) # generate the default grid .... self.thumbnails_images = [] self.thumbnails_status = [] self.thumbnails_labels = [] for row in range(self.tb_grid_rows): for col in range(self.tb_grid_cols): corner_x = self.tb_outer_margin + self.tb_col_width * col corner_y = self.tb_outer_margin + self.tb_row_height * row tb_image = ScreenImage("tb_image_{0:d}_{1:d}".format(row, col), tb_empty, self.tb_width, self.tb_height, True) tb_image.position = (corner_x, corner_y) tb_image.tag = (row, col) tb_image.click_callback = self.thumbnail_image_click self.container_thumbnails.append(tb_image) self.thumbnails_images.append(tb_image) tb_status = ScreenImage( "tb_status_{0:d}_{1:d}".format(row, col), default_status, self.tb_width, self.tb_progress_h) tb_status.position = (corner_x, tb_image.get_bottom() + self.tb_inner_margin) self.container_thumbnails.append(tb_status) self.thumbnails_status.append(tb_status) tb_label = ScreenLabel("tb_label_{0:d}_{1:d}".format(row, col), "<IMAGE FILE NAME>", 12, self.tb_width, centered=1) tb_label.position = (corner_x, tb_status.get_bottom() + self.tb_inner_margin) tb_label.background = self.container_thumbnails.back_color tb_label.set_color(self.text_color) self.container_thumbnails.append(tb_label) self.thumbnails_labels.append(tb_label) # the paginator ... self.paginator = ScreenPaginator("tb_paginator", 16, self.tb_grid_cols * self.tb_grid_rows, len(self.chart_image_list), self.container_thumbnails.width, text_color=button_text_color, back_color=button_back_color) self.paginator.position = (self.container_thumbnails.get_left(), self.container_thumbnails.get_bottom() + 20) self.paginator.page_selected_callback = self.load_page self.elements.append(self.paginator) img_container_width = self.width - 45 - self.container_thumbnails.width self.lbl_page_descriptor = ScreenLabel( "lbl_page_descriptor", "Page XXX of YYY (ZZZZZ elements)", 16, img_container_width) self.lbl_page_descriptor.position = ( self.container_thumbnails.get_right() + 15, self.container_thumbnails.get_top()) self.lbl_page_descriptor.background = self.general_background self.lbl_page_descriptor.set_color(self.text_color) self.elements.append(self.lbl_page_descriptor) img_container_height = int(img_container_width) + 100 self.container_image_info = ScreenContainer( "container_image_info", (img_container_width, img_container_height), back_color=(15, 30, 50)) self.container_image_info.position = ( self.lbl_page_descriptor.get_left(), self.lbl_page_descriptor.get_bottom() + 10) self.elements.append(self.container_image_info) base_img = np.zeros((2, 2, 3), np.uint8) self.img_display = ScreenImage("img_display", base_img, self.container_image_info.width - 20, self.container_image_info.width - 20, True) self.img_display.position = (10, 10) self.container_image_info.append(self.img_display) self.img_title = ScreenLabel("img_title", "<Image File Name>" * 10, 16, self.img_display.width) self.img_title.position = (10, self.img_display.get_bottom() + 10) self.img_title.background = self.container_image_info.back_color self.img_title.set_color(self.text_color) self.container_image_info.append(self.img_title) self.btn_label_image = ScreenButton("btn_label_image", "Annotate", 18, 100, text_color=button_text_color, back_color=button_back_color) self.btn_label_image.position = (int( (self.container_image_info.width - self.btn_label_image.width) / 2), self.img_title.get_bottom() + 10) self.btn_label_image.click_callback = self.btn_annotate_click self.container_image_info.append(self.btn_label_image) self.btn_prev_image = ScreenButton("btn_prev_image", "Previous", 18, 100, text_color=button_text_color, back_color=button_back_color) self.btn_prev_image.position = (self.btn_label_image.get_left() - self.btn_prev_image.width - 20, self.img_title.get_bottom() + 10) self.btn_prev_image.click_callback = self.btn_prev_image_click self.container_image_info.append(self.btn_prev_image) self.btn_next_image = ScreenButton("btn_next_image", "Next", 18, 100, text_color=button_text_color, back_color=button_back_color) self.btn_next_image.position = (self.btn_label_image.get_right() + 20, self.img_title.get_bottom() + 10) self.btn_next_image.click_callback = self.btn_next_image_click self.container_image_info.append(self.btn_next_image) self.img_title.set_text("[Select Image]") # ... general ... self.btn_exit = ScreenButton("btn_exit", "Exit", 18, 100, text_color=button_text_color, back_color=button_back_color) self.btn_exit.position = (self.width - 15 - self.btn_exit.width, self.height - 15 - self.btn_exit.height) self.btn_exit.click_callback = self.btn_exit_click self.elements.append(self.btn_exit) def gen_status_image(self, all_status, width, height, border): result = np.zeros((height, width, 3), np.uint8) approx_width = (width - (len(all_status) + 1) * border) / len(all_status) last_x = 0 for idx, value in enumerate(all_status): left = last_x + border right = left + approx_width if value == 2: # labeled and verified color = (128, 255, 128) elif value == 1: # labeled but unverified color = (192, 192, 64) else: # not labeled or verified color = (192, 64, 64) result[border:-border, int(left):int(right), :] = color last_x = right return result def btn_exit_click(self, button): # Just exit self.return_screen = None print("APPLICATION FINISHED") def refresh_page(self): self.load_page(None, self.current_page, True) def load_page(self, paginator, new_page, refresh=False): if new_page == self.current_page and not refresh: # page not changed ... return self.current_page = new_page page_size = len(self.thumbnails_images) current_elements = self.chart_image_list[self.current_page * page_size:(self.current_page + 1) * page_size] for idx in range(page_size): row = int(idx / self.tb_grid_cols) col = idx % self.tb_grid_cols if idx < len(current_elements): # update image ... chart_path = current_elements[idx] current_img = cv2.imread(self.chart_dir + chart_path) current_img = cv2.cvtColor(current_img, cv2.COLOR_BGR2RGB) self.thumbnails_images[idx].set_image(current_img, self.tb_width, self.tb_height, keep_aspect=True) # center new image corner_x = self.tb_outer_margin + self.tb_col_width * col corner_y = self.tb_outer_margin + self.tb_row_height * row self.center_image_in_box(self.thumbnails_images[idx], corner_x, corner_y, self.tb_width, self.tb_height) # find annotation path ... relative_dir, img_filename = os.path.split(chart_path) img_base, ext = os.path.splitext(img_filename) # output dir output_dir = self.annotation_dir + relative_dir annotation_filename = output_dir + "/" + img_base + ".xml" # default : No annotations ... if os.path.exists(annotation_filename): # read the annotation ... print(annotation_filename) image_info = ImageInfo.FromXML(annotation_filename, current_img) status_ints = ImageInfo.GetAllStatuses(image_info) else: status_ints = ImageInfo.GetAllStatuses(None) status = self.gen_status_image(status_ints, self.tb_width, self.tb_progress_h, self.tb_progress_border) self.thumbnails_status[idx].set_image(status, self.tb_width, self.tb_progress_h) self.thumbnails_labels[idx].set_text(chart_path[1:]) self.thumbnails_images[idx].visible = True self.thumbnails_status[idx].visible = True self.thumbnails_labels[idx].visible = True else: # special case for the last page ... self.thumbnails_images[idx].visible = False self.thumbnails_status[idx].visible = False self.thumbnails_labels[idx].visible = False msg = "Page {0:d} of {1:d} ({2:d} elements)".format( self.current_page + 1, self.paginator.total_pages, len(self.chart_image_list)) self.lbl_page_descriptor.set_text(msg) if not refresh: self.selected_element = None self.update_selected_image(0) def update_selected_image(self, new_index): if new_index == self.selected_element: # no change in selection ... return self.selected_element = new_index page_size = len(self.thumbnails_images) for idx in range(page_size): if idx == self.selected_element: self.thumbnails_images[idx].border_width = 2 self.thumbnails_images[idx].border_color = (255, 0, 0) self.thumbnails_labels[idx].set_color((255, 0, 0)) # update image .... self.img_display.set_image( self.thumbnails_images[idx].original_image, self.container_image_info.width - 20, self.container_image_info.width - 20, True) self.center_image_in_box(self.img_display, 10, 10, self.container_image_info.width - 20, self.container_image_info.width - 20) # update image name ... self.img_title.set_text(self.thumbnails_labels[idx].text) else: self.thumbnails_images[idx].border_width = 0 self.thumbnails_images[idx].border_color = (0, 0, 0) self.thumbnails_labels[idx].set_color(self.text_color) def center_image_in_box(self, screen_image, x, y, width, height): assert isinstance(screen_image, ScreenImage) delta_x = int((width - screen_image.width) / 2) delta_y = int((height - screen_image.height) / 2) screen_image.position = (x + delta_x, y + delta_y) def thumbnail_image_click(self, thumbnail_image): # print((thumbnail_image.name, thumbnail_image.tag)) row, col = thumbnail_image.tag idx = row * self.tb_grid_cols + col self.update_selected_image(idx) def btn_annotate_click(self, button): # find image name based on current page page_size = len(self.thumbnails_images) current_elements = self.chart_image_list[self.current_page * page_size:(self.current_page + 1) * page_size] chart_path = current_elements[self.selected_element] # create the child sub-menu .... image_annotator = ChartImageAnnotator(self.size, self.chart_dir, self.annotation_dir, chart_path, self, self.admin_mode) image_annotator.prepare_screen() self.return_screen = image_annotator def btn_next_image_click(self, button): page_size = len(self.thumbnails_images) current_elements = self.chart_image_list[self.current_page * page_size:(self.current_page + 1) * page_size] if self.selected_element + 1 < len(current_elements): # move to next within same page ... self.update_selected_image(self.selected_element + 1) else: if self.current_page + 1 < self.paginator.total_pages: self.paginator.set_current_page(self.current_page + 1) self.load_page(self.paginator, self.current_page + 1, True) self.update_selected_image(0) else: print("Current element is the last on the list of images") def btn_prev_image_click(self, button): page_size = len(self.thumbnails_images) if self.selected_element > 0: # move to the previous within the same page ... self.update_selected_image(self.selected_element - 1) else: if self.current_page > 0: self.paginator.set_current_page(self.current_page - 1) self.load_page(self.paginator, self.current_page - 1, True) self.update_selected_image(page_size - 1) else: print("Current element is the first on the list of images")
def create_controllers(self): button_text_color = (35, 50, 20) button_back_color = (228, 228, 228) # thumbnail grid parameters tb_empty = np.zeros((3, 4, 3), dtype=np.uint8) default_status = self.gen_status_image([0] * 6, self.tb_width, self.tb_progress_h, self.tb_progress_border) # create the thumbnail container .... tb_contained_width = self.tb_col_width * self.tb_grid_cols + self.tb_outer_margin tb_contained_height = self.tb_row_height * self.tb_grid_rows + self.tb_outer_margin self.container_thumbnails = ScreenContainer( "container_nav_buttons", (tb_contained_width, tb_contained_height), back_color=(15, 30, 50)) self.container_thumbnails.position = (10, 10) self.elements.append(self.container_thumbnails) # generate the default grid .... self.thumbnails_images = [] self.thumbnails_status = [] self.thumbnails_labels = [] for row in range(self.tb_grid_rows): for col in range(self.tb_grid_cols): corner_x = self.tb_outer_margin + self.tb_col_width * col corner_y = self.tb_outer_margin + self.tb_row_height * row tb_image = ScreenImage("tb_image_{0:d}_{1:d}".format(row, col), tb_empty, self.tb_width, self.tb_height, True) tb_image.position = (corner_x, corner_y) tb_image.tag = (row, col) tb_image.click_callback = self.thumbnail_image_click self.container_thumbnails.append(tb_image) self.thumbnails_images.append(tb_image) tb_status = ScreenImage( "tb_status_{0:d}_{1:d}".format(row, col), default_status, self.tb_width, self.tb_progress_h) tb_status.position = (corner_x, tb_image.get_bottom() + self.tb_inner_margin) self.container_thumbnails.append(tb_status) self.thumbnails_status.append(tb_status) tb_label = ScreenLabel("tb_label_{0:d}_{1:d}".format(row, col), "<IMAGE FILE NAME>", 12, self.tb_width, centered=1) tb_label.position = (corner_x, tb_status.get_bottom() + self.tb_inner_margin) tb_label.background = self.container_thumbnails.back_color tb_label.set_color(self.text_color) self.container_thumbnails.append(tb_label) self.thumbnails_labels.append(tb_label) # the paginator ... self.paginator = ScreenPaginator("tb_paginator", 16, self.tb_grid_cols * self.tb_grid_rows, len(self.chart_image_list), self.container_thumbnails.width, text_color=button_text_color, back_color=button_back_color) self.paginator.position = (self.container_thumbnails.get_left(), self.container_thumbnails.get_bottom() + 20) self.paginator.page_selected_callback = self.load_page self.elements.append(self.paginator) img_container_width = self.width - 45 - self.container_thumbnails.width self.lbl_page_descriptor = ScreenLabel( "lbl_page_descriptor", "Page XXX of YYY (ZZZZZ elements)", 16, img_container_width) self.lbl_page_descriptor.position = ( self.container_thumbnails.get_right() + 15, self.container_thumbnails.get_top()) self.lbl_page_descriptor.background = self.general_background self.lbl_page_descriptor.set_color(self.text_color) self.elements.append(self.lbl_page_descriptor) img_container_height = int(img_container_width) + 100 self.container_image_info = ScreenContainer( "container_image_info", (img_container_width, img_container_height), back_color=(15, 30, 50)) self.container_image_info.position = ( self.lbl_page_descriptor.get_left(), self.lbl_page_descriptor.get_bottom() + 10) self.elements.append(self.container_image_info) base_img = np.zeros((2, 2, 3), np.uint8) self.img_display = ScreenImage("img_display", base_img, self.container_image_info.width - 20, self.container_image_info.width - 20, True) self.img_display.position = (10, 10) self.container_image_info.append(self.img_display) self.img_title = ScreenLabel("img_title", "<Image File Name>" * 10, 16, self.img_display.width) self.img_title.position = (10, self.img_display.get_bottom() + 10) self.img_title.background = self.container_image_info.back_color self.img_title.set_color(self.text_color) self.container_image_info.append(self.img_title) self.btn_label_image = ScreenButton("btn_label_image", "Annotate", 18, 100, text_color=button_text_color, back_color=button_back_color) self.btn_label_image.position = (int( (self.container_image_info.width - self.btn_label_image.width) / 2), self.img_title.get_bottom() + 10) self.btn_label_image.click_callback = self.btn_annotate_click self.container_image_info.append(self.btn_label_image) self.btn_prev_image = ScreenButton("btn_prev_image", "Previous", 18, 100, text_color=button_text_color, back_color=button_back_color) self.btn_prev_image.position = (self.btn_label_image.get_left() - self.btn_prev_image.width - 20, self.img_title.get_bottom() + 10) self.btn_prev_image.click_callback = self.btn_prev_image_click self.container_image_info.append(self.btn_prev_image) self.btn_next_image = ScreenButton("btn_next_image", "Next", 18, 100, text_color=button_text_color, back_color=button_back_color) self.btn_next_image.position = (self.btn_label_image.get_right() + 20, self.img_title.get_bottom() + 10) self.btn_next_image.click_callback = self.btn_next_image_click self.container_image_info.append(self.btn_next_image) self.img_title.set_text("[Select Image]") # ... general ... self.btn_exit = ScreenButton("btn_exit", "Exit", 18, 100, text_color=button_text_color, back_color=button_back_color) self.btn_exit.position = (self.width - 15 - self.btn_exit.width, self.height - 15 - self.btn_exit.height) self.btn_exit.click_callback = self.btn_exit_click self.elements.append(self.btn_exit)
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)
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 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)
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 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)