def prepare_html_subpages(images_html,output_dir,options=None): if options is None: options = PostProcessingOptions() # Count items in each category image_counts = {} for res, array in images_html.items(): image_counts[res] = len(array) # Optionally sort by filename before writing to html if options.sort_html_by_filename: images_html_sorted = {} for res, array in images_html.items(): sorted_array = sorted(array, key=lambda x: x['filename']) images_html_sorted[res] = sorted_array images_html = images_html_sorted # Write the individual HTML files for res, array in images_html.items(): write_html_image_list( filename=os.path.join(output_dir, '{}.html'.format(res)), images=array, options={ 'headerHtml': '<h1>{}</h1>'.format(res.upper()) }) return image_counts
def prepare_html_subpages(images_html, output_dir, options=None): """ Write out a series of html image lists, e.g. the fp/tp/fn/tn pages. image_html is a dictionary mapping an html page name (e.g. "fp") to a list of image structs friendly to write_html_image_list """ if options is None: options = PostProcessingOptions() # Count items in each category image_counts = {} for res, array in images_html.items(): image_counts[res] = len(array) # Optionally sort by filename before writing to html if options.sort_html_by_filename: images_html_sorted = {} for res, array in images_html.items(): sorted_array = sorted(array, key=lambda x: x['filename']) images_html_sorted[res] = sorted_array images_html = images_html_sorted # Write the individual HTML files for res, array in images_html.items(): write_html_image_list( filename=os.path.join(output_dir, '{}.html'.format(res)), images=array, options={ 'headerHtml': '<h1>{}</h1>'.format(res.upper()) }) return image_counts
class DbVizOptions: # Set to None to visualize all images num_to_visualize = None # Target size for rendering; set either dimension to -1 to preserve aspect ratio viz_size = (675, -1) htmlOptions = write_html_image_list() sort_by_filename = True trim_to_images_with_bboxes = False random_seed = 0 # None add_search_links = False include_filename_links = False # These are mutually exclusive; both are category names, not IDs classes_to_exclude = None classes_to_include = None # Special tag used to say "show me all images with multiple categories" multiple_categories_tag = '*multiple*' # We sometimes flatten image directories by replacing a path separator with # another character. Leave blank for the typical case where this isn't necessary. pathsep_replacement = '' # '~' # Control rendering parallelization parallelize_rendering_n_cores = 100 parallelize_rendering = False
class BboxDbVizOptions: # Set to None to visualize all images num_to_visualize = None # Target size for rendering; set either dimension to -1 to preserve aspect ratio viz_size = (675, -1) htmlOptions = write_html_image_list() sort_by_filename = True trim_to_images_with_bboxes = False random_seed = 0; # None # We sometimes flatten image directories by replacing a path separator with # another character. Leave blank for the typical case where this isn't necessary. pathsep_replacement = '' # '~'
def processImages(db_path, output_dir, image_base_dir, options=None, bbox_db=None): """ Writes images and html to output_dir to visualize the annotations in the json file db_path. Returns the html filename and the bbox database. """ if options is None: options = DbVizOptions() print(options.__dict__) os.makedirs(os.path.join(output_dir, 'rendered_images'), exist_ok=True) assert (os.path.isfile(db_path)) assert (os.path.isdir(image_base_dir)) if bbox_db is None: print('Loading the database...') bbox_db = json.load(open(db_path)) print('...done') annotations = bbox_db['annotations'] images = bbox_db['images'] categories = bbox_db['categories'] # Optionally remove all images without bounding boxes, *before* sampling if options.trim_to_images_with_bboxes: bHasBbox = [False] * len(annotations) for iAnn, ann in enumerate(annotations): if 'bbox' in ann: assert isinstance(ann['bbox'], list) bHasBbox[iAnn] = True annotationsWithBboxes = list(compress(annotations, bHasBbox)) imageIDsWithBboxes = [x['image_id'] for x in annotationsWithBboxes] imageIDsWithBboxes = set(imageIDsWithBboxes) bImageHasBbox = [False] * len(images) for iImage, image in enumerate(images): imageID = image['id'] if imageID in imageIDsWithBboxes: bImageHasBbox[iImage] = True imagesWithBboxes = list(compress(images, bImageHasBbox)) images = imagesWithBboxes # put the annotations in a dataframe so we can select all annotations for a given image df_anno = pd.DataFrame(annotations) df_img = pd.DataFrame(images) # construct label map label_map = {} for cat in categories: label_map[int(cat['id'])] = cat['name'] # take a sample of images if options.num_to_visualize is not None: df_img = df_img.sample(n=options.num_to_visualize, random_state=options.random_seed) images_html = [] # iImage = 0 for iImage in tqdm(range(len(df_img))): img_id = df_img.iloc[iImage]['id'] img_relative_path = df_img.iloc[iImage]['file_name'] img_path = os.path.join( image_base_dir, imageFilenameToPath(img_relative_path, image_base_dir)) if not os.path.exists(img_path): print('Image {} cannot be found'.format(img_path)) continue annos_i = df_anno.loc[df_anno['image_id'] == img_id, :] # all annotations on this image try: originalImage = vis_utils.open_image(img_path) original_size = originalImage.size image = vis_utils.resize_image(originalImage, options.viz_size[0], options.viz_size[1]) except Exception as e: print('Image {} failed to open. Error: {}'.format(img_path, e)) continue bboxes = [] boxClasses = [] # All the class labels we've seen for this image (with out without bboxes) imageCategories = set() annotationLevelForImage = '' # Iterate over annotations for this image # iAnn = 0; anno = annos_i.iloc[iAnn] for iAnn, anno in annos_i.iterrows(): if 'sequence_level_annotation' in anno: bSequenceLevelAnnotation = anno['sequence_level_annotation'] if bSequenceLevelAnnotation: annLevel = 'sequence' else: annLevel = 'image' if annotationLevelForImage == '': annotationLevelForImage = annLevel elif annotationLevelForImage != annLevel: annotationLevelForImage = 'mixed' categoryID = anno['category_id'] categoryName = label_map[categoryID] if options.add_search_links: categoryName = categoryName.replace('"', '') categoryName = '<a href="https://www.bing.com/images/search?q={}">{}</a>'.format( categoryName, categoryName) imageCategories.add(categoryName) if 'bbox' in anno: bbox = anno['bbox'] if isinstance(bbox, float): assert math.isnan( bbox ), "I shouldn't see a bbox that's neither a box nor NaN" continue bboxes.append(bbox) boxClasses.append(anno['category_id']) imageClasses = ', '.join(imageCategories) # render bounding boxes in-place vis_utils.render_db_bounding_boxes(bboxes, boxClasses, image, original_size, label_map) file_name = '{}_gtbbox.jpg'.format(img_id.lower().split('.jpg')[0]) file_name = file_name.replace('/', '~') image.save(os.path.join(output_dir, 'rendered_images', file_name)) labelLevelString = '' if len(annotationLevelForImage) > 0: labelLevelString = ' (annotation level: {})'.format( annotationLevelForImage) images_html.append({ 'filename': '{}/{}'.format('rendered_images', file_name), 'title': '{}<br/>{}, number of boxes: {}, class labels: {}{}'.format( img_relative_path, img_id, len(bboxes), imageClasses, labelLevelString), 'textStyle': 'font-family:verdana,arial,calibri;font-size:80%;text-align:left;margin-top:20;margin-bottom:5' }) # ...for each image if options.sort_by_filename: images_html = sorted(images_html, key=lambda x: x['filename']) htmlOutputFile = os.path.join(output_dir, 'index.html') htmlOptions = options.htmlOptions htmlOptions['headerHtml'] = '<h1>Sample annotations from {}</h1>'.format( db_path) write_html_image_list(filename=htmlOutputFile, images=images_html, options=htmlOptions) print('Visualized {} images, wrote results to {}'.format( len(images_html), htmlOutputFile)) return htmlOutputFile, bbox_db
def render_images_for_directory(iDir, directoryHtmlFiles, suspiciousDetections, options): nDirs = len(directoryHtmlFiles) if options.pbar is not None: options.pbar.update() if options.debugMaxRenderDir > 0 and iDir > options.debugMaxRenderDir: return None dirName = 'dir{:0>4d}'.format(iDir) # suspiciousDetectionsThisDir is a list of DetectionLocation objects suspiciousDetectionsThisDir = suspiciousDetections[iDir] if len(suspiciousDetectionsThisDir) == 0: return None timeStr = datetime.now().strftime('%H:%M:%S') print('Processing directory {} of {} ({})'.format(iDir, nDirs, timeStr)) dirBaseDir = os.path.join(options.outputBase, dirName) os.makedirs(dirBaseDir, exist_ok=True) directoryDetectionHtmlFiles = [] directoryDetectionImageInfo = [] # For each problematic detection in this directory # # iDetection = 0; detection = suspiciousDetectionsThisDir[iDetection]; nDetections = len(suspiciousDetectionsThisDir) bPrintedMissingImageWarning = False for iDetection, detection in enumerate(suspiciousDetectionsThisDir): if options.debugMaxRenderDetection > 0 and iDetection > options.debugMaxRenderDetection: break nInstances = len(detection.instances) print('Processing detection {} of {} ({} instances)'.format( iDetection, nDetections, nInstances)) detectionName = 'detection{:0>4d}'.format(iDetection) detectionBaseDir = os.path.join(dirBaseDir, detectionName) os.makedirs(detectionBaseDir, exist_ok=True) # _ = pretty_print_object(detection) assert (nInstances >= options.occurrenceThreshold) imageInfo = [] # Render images # iInstance = 0; instance = detection.instances[iInstance] for iInstance, instance in enumerate(detection.instances): if options.debugMaxRenderInstance >= 0 and iInstance >= options.debugMaxRenderInstance: break imageRelativeFilename = 'image{:0>4d}.jpg'.format(iInstance) imageOutputFilename = os.path.join(detectionBaseDir, imageRelativeFilename) thisImageInfo = {} thisImageInfo['filename'] = imageRelativeFilename confidence = instance.bbox[4] confidenceStr = '{:.2f}'.format(confidence) t = confidenceStr + ' (' + instance.filename + ')' thisImageInfo['title'] = t imageInfo.append(thisImageInfo) inputFileName = os.path.join(options.imageBase, instance.filename) if not os.path.isfile(inputFileName): if options.bPrintMissingImageWarnings: if (options.missingImageWarningType == 'all') or (not bPrintedMissingImageWarning): print('Warning: could not find file {}'.format( inputFileName)) bPrintedMissingImageWarning = True else: render_bounding_box(instance.bbox, inputFileName, imageOutputFilename, 15) # ...for each instance # Write html for this detection detectionHtmlFile = os.path.join(detectionBaseDir, 'index.html') htmlOptions = write_html_image_list.write_html_image_list() htmlOptions['defaultImageStyle'] = 'max-width:650px;' write_html_image_list.write_html_image_list(detectionHtmlFile, imageInfo, htmlOptions) thisDirectoryImageInfo = {} directoryDetectionHtmlFiles.append(detectionHtmlFile) # Use the first image from this detection (arbitrary) as the canonical example # that we'll render for the directory-level page. thisDirectoryImageInfo['filename'] = os.path.join( detectionName, imageInfo[0]['filename']) detectionHtmlFileRelative = os.path.relpath(detectionHtmlFile, dirBaseDir) title = '<a href="{}">{}</a>'.format(detectionHtmlFileRelative, detectionName) thisDirectoryImageInfo['title'] = title directoryDetectionImageInfo.append(thisDirectoryImageInfo) # ...for each detection # Write the html file for this directory directoryHtmlFile = os.path.join(dirBaseDir, 'index.html') htmlOptions = write_html_image_list.write_html_image_list() htmlOptions['defaultImageStyle'] = 'max-width:650px;' write_html_image_list.write_html_image_list(directoryHtmlFile, directoryDetectionImageInfo, htmlOptions) return directoryHtmlFile
def process_images(db_path,output_dir,image_base_dir,options=None): """ Writes images and html to output_dir to visualize the annotations in the json file db_path. db_path can also be a previously-loaded database. Returns the html filename and the database: return htmlOutputFile,image_db """ if options is None: options = DbVizOptions() print(options.__dict__) os.makedirs(os.path.join(output_dir, 'rendered_images'), exist_ok=True) assert(os.path.isdir(image_base_dir)) if isinstance(db_path,str): assert(os.path.isfile(db_path)) print('Loading database from {}...'.format(db_path)) image_db = json.load(open(db_path)) print('...done') elif isinstance(db_path,dict): print('Using previously-loaded DB') image_db = db_path else: raise ValueError('Illegal dictionary or filename') annotations = image_db['annotations'] images = image_db['images'] categories = image_db['categories'] # Optionally remove all images without bounding boxes, *before* sampling if options.trim_to_images_with_bboxes: bHasBbox = [False] * len(annotations) for iAnn,ann in enumerate(annotations): if 'bbox' in ann: assert isinstance(ann['bbox'],list) bHasBbox[iAnn] = True annotationsWithBboxes = list(compress(annotations, bHasBbox)) imageIDsWithBboxes = [x['image_id'] for x in annotationsWithBboxes] imageIDsWithBboxes = set(imageIDsWithBboxes) bImageHasBbox = [False] * len(images) for iImage,image in enumerate(images): imageID = image['id'] if imageID in imageIDsWithBboxes: bImageHasBbox[iImage] = True imagesWithBboxes = list(compress(images, bImageHasBbox)) images = imagesWithBboxes # Optionally remove images with specific labels, *before* sampling if options.classes_to_exclude is not None: print('Indexing database') indexed_db = IndexedJsonDb(image_db) bValidClass = [True] * len(images) for iImage,image in enumerate(images): classes = indexed_db.get_classes_for_image(image) for excludedClass in options.classes_to_exclude: if excludedClass in classes: bValidClass[iImage] = False break imagesWithValidClasses = list(compress(images, bValidClass)) images = imagesWithValidClasses # Put the annotations in a dataframe so we can select all annotations for a given image print('Creating data frames') df_anno = pd.DataFrame(annotations) df_img = pd.DataFrame(images) # Construct label map label_map = {} for cat in categories: label_map[int(cat['id'])] = cat['name'] # Take a sample of images if options.num_to_visualize is not None: df_img = df_img.sample(n=options.num_to_visualize,random_state=options.random_seed) images_html = [] # Set of dicts representing inputs to render_db_bounding_boxes: # # bboxes, boxClasses, image_path rendering_info = [] print('Preparing rendering list') # iImage = 0 for iImage in tqdm(range(len(df_img))): img_id = df_img.iloc[iImage]['id'] img_relative_path = df_img.iloc[iImage]['file_name'] img_path = os.path.join(image_base_dir, image_filename_to_path(img_relative_path, image_base_dir)) annos_i = df_anno.loc[df_anno['image_id'] == img_id, :] # all annotations on this image bboxes = [] boxClasses = [] # All the class labels we've seen for this image (with out without bboxes) imageCategories = set() annotationLevelForImage = '' # Iterate over annotations for this image # iAnn = 0; anno = annos_i.iloc[iAnn] for iAnn,anno in annos_i.iterrows(): if 'sequence_level_annotation' in anno: bSequenceLevelAnnotation = anno['sequence_level_annotation'] if bSequenceLevelAnnotation: annLevel = 'sequence' else: annLevel = 'image' if annotationLevelForImage == '': annotationLevelForImage = annLevel elif annotationLevelForImage != annLevel: annotationLevelForImage = 'mixed' categoryID = anno['category_id'] categoryName = label_map[categoryID] if options.add_search_links: categoryName = categoryName.replace('"','') categoryName = '<a href="https://www.bing.com/images/search?q={}">{}</a>'.format(categoryName,categoryName) imageCategories.add(categoryName) if 'bbox' in anno: bbox = anno['bbox'] if isinstance(bbox,float): assert math.isnan(bbox), "I shouldn't see a bbox that's neither a box nor NaN" continue bboxes.append(bbox) boxClasses.append(anno['category_id']) imageClasses = ', '.join(imageCategories) file_name = '{}_gtbbox.jpg'.format(img_id.lower().split('.jpg')[0]) file_name = file_name.replace('/', '~') rendering_info.append({'bboxes':bboxes, 'boxClasses':boxClasses, 'img_path':img_path, 'output_file_name':file_name}) labelLevelString = '' if len(annotationLevelForImage) > 0: labelLevelString = ' (annotation level: {})'.format(annotationLevelForImage) # We're adding html for an image before we render it, so it's possible this image will # fail to render. For applications where this script is being used to debua a database # (the common case?), this is useful behavior, for other applications, this is annoying. # # TODO: optionally write html only for images where rendering succeeded images_html.append({ 'filename': '{}/{}'.format('rendered_images', file_name), 'title': '{}<br/>{}, number of boxes: {}, class labels: {}{}'.format(img_relative_path,img_id, len(bboxes), imageClasses, labelLevelString), 'textStyle': 'font-family:verdana,arial,calibri;font-size:80%;text-align:left;margin-top:20;margin-bottom:5' }) # ...for each image def render_image_info(rendering_info): img_path = rendering_info['img_path'] bboxes = rendering_info['bboxes'] bboxClasses = rendering_info['boxClasses'] output_file_name = rendering_info['output_file_name'] if not os.path.exists(img_path): print('Image {} cannot be found'.format(img_path)) return try: original_image = vis_utils.open_image(img_path) original_size = original_image.size image = vis_utils.resize_image(original_image, options.viz_size[0], options.viz_size[1]) except Exception as e: print('Image {} failed to open. Error: {}'.format(img_path, e)) return vis_utils.render_db_bounding_boxes(boxes=bboxes, classes=bboxClasses, image=image, original_size=original_size, label_map=label_map) image.save(os.path.join(output_dir, 'rendered_images', output_file_name)) # ...def render_image_info print('Rendering images') start_time = time.time() if options.parallelize_rendering: if options.parallelize_rendering_n_cores is None: pool = ThreadPool() else: print('Rendering images with {} workers'.format(options.parallelize_rendering_n_cores)) pool = ThreadPool(options.parallelize_rendering_n_cores) tqdm(pool.imap(render_image_info, rendering_info), total=len(rendering_info)) else: for file_info in tqdm(rendering_info): render_image_info(file_info) elapsed = time.time() - start_time print('Rendered {} images in {}'.format(len(rendering_info),humanfriendly.format_timespan(elapsed))) if options.sort_by_filename: images_html = sorted(images_html, key=lambda x: x['filename']) htmlOutputFile = os.path.join(output_dir, 'index.html') htmlOptions = options.htmlOptions if isinstance(db_path,str): htmlOptions['headerHtml'] = '<h1>Sample annotations from {}</h1>'.format(db_path) else: htmlOptions['headerHtml'] = '<h1>Sample annotations</h1>' write_html_image_list( filename=htmlOutputFile, images=images_html, options=htmlOptions) print('Visualized {} images, wrote results to {}'.format(len(images_html),htmlOutputFile)) return htmlOutputFile,image_db
def visualize_sequences(datasets_table, sequences, args): num_images = 0 images_html = [] rendering_info = [] for seq in sequences: if 'images' not in seq: continue # dataset and seq_id are required fields dataset_name = seq['dataset'] seq_id = seq['seq_id'] # sort the images in the sequence images_in_seq = sorted(seq['images'], key=lambda x: x['frame_num']) if len(seq['images']) > 1 else seq['images'] for im in images_in_seq: if args.trim_to_images_bboxes_labeled and 'bbox' not in im: continue num_images += 1 blob_path = MegadbUtils.get_full_path(datasets_table, dataset_name, im['file']) frame_num = im.get('frame_num', -1) im_class = im.get('class', None) if im_class is None: # if no class label on the image, show the class label on the sequence im_class = seq.get('class', []) rendering = {} rendering['blob_service'] = MegadbUtils.get_blob_service(datasets_table, dataset_name) rendering['container_name'] = datasets_table[dataset_name]['container'] rendering['blob_path'] = blob_path rendering['bbox'] = im.get('bbox', []) annotated_img_name = 'anno_' + blob_path.replace('/', args.pathsep_replacement).replace('\\', args.pathsep_replacement) rendering['annotated_img_name'] = annotated_img_name rendering_info.append(rendering) images_html.append({ 'filename': 'rendered_images/{}'.format(annotated_img_name), 'title': 'Seq ID: {}. Frame number: {}<br/> Image file: {}<br/> number of boxes: {}, image class labels: {}'.format(seq_id, frame_num, blob_path, len(rendering['bbox']), im_class), 'textStyle': 'font-family:verdana,arial,calibri;font-size:80%;text-align:left;margin-top:20;margin-bottom:5' }) if num_images >= args.num_to_visualize: print('num_images visualized is {}'.format(num_images)) break # pool = ThreadPool() render_image_info_partial = partial(render_image_info, args=args) # print('len of rendering_info', len(rendering_info)) # tqdm(pool.imap_unordered(render_image_info_partial, rendering_info), total=len(rendering_info)) for rendering in tqdm(rendering_info): render_image_info_partial(rendering) print('Making HTML...') html_path = os.path.join(args.output_dir, 'index.html') # options = write_html_image_list() # options['headerHtml'] write_html_image_list( filename=html_path, images=images_html )
def renderImagesForDirectory(iDir, directoryHtmlFiles, suspiciousDetections, options): nDirs = len(directoryHtmlFiles) if options.pbar is not None: options.pbar.update() if options.debugMaxRenderDir > 0 and iDir > options.debugMaxRenderDir: return None dirName = 'dir{:0>4d}'.format(iDir) # suspiciousDetectionsThisDir is a list of DetectionLocation objects suspiciousDetectionsThisDir = suspiciousDetections[iDir] if len(suspiciousDetectionsThisDir) == 0: return None timeStr = datetime.now().strftime('%H:%M:%S') print('Processing directory {} of {} ({})'.format(iDir, nDirs, timeStr)) dirBaseDir = os.path.join(options.outputBase, dirName) os.makedirs(dirBaseDir, exist_ok=True) directoryDetectionHtmlFiles = [] directoryDetectionExampleImages = [] directoryDetectionTitles = [] # For each problematic detection in this directory # # iDetection = 0; detection = suspiciousDetectionsThisDir[iDetection]; nDetections = len(suspiciousDetectionsThisDir) for iDetection, detection in enumerate(suspiciousDetectionsThisDir): if options.debugMaxRenderDetection > 0 and iDetection > options.debugMaxRenderDetection: break nInstances = len(detection.instances) print('Processing detection {} of {} ({} instances)'.format( iDetection, nDetections, nInstances)) detectionName = 'detection{:0>4d}'.format(iDetection) detectionBaseDir = os.path.join(dirBaseDir, detectionName) os.makedirs(detectionBaseDir, exist_ok=True) # _ = prettyPrintObject(detection) assert (nInstances >= options.occurrenceThreshold) imageFileNames = [] titles = [] # Render images # iInstance = 0; instance = detection.instances[iInstance] for iInstance, instance in enumerate(detection.instances): if options.debugMaxRenderInstance > 9 and iInstance > options.debugMaxRenderInstance: break imageRelativeFilename = 'image{:0>4d}.jpg'.format(iInstance) imageOutputFilename = os.path.join(detectionBaseDir, imageRelativeFilename) imageFileNames.append(imageRelativeFilename) confidence = instance.bbox[4] confidenceStr = '{:.2f}'.format(confidence) t = confidenceStr + ' (' + instance.filename + ')' titles.append(t) inputFileName = os.path.join(options.imageBase, instance.filename) if not os.path.isfile(inputFileName): print('Warning: could not find file {}'.format(inputFileName)) else: # render_bounding_box(instance.bbox,1,None,inputFileName,imageOutputFilename,0,10) render_bounding_box(instance.bbox, inputFileName, imageOutputFilename, 15) # ...for each instance # Write html for this detection detectionHtmlFile = os.path.join(detectionBaseDir, 'index.html') htmlOptions = write_html_image_list.write_html_image_list() htmlOptions['imageStyle'] = 'max-width:700px;' write_html_image_list.write_html_image_list(detectionHtmlFile, imageFileNames, titles, htmlOptions) directoryDetectionHtmlFiles.append(detectionHtmlFile) directoryDetectionExampleImages.append( os.path.join(detectionName, imageFileNames[0])) detectionHtmlFileRelative = os.path.relpath(detectionHtmlFile, dirBaseDir) title = '<a href="{}">{}</a>'.format(detectionHtmlFileRelative, detectionName) directoryDetectionTitles.append(title) # ...for each detection # Write the html file for this directory directoryHtmlFile = os.path.join(dirBaseDir, 'index.html') write_html_image_list.write_html_image_list( directoryHtmlFile, directoryDetectionExampleImages, directoryDetectionTitles, htmlOptions) return directoryHtmlFile
if len(annos_i) > 0: bboxes = list(annos_i.loc[:, 'bbox']) classes = list(annos_i.loc[:, 'category_id']) vis_utils.render_iMerit_boxes(bboxes, classes, image, label_map) # image changed in place file_name = '{}_gtbbox.jpg'.format(img_name.lower().split('.jpg')[0]) image.save(os.path.join(output_dir, 'rendered_images', file_name)) images_html.append({ 'filename': '{}/{}'.format('rendered_images', file_name), 'title': '{}, number of boxes: {}'.format(img_name, len(annos_i)), 'textStyle': 'font-family:verdana,arial,calibri;font-size:80%;text-align:left;margin-top:20;margin-bottom:5' }) #%% Write to HTML images_html = sorted(images_html, key=lambda x: x['filename']) write_html_image_list( filename=os.path.join(output_dir, 'index.html'), images=images_html, options={ 'headerHtml': '<h1>Sample annotations from {}</h1>'.format(incoming_annotation_path) }) print('Visualized {} images.'.format(len(images_html)))
def _compare_batch_results(options,output_index,pairwise_options): assert options.pairwise_options is None random.seed(options.random_seed) ##%% Validate inputs assert os.path.isfile(pairwise_options.results_filename_a) assert os.path.isfile(pairwise_options.results_filename_b) assert os.path.isdir(options.image_folder) os.makedirs(options.output_folder,exist_ok=True) ##%% Load both result sets with open(pairwise_options.results_filename_a,'r') as f: results_a = json.load(f) with open(pairwise_options.results_filename_b,'r') as f: results_b = json.load(f) assert results_a['detection_categories'] == detection_categories assert results_b['detection_categories'] == detection_categories if pairwise_options.results_description_a is None: if 'detector' not in results_a['info']: print('No model metadata supplied for results-A, assuming MDv4') pairwise_options.results_description_a = 'MDv4 (assumed)' else: pairwise_options.results_description_a = results_a['info']['detector'] if pairwise_options.results_description_b is None: if 'detector' not in results_b['info']: print('No model metadata supplied for results-B, assuming MDv4') pairwise_options.results_description_b = 'MDv4 (assumed)' else: pairwise_options.results_description_b = results_b['info']['detector'] images_a = results_a['images'] images_b = results_b['images'] filename_to_image_a = {im['file']:im for im in images_a} filename_to_image_b = {im['file']:im for im in images_b} ##%% Make sure they represent the same set of images filenames_a = [im['file'] for im in images_a] filenames_b_set = set([im['file'] for im in images_b]) assert len(images_a) == len(images_b) assert len(filenames_a) == len(images_a) assert len(filenames_b_set) == len(images_b) for fn in filenames_a: assert fn in filenames_b_set ##%% Find differences # Each of these maps a filename to a two-element list (the image in set A, the image in set B) # # Right now, we only handle a very simple notion of class transition, where the detection # of maximum confidence changes class *and* both images have an above-threshold detection. common_detections = {} common_non_detections = {} detections_a_only = {} detections_b_only = {} class_transitions = {} # fn = filenames_a[0] for fn in tqdm(filenames_a): im_a = filename_to_image_a[fn] im_b = filename_to_image_b[fn] categories_above_threshold_a = set() if not 'detections' in im_a: assert 'failure' in im_a and im_a['failure'] is not None continue if not 'detections' in im_b: assert 'failure' in im_b and im_b['failure'] is not None continue invalid_category_error = False # det = im_a['detections'][0] for det in im_a['detections']: category_id = det['category'] if category_id not in detection_categories: print('Warning: unexpected category {} for model A on file {}'.format(category_id,fn)) invalid_category_error = True break conf = det['conf'] if conf >= pairwise_options.detection_thresholds_a[detection_categories[category_id]]: categories_above_threshold_a.add(category_id) if invalid_category_error: continue categories_above_threshold_b = set() for det in im_b['detections']: if category_id not in detection_categories: print('Warning: unexpected category {} for model B on file {}'.format(category_id,fn)) invalid_category_error = True break category_id = det['category'] conf = det['conf'] if conf >= pairwise_options.detection_thresholds_b[detection_categories[category_id]]: categories_above_threshold_b.add(category_id) if invalid_category_error: continue im_pair = (im_a,im_b) detection_a = (len(categories_above_threshold_a) > 0) detection_b = (len(categories_above_threshold_b) > 0) if detection_a and detection_b: if categories_above_threshold_a == categories_above_threshold_b: common_detections[fn] = im_pair else: class_transitions[fn] = im_pair elif (not detection_a) and (not detection_b): common_non_detections[fn] = im_pair elif detection_a and (not detection_b): detections_a_only[fn] = im_pair else: assert detection_b and (not detection_a) detections_b_only[fn] = im_pair # ...for each filename print('Of {} files:\n{} common detections\n{} common non-detections\n{} A only\n{} B only\n{} class transitions'.format( len(filenames_a),len(common_detections), len(common_non_detections),len(detections_a_only), len(detections_b_only),len(class_transitions))) ##%% Sample and plot differences from multiprocessing.pool import ThreadPool if options.n_rendering_threads > 1: print('Rendering images with {} workers'.format(options.n_rendering_threads)) pool = ThreadPool(options.n_rendering_threads) categories_to_image_pairs = { 'common_detections':common_detections, 'common_non_detections':common_non_detections, 'detections_a_only':detections_a_only, 'detections_b_only':detections_b_only, 'class_transitions':class_transitions } categories_to_page_titles = { 'common_detections':'Detections common to both models', 'common_non_detections':'Non-detections common to both models', 'detections_a_only':'Detections reported by model A only', 'detections_b_only':'Detections reported by model B only', 'class_transitions':'Detections reported as different classes by models A and B' } local_output_folder = os.path.join(options.output_folder,'cmp_' + \ str(output_index).zfill(3)) def render_detection_comparisons(category,image_pairs,image_filenames): print('Rendering detections for category {}'.format(category)) category_folder = os.path.join(local_output_folder,category) os.makedirs(category_folder,exist_ok=True) # Render two sets of results (i.e., a comparison) for a single # image. def render_image_pair(fn): input_image_path = os.path.join(options.image_folder,fn) assert os.path.isfile(input_image_path), 'Image {} does not exist'.format(input_image_path) im = visualization_utils.open_image(input_image_path) image_pair = image_pairs[fn] detections_a = image_pair[0]['detections'] detections_b = image_pair[1]['detections'] """ def render_detection_bounding_boxes(detections, image, label_map={}, classification_label_map={}, confidence_threshold=0.8, thickness=4, expansion=0, classification_confidence_threshold=0.3, max_classifications=3, colormap=COLORS): """ if options.target_width is not None: im = visualization_utils.resize_image(im, options.target_width) visualization_utils.render_detection_bounding_boxes(detections_a,im, confidence_threshold=pairwise_options.rendering_confidence_threshold_a, thickness=4,expansion=0, colormap=options.colormap_a, textalign=visualization_utils.TEXTALIGN_LEFT) visualization_utils.render_detection_bounding_boxes(detections_b,im, confidence_threshold=pairwise_options.rendering_confidence_threshold_b, thickness=2,expansion=0, colormap=options.colormap_b, textalign=visualization_utils.TEXTALIGN_RIGHT) output_image_fn = path_utils.flatten_path(fn) output_image_path = os.path.join(category_folder,output_image_fn) im.save(output_image_path) return output_image_path # ...def render_image_pair() # fn = image_filenames[0] if options.n_rendering_threads <= 1: output_image_paths = [] for fn in tqdm(image_filenames): output_image_paths.append(render_image_pair(fn)) else: output_image_paths = list(tqdm(pool.imap(render_image_pair, image_filenames), total=len(image_filenames))) return output_image_paths # ...def render_detection_comparisons() # For each category, generate comparison images and the # comparison HTML page. # # category = 'common_detections' for category in categories_to_image_pairs.keys(): # Choose detection pairs we're going to render for this category image_pairs = categories_to_image_pairs[category] image_filenames = list(image_pairs.keys()) if len(image_filenames) > options.max_images_per_category: print('Sampling {} of {} image pairs for category {}'.format( options.max_images_per_category, len(image_filenames), category)) image_filenames = random.sample(image_filenames, options.max_images_per_category) assert len(image_filenames) <= options.max_images_per_category input_image_absolute_paths = [os.path.join(options.image_folder,fn) for fn in image_filenames] category_image_output_paths = render_detection_comparisons(category, image_pairs,image_filenames) category_html_filename = os.path.join(local_output_folder, category + '.html') category_image_output_paths_relative = [os.path.relpath(s,local_output_folder) \ for s in category_image_output_paths] image_info = [] assert len(category_image_output_paths_relative) == len(input_image_absolute_paths) import urllib for i_fn,fn in enumerate(category_image_output_paths_relative): info = { 'filename': fn, 'title': fn, 'textStyle': 'font-family:verdana,arial,calibri;font-size:80%;text-align:left;margin-top:20;margin-bottom:5', 'linkTarget': urllib.parse.quote(input_image_absolute_paths[i_fn]) } image_info.append(info) category_page_header_string = '<h1>{}</h1>'.format(categories_to_page_titles[category]) category_page_header_string += '<p style="font-weight:bold;">\n' category_page_header_string += 'Model A: {}<br/>\n'.format(pairwise_options.results_description_a) category_page_header_string += 'Model B: {}'.format(pairwise_options.results_description_b) category_page_header_string += '</p>\n' category_page_header_string += '<p>\n' category_page_header_string += 'Detection thresholds for A ({}):\n{}<br/>'.format( pairwise_options.results_description_a,str(pairwise_options.detection_thresholds_a)) category_page_header_string += 'Detection thresholds for B ({}):\n{}<br/>'.format( pairwise_options.results_description_b,str(pairwise_options.detection_thresholds_b)) category_page_header_string += 'Rendering threshold for A ({}):\n{}<br/>'.format( pairwise_options.results_description_a,str(pairwise_options.rendering_confidence_threshold_a)) category_page_header_string += 'Rendering threshold for B ({}):\n{}<br/>'.format( pairwise_options.results_description_b,str(pairwise_options.rendering_confidence_threshold_b)) category_page_header_string += '</p>\n' write_html_image_list( category_html_filename, images=image_info, options={ 'headerHtml': category_page_header_string }) # ...for each category ##%% Write the top-level HTML file content html_output_string = '' html_output_string += '<p>Comparing <b>{}</b> (A, red) to <b>{}</b> (B, blue)</p>'.format( pairwise_options.results_description_a,pairwise_options.results_description_b) html_output_string += '<div class="contentdiv">\n' html_output_string += 'Detection thresholds for {}:\n{}<br/>'.format( pairwise_options.results_description_a,str(pairwise_options.detection_thresholds_a)) html_output_string += 'Detection thresholds for {}:\n{}<br/>'.format( pairwise_options.results_description_b,str(pairwise_options.detection_thresholds_b)) html_output_string += 'Rendering threshold for {}:\n{}<br/>'.format( pairwise_options.results_description_a,str(pairwise_options.rendering_confidence_threshold_a)) html_output_string += 'Rendering threshold for {}:\n{}<br/>'.format( pairwise_options.results_description_b,str(pairwise_options.rendering_confidence_threshold_b)) html_output_string += '<br/>' html_output_string += 'Rendering a maximum of {} images per category<br/>'.format(options.max_images_per_category) html_output_string += '<br/>' html_output_string += ('Of {} total files:<br/><br/><div style="margin-left:15px;">{} common detections<br/>{} common non-detections<br/>{} A only<br/>{} B only<br/>{} class transitions</div><br/>'.format( len(filenames_a),len(common_detections), len(common_non_detections),len(detections_a_only), len(detections_b_only),len(class_transitions))) html_output_string += 'Comparison pages:<br/><br/>\n' html_output_string += '<div style="margin-left:15px;">\n' comparison_path_relative = os.path.relpath(local_output_folder,options.output_folder) for category in categories_to_image_pairs.keys(): category_html_filename = os.path.join(comparison_path_relative,category + '.html') html_output_string += '<a href="{}">{}</a><br/>\n'.format( category_html_filename,category) html_output_string += '</div>\n' html_output_string += '</div>\n' return html_output_string
targetWidth = 600 wpercent = (targetWidth / float(img.size[0])) hsize = int((float(img.size[1]) * float(wpercent))) imgResized = img.resize((targetWidth, hsize), Image.ANTIALIAS) return imgResized outputDir = r'd:\temp\ocrTest' os.makedirs(outputDir, exist_ok=True) outputSummaryFile = os.path.join(outputDir, 'summary.html') htmlImageList = [] htmlTitleList = [] options = write_html_image_list.write_html_image_list() for iImage, regionSet in enumerate(imageRegions): # Add image name and resized image image = resizeImage(images[iImage]) fn = os.path.join(outputDir, 'img_{}_base.png'.format(iImage)) image.save(fn) title = 'Image: {}'.format(ntpath.basename(imageFileNames[iImage])) htmlImageList.append({'filename': fn, 'title': title}) bPrintedDate = False # Add results and individual region images for iRegion, region in enumerate(regionSet):
def visualize_incoming_annotations(args): print('Connecting to MegaDB to get the datasets table...') megadb_utils = MegadbUtils() datasets_table = megadb_utils.get_datasets_table() print('Loading the MegaDB entries...') with open(args.megadb_entries) as f: sequences = json.load(f) print(f'Total number of sequences: {len(sequences)}') dataset_seq_images = defaultdict(dict) for seq in sequences: dataset_seq_images[seq['dataset']][seq['seq_id']] = seq['images'] print('Loading incoming annotation entries...') incoming = IndexedJsonDb(args.incoming_annotation) print( f'Number of images in this annotation file: {len(incoming.image_id_to_image)}' ) if args.num_to_visualize != -1 and args.num_to_visualize <= len( incoming.image_id_to_image): incoming_id_to_anno = sample( list(incoming.image_id_to_annotations.items()), args.num_to_visualize) else: incoming_id_to_anno = incoming.image_id_to_annotations.items() # The file_name field in the incoming json looks like alka_squirrels.seq2020_05_07_25C.frame119221.jpg # we need to use the dataset, sequence and frame info to find the actual path in blob storage # using the sequences images_html = [] for image_id, annotations in tqdm(incoming_id_to_anno): if args.trim_to_images_bboxes_labeled and annotations[0][ 'category_id'] == 5: # category_id 5 is No Object Visible continue anno_file_name = incoming.image_id_to_image[image_id]['file_name'] parts = anno_file_name.split('.') dataset_name = parts[0] seq_id = parts[1].split('seq')[1] frame_num = int(parts[2].split('frame')[1]) im_rel_path = get_image_rel_path(dataset_seq_images, dataset_name, seq_id, frame_num) if im_rel_path is None: print(f'Not found in megadb entries: dataset {dataset_name},' f' seq_id {seq_id}, frame_num {frame_num}') continue im_full_path = megadb_utils.get_full_path(datasets_table, dataset_name, im_rel_path) # download the image container_client = megadb_utils.get_storage_client( datasets_table, dataset_name) downloader = container_client.download_blob(im_full_path) image_file = io.BytesIO() blob_props = downloader.download_to_stream(image_file) image = vis_utils.open_image(image_file) boxes = [anno['bbox'] for anno in annotations] classes = [anno['category_id'] for anno in annotations] vis_utils.render_iMerit_boxes(boxes, classes, image, label_map=incoming.cat_id_to_name) file_name = '{}_gtbbox.jpg'.format( os.path.splitext(anno_file_name)[0].replace('/', '~')) image = vis_utils.resize_image(image, args.output_image_width) image.save(os.path.join(args.output_dir, 'rendered_images', file_name)) images_html.append({ 'filename': '{}/{}'.format('rendered_images', file_name), 'title': '{}, number of boxes: {}'.format( anno_file_name, len([b for b in boxes if len(b) > 0])), 'textStyle': 'font-family:verdana,arial,calibri;font-size:80%;text-align:left;margin-top:20;margin-bottom:5' }) # Write to HTML images_html = sorted(images_html, key=lambda x: x['filename']) write_html_image_list(filename=os.path.join(args.output_dir, 'index.html'), images=images_html, options={ 'headerHtml': '<h1>Sample annotations from {}</h1>'.format( args.incoming_annotation) }) print('Visualized {} images.'.format(len(images_html)))