def read_annotation_xml(xml_filename, img_filename): info = ImageInfo.FromXML(xml_filename, img_filename) if len(info.panels) > 1: print('Warning!! Multiple Panels Detected') result = [chartinfo for chartinfo in info.panels] offsets = [(0, 0)] return result, offsets
def __init__(self, size, chart_dir, annotation_dir, admin_mode): Screen.__init__(self, "Chart Ground Truth Annotation Interface", size) # load the chart directory info ... self.chart_image_list = ImageInfo.ListChartDirectory(chart_dir, "") print("A total of {0:d} chart images were found".format( len(self.chart_image_list))) self.general_background = (20, 50, 85) self.text_color = (255, 255, 255) self.admin_mode = admin_mode # about the grid .... self.tb_grid_cols = 6 self.tb_grid_rows = 4 self.tb_width = 160 self.tb_height = 120 self.tb_outer_margin = 16 self.tb_inner_margin = 4 self.tb_label_height = 14 # this is approx self.tb_progress_h = 16 self.tb_progress_border = 4 self.tb_col_width = (self.tb_outer_margin + self.tb_width) self.tb_row_height = (self.tb_outer_margin + self.tb_inner_margin * 2 + self.tb_progress_h + self.tb_label_height + self.tb_height) self.elements.back_color = self.general_background self.chart_dir = chart_dir self.annotation_dir = annotation_dir # define here all graphic object references .... # ...for thumbnails .... self.container_thumbnails = None self.thumbnails_images = None self.thumbnails_status = None self.thumbnails_labels = None self.paginator = None # ... right side image options ... self.lbl_page_descriptor = None self.container_image_info = None self.img_display = None self.img_title = None self.btn_label_image = None self.btn_next_image = None self.btn_prev_image = None # ... general ... self.btn_exit = None # ... create them!!! ..... self.create_controllers() # ... load current page data .... self.current_page = None self.selected_element = 0 self.load_page(None, 0)
def 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 load_page(self, paginator, new_page, refresh=False): if new_page == self.current_page and not refresh: # page not changed ... return self.current_page = new_page page_size = len(self.thumbnails_images) current_elements = self.chart_image_list[self.current_page * page_size:(self.current_page + 1) * page_size] for idx in range(page_size): row = int(idx / self.tb_grid_cols) col = idx % self.tb_grid_cols if idx < len(current_elements): # update image ... chart_path = current_elements[idx] current_img = cv2.imread(self.chart_dir + chart_path) current_img = cv2.cvtColor(current_img, cv2.COLOR_BGR2RGB) self.thumbnails_images[idx].set_image(current_img, self.tb_width, self.tb_height, keep_aspect=True) # center new image corner_x = self.tb_outer_margin + self.tb_col_width * col corner_y = self.tb_outer_margin + self.tb_row_height * row self.center_image_in_box(self.thumbnails_images[idx], corner_x, corner_y, self.tb_width, self.tb_height) # find annotation path ... relative_dir, img_filename = os.path.split(chart_path) img_base, ext = os.path.splitext(img_filename) # output dir output_dir = self.annotation_dir + relative_dir annotation_filename = output_dir + "/" + img_base + ".xml" # default : No annotations ... if os.path.exists(annotation_filename): # read the annotation ... print(annotation_filename) image_info = ImageInfo.FromXML(annotation_filename, current_img) status_ints = ImageInfo.GetAllStatuses(image_info) else: status_ints = ImageInfo.GetAllStatuses(None) status = self.gen_status_image(status_ints, self.tb_width, self.tb_progress_h, self.tb_progress_border) self.thumbnails_status[idx].set_image(status, self.tb_width, self.tb_progress_h) self.thumbnails_labels[idx].set_text(chart_path[1:]) self.thumbnails_images[idx].visible = True self.thumbnails_status[idx].visible = True self.thumbnails_labels[idx].visible = True else: # special case for the last page ... self.thumbnails_images[idx].visible = False self.thumbnails_status[idx].visible = False self.thumbnails_labels[idx].visible = False msg = "Page {0:d} of {1:d} ({2:d} elements)".format( self.current_page + 1, self.paginator.total_pages, len(self.chart_image_list)) self.lbl_page_descriptor.set_text(msg) if not refresh: self.selected_element = None self.update_selected_image(0)
def split_dir_annotations(in_img_dir, in_annot_dir, out_img_dir, out_annot_dir, rel_path): input_dir = in_annot_dir + rel_path elements = os.listdir(input_dir) panels_per_type = {} for element in elements: element_path = input_dir + element if os.path.isdir(element_path): sub_dir_stats = split_dir_annotations(in_img_dir, in_annot_dir, out_img_dir, out_annot_dir, element_path + "/") # add stats for chart_type in sub_dir_stats: if chart_type in panels_per_type: # already collected this type ... panels_per_type[chart_type] += sub_dir_stats[chart_type] else: # first of this type panels_per_type[chart_type] = sub_dir_stats[chart_type] else: print("Processing: " + element_path) # load the corresponding image base, ext = os.path.splitext(element) img_path = in_img_dir + rel_path + base + ".jpg" current_img = cv2.imread(img_path) # load the annotation image_info = ImageInfo.FromXML(element_path, current_img) # For each panel .... for panel_idx, panel in enumerate(image_info.panels): type_str, orientation_str = panel.get_description() chart_type = type_str + "_" + orientation_str if chart_type in panels_per_type: panels_per_type[chart_type] += 1 else: panels_per_type[chart_type] = 1 # if the panel is non-chart, then skip if panel.type == ChartInfo.TypeNonChart: continue # crop the panel from main image panel_img = image_info.get_panel_image(panel_idx) # Save panel image to output image directory os.makedirs(out_img_dir + "/" + chart_type + rel_path, exist_ok=True) out_img_panel_path = out_img_dir + "/" + chart_type + rel_path + base + "_panel_" + str( panel_idx + 1) + ".jpg" cv2.imwrite(out_img_panel_path, panel_img) # Create a new annotation structure ... only for that panel panel_annotation = ImageInfo.CreateDefault(panel_img) panel_annotation.panels[0] = panel # Save panel annotation to output image directory os.makedirs(out_annot_dir + "/" + chart_type + rel_path, exist_ok=True) out_panel_annotation = out_annot_dir + "/" + chart_type + rel_path + base + "_panel_" + str( panel_idx + 1) + ".xml" tempo_xml = panel_annotation.to_XML() with open(out_panel_annotation, "w") as out_annot_file: out_annot_file.write(tempo_xml) return panels_per_type
def __load_data(self, cache_all_annotations): # Load image list from dir ... self.img_list = ImageInfo.ListChartDirectory(self.img_dir, "") self.auto_check_stats = { "total_no_annotation": 0, "total_multi_panel": 0, "total_no_test": 0, "total_passed": 0, "total_failed": 0, } for idx, chart_path in enumerate(self.img_list): current_img = None # find annotation path ... relative_dir, img_filename = os.path.split(chart_path) img_base, ext = os.path.splitext(img_filename) # output dir output_dir = self.annotation_dir + relative_dir annotation_filename = output_dir + "/" + img_base + ".xml" if os.path.exists(annotation_filename): self.img_annotations.append(annotation_filename) self.total_annotation_files += 1 image_info = ImageInfo.FromXML(annotation_filename, current_img) if len(image_info.panels) == 1: # add to single panel index self.all_single_panel.append(idx) type_desc, orientation = image_info.panels[ 0].get_description() if orientation == "": current_type = type_desc else: current_type = "{0:s} ({1:s})".format( type_desc, orientation) # if type_desc in ["non-chart"]: # print(annotation_filename) status_ints = ImageInfo.GetAllStatuses(image_info) self.img_statuses.append(status_ints) if current_type in self.single_per_type: self.single_per_type[current_type].append( (idx, status_ints)) else: self.single_per_type[current_type] = [(idx, status_ints)] if not "auto_check_passed" in image_info.panels[ 0].properties: self.auto_check_stats["total_no_test"] += 1 else: if int(image_info.panels[0]. properties["auto_check_passed"]) > 0: self.auto_check_stats["total_passed"] += 1 else: self.auto_check_stats["total_failed"] += 1 else: # add to multi-panel index self.all_multi_panel.append(idx) # TODO: multi-panel case is not handled yet ... self.img_statuses.append(None) self.auto_check_stats["total_multi_panel"] += 1 # keep or discard the current annotation ... if cache_all_annotations: self.cache_annotations.append(image_info) else: self.cache_annotations.append(None) else: self.img_annotations.append(None) self.cache_annotations.append(None) self.img_statuses.append(ImageInfo.GetNullStatuses()) self.auto_check_stats["total_no_annotation"] += 1
def __init__(self, img_dir, annotation_dir, cache_all_annotations=False): self.img_dir = img_dir self.annotation_dir = annotation_dir # Load image list from dir ... self.img_list = ImageInfo.ListChartDirectory(img_dir, "") self.total_annotation_files = 0 self.img_annotations = [] self.cache_annotations = [] self.img_statuses = [] self.all_single_panel = [] self.all_multi_panel = [] self.single_per_type = {} for idx, chart_path in enumerate(self.img_list): current_img = None # find annotation path ... relative_dir, img_filename = os.path.split(chart_path) img_base, ext = os.path.splitext(img_filename) # output dir output_dir = self.annotation_dir + relative_dir annotation_filename = output_dir + "/" + img_base + ".xml" if os.path.exists(annotation_filename): self.img_annotations.append(annotation_filename) self.total_annotation_files += 1 image_info = ImageInfo.FromXML(annotation_filename, current_img) if len(image_info.panels) == 1: # add to single panel index self.all_single_panel.append(idx) type_desc, orientation = image_info.panels[ 0].get_description() if orientation == "": current_type = type_desc else: current_type = "{0:s} ({1:s})".format( type_desc, orientation) status_ints = ImageInfo.GetAllStatuses(image_info) self.img_statuses.append(status_ints) if current_type in self.single_per_type: self.single_per_type[current_type].append( (idx, status_ints)) else: self.single_per_type[current_type] = [(idx, status_ints)] else: # add to multi-panel index self.all_multi_panel.append(idx) # TODO: multi-panel case is not handled yet ... self.img_statuses.append(None) # keep or discard the current annotation ... if cache_all_annotations: self.cache_annotations.append(image_info) else: self.cache_annotations.append(None) else: self.img_annotations.append(None) self.cache_annotations.append(None) self.img_statuses.append(ImageInfo.GetNullStatuses())
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 __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 main(): if len(sys.argv) < 3: print("Usage:") print("") print("python chart_update_annotations.py src_config dst_config") print("") print("Where") print("\tsrc_config\tSource Configuration (newer annotations)") print( "\tdst_config\tDestination Configuration (annotations to update)") return # Load and show general statistics .... config1_filename = sys.argv[1] config2_filename = sys.argv[2] stats1 = load_stats(config1_filename) stats2 = load_stats(config2_filename) print("Source Info ({0:s})".format(config1_filename)) stats1.print_general_stats() print("") print("Destination Info ({0:s})".format(config2_filename)) stats2.print_general_stats() # Find matching files which might be overwritten ... common_annotations = stats1.find_common_annotations(stats2) print("") print("A total of {0:d} shared images were found".format( len(common_annotations))) count_src_null = 0 count_newer = 0 count_higher = 0 for base_name, src_idx, dst_idx in common_annotations: if src_idx is None: # common image with no annotation at source ... skip ... count_src_null += 1 continue dst_status = stats2.img_statuses[dst_idx] # check if destination does not have annotations for this file or # if the status of the file at source is absolute higher than file at destination ... if dst_idx is None or ImageInfo.CheckNewerStatus( dst_status, stats1.img_statuses[src_idx]): # source is newer (or destination is inexistent) .. must replace file in destination ... count_newer += 1 src_filename = stats1.img_annotations[src_idx] dst_filename = stats2.img_annotations[dst_idx] print("Replacing: {0:s}".format(base_name)) shutil.copy(src_filename, dst_filename) else: count_higher += 1 print("Higher Status at Destination: {0:s}".format(base_name)) print("") print("A total of ...") print(" - {0:d} images had no annotation at source".format(count_src_null)) print(" - {0:d} images were replaced at destination".format(count_newer)) print(" - {0:d} images were not modified at destination".format( count_higher))