class BaseImageAnnotator(Screen): # four view modes for the image ... ViewModeRawData = 0 ViewModeInvertedData = 1 ViewModeGrayData = 2 ViewModeRawNoData = 3 ViewModeInvertedNoData = 4 ViewModeGrayNoData = 5 def __init__(self, title, size): Screen.__init__(self, title, size) self.base_rgb_image = None self.base_inv_image = None self.base_gray_image = None self.view_mode = BaseImageAnnotator.ViewModeRawData self.view_scale = 1.0 self.view_overlay_opacity = 1.0 self.container_view_buttons = None self.lbl_zoom = None self.btn_zoom_reduce = None self.btn_zoom_increase = None self.btn_zoom_zero = None self.lbl_view_data = None self.btn_view_raw_data = None self.btn_view_inv_data = None self.btn_view_gray_data = None self.lbl_view_clear = None self.btn_view_raw_clear = None self.btn_view_inv_clear = None self.btn_view_gray_clear = None self.container_images = None self.canvas_select = None self.canvas_display = None self.img_main = None def create_image_annotator_controls(self, container_top, container_width, general_background, text_color, button_text_color, button_back_color): # View panel with view control buttons self.container_view_buttons = ScreenContainer( "container_view_buttons", (container_width, 160), back_color=general_background) self.container_view_buttons.position = ( self.width - self.container_view_buttons.width - 10, container_top) self.elements.append(self.container_view_buttons) button_2_width = 130 button_2_left = int(container_width * 0.25) - button_2_width / 2 button_2_right = int(container_width * 0.75) - button_2_width / 2 # zoom .... self.lbl_zoom = ScreenLabel("lbl_zoom", "Zoom: 100%", 21, 290, 1) self.lbl_zoom.position = (5, 5) self.lbl_zoom.set_background(general_background) self.lbl_zoom.set_color(text_color) self.container_view_buttons.append(self.lbl_zoom) self.btn_zoom_reduce = ScreenButton("btn_zoom_reduce", "[ - ]", 21, 90) self.btn_zoom_reduce.set_colors(button_text_color, button_back_color) self.btn_zoom_reduce.position = (10, self.lbl_zoom.get_bottom() + 10) self.btn_zoom_reduce.click_callback = self.btn_zoom_reduce_click self.container_view_buttons.append(self.btn_zoom_reduce) self.btn_zoom_increase = ScreenButton("btn_zoom_increase", "[ + ]", 21, 90) self.btn_zoom_increase.set_colors(button_text_color, button_back_color) self.btn_zoom_increase.position = (self.container_view_buttons.width - self.btn_zoom_increase.width - 10, self.lbl_zoom.get_bottom() + 10) self.btn_zoom_increase.click_callback = self.btn_zoom_increase_click self.container_view_buttons.append(self.btn_zoom_increase) self.btn_zoom_zero = ScreenButton("btn_zoom_zero", "100%", 21, 90) self.btn_zoom_zero.set_colors(button_text_color, button_back_color) self.btn_zoom_zero.position = ( (self.container_view_buttons.width - self.btn_zoom_zero.width) / 2, self.lbl_zoom.get_bottom() + 10) self.btn_zoom_zero.click_callback = self.btn_zoom_zero_click self.container_view_buttons.append(self.btn_zoom_zero) # --- # Data views button_4_width = 65 button_4_gap = int((container_width - 20 - button_4_width * 4) / 3) button_4_left_1 = 10 button_4_left_2 = 10 + (button_4_width + button_4_gap) button_4_left_3 = 10 + (button_4_width + button_4_gap) * 2 button_4_left_4 = 10 + (button_4_width + button_4_gap) * 3 self.lbl_view_data = ScreenLabel("lbl_view_data", "Data", 18, button_4_width) self.lbl_view_data.position = (button_4_left_1, self.btn_zoom_zero.get_bottom() + 10) self.lbl_view_data.set_background(general_background) self.lbl_view_data.set_color(text_color) self.container_view_buttons.append(self.lbl_view_data) self.btn_view_raw_data = ScreenButton("btn_view_raw_data", "RGB", 18, button_4_width) self.btn_view_raw_data.set_colors(button_text_color, button_back_color) self.btn_view_raw_data.position = (button_4_left_2, self.btn_zoom_zero.get_bottom() + 10) self.btn_view_raw_data.click_callback = self.btn_view_raw_data_click self.container_view_buttons.append(self.btn_view_raw_data) self.btn_view_inv_data = ScreenButton("btn_view_inv_data", "INV", 18, button_4_width) self.btn_view_inv_data.set_colors(button_text_color, button_back_color) self.btn_view_inv_data.position = (button_4_left_3, self.btn_zoom_zero.get_bottom() + 10) self.btn_view_inv_data.click_callback = self.btn_view_inv_data_click self.container_view_buttons.append(self.btn_view_inv_data) self.btn_view_gray_data = ScreenButton("btn_view_gray", "Gray", 18, button_4_width) self.btn_view_gray_data.set_colors(button_text_color, button_back_color) self.btn_view_gray_data.position = (button_4_left_4, self.btn_zoom_zero.get_bottom() + 10) self.btn_view_gray_data.click_callback = self.btn_view_gray_data_click self.container_view_buttons.append(self.btn_view_gray_data) # ------ # clear views ... self.lbl_view_clear = ScreenLabel("lbl_view_clear", "Clear", 18, button_4_width) self.lbl_view_clear.position = (button_4_left_1, self.btn_view_raw_data.get_bottom() + 10) self.lbl_view_clear.set_background(general_background) self.lbl_view_clear.set_color(text_color) self.container_view_buttons.append(self.lbl_view_clear) self.btn_view_raw_clear = ScreenButton("btn_view_raw_clear", "RGB", 21, button_4_width) self.btn_view_raw_clear.set_colors(button_text_color, button_back_color) self.btn_view_raw_clear.position = ( button_4_left_2, self.btn_view_raw_data.get_bottom() + 10) self.btn_view_raw_clear.click_callback = self.btn_view_raw_clear_click self.container_view_buttons.append(self.btn_view_raw_clear) self.btn_view_inv_clear = ScreenButton("btn_view_inv_clear", "INV", 21, button_4_width) self.btn_view_inv_clear.set_colors(button_text_color, button_back_color) self.btn_view_inv_clear.position = ( button_4_left_3, self.btn_view_raw_data.get_bottom() + 10) self.btn_view_inv_clear.click_callback = self.btn_view_inv_clear_click self.container_view_buttons.append(self.btn_view_inv_clear) self.btn_view_gray_clear = ScreenButton("btn_view_gray_clear", "Gray", 21, button_4_width) self.btn_view_gray_clear.set_colors(button_text_color, button_back_color) self.btn_view_gray_clear.position = ( button_4_left_4, self.btn_view_raw_data.get_bottom() + 10) self.btn_view_gray_clear.click_callback = self.btn_view_gray_clear_click self.container_view_buttons.append(self.btn_view_gray_clear) image_width = self.width - self.container_view_buttons.width - 30 image_height = self.height - container_top - 10 self.container_images = ScreenContainer("container_images", (image_width, image_height), back_color=(0, 0, 0)) self.container_images.position = (10, container_top) self.elements.append(self.container_images) # ... image objects ... tempo_blank = np.zeros((50, 50, 3), np.uint8) tempo_blank[:, :, :] = 255 self.img_main = ScreenImage("img_main", tempo_blank, 0, 0, True, cv2.INTER_NEAREST) self.img_main.position = (0, 0) self.img_main.mouse_button_down_callback = self.img_main_mouse_button_down self.img_main.double_click_callback = self.img_main_mouse_double_click self.container_images.append(self.img_main) self.canvas_select = ScreenCanvas("canvas_select", 100, 100) self.canvas_select.position = (0, 0) self.canvas_select.locked = True self.canvas_select.object_edited_callback = self.canvas_select_object_edited # self.canvas_select.object_selected_callback = self.canvas_select_selection_changed self.container_images.append(self.canvas_select) # default selection objects # 1) A polygon .... base_points = np.array([[10, 10], [20, 10], [20, 20], [10, 20]], dtype=np.float64) self.canvas_select.add_polygon_element("selection_polygon", base_points) self.canvas_select.elements["selection_polygon"].visible = False # 2) A rectangle .... self.canvas_select.add_rectangle_element("selection_rectangle", 10, 10, 10, 10) self.canvas_select.elements["selection_rectangle"].visible = False self.canvas_display = ScreenCanvas("canvas_display", 100, 100) self.canvas_display.position = (0, 0) self.canvas_display.locked = True self.canvas_display.object_edited_callback = self.canvas_display_object_edited self.container_images.append(self.canvas_display) def copy_view(self, other_window): # buggy ... need to fix some canvas sizing issues .... """ self.view_scale = other_window.view_scale self.view_mode = other_window.view_mode self.update_current_view(True) self.container_images.v_scroll.active = other_window.container_images.v_scroll.active self.container_images.v_scroll.value = other_window.container_images.v_scroll.value self.container_images.h_scroll.active = other_window.container_images.h_scroll.active self.container_images.h_scroll.value = other_window.container_images.h_scroll.value """ pass def btn_zoom_reduce_click(self, button): if self.view_scale <= 1.0: # reduce in quarters ... self.update_view_scale(self.view_scale - 0.25) else: # reduce in halves .... self.update_view_scale(self.view_scale - 0.50) def btn_zoom_increase_click(self, button): if self.view_scale < 1.0: # increase in quarters ... self.update_view_scale(self.view_scale + 0.25) else: # increase in halves ... self.update_view_scale(self.view_scale + 0.50) def btn_zoom_zero_click(self, button): self.update_view_scale(1.0) def btn_view_raw_data_click(self, button): self.view_mode = BaseImageAnnotator.ViewModeRawData self.update_current_view() def btn_view_inv_data_click(self, button): self.view_mode = BaseImageAnnotator.ViewModeInvertedData self.update_current_view() def btn_view_gray_data_click(self, button): self.view_mode = BaseImageAnnotator.ViewModeGrayData self.update_current_view() def btn_view_raw_clear_click(self, button): self.view_mode = BaseImageAnnotator.ViewModeRawNoData self.update_current_view() def btn_view_inv_clear_click(self, button): self.view_mode = BaseImageAnnotator.ViewModeInvertedNoData self.update_current_view() def btn_view_gray_clear_click(self, button): self.view_mode = BaseImageAnnotator.ViewModeGrayNoData self.update_current_view() def update_view_scale(self, new_scale): prev_scale = self.view_scale if 0.25 <= new_scale <= 4.0: self.view_scale = new_scale else: return # keep previous offsets ... scroll_offset_y = self.container_images.v_scroll.value if self.container_images.v_scroll.active else 0 scroll_offset_x = self.container_images.h_scroll.value if self.container_images.h_scroll.active else 0 prev_center_y = scroll_offset_y + self.container_images.height / 2 prev_center_x = scroll_offset_x + self.container_images.width / 2 # compute new scroll bar offsets scale_factor = (new_scale / prev_scale) new_off_y = prev_center_y * scale_factor - self.container_images.height / 2 new_off_x = prev_center_x * scale_factor - self.container_images.width / 2 # update view .... self.update_current_view(True) # set offsets if self.container_images.v_scroll.active and 0 <= new_off_y <= self.container_images.v_scroll.max: self.container_images.v_scroll.value = new_off_y if self.container_images.h_scroll.active and 0 <= new_off_x <= self.container_images.h_scroll.max: self.container_images.h_scroll.value = new_off_x # re-scale objects from both canvas # ... Canvas for Selection # ....... selection polygon ... selection_polygon = self.canvas_select.elements["selection_polygon"] selection_polygon.points *= scale_factor # ....... selection rectangle ... selection_rectangle = self.canvas_select.elements[ "selection_rectangle"] selection_rectangle.x *= scale_factor selection_rectangle.y *= scale_factor selection_rectangle.w *= scale_factor selection_rectangle.h *= scale_factor # ... Canvas for display ... for polygon_name in self.canvas_display.elements: display_polygon = self.canvas_display.elements[polygon_name] display_polygon.points *= scale_factor # update scale text ... self.lbl_zoom.set_text("Zoom: " + str(int(round(self.view_scale * 100, 0))) + "%") def update_current_view(self, resized=False): if self.view_mode in [ BaseImageAnnotator.ViewModeGrayData, BaseImageAnnotator.ViewModeGrayNoData ]: # gray scale mode base_image = self.base_gray_image elif self.view_mode in [ BaseImageAnnotator.ViewModeInvertedData, BaseImageAnnotator.ViewModeInvertedNoData ]: if self.base_inv_image is None: self.base_inv_image = 255 - self.base_rgb_image base_image = self.base_inv_image else: base_image = self.base_rgb_image h, w, c = base_image.shape modified_image = base_image.copy() if self.view_mode in [ BaseImageAnnotator.ViewModeRawData, BaseImageAnnotator.ViewModeInvertedData, BaseImageAnnotator.ViewModeGrayData ]: self.canvas_display.visible = True # This function must be implemented by the child class self.custom_view_update(modified_image) else: self.canvas_display.visible = False # finally, resize ... modified_image = cv2.resize( modified_image, (int(w * self.view_scale), int(h * self.view_scale)), interpolation=cv2.INTER_NEAREST) if self.view_overlay_opacity < 1.0: resized_base = cv2.resize( base_image, (int(w * self.view_scale), int(h * self.view_scale)), interpolation=cv2.INTER_NEAREST) # add transparency effect ... modified_image = resized_base.astype( np.float64) * 0.5 + modified_image.astype(np.float64) * 0.5 modified_image = modified_image.astype(np.uint8) # update canvas size .... self.canvas_select.height, self.canvas_select.width, _ = modified_image.shape self.canvas_display.height, self.canvas_display.width, _ = modified_image.shape # replace/update image self.img_main.set_image(modified_image, 0, 0, True, cv2.INTER_NEAREST) if resized: self.container_images.recalculate_size() def img_main_mouse_button_down(self, img, pos, button): pass def img_main_mouse_double_click(self, element, pos, button): pass def canvas_select_object_edited(self, canvas, element_name): pass def canvas_display_object_edited(self, canvas, element_name): pass
class 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)
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 ChartImageAnnotator(BaseImageAnnotator): ModeNavigate = 0 ModeEditPanels = 1 ModeSelectPanelSplit = 2 ModeConfirmOverwritePanels = 3 ModeEditClass = 4 ModeConfirmOverwriteClass = 5 ModeConfirmExit = 6 SplitOperationXSplit = 0 SplitOperationYSplit = 1 SplitOperationMerge = 2 WaitModeNone = 0 WaitModeText = 1 WaitModeLegend = 2 WaitModeAxes = 3 WaitModeData = 4 def __init__(self, size, chart_dir, annotation_dir, relative_path, parent_menu, admin_mode): BaseImageAnnotator.__init__(self, "Chart Ground Truth Annotation Interface", size) self.general_background = (20, 85, 50) self.text_color = (255, 255, 255) self.parent = parent_menu self.chart_dir = chart_dir self.annotation_dir = annotation_dir self.relative_path = relative_path self.admin_mode = admin_mode self.time_stats = TimeStats() self.in_menu_time_start = time.time() self.wait_mode = ChartImageAnnotator.WaitModeNone # find output path ... relative_dir, img_filename = os.path.split(self.relative_path) img_base, ext = os.path.splitext(img_filename) # output dir self.output_dir = self.annotation_dir + relative_dir self.annotation_filename = self.output_dir + "/" + img_base + ".xml" # first ... load image .... self.base_rgb_image = cv2.imread(self.chart_dir + self.relative_path) self.base_rgb_image = cv2.cvtColor(self.base_rgb_image, cv2.COLOR_BGR2RGB) # ... and cache the gray-scale version self.base_gray_image = np.zeros(self.base_rgb_image.shape, self.base_rgb_image.dtype) self.base_gray_image[:, :, 0] = cv2.cvtColor(self.base_rgb_image, cv2.COLOR_RGB2GRAY) self.base_gray_image[:, :, 1] = self.base_gray_image[:, :, 0].copy() self.base_gray_image[:, :, 2] = self.base_gray_image[:, :, 0].copy() # load annotations for this image .... (if any) if os.path.exists(self.annotation_filename): # annotation found! self.image_info = ImageInfo.FromXML(self.annotation_filename, self.base_rgb_image) else: # create an empty annotation self.image_info = ImageInfo.CreateDefault(self.base_rgb_image) self.split_panel_operation = None self.tempo_panel_tree = None self.selected_panel = 0 self.unsaved_changes = False self.elements.back_color = self.general_background self.label_title = None self.container_confirm_buttons = None self.lbl_confirm_message = None self.btn_confirm_cancel = None self.btn_confirm_accept = None self.container_panels_buttons = None self.lbl_panels_title = None self.btn_label_panels = None self.btn_panels_verify = None self.lbl_panels_current = None self.btn_panels_prev = None self.btn_panels_next = None self.container_annotation_buttons = None self.lbl_edit_title = None self.btn_edit_class = None self.btn_edit_text = None self.btn_edit_legend = None self.btn_edit_axis = None self.btn_edit_data = None self.btn_verify_class = None self.btn_verify_text = None self.btn_verify_legend = None self.btn_verify_axis = None self.btn_verify_data = None self.container_split_panels = None self.lbl_split_panel_title = None self.btn_split_panel_horizontal = None self.btn_split_panel_vertical = None self.btn_merge_panel = None self.btn_split_return = None self.container_classify_panels = None self.lbl_class_panel_title = None self.lbx_class_panel_class = None self.btn_class_panel_continue = None self.btn_save = None self.btn_auto_check = None self.btn_return = None # generate the interface! 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 - {0:s}".format(self.relative_path), 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 self.create_image_annotator_controls(container_top, container_width, self.general_background, self.text_color, button_text_color, button_back_color) # =========================== # 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) # =========================== # panel for multi-panel figure options panel_buttons_bg = (10, 55, 25) self.container_panels_buttons = ScreenContainer( "container_panels_buttons", (container_width, 160), back_color=panel_buttons_bg) self.container_panels_buttons.position = ( self.container_view_buttons.get_left(), self.container_view_buttons.get_bottom() + 20) self.elements.append(self.container_panels_buttons) self.lbl_panels_title = ScreenLabel("lbl_panels_title", "Options for Multi-Panel Images", 21, 290, 1) self.lbl_panels_title.position = (5, 5) self.lbl_panels_title.set_background(panel_buttons_bg) self.lbl_panels_title.set_color(self.text_color) self.container_panels_buttons.append(self.lbl_panels_title) if self.admin_mode: self.btn_label_panels = ScreenButton("btn_label_panels", "Annotate", 21, button_2_width) self.btn_label_panels.position = ( button_2_left, self.lbl_panels_title.get_bottom() + 10) self.btn_panels_verify = ScreenButton("btn_panels_verify", "Mark Verified", 21, button_2_width) self.btn_panels_verify.position = ( button_2_right, self.lbl_panels_title.get_bottom() + 10) self.btn_panels_verify.set_colors(button_text_color, button_back_color) self.btn_panels_verify.click_callback = self.btn_panels_verify_click self.container_panels_buttons.append(self.btn_panels_verify) else: self.btn_label_panels = ScreenButton("btn_label_panels", "Annotate Panels", 21, button_width) self.btn_label_panels.position = ( button_left, self.lbl_panels_title.get_bottom() + 10) self.btn_panels_verify = None self.btn_label_panels.set_colors(button_text_color, button_back_color) self.btn_label_panels.click_callback = self.btn_label_panels_click self.container_panels_buttons.append(self.btn_label_panels) self.lbl_panels_current = ScreenLabel("lbl_panels_title", "Current Panel: 1 / 1", 21, 290, 1) self.lbl_panels_current.position = ( 5, self.btn_label_panels.get_bottom() + 20) self.lbl_panels_current.set_background(panel_buttons_bg) self.lbl_panels_current.set_color(self.text_color) self.container_panels_buttons.append(self.lbl_panels_current) self.btn_panels_prev = ScreenButton("btn_panels_prev", "Previous", 21, 130) self.btn_panels_prev.set_colors(button_text_color, button_back_color) self.btn_panels_prev.position = (10, self.lbl_panels_current.get_bottom() + 10) self.btn_panels_prev.click_callback = self.btn_panels_prev_click self.container_panels_buttons.append(self.btn_panels_prev) self.btn_panels_next = ScreenButton("btn_panels_next", "Next", 21, 130) self.btn_panels_next.set_colors(button_text_color, button_back_color) self.btn_panels_next.position = (self.container_panels_buttons.width - self.btn_panels_next.width - 10, self.lbl_panels_current.get_bottom() + 10) self.btn_panels_next.click_callback = self.btn_panels_next_click self.container_panels_buttons.append(self.btn_panels_next) # ================================================= self.container_annotation_buttons = ScreenContainer( "container_annotation_buttons", (container_width, 380), back_color=panel_buttons_bg) self.container_annotation_buttons.position = ( self.container_panels_buttons.get_left(), self.container_panels_buttons.get_bottom() + 20) self.elements.append(self.container_annotation_buttons) self.lbl_edit_title = ScreenLabel("lbl_edit_title", "Options for Current Panel", 21, 290, 1) self.lbl_edit_title.position = (5, 5) self.lbl_edit_title.set_background(panel_buttons_bg) self.lbl_edit_title.set_color(self.text_color) self.container_annotation_buttons.append(self.lbl_edit_title) if self.admin_mode: edit_button_left = button_2_left edit_button_width = 180 verify_button_left = edit_button_left + edit_button_width + 10 verify_button_width = 90 else: edit_button_left = button_left edit_button_width = button_width verify_button_left = 0 verify_button_width = 0 self.btn_edit_class = ScreenButton("btn_edit_class", "Edit Classification", 21, edit_button_width) self.btn_edit_class.set_colors(button_text_color, button_back_color) self.btn_edit_class.position = (edit_button_left, self.lbl_edit_title.get_bottom() + 10) self.btn_edit_class.click_callback = self.btn_edit_class_click self.container_annotation_buttons.append(self.btn_edit_class) self.btn_edit_text = ScreenButton("btn_edit_text", "Edit Text", 21, edit_button_width) self.btn_edit_text.set_colors(button_text_color, button_back_color) self.btn_edit_text.position = (edit_button_left, self.btn_edit_class.get_bottom() + 10) self.btn_edit_text.click_callback = self.btn_edit_text_click self.container_annotation_buttons.append(self.btn_edit_text) self.btn_edit_legend = ScreenButton("btn_edit_legend", "Edit Legend", 21, edit_button_width) self.btn_edit_legend.set_colors(button_text_color, button_back_color) self.btn_edit_legend.position = (edit_button_left, self.btn_edit_text.get_bottom() + 10) self.btn_edit_legend.click_callback = self.btn_edit_legend_click self.container_annotation_buttons.append(self.btn_edit_legend) self.btn_edit_axis = ScreenButton("btn_edit_axis", "Edit Axis", 21, edit_button_width) self.btn_edit_axis.set_colors(button_text_color, button_back_color) self.btn_edit_axis.position = (edit_button_left, self.btn_edit_legend.get_bottom() + 10) self.btn_edit_axis.click_callback = self.btn_edit_axis_click self.container_annotation_buttons.append(self.btn_edit_axis) self.btn_edit_data = ScreenButton("btn_edit_data", "Edit Data", 21, edit_button_width) self.btn_edit_data.set_colors(button_text_color, button_back_color) self.btn_edit_data.position = (edit_button_left, self.btn_edit_axis.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_auto_check = ScreenButton("btn_auto_check", "Auto Check", 21, button_2_width) self.btn_auto_check.set_colors(button_text_color, button_back_color) self.btn_auto_check.position = (button_2_left, self.btn_edit_data.get_bottom() + 30) self.btn_auto_check.click_callback = self.btn_auto_check_click self.container_annotation_buttons.append(self.btn_auto_check) self.btn_save = ScreenButton("btn_save", "Save", 21, button_2_width) self.btn_save.set_colors(button_text_color, button_back_color) self.btn_save.position = (button_2_right, self.btn_edit_data.get_bottom() + 30) self.btn_save.click_callback = self.btn_save_click self.container_annotation_buttons.append(self.btn_save) self.btn_return = ScreenButton("btn_return", "Return", 21, button_width) self.btn_return.set_colors(button_text_color, button_back_color) self.btn_return.position = (button_left, self.container_annotation_buttons.height - self.btn_return.height - 10) self.btn_return.click_callback = self.btn_return_click self.container_annotation_buttons.append(self.btn_return) if self.admin_mode: self.btn_verify_class = ScreenButton("btn_verify_class", "Verified", 21, verify_button_width) self.btn_verify_class.set_colors(button_text_color, button_back_color) self.btn_verify_class.position = (verify_button_left, self.btn_edit_class.get_top()) self.btn_verify_class.click_callback = self.btn_verify_class_click self.container_annotation_buttons.append(self.btn_verify_class) self.btn_verify_text = ScreenButton("btn_verify_text", "Verified", 21, verify_button_width) self.btn_verify_text.set_colors(button_text_color, button_back_color) self.btn_verify_text.position = (verify_button_left, self.btn_edit_text.get_top()) self.btn_verify_text.click_callback = self.btn_verify_text_click self.container_annotation_buttons.append(self.btn_verify_text) self.btn_verify_legend = ScreenButton("btn_verify_legend", "Verified", 21, verify_button_width) self.btn_verify_legend.set_colors(button_text_color, button_back_color) self.btn_verify_legend.position = (verify_button_left, self.btn_edit_legend.get_top()) self.btn_verify_legend.click_callback = self.btn_verify_legend_click self.container_annotation_buttons.append(self.btn_verify_legend) self.btn_verify_axis = ScreenButton("btn_verify_axis", "Verified", 21, verify_button_width) self.btn_verify_axis.set_colors(button_text_color, button_back_color) self.btn_verify_axis.position = (verify_button_left, self.btn_edit_axis.get_top()) self.btn_verify_axis.click_callback = self.btn_verify_axis_click self.container_annotation_buttons.append(self.btn_verify_axis) self.btn_verify_data = ScreenButton("btn_verify_data", "Verified", 21, verify_button_width) self.btn_verify_data.set_colors(button_text_color, button_back_color) self.btn_verify_data.position = (verify_button_left, self.btn_edit_data.get_top()) self.btn_verify_data.click_callback = self.btn_verify_data_click self.container_annotation_buttons.append(self.btn_verify_data) # ======= self.container_split_panels = ScreenContainer( "container_split_panels", (container_width, 220), back_color=panel_buttons_bg) self.container_split_panels.position = ( self.container_view_buttons.get_left(), self.container_view_buttons.get_bottom() + 20) self.elements.append(self.container_split_panels) self.lbl_split_panel_title = ScreenLabel("lbl_split_panel_title", "Edit Panels Options", 21, 290, 1) self.lbl_split_panel_title.position = (5, 5) self.lbl_split_panel_title.set_background(panel_buttons_bg) self.lbl_split_panel_title.set_color(self.text_color) self.container_split_panels.append(self.lbl_split_panel_title) self.btn_split_panel_horizontal = ScreenButton( "btn_split_panel_horizontal", "Y - Split", 21, button_width) self.btn_split_panel_horizontal.set_colors(button_text_color, button_back_color) self.btn_split_panel_horizontal.position = ( button_left, self.lbl_split_panel_title.get_bottom() + 10) self.btn_split_panel_horizontal.click_callback = self.btn_split_panel_horizontal_click self.container_split_panels.append(self.btn_split_panel_horizontal) self.btn_split_panel_vertical = ScreenButton( "btn_split_panel_vertical", "X - Split", 21, button_width) self.btn_split_panel_vertical.set_colors(button_text_color, button_back_color) self.btn_split_panel_vertical.position = ( button_left, self.btn_split_panel_horizontal.get_bottom() + 10) self.btn_split_panel_vertical.click_callback = self.btn_split_panel_vertical_click self.container_split_panels.append(self.btn_split_panel_vertical) self.btn_merge_panel = ScreenButton("btn_merge_panel", "Merge Panels", 21, button_width) self.btn_merge_panel.set_colors(button_text_color, button_back_color) self.btn_merge_panel.position = ( button_left, self.btn_split_panel_vertical.get_bottom() + 10) self.btn_merge_panel.click_callback = self.btn_merge_panel_click self.container_split_panels.append(self.btn_merge_panel) self.btn_split_return = ScreenButton("btn_split_return", "Return", 21, button_width) self.btn_split_return.set_colors(button_text_color, button_back_color) self.btn_split_return.position = (button_left, self.btn_merge_panel.get_bottom() + 10) self.btn_split_return.click_callback = self.btn_split_return_click self.container_split_panels.append(self.btn_split_return) self.container_split_panels.visible = False # ===================================== self.container_classify_panels = ScreenContainer( "container_classify_panels", (container_width, 420), back_color=panel_buttons_bg) self.container_classify_panels.position = ( self.container_view_buttons.get_left(), self.container_view_buttons.get_bottom() + 20) self.elements.append(self.container_classify_panels) self.lbl_class_panel_title = ScreenLabel("lbl_class_panel_title", "Classify Panel", 21, 290, 1) self.lbl_class_panel_title.position = (5, 5) self.lbl_class_panel_title.set_background(panel_buttons_bg) self.lbl_class_panel_title.set_color(self.text_color) self.container_classify_panels.append(self.lbl_class_panel_title) self.lbx_class_panel_class = ScreenTextlist( "lbx_class_panel_class", (container_width - 20, 320), 22) self.lbx_class_panel_class.position = ( 10, self.lbl_class_panel_title.get_bottom() + 10) self.container_classify_panels.append(self.lbx_class_panel_class) self.add_chart_types() self.btn_class_panel_continue = ScreenButton( "btn_class_panel_continue", "Continue", 21, button_width) self.btn_class_panel_continue.set_colors(button_text_color, button_back_color) self.btn_class_panel_continue.position = ( button_left, self.container_classify_panels.height - self.btn_class_panel_continue.height - 10) self.btn_class_panel_continue.click_callback = self.btn_class_panel_continue_click self.container_classify_panels.append(self.btn_class_panel_continue) self.container_classify_panels.visible = False # ======================================= self.set_editor_mode(ChartImageAnnotator.ModeNavigate) self.update_panel_info() def add_chart_types(self): self.lbx_class_panel_class.add_option(str(ChartInfo.TypeNonChart), "Non-Chart") self.lbx_class_panel_class.add_option(str(ChartInfo.TypeLine), "Line Chart") self.lbx_class_panel_class.add_option(str(ChartInfo.TypeScatter), "Scatter Chart") self.lbx_class_panel_class.add_option( str(ChartInfo.TypeBar) + "-" + str(ChartInfo.OrientationHorizontal), "Bar Chart (Horizontal)") self.lbx_class_panel_class.add_option( str(ChartInfo.TypeBar) + "-" + str(ChartInfo.OrientationVertical), "Bar Chart (Vertical)") self.lbx_class_panel_class.add_option( str(ChartInfo.TypeBox) + "-" + str(ChartInfo.OrientationHorizontal), "Box Chart (Horizontal)") self.lbx_class_panel_class.add_option( str(ChartInfo.TypeBox) + "-" + str(ChartInfo.OrientationVertical), "Box Chart (Vertical)") def prepare_screen(self): if self.wait_mode != ChartImageAnnotator.WaitModeNone: # get delta of time on previous screen ... prepare to count towards this menu delta = self.get_reset_time_delta() # add delta on the corresponding process... if self.wait_mode == ChartImageAnnotator.WaitModeText: self.time_stats.time_text += delta elif self.wait_mode == ChartImageAnnotator.WaitModeLegend: self.time_stats.time_legend += delta elif self.wait_mode == ChartImageAnnotator.WaitModeAxes: self.time_stats.time_axes += delta elif self.wait_mode == ChartImageAnnotator.WaitModeData: self.time_stats.time_data += delta self.wait_mode = ChartImageAnnotator.WaitModeNone # call parent prepare screen ... Screen.prepare_screen(self) def btn_confirm_accept_click(self, button): if self.edition_mode == ChartImageAnnotator.ModeConfirmOverwritePanels: # commit changes .... # ... create an empty annotation ... self.image_info = ImageInfo.CreateDefault(self.base_rgb_image) # copy the panel structure ... self.image_info.panel_tree = PanelTree.Copy(self.tempo_panel_tree) # ... get the empty annotation for each panel ... self.image_info.reset_panels_info() # go back to navigation mode ... self.set_editor_mode(ChartImageAnnotator.ModeNavigate) self.update_current_view(False) self.selected_panel = 0 self.update_panel_info() self.unsaved_changes = True elif self.edition_mode == ChartImageAnnotator.ModeConfirmOverwriteClass: # commit changes .... # ... get selected class ... if "-" in self.lbx_class_panel_class.selected_option_value: # Chart type with orientation type_str, orientation_str = self.lbx_class_panel_class.selected_option_value.split( "-") type_value = int(type_str) orientation = int(orientation_str) else: # No orientation type_value = int( self.lbx_class_panel_class.selected_option_value) orientation = None if self.admin_mode: overwrite = input("Discard Chart Info (y/n)? ").lower() in [ "y", "yes", "1", "true" ] else: overwrite = True if overwrite: # ... create an empty panel annotation ... self.image_info.panels[self.selected_panel] = ChartInfo( type_value, orientation) else: # ... admin request simple overwrite of values which might lead to inconsistencies ... self.image_info.panels[self.selected_panel].type = type_value self.image_info.panels[ self.selected_panel].orientation = orientation # go back to navigation mode ... self.set_editor_mode(ChartImageAnnotator.ModeNavigate) self.update_current_view(False) elif self.edition_mode == ChartImageAnnotator.ModeConfirmExit: # return with unsaved changes lost print("Unsaved changes on " + self.relative_path + " were lost") delta = self.get_reset_time_delta() self.time_stats.time_main += delta self.parent.update_annotation_times(self.time_stats) self.parent.refresh_page() self.return_screen = self.parent def btn_confirm_cancel_click(self, button): if self.edition_mode == ChartImageAnnotator.ModeSelectPanelSplit: # simply go back ... self.set_editor_mode(ChartImageAnnotator.ModeEditPanels) elif self.edition_mode == ChartImageAnnotator.ModeConfirmOverwritePanels: # go back to navigation mode ... no changes commited self.set_editor_mode(ChartImageAnnotator.ModeNavigate) self.update_current_view(False) elif self.edition_mode == ChartImageAnnotator.ModeConfirmOverwriteClass: # go back to navigation mode ... no changes commited self.set_editor_mode(ChartImageAnnotator.ModeNavigate) self.update_current_view(False) elif self.edition_mode == ChartImageAnnotator.ModeConfirmExit: # go back to navigation mode ... no changes commited self.set_editor_mode(ChartImageAnnotator.ModeNavigate) self.update_current_view(False) def get_reset_time_delta(self): new_time = time.time() delta = new_time - self.in_menu_time_start self.in_menu_time_start = new_time return delta def btn_label_panels_click(self, button): self.time_stats.time_main += self.get_reset_time_delta() self.tempo_panel_tree = PanelTree.Copy(self.image_info.panel_tree) self.set_editor_mode(ChartImageAnnotator.ModeEditPanels) def btn_panels_prev_click(self, button): if self.selected_panel > 0: self.selected_panel -= 1 self.update_current_view(False) self.update_panel_info() def btn_panels_next_click(self, button): if self.selected_panel + 1 < len(self.image_info.panels): self.selected_panel += 1 self.update_current_view(False) self.update_panel_info() def btn_return_click(self, button): if not self.unsaved_changes: print("Edition for " + self.relative_path + " completed") delta = self.get_reset_time_delta() self.time_stats.time_main += delta self.parent.update_annotation_times(self.time_stats) self.parent.refresh_page() self.return_screen = self.parent else: # go into confirmation mode self.set_editor_mode(ChartImageAnnotator.ModeConfirmExit) def btn_save_click(self, button): # create dirs (if they don't exist) os.makedirs(self.output_dir, exist_ok=True) xml_str = self.image_info.to_XML() with open(self.annotation_filename, 'w', encoding="utf-8") as out_file: out_file.write(xml_str) print("Data saved to: " + self.annotation_filename) self.unsaved_changes = False def save_json_file(self, json_content, json_filename): with open(json_filename, "w") as tempo_file: tempo_str = json.dumps(json_content, indent="\t") tempo_file.write(tempo_str) def btn_auto_check_click(self, button): status = ImageInfo.GetAllStatuses(self.image_info) false_status = [(2 if val > 0 else 0) for val in status] panel_info = self.image_info.panels[self.selected_panel] self.unsaved_changes = True panel_info.properties["auto_check_passed"] = 0 # class ... if status[1] >= 1: try: tempo_json = ChartJSON_Exporter.prepare_chart_image_json( panel_info, false_status, 1, False) print("Task 1: Annotation Seems Okay!") self.save_json_file(tempo_json, "TEMPO_VALID_JSON.json") except Exception as e: print("Errors on Task 1 data (Chart Image Classification): " + str(e)) return # text detection, classification and recognition ... if status[2] >= 1: try: tempo_json = ChartJSON_Exporter.prepare_chart_image_json( panel_info, false_status, 3, False) print("Tasks 2 and 3: Annotation Seems Okay!") self.save_json_file(tempo_json, "TEMPO_VALID_JSON.json") except Exception as e: print( "Errors on Task 2 or 3 (Text Detection, Recognition, and Classification): " + str(e)) return if status[3] >= 1 and status[4] >= 1: try: tempo_json = ChartJSON_Exporter.prepare_chart_image_json( panel_info, false_status, 5, False) print("Tasks 4 and 5: Annotation Seems Okay!") self.save_json_file(tempo_json, "TEMPO_VALID_JSON.json") except Exception as e: print( "Errors on Task 4 and/or 5 (Axes and Legend Recognition): " + str(e)) return if status[5] >= 1: try: tempo_json = ChartJSON_Exporter.prepare_chart_image_json( panel_info, false_status, 7, False) print("Tasks 6a/6b: Annotation Seems Okay!") self.save_json_file(tempo_json, "TEMPO_VALID_JSON.json") except Exception as e: print("Errors on Task 6a/6b (Data Extraction): " + str(e)) return panel_info.properties["auto_check_passed"] = 1 print("Chart meets all annotation requirements!") def btn_edit_class_click(self, button): prev_key = self.get_image_class_key() self.lbx_class_panel_class.change_option_selected(prev_key) delta = self.get_reset_time_delta() self.time_stats.time_main += delta self.set_editor_mode(ChartImageAnnotator.ModeEditClass) def btn_edit_text_click(self, button): # prepare time count for text ... delta = self.get_reset_time_delta() self.time_stats.time_main += delta self.wait_mode = ChartImageAnnotator.WaitModeText panel_image = self.image_info.get_panel_image(self.selected_panel) text_annotator = ChartTextAnnotator( self.size, panel_image, self.image_info.panels[self.selected_panel], self, self.admin_mode) text_annotator.prepare_screen() # text_annotator.copy_view(self) self.return_screen = text_annotator def btn_edit_legend_click(self, button): # prepare time count for legend ... delta = self.get_reset_time_delta() self.time_stats.time_main += delta self.wait_mode = ChartImageAnnotator.WaitModeLegend panel_image = self.image_info.get_panel_image(self.selected_panel) legend_annotator = ChartLegendAnnotator( self.size, panel_image, self.image_info.panels[self.selected_panel], self) legend_annotator.prepare_screen() self.return_screen = legend_annotator def btn_edit_axis_click(self, button): # prepare time count for axis ... delta = self.get_reset_time_delta() self.time_stats.time_main += delta self.wait_mode = ChartImageAnnotator.WaitModeAxes panel_image = self.image_info.get_panel_image(self.selected_panel) axes_annotator = ChartAxesAnnotator( self.size, panel_image, self.image_info.panels[self.selected_panel], self) axes_annotator.prepare_screen() self.return_screen = axes_annotator def btn_edit_data_click(self, button): current_panel = self.image_info.panels[self.selected_panel] if not current_panel.check_classes(): print("Must select a valid chart type to annotate its data!") return if not current_panel.check_text(): print("Text must be annotated first!") return if not current_panel.check_axes(): print("Axes must be annotated first!") return panel_image = self.image_info.get_panel_image(self.selected_panel) if self.image_info.panels[ self.selected_panel].type == ChartInfo.TypeBar: data_annotator = BarChartAnnotator(self.size, panel_image, current_panel, self) elif self.image_info.panels[ self.selected_panel].type == ChartInfo.TypeBox: data_annotator = BoxChartAnnotator(self.size, panel_image, current_panel, self) elif self.image_info.panels[ self.selected_panel].type == ChartInfo.TypeLine: data_annotator = LineChartAnnotator(self.size, panel_image, current_panel, self) elif self.image_info.panels[ self.selected_panel].type == ChartInfo.TypeScatter: data_annotator = ScatterChartAnnotator(self.size, panel_image, current_panel, self) else: raise Exception("Not implemented!!") # prepare time count for data ... delta = self.get_reset_time_delta() self.time_stats.time_main += delta self.wait_mode = ChartImageAnnotator.WaitModeData data_annotator.prepare_screen() self.return_screen = data_annotator def custom_view_update(self, modified_image): if self.edition_mode in [ ChartImageAnnotator.ModeEditPanels, ChartImageAnnotator.ModeSelectPanelSplit, ChartImageAnnotator.ModeConfirmOverwritePanels ]: # get panels from temporary tree panel_nodes = self.tempo_panel_tree.root.get_leaves() temporary_tree = True else: # get from current tree panel_nodes = self.image_info.panel_tree.root.get_leaves() temporary_tree = False for idx, panel_node in enumerate(panel_nodes): # mark the panel boundaries ... if idx == self.selected_panel and self.edition_mode == ChartImageAnnotator.ModeNavigate: border_color = (0, 255, 0) else: border_color = (255, 0, 0) if not temporary_tree: # show the chart type (and orientation) chart_type, chart_orientation = self.image_info.panels[ idx].get_description() chart_desc = "{0:s} ({1:s})".format(chart_type, chart_orientation) font_scale = 2 font_face = cv2.FONT_HERSHEY_PLAIN font_thickness = 1 font_padding = 2 text_size, font_baseline = cv2.getTextSize( chart_desc, font_face, font_scale, font_thickness) modified_image[panel_node.y1:panel_node.y1 + text_size[1] + 10, panel_node.x1:panel_node.x1 + text_size[0] + 10] = 255 vertical_displacement = font_padding + text_size[1] text_loc = (panel_node.x1 + font_padding, panel_node.y1 + vertical_displacement) shadow_loc = (text_loc[0] + 2, text_loc[1] + 1) cv2.putText(modified_image, chart_desc, shadow_loc, font_face, font_scale, (0, 0, 0), thickness=font_thickness, lineType=cv2.LINE_AA) cv2.putText(modified_image, chart_desc, text_loc, font_face, font_scale, border_color, thickness=font_thickness, lineType=cv2.LINE_AA) cv2.rectangle(modified_image, (panel_node.x1, panel_node.y1), (panel_node.x2, panel_node.y2), border_color, thickness=2) # TODO: show here any relevant annotations on the modified image ... def btn_split_panel_horizontal_click(self, button): self.split_panel_operation = ChartImageAnnotator.SplitOperationYSplit self.set_editor_mode(ChartImageAnnotator.ModeSelectPanelSplit) def btn_split_panel_vertical_click(self, button): self.split_panel_operation = ChartImageAnnotator.SplitOperationXSplit self.set_editor_mode(ChartImageAnnotator.ModeSelectPanelSplit) def btn_merge_panel_click(self, button): self.split_panel_operation = ChartImageAnnotator.SplitOperationMerge self.set_editor_mode(ChartImageAnnotator.ModeSelectPanelSplit) def btn_split_return_click(self, button): # add time delta for panels ... from here, confirm or not will count towards main screen time delta = self.get_reset_time_delta() self.time_stats.time_panels += delta if self.tempo_panel_tree == self.image_info.panel_tree: # nothing changed ... self.set_editor_mode(ChartImageAnnotator.ModeNavigate) else: self.set_editor_mode( ChartImageAnnotator.ModeConfirmOverwritePanels) def set_editor_mode(self, new_mode): self.edition_mode = new_mode # Navigation mode ... self.container_panels_buttons.visible = ( self.edition_mode == ChartImageAnnotator.ModeNavigate) self.container_annotation_buttons.visible = ( self.edition_mode == ChartImageAnnotator.ModeNavigate) # Edit panels ... self.container_split_panels.visible = ( self.edition_mode == ChartImageAnnotator.ModeEditPanels) self.container_classify_panels.visible = ( self.edition_mode == ChartImageAnnotator.ModeEditClass) # Confirm panel and buttons ... self.container_confirm_buttons.visible = self.edition_mode in [ ChartImageAnnotator.ModeSelectPanelSplit, ChartImageAnnotator.ModeConfirmOverwritePanels, ChartImageAnnotator.ModeConfirmOverwriteClass, ChartImageAnnotator.ModeConfirmExit ] if self.edition_mode == ChartImageAnnotator.ModeSelectPanelSplit: if self.split_panel_operation == ChartImageAnnotator.SplitOperationYSplit: self.lbl_confirm_message.set_text("Select Point for Y - Split") elif self.split_panel_operation == ChartImageAnnotator.SplitOperationXSplit: self.lbl_confirm_message.set_text("Select Point for X - Split") elif self.split_panel_operation == ChartImageAnnotator.SplitOperationMerge: self.lbl_confirm_message.set_text("Select a panel to Merge") elif self.edition_mode == ChartImageAnnotator.ModeConfirmOverwritePanels: self.lbl_confirm_message.set_text( "Discard Previous Image Annotations?") elif self.edition_mode == ChartImageAnnotator.ModeConfirmOverwriteClass: self.lbl_confirm_message.set_text( "Discard Previous Panel Annotations?") elif self.edition_mode == ChartImageAnnotator.ModeConfirmExit: self.lbl_confirm_message.set_text("Discard unsaved changes?") self.btn_confirm_accept.visible = ( self.container_confirm_buttons.visible and self.edition_mode != ChartImageAnnotator.ModeSelectPanelSplit) def img_main_mouse_button_down(self, img, pos, button): if self.edition_mode == ChartImageAnnotator.ModeSelectPanelSplit: scaled_x, scaled_y = pos click_x = int(scaled_x / self.view_scale) click_y = int(scaled_y / self.view_scale) selected_nodes = self.tempo_panel_tree.root.find_point_containers( click_x, click_y, False) if len(selected_nodes) == 1: current_node = selected_nodes[0] if self.split_panel_operation == ChartImageAnnotator.SplitOperationYSplit: current_node.horizontal_split(click_y) elif self.split_panel_operation == ChartImageAnnotator.SplitOperationXSplit: current_node.vertical_split(click_x) elif self.split_panel_operation == ChartImageAnnotator.SplitOperationMerge: current_node.merge_with_parent() self.set_editor_mode(ChartImageAnnotator.ModeEditPanels) self.update_current_view(False) def update_panel_info(self): msg = "Current Panel: {0:d} / {1:d}".format( self.selected_panel + 1, len(self.image_info.panels)) self.lbl_panels_current.set_text(msg) def btn_class_panel_continue_click(self, button): # finish time for classification ... from here, next time will count towards main menu ... delta = self.get_reset_time_delta() self.time_stats.time_classification += delta prev_key = self.get_image_class_key() if prev_key != self.lbx_class_panel_class.selected_option_value: self.set_editor_mode(ChartImageAnnotator.ModeConfirmOverwriteClass) else: # nothing was changed ... self.set_editor_mode(ChartImageAnnotator.ModeNavigate) def get_image_class_key(self): current_panel = self.image_info.panels[self.selected_panel] class_key = str(current_panel.type) if current_panel.type in [ChartInfo.TypeBar, ChartInfo.TypeBox]: class_key += "-" + str(current_panel.orientation) return class_key def subtool_completed(self, data_changed): if data_changed: self.unsaved_changes = True def btn_panels_verify_click(self, button): self.image_info.properties["VERIFIED_01_PANELS"] = time.time() self.unsaved_changes = True print("Image Panels have been marked as verified!!!") def btn_verify_class_click(self, button): current_panel = self.image_info.panels[self.selected_panel] current_panel.set_classes_verified(True) self.unsaved_changes = True print("Current Panels Classification has been marked as verified!!!") def btn_verify_text_click(self, button): current_panel = self.image_info.panels[self.selected_panel] current_panel.set_text_verified(True) self.unsaved_changes = True print("Current Panels Text has been marked as verified!!!") def btn_verify_legend_click(self, button): current_panel = self.image_info.panels[self.selected_panel] current_panel.set_legend_verified(True) self.unsaved_changes = True print("Current Panels Legend has been marked as verified!!!") def btn_verify_axis_click(self, button): current_panel = self.image_info.panels[self.selected_panel] current_panel.set_axes_verified(True) self.unsaved_changes = True print("Current Panels Axes have been marked as verified!!!") def btn_verify_data_click(self, button): current_panel = self.image_info.panels[self.selected_panel] current_panel.set_data_verified(True) self.unsaved_changes = True print("Current Panels Data has been marked as verified!!!")
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)