Spaces:
Sleeping
Sleeping
| from pycocotools.coco import COCO | |
| import os | |
| import random | |
| from functools import reduce | |
| from io import StringIO | |
| from detectron2.utils.visualizer import Visualizer | |
| import matplotlib.pyplot as plt | |
| import numpy as np | |
| import pandas as pd | |
| from scipy import stats | |
| from cubercnn import data, util, vis | |
| from cubercnn.config import get_cfg_defaults | |
| from cubercnn.data.build import (build_detection_test_loader, | |
| build_detection_train_loader) | |
| from cubercnn.data.dataset_mapper import DatasetMapper3D | |
| from cubercnn.data.datasets import load_omni3d_json, simple_register | |
| from detectron2.config import get_cfg | |
| from detectron2.data import DatasetCatalog, MetadataCatalog | |
| from detectron2.structures.boxes import BoxMode | |
| from detectron2.utils.logger import setup_logger | |
| color = '#384860' | |
| second_color = '#97a6c4' | |
| def load_gt(dataset='SUNRGBD', mode='test', single_im=True, filter=False, img_idx=150): | |
| # we can do this block of code to get the categories reduced number of categories in the sunrgbd dataset as there normally is 83 categories, however we only work with 38. | |
| config_file = 'configs/Base_Omni3D.yaml' | |
| if filter: | |
| cfg, filter_settings = get_config_and_filter_settings(config_file) | |
| else: | |
| filter_settings = None | |
| if mode == 'test': | |
| dataset_paths_to_json = ['datasets/Omni3D/'+dataset+'_test.json'] | |
| elif mode == 'train': | |
| dataset_paths_to_json = ['datasets/Omni3D/'+dataset+'_train.json'] | |
| # Get Image and annotations | |
| try: | |
| dataset = data.Omni3D(dataset_paths_to_json, filter_settings=filter_settings) | |
| except: | |
| print('Dataset does not exist or is not in the correct format!') | |
| exit() | |
| imgIds = dataset.getImgIds() | |
| imgs = dataset.loadImgs(imgIds) | |
| if single_im: | |
| # img = random.choice(imgs) | |
| # 730 and 150 are used in the report | |
| img = imgs[img_idx] | |
| annIds = dataset.getAnnIds(imgIds=img['id']) | |
| else: | |
| # get all annotations | |
| img = imgs | |
| annIds = dataset.getAnnIds() | |
| anns = dataset.loadAnns(annIds) | |
| # Extract necessary annotations | |
| R_cams = [] | |
| center_cams = [] | |
| dimensions_all = [] | |
| cats = [] | |
| bboxes = [] | |
| for instance in anns: | |
| if 'bbox2D_tight' in instance and instance['bbox2D_tight'][0] != -1: | |
| bboxes.append(instance['bbox2D_tight']) # boxes are XYXY_ABS by default | |
| elif 'bbox2D_trunc' in instance and not np.all([val==-1 for val in instance['bbox2D_trunc']]): | |
| bboxes.append(instance['bbox2D_trunc']) # boxes are XYXY_ABS by default | |
| elif 'bbox2D_proj' in instance: | |
| bboxes.append(instance['bbox2D_proj']) # boxes are XYXY_ABS by default | |
| else: | |
| continue | |
| R_cams.append(instance['R_cam']) | |
| center_cams.append(instance['center_cam']) | |
| dimensions_all.append(instance['dimensions']) | |
| cats.append(instance['category_name']) | |
| return img, R_cams, center_cams, dimensions_all, cats, bboxes | |
| def plot_scene(image_path, output_dir, center_cams, dimensions_all, Rs, K, cats, bboxes): | |
| # TODO: currently this function does not filter out invalid annotations, but it should have the option to do so. | |
| # Compute meshes | |
| meshes = [] | |
| meshes_text = [] | |
| for idx, (center_cam, dimensions, pose, cat) in enumerate(zip( | |
| center_cams, dimensions_all, Rs, cats | |
| )): | |
| bbox3D = center_cam + dimensions | |
| meshes_text.append('{}'.format(cat)) | |
| color = [c/255.0 for c in util.get_color(idx)] | |
| box_mesh = util.mesh_cuboid(bbox3D, pose, color=color) | |
| meshes.append(box_mesh) | |
| image_name = util.file_parts(image_path)[1] | |
| print('File: {} with {} dets'.format(image_name, len(meshes))) | |
| np.random.seed(0) | |
| colors = [np.concatenate([np.random.random(3), np.array([0.6])], axis=0) for _ in range(len(meshes))] | |
| # Plot | |
| image = util.imread('datasets/'+image_path) | |
| if len(meshes) > 0: | |
| im_drawn_rgb, im_topdown, _ = vis.draw_scene_view(image, np.array(K), meshes, colors=colors, text=meshes_text, scale=image.shape[0], blend_weight=0.5, blend_weight_overlay=0.85) | |
| if False: | |
| im_concat = np.concatenate((im_drawn_rgb, im_topdown), axis=1) | |
| vis.imshow(im_concat) | |
| util.imwrite(im_drawn_rgb, os.path.join(output_dir, image_name+'_boxes.jpg')) | |
| util.imwrite(im_topdown, os.path.join(output_dir, image_name+'_novel.jpg')) | |
| v_pred = Visualizer(image, None) | |
| #bboxes = [[320, 150, 560, 340]] # low loss | |
| #bboxes = [[350, 220, 440, 290]] # high loss | |
| #bboxes = [[340, 163, 540, 297]] # fail loss | |
| v_pred = v_pred.overlay_instances(boxes=np.array(bboxes), assigned_colors=colors)#[np.array([0.5,0,0.5])])#colors) | |
| util.imwrite(v_pred.get_image(), os.path.join(output_dir, image_name+'_pred_boxes.jpg')) | |
| #im_drawn_rgb, im_topdown, _ = vis.draw_scene_view(v_pred.get_image(), np.array(K), meshes, colors=colors, text=meshes_text, scale=image.shape[0], blend_weight=0.5, blend_weight_overlay=0.85) | |
| #util.imwrite(im_drawn_rgb, os.path.join(output_dir, image_name+'_boxes_with_2d.jpg')) | |
| else: | |
| print('No meshes') | |
| util.imwrite(image, os.path.join(output_dir, image_name+'_boxes.jpg')) | |
| def show_data(dataset, filter_invalid=False, output_dir='output/playground'): | |
| # Load Image and Ground Truths | |
| image, Rs, center_cams, dimensions_all, cats, bboxes = load_gt(dataset, filter=filter_invalid) | |
| # Create Output Directory | |
| util.mkdir_if_missing(output_dir) | |
| plot_scene(image['file_path'], output_dir, center_cams, dimensions_all, Rs, image['K'], cats, bboxes) | |
| def category_distribution(dataset): | |
| '''Plot a histogram of the category distribution in the dataset.''' | |
| # Load Image and Ground Truths | |
| image, Rs, center_cams, dimensions_all, cats, bboxes = load_gt(dataset, mode='train', single_im=False) | |
| image_t, Rs_t, center_cams_t, dimensions_all_t, cats_t, bboxes = load_gt(dataset, mode='test', single_im=False) | |
| config_file = 'configs/Base_Omni3D.yaml' | |
| cfg, filter_settings = get_config_and_filter_settings(config_file) | |
| annotation_file = 'datasets/Omni3D/SUNRGBD_train.json' | |
| coco_api = COCO(annotation_file) | |
| meta = MetadataCatalog.get('SUNRGBD') | |
| cat_ids = sorted(coco_api.getCatIds(filter_settings['category_names'])) | |
| cats_sun = coco_api.loadCats(cat_ids) | |
| thing_classes = [c["name"] for c in sorted(cats_sun, key=lambda x: x["id"])] | |
| output_dir = 'output/figures/' + dataset | |
| util.mkdir_if_missing(output_dir) | |
| # histogram of categories | |
| cats_all = cats + cats_t | |
| # cats_unique = list(set(cats_all)) | |
| cats_unique = thing_classes | |
| print('cats unique: ', len(cats_unique)) | |
| # make dict with count of each category | |
| cats_count = {cat: cats_all.count(cat) for cat in cats_unique} | |
| cats_sorted = dict(sorted(cats_count.items(), key=lambda x: x[1], reverse=True)) | |
| plt.figure(figsize=(14,5)) | |
| plt.bar(cats_sorted.keys(), cats_sorted.values()) | |
| plt.xticks(rotation=60, size=9) | |
| plt.title('Category Distribution') | |
| plt.savefig(os.path.join(output_dir, 'category_distribution.png'),dpi=300, bbox_inches='tight') | |
| plt.close() | |
| return cats_sorted | |
| def spatial_statistics(dataset): | |
| '''Compute spatial statistics of the dataset. | |
| wanted to reproduce fig. 7 from the omni3D paper | |
| however, we must standardise the images for it to work | |
| ''' | |
| # Load Image and Ground | |
| # this function filters out invalid images if there are no valid annotations in the image | |
| # annnotations in each image can also be marked as is_ignore => True | |
| image_root = 'datasets' | |
| cfg, filter_settings = get_config_and_filter_settings() | |
| dataset_names = ['SUNRGBD_train','SUNRGBD_test','SUNRGBD_val'] | |
| output_dir = 'output/figures/' + dataset | |
| # this is almost the same as the simple_register function, but it also stores the model metadata | |
| # which is needed for the load_omni3d_json function | |
| data.register_and_store_model_metadata(None, output_dir, filter_settings=filter_settings) | |
| data_dicts = [] | |
| for dataset_name in dataset_names: | |
| json_file = 'datasets/Omni3D/'+dataset_name+'.json' | |
| data_dict = load_omni3d_json(json_file, image_root, dataset_name, filter_settings, filter_empty=True) | |
| data_dicts.extend(data_dict) | |
| # standardise the images to a fixed size | |
| # and map the annotations to the standardised images | |
| std_image_size = (480//4, 640//4) | |
| tot_outliers = 0 | |
| img = np.zeros(std_image_size) | |
| for img_dict in data_dicts: | |
| original_width = img_dict['width'] | |
| original_height = img_dict['height'] | |
| # Calculate the scale factor for resizing | |
| scale_x = std_image_size[1] / original_width | |
| scale_y = std_image_size[0] / original_height | |
| # Update the image size in the annotation | |
| img_dict['width'] = std_image_size[1] | |
| img_dict['height'] = std_image_size[0] | |
| for anno in img_dict['annotations']: | |
| if not anno['ignore']: | |
| # Update the 2D box coordinates (boxes are XYWH) | |
| anno['bbox2D_tight'][0] *= scale_x | |
| anno['bbox2D_tight'][1] *= scale_y | |
| anno['bbox2D_tight'][2] *= scale_x | |
| anno['bbox2D_tight'][3] *= scale_y | |
| # get the centerpoint of the annotation as (x, y) | |
| # x0, y0, x1, y1 = BoxMode.convert(anno['bbox2D_tight'], BoxMode.XYWH_ABS, BoxMode.XYXY_ABS) | |
| x0, y0, x1, y1 = anno['bbox2D_tight'] | |
| x_m, y_m = int((x0+x1)/2), int((y0+y1)/2) | |
| if x_m >= std_image_size[1] or x_m < 0: | |
| # print(f'x out of line {x_m}') | |
| tot_outliers += 1 | |
| elif y_m >= std_image_size[0] or y_m < 0: | |
| # print(f'y out of line {y_m}') | |
| tot_outliers += 1 | |
| else: | |
| img[y_m, x_m] += 1 | |
| else: | |
| # Remove the annotation if it is marked as ignore | |
| img_dict['annotations'].remove(anno) | |
| print('num center points outside frame: ', tot_outliers) | |
| img = img/img.max() | |
| # this point is so large that all the points become invisible, so I remove it. | |
| img[0,0] = 0.00 | |
| img = img/img.max() | |
| plt.figure() | |
| plt.imshow(img, cmap='gray_r', vmin=0, vmax=1) | |
| plt.xticks([]); plt.yticks([]) | |
| plt.title('Histogram of 2D box centre points') | |
| # plt.box(False) | |
| plt.savefig(os.path.join(output_dir, '2d_histogram.png'),dpi=300, bbox_inches='tight') | |
| plt.close() | |
| return | |
| def AP_vs_no_of_classes(dataset, files:list=['output/Baseline_sgd/log.txt','output/omni_equalised/log.txt','output/omni_pseudo_gt/log.txt','output/proposal_AP/log.txt','output/exp_10_iou_zpseudogt_dims_depthrange_rotalign_ground/log.txt']): | |
| '''Search the log file for the precision numbers corresponding to the last iteration | |
| then parse it in as a pd.DataFrame and plot the AP vs number of classes''' | |
| # search the file from the back until the line | |
| # cubercnn.vis.logperf INFO: Performance for each of 38 categories on SUNRGBD_test: | |
| # is found | |
| target_line = "cubercnn.vis.logperf INFO: Performance for each of 38 categories on SUNRGBD_test:" | |
| model_names = ['Base Cube R-CNN', 'Time-eq.', 'Pseudo GT', 'Proposal', 'Weak loss'] | |
| df = [] | |
| for file, model_name in zip(files, model_names): | |
| df_i = search_file_backwards(file, target_line).rename(columns={'AP3D':f'{model_name} AP3D', 'AP2D':f'{model_name} AP2D'}) | |
| assert df_i is not None, 'df not found' | |
| df.append(df_i) | |
| # merge df's | |
| df = reduce(lambda x, y: pd.merge(x, y, on = 'category'), df) | |
| # sort df by ap3d of model 1 | |
| df = df.sort_values(by='Base Cube R-CNN AP3D', ascending=False) | |
| cats = category_distribution(dataset) | |
| df.sort_values(by='category', inplace=True) | |
| cats = dict(sorted(cats.items())) | |
| merged_df = pd.merge(df.reset_index(), pd.DataFrame(cats.values(), columns=['cats']), left_index=True, right_index=True) | |
| merged_df = merged_df.sort_values(by='cats') | |
| merged_df = merged_df.drop('index',axis=1) | |
| merged_df = merged_df.reset_index(drop=True) | |
| fig, ax = plt.subplots(figsize=(12,8)) | |
| for model_name in model_names: | |
| if model_name == 'Base Cube R-CNN': | |
| scale = 114 | |
| else: | |
| scale = 10.15 | |
| # convert the annotation time to hours | |
| time = merged_df['cats']*scale / 60 / 60 | |
| ax.scatter(time, merged_df[f'{model_name} AP3D'].values, s=merged_df[f'{model_name} AP2D'].values*2, alpha=0.5, label=model_name) | |
| for i, txt in enumerate(merged_df['category']): | |
| ax.text(time[i], merged_df[f'{model_name} AP3D'].values[i], txt, fontsize=merged_df[f'{model_name} AP3D'].values[i]*0.3+3) | |
| correlation_coef = np.corrcoef(time, merged_df[f'{model_name} AP3D'].values)[0, 1] | |
| line_fit = np.polyfit(time, merged_df[f'{model_name} AP3D'].values, 1) | |
| # plot the line of best fit | |
| ax.plot(time, np.poly1d(line_fit)(time), linestyle='--',alpha=0.5, label=f'Linear fit (R={correlation_coef:.2f})') | |
| # Set labels and title | |
| ax.set_xlabel('Annotation time (h)') | |
| ax.set_ylabel('AP3D') | |
| ax.set_xscale('log') | |
| ax.set_title('AP3D vs class-wise annotation time') | |
| ax.legend(title='AP3D scaled by AP2D') | |
| # Save the plot | |
| plt.savefig('output/figures/'+dataset+'/AP_vs_no_of_classes_all.png', dpi=300, bbox_inches='tight') | |
| plt.close() | |
| return | |
| def AP3D_vs_AP2D(dataset, mode = 'standard', files=['output/Baseline_sgd/log.txt','output/omni_equalised/log.txt','output/omni_pseudo_gt/log.txt','output/proposal_AP/log.txt','output/exp_10_iou_zpseudogt_dims_depthrange_rotalign_ground/log.txt']): | |
| '''Search the log file for the precision numbers corresponding to the last iteration | |
| then parse it in as a pd.DataFrame and plot the AP vs number of classes''' | |
| # search the file from the back until the line | |
| # cubercnn.vis.logperf INFO: Performance for each of 38 categories on SUNRGBD_test: | |
| # is found | |
| target_line = "cubercnn.vis.logperf INFO: Performance for each of 38 categories on SUNRGBD_test:" | |
| model_names = ['Base Cube R-CNN', 'Time-eq.', 'Pseudo GT', 'Proposal', 'Weak loss'] | |
| df = [] | |
| for file, model_name in zip(files, model_names): | |
| df_i = search_file_backwards(file, target_line).rename(columns={'AP3D':f'{model_name} AP3D', 'AP2D':f'{model_name} AP2D'}) | |
| assert df_i is not None, 'df not found' | |
| df.append(df_i) | |
| # merge df's | |
| df = reduce(lambda x, y: pd.merge(x, y, on = 'category'), df) | |
| # sort df by ap3d of model 1 | |
| df = df.sort_values(by='Base Cube R-CNN AP3D', ascending=False) | |
| cats = category_distribution(dataset) | |
| df.sort_values(by='category', inplace=True) | |
| cats = dict(sorted(cats.items())) | |
| merged_df = pd.merge(df.reset_index(), pd.DataFrame(cats.values(), columns=['cats']), left_index=True, right_index=True) | |
| merged_df = merged_df.sort_values(by='cats') | |
| merged_df = merged_df.drop('index',axis=1) | |
| merged_df = merged_df.reset_index(drop=True) | |
| # mode = 'standard' # 'log' | |
| fig, ax = plt.subplots(figsize=(12,8)) | |
| for model_name in model_names: | |
| if mode == 'standard': s=merged_df[f'{model_name} AP2D'].values*2 | |
| else: s = None | |
| # we have to add 0.001 to the values to avoid log(0) errors | |
| ax.scatter(merged_df[f'{model_name} AP2D'].values+0.001, merged_df[f'{model_name} AP3D'].values+0.001, alpha=0.5, label=model_name, s=s) | |
| for i, txt in enumerate(merged_df['category']): | |
| if mode == 'standard': fontsize=merged_df[f'{model_name} AP3D'].values[i]*0.3+3 | |
| else: fontsize=7 | |
| ax.text(merged_df[f'{model_name} AP2D'].values[i]+0.001, merged_df[f'{model_name} AP3D'].values[i]+0.001, txt,fontsize=fontsize) | |
| # plot average line | |
| ax.plot((0, 70), (0, 70), linestyle='--', color=color, alpha=0.3, label=f'AP2D=AP3D') | |
| # Set labels and title | |
| if mode == 'log': | |
| ax.set_xscale('log') | |
| ax.set_yscale('log') | |
| ax.set_xlabel('AP2D') | |
| ax.set_ylabel('AP3D') | |
| # ax.set_xlim(0.1, 75); ax.set_ylim(0.1, 75) | |
| ax.set_title('AP in 3D vs AP in 2D') | |
| ax.legend() | |
| # if mode == 'log': | |
| # # for some obscure reason the log plot fails to save | |
| # plt.show() | |
| # # Save the plot | |
| # else: | |
| plt.savefig('output/figures/'+dataset+f'/AP3D_vs_AP2D_all_{mode}.png', dpi=300, bbox_inches='tight') | |
| plt.close() | |
| return | |
| def search_file_backwards(file_path:str, target_line:str) -> pd.DataFrame: | |
| '''Search a file backwards for a target line and return the table of the performance of the model. The point of this is to parse the part of the log file that looks like this | |
| | category | AP2D | AP3D | category | AP2D | AP3D | category | AP2D | AP3D | | |
| |:----------:|:--------|:----------|:-----------:|:---------|:---------|:------------:|:----------|:-----------| | |
| | chair | 45.9374 | 53.4913 | table | 34.5982 | 39.7769 | cabinet | 16.3693 | 14.0878 | | |
| | lamp | 24.8081 | 7.67653 | books | 0.928978 | 0.599711 | sofa | 49.2354 | 57.9649 | | |
| ... | |
| To a pandas DataFrame that has 3 columns: category, AP2D, AP3D''' | |
| import re | |
| with open(file_path, 'r') as file: | |
| lines = file.readlines() | |
| for i, line in enumerate(reversed(lines)): | |
| is_found = re.search(f'.*{target_line}$', line) | |
| if is_found: | |
| table = lines[-i:-i+15] | |
| tab_as_str= ' '.join(table) | |
| # i know this is really ugly | |
| df = pd.read_csv( StringIO(tab_as_str.replace(' ', '')), # Get rid of whitespaces | |
| sep='|',).dropna(axis=1, how='all').drop(0) | |
| # https://stackoverflow.com/a/65884212 | |
| df.columns = pd.MultiIndex.from_frame(df.columns.str.split('.', expand=True) | |
| .to_frame().fillna('0')) | |
| df = df.stack().reset_index(level=1, drop=True).reset_index().drop('index', axis=1) | |
| df['AP3D'] = df['AP3D'].astype(float) | |
| df['AP2D'] = df['AP2D'].astype(float) | |
| return df | |
| return None | |
| def get_config_and_filter_settings(config_file='configs/Base_Omni3D.yaml'): | |
| # we must load the config file to get the filter settings | |
| cfg = get_cfg() | |
| get_cfg_defaults(cfg) | |
| cfg.merge_from_file(config_file) | |
| # must setup logger to get info about filtered out annotations | |
| setup_logger(output=cfg.OUTPUT_DIR, name="cubercnn") | |
| filter_settings = data.get_filter_settings_from_cfg(cfg) | |
| return cfg, filter_settings | |
| def init_dataloader(): | |
| ''' dataloader stuff. | |
| currently not used anywhere, because I'm not sure what the difference between the omni3d dataset and load omni3D json functions are. this is a 3rd alternative to this. The train script calls something similar to this.''' | |
| cfg, filter_settings = get_config_and_filter_settings() | |
| dataset_names = ['SUNRGBD_train','SUNRGBD_val'] | |
| dataset_paths_to_json = ['datasets/Omni3D/'+dataset_name+'.json' for dataset_name in dataset_names] | |
| for dataset_name in dataset_names: | |
| simple_register(dataset_name, filter_settings, filter_empty=True) | |
| # Get Image and annotations | |
| datasets = data.Omni3D(dataset_paths_to_json, filter_settings=filter_settings) | |
| data.register_and_store_model_metadata(datasets, cfg.OUTPUT_DIR, filter_settings) | |
| thing_classes = MetadataCatalog.get('omni3d_model').thing_classes | |
| dataset_id_to_contiguous_id = MetadataCatalog.get('omni3d_model').thing_dataset_id_to_contiguous_id | |
| infos = datasets.dataset['info'] | |
| dataset_id_to_unknown_cats = {} | |
| possible_categories = set(i for i in range(cfg.MODEL.ROI_HEADS.NUM_CLASSES + 1)) | |
| dataset_id_to_src = {} | |
| for info in infos: | |
| dataset_id = info['id'] | |
| known_category_training_ids = set() | |
| if not dataset_id in dataset_id_to_src: | |
| dataset_id_to_src[dataset_id] = info['source'] | |
| for id in info['known_category_ids']: | |
| if id in dataset_id_to_contiguous_id: | |
| known_category_training_ids.add(dataset_id_to_contiguous_id[id]) | |
| # determine and store the unknown categories. | |
| unknown_categories = possible_categories - known_category_training_ids | |
| dataset_id_to_unknown_cats[dataset_id] = unknown_categories | |
| from detectron2 import data as d2data | |
| NoOPaug = d2data.transforms.NoOpTransform() | |
| # def NoOPaug(input): | |
| # return input | |
| # TODO: how to load in images without having them resized? | |
| # data_mapper = DatasetMapper3D(cfg, augmentations=[NoOPaug], is_train=True) | |
| data_mapper = DatasetMapper3D(cfg, is_train=True) | |
| # test loader does resize images, like the train loader does | |
| # this is the function that filters out the invalid annotations | |
| data_loader = build_detection_train_loader(cfg, mapper=data_mapper, dataset_id_to_src=dataset_id_to_src, num_workers=1) | |
| # data_loader = build_detection_test_loader(cfg, dataset_names[1], num_workers=1) | |
| # this is a detectron 2 thing that we just have to do | |
| data_mapper.dataset_id_to_unknown_cats = dataset_id_to_unknown_cats | |
| for item in data_loader: | |
| print(item) | |
| def vol_over_cat(dataset): | |
| ''' | |
| Errorbarplot of volume of object category | |
| ''' | |
| # Load Image and Ground Truths | |
| image, Rs, center_cams, dimensions_all, cats, bboxes = load_gt(dataset, mode='train', single_im=False) | |
| image_t, Rs_t, center_cams_t, dimensions_all_t, cats_t, bboxes = load_gt(dataset, mode='test', single_im=False) | |
| output_dir = 'output/figures/' + dataset | |
| util.mkdir_if_missing(output_dir) | |
| # histogram of categories | |
| cats_all = cats + cats_t | |
| cats_unique = list(set(cats_all)) | |
| # Create dictionary with np.prod(dimensions) for each category | |
| cats_vol = {cat: [] for cat in cats_unique} | |
| for cat, dims in zip(cats, dimensions_all): | |
| if np.prod(dims) > 0: | |
| cats_vol[cat].append(np.prod(dims)) | |
| for cat, dims in zip(cats_t, dimensions_all_t): | |
| if np.prod(dims) > 0: | |
| cats_vol[cat].append(np.prod(dims)) | |
| # make dict with mean and std of each category | |
| cats_mean = {cat: np.mean(cats_vol[cat]) for cat in cats_unique} | |
| cats_error = {cat: np.std(cats_vol[cat]) for cat in cats_unique} | |
| keys = np.array(list(cats_mean.keys())) | |
| means = np.array(list(cats_mean.values())) | |
| errors = np.array(list(cats_error.values())) | |
| # Calculate Z-scores for 5th and 95th percentiles | |
| from scipy.stats import norm | |
| z_lower = norm.ppf(0.05) | |
| z_upper = norm.ppf(0.95) | |
| bounds = [] | |
| for mean, std in zip(means, errors): | |
| # Calculate the lower and upper bounds of the interval | |
| lower_bound = mean + z_lower * std | |
| upper_bound = mean + z_upper * std | |
| bounds.append((max(0,lower_bound), upper_bound)) | |
| plt.figure(figsize=(14,5)) | |
| for i, (mean, (lower_bound, upper_bound)) in enumerate(zip(means, bounds)): | |
| plt.vlines(x=i, ymin=lower_bound, ymax=upper_bound, color='gray', linewidth=2) | |
| plt.plot([i], [mean], marker='o', color=color) | |
| plt.xticks(np.arange(len(keys)), keys, rotation=60, size=9) | |
| plt.xlabel('Category') | |
| plt.ylabel('Volume') | |
| plt.title('Category Distribution') | |
| plt.savefig(os.path.join(output_dir, 'volume_distribution.png'), dpi=300, bbox_inches='tight') | |
| plt.close() | |
| def gt_stats(dataset): | |
| ''' | |
| Errorbarplot of volume of object category | |
| ''' | |
| # Load Image and Ground Truths | |
| image, Rs, center_cams, dimensions_all, cats, bboxes = load_gt(dataset, mode='train', single_im=False) | |
| image_t, Rs_t, center_cams_t, dimensions_all_t, cats_t, bboxes = load_gt(dataset, mode='test', single_im=False) | |
| output_dir = 'output/figures/' + dataset | |
| util.mkdir_if_missing(output_dir) | |
| # histogram of centers | |
| center_all = center_cams + center_cams_t | |
| center_all = np.transpose(np.array(center_all)) | |
| # Filter -1 annotations | |
| valid_columns = center_all[0] != -1 | |
| center_all = center_all[:,valid_columns] | |
| x_label = ['x', 'y', 'z'] | |
| fig, axes = plt.subplots(1, len(center_all), figsize=(18, 5)) | |
| for i in range(len(center_all)): | |
| axes[i].hist(center_all[i], color=color, bins=20) | |
| axes[i].set_xlabel(x_label[i]) | |
| axes[i].set_ylabel('Count') | |
| fig.suptitle('Center Distribution in Meters') | |
| plt.savefig(os.path.join(output_dir, 'center.png'), dpi=300, bbox_inches='tight') | |
| plt.close() | |
| # histogram of dimensions | |
| dimensions_all = dimensions_all + dimensions_all_t | |
| dimensions_all = np.transpose(np.array(dimensions_all)) | |
| # Filter -1 annotations | |
| valid_columns = dimensions_all[0] != -1 | |
| dimensions_all = dimensions_all[:,valid_columns] | |
| x_label = ['w', 'h', 'l'] | |
| fig, axes = plt.subplots(1, len(dimensions_all), figsize=(18, 5)) | |
| for i in range(len(dimensions_all)): | |
| axes[i].hist(dimensions_all[i], color=color, bins=20) | |
| axes[i].set_xlabel(x_label[i]) | |
| axes[i].set_ylabel('Count') | |
| fig.suptitle('Dimensions Distribution in Meters') | |
| plt.savefig(os.path.join(output_dir, 'dimensions.png'), dpi=300, bbox_inches='tight') | |
| plt.close() | |
| def report_figures(dataset, filter_invalid=False, output_dir='output/report_images'): | |
| # Create Output Directory | |
| util.mkdir_if_missing(output_dir) | |
| util.mkdir_if_missing(output_dir+'/low_green') | |
| util.mkdir_if_missing(output_dir+'/high_green') | |
| util.mkdir_if_missing(output_dir+'/fail_green') | |
| util.mkdir_if_missing(output_dir+'/low_red') | |
| util.mkdir_if_missing(output_dir+'/high_red') | |
| util.mkdir_if_missing(output_dir+'/fail_red') | |
| util.mkdir_if_missing(output_dir+'/low_blue') | |
| util.mkdir_if_missing(output_dir+'/high_blue') | |
| util.mkdir_if_missing(output_dir+'/fail_blue') | |
| # Load Image and Ground Truths | |
| image, Rs, center_cams, dimensions_all, cats, bboxes = load_gt(dataset, filter=filter_invalid, img_idx=352) | |
| gt_center = center_cams[1:] | |
| gt_dim = dimensions_all[1:] | |
| gt_Rs = Rs[1:] | |
| cats = cats[1:] | |
| gt_bb = bboxes[1:] | |
| # Make low loss boxes for IoU, ps. z and proj | |
| center = gt_center[-1] | |
| dim = gt_dim[-1] | |
| R = gt_Rs[-1] | |
| cat = cats[-1] | |
| bb = gt_bb[-1] | |
| plot_scene(image['file_path'], output_dir+'/low_green', [center], [dim], [R], image['K'], [cat], [bb]) | |
| # Make high loss boxes for IoU, ps. z and proj | |
| center = [gt_center[-1][0],gt_center[-1][1],gt_center[-1][2]+3] | |
| dim = gt_dim[-1] | |
| R = gt_Rs[-1] | |
| cat = cats[-1] | |
| bb = gt_bb[-1] | |
| plot_scene(image['file_path'], output_dir+'/high_green', [center], [dim], [R], image['K'], [cat], [bb]) | |
| # Make fail loss boxes for IoU, ps. z and proj | |
| center = [gt_center[-1][0]-0.03,gt_center[-1][1],gt_center[-1][2]] | |
| dim = [0.05,0.71,0.05] | |
| R = util.euler2mat(np.array([0,0,45])) | |
| cat = cats[-1] | |
| bb = gt_bb[-1] | |
| plot_scene(image['file_path'], output_dir+'/fail_green', [center], [dim], [R], image['K'], [cat], [bb]) | |
| # Make low loss boxes for range and seg | |
| center = gt_center[0] | |
| dim = gt_dim[0] | |
| R = gt_Rs[0] | |
| cat = cats[0] | |
| bb = gt_bb[0] | |
| plot_scene(image['file_path'], output_dir+'/low_red', [center], [dim], [R], image['K'], [cat], [bb]) | |
| # Make high loss boxes for range and seg | |
| center = [gt_center[0][0],gt_center[0][1]+0.3,gt_center[0][2]] | |
| dim = [gt_dim[0][0]+1.5,gt_dim[0][1]-0.6,gt_dim[0][2]] | |
| R = gt_Rs[0] | |
| cat = cats[0] | |
| bb = gt_bb[0] | |
| plot_scene(image['file_path'], output_dir+'/high_red', [center], [dim], [R], image['K'], [cat], [bb]) | |
| # Make fail loss boxes for range and seg | |
| center = [gt_center[0][0]+0.25,gt_center[0][1],gt_center[0][2]] | |
| dim = [gt_dim[0][0]+0.7,gt_dim[0][1],gt_dim[0][2]] | |
| R = gt_Rs[-1] | |
| cat = cats[-1] | |
| bb = gt_bb[-1] | |
| plot_scene(image['file_path'], output_dir+'/fail_red', [center], [dim], [R], image['K'], [cat], [bb]) | |
| # Make low loss boxes for dim, pose and align | |
| center = gt_center[1:] | |
| dim = [[gt_dim[1][0]*1.5,gt_dim[1][1],gt_dim[1][2]*1.5], gt_dim[2]] | |
| R = gt_Rs[1:] | |
| cat = cats[1:] | |
| bb = gt_bb[1:] | |
| plot_scene(image['file_path'], output_dir+'/low_blue', center, dim, R, image['K'], cat, bb) | |
| # Make high loss boxes for dim, pose and align | |
| center = gt_center[1:] | |
| dim = gt_dim[1:] | |
| R = [util.euler2mat(util.mat2euler(np.array(gt_Rs[1]))+[20,0,0]), util.euler2mat(util.mat2euler(np.array(gt_Rs[2]))+[-20,0,0])] | |
| cat = cats[1:] | |
| bb = gt_bb[1:] | |
| plot_scene(image['file_path'], output_dir+'/high_blue', center, dim, R, image['K'], cat, bb) | |
| # Make fail loss boxes for dim, pose and align | |
| center = gt_center[1:] | |
| dim = [[gt_dim[1][0],gt_dim[1][1],gt_dim[1][2]],[gt_dim[2][1],gt_dim[2][0],gt_dim[2][2]]] | |
| R = [util.euler2mat(util.mat2euler(np.array(gt_Rs[1]))+[1,0,0]), util.euler2mat(util.mat2euler(np.array(gt_Rs[2]))+[1,0,0])] | |
| cat = cats[1:] | |
| bb = gt_bb[1:] | |
| plot_scene(image['file_path'], output_dir+'/fail_blue', center, dim, R, image['K'], cat, bb) | |
| return True | |
| def gt_stats_in_terms_of_sigma(dataset): | |
| ''' | |
| Errorbarplot of volume of object category | |
| ''' | |
| # Load Image and Ground Truths | |
| image, Rs, center_cams, dimensions_all, cats, bboxes = load_gt(dataset, mode='train', single_im=False) | |
| image_t, Rs_t, center_cams_t, dimensions_all_t, cats_t, bboxes = load_gt(dataset, mode='test', single_im=False) | |
| output_dir = 'output/figures/' + dataset | |
| util.mkdir_if_missing(output_dir) | |
| # histogram of centers | |
| center_all = center_cams + center_cams_t | |
| center_all = np.transpose(np.array(center_all)) | |
| # Filter -1 annotations | |
| valid_columns = center_all[0] != -1 | |
| center_all = center_all[:,valid_columns] | |
| x_label = ['x', 'y', 'z'] | |
| fig, axes = plt.subplots(1, len(center_all), figsize=(18, 5)) | |
| for i in range(len(center_all)): | |
| axes[i].hist(center_all[i], color=color, bins=20) | |
| axes[i].set_xlabel(x_label[i]) | |
| axes[i].set_ylabel('Count') | |
| fig.suptitle('Center Distribution in Meters') | |
| plt.savefig(os.path.join(output_dir, 'center.png'), dpi=300, bbox_inches='tight') | |
| plt.close() | |
| # histogram of dimensions | |
| dimensions_all = dimensions_all + dimensions_all_t | |
| dimensions_all = np.transpose(np.array(dimensions_all)) | |
| # Filter -1 annotations | |
| valid_columns = dimensions_all[0] != -1 | |
| dimensions_all = dimensions_all[:,valid_columns] | |
| x_label = ['w', 'h', 'l'] | |
| fig, axes = plt.subplots(1, len(dimensions_all), figsize=(18, 5)) | |
| for i in range(len(dimensions_all)): | |
| axes[i].hist(dimensions_all[i], color=color, bins=20, density=True) | |
| # Plot normal distribution | |
| mu, sigma = np.mean(dimensions_all[i]), np.std(dimensions_all[i]) | |
| x = np.linspace(mu - 3 * sigma, mu + 3 * sigma, 100) | |
| axes[i].plot(x, stats.norm.pdf(x, mu, sigma)) | |
| y_lim = axes[i].get_ylim()[1] | |
| axes[i].vlines(mu+sigma, 0, y_lim, linestyle='--', label='$\sigma$', color='gray') | |
| axes[i].vlines(mu-sigma, 0, y_lim, linestyle='--', label='$\sigma$', color='gray') | |
| axes[i].vlines(1.4, 0, y_lim, linestyle='--', color='red', label='pred') | |
| if i != 0: | |
| axes[i].plot((mu+sigma,1.4), (y_lim/2,y_lim/2), color='c', label='loss') | |
| axes[i].set_xlabel(x_label[i]) | |
| axes[i].set_ylabel('density') | |
| # Set xticks in terms of sigma | |
| xticks = [mu - 3 * sigma, mu - 2 * sigma, mu - sigma, mu, mu + sigma, mu + 2 * sigma, mu + 3 * sigma, mu + 4 * sigma, mu + 5 * sigma, mu + 6 * sigma] | |
| xticklabels = ['-3$\sigma$', '-2$\sigma$', '-$\sigma$', '0', '$\sigma$', '$2\sigma$', '$3\sigma$', '$4\sigma$', '$5\sigma$', '$6\sigma$'] | |
| axes[i].set_xticks(xticks) | |
| axes[i].set_xticklabels(xticklabels) | |
| axes[-1].legend() | |
| fig.suptitle('Dimensions Distribution in Meters') | |
| plt.savefig(os.path.join(output_dir, 'dimensions_sigma.png'), dpi=300, bbox_inches='tight') | |
| plt.close() | |
| return True | |
| def parallel_coordinate_plot(dataset='SUNRGBD', files:list=['output/Baseline_sgd/log.txt','output/omni_equalised/log.txt','output/omni_pseudo_gt/log.txt','output/proposal_AP/log.txt','output/exp_10_iou_zpseudogt_dims_depthrange_rotalign_ground/log.txt']): | |
| '''Search the log file for the precision numbers corresponding to the last iteration | |
| then parse it in as a pd.DataFrame and plot the AP vs number of classes''' | |
| import plotly.graph_objects as go | |
| # df with each model as a column and performance for each class as rows | |
| # search the file from the back until the line | |
| # cubercnn.vis.logperf INFO: Performance for each of 38 categories on SUNRGBD_test: | |
| # is found | |
| target_line = "cubercnn.vis.logperf INFO: Performance for each of 38 categories on SUNRGBD_test:" | |
| model_names = ['Base Cube R-CNN', 'Time-eq.', 'Pseudo GT', 'Proposal', 'Weak loss'] | |
| df = [] | |
| for file, model_name in zip(files, model_names): | |
| df_i = search_file_backwards(file, target_line).drop(['AP2D'], axis=1).rename(columns={'AP3D':model_name}) | |
| assert df_i is not None, 'df not found' | |
| df.append(df_i) | |
| # merge df's | |
| df = reduce(lambda x, y: pd.merge(x, y, on = 'category'), df) | |
| # sort df by ap3d of model 1 | |
| df = df.sort_values(by='Base Cube R-CNN', ascending=False) | |
| # encode each category as a number | |
| df['category_num'] = list(reversed([i for i in range(len(df))])) | |
| # https://plotly.com/python/parallel-coordinates-plot/ | |
| fig = go.Figure(data= | |
| go.Parcoords( | |
| line = dict(color = df.iloc[:, 1], | |
| # colorscale = [[0,'purple'],[0.5,'lightseagreen'],[1,'gold']]), | |
| colorscale = 'Viridis'), | |
| visible = True, | |
| dimensions = list([ | |
| dict(tickvals = df['category_num'], | |
| ticktext = df['category'], | |
| label = 'Categories', values = df['category_num']), | |
| dict(range = [0,70], | |
| constraintrange = [5,70], | |
| label = model_names[0], values = df[model_names[0]]), | |
| dict(range = [0,40], | |
| label = model_names[2], values = df[model_names[2]]), | |
| dict(range = [0,40], | |
| label = model_names[4], values = df[model_names[4]]), | |
| dict(range = [0,40], | |
| label = model_names[1], values = df[model_names[1]]), | |
| dict(range = [0,40], | |
| label = model_names[3], values = df[model_names[3]]), | |
| ]), | |
| ) | |
| ) | |
| fig.update_layout( | |
| plot_bgcolor = 'white', | |
| paper_bgcolor = 'white', | |
| title={ | |
| 'text': "AP3D per category for each model", | |
| 'y':0.96, | |
| 'x':0.5, | |
| 'xanchor': 'center', | |
| 'yanchor': 'top'}, | |
| margin=dict(l=65, r=25, t=80, b=5) | |
| ) | |
| # pip install --upgrade "kaleido==0.1.*" | |
| fig.write_image('output/figures/SUNRGBD/parallel_coordinate_plot.png', scale=3, format='png') | |
| # fig.show() | |
| if __name__ == '__main__': | |
| # show_data('SUNRGBD', filter_invalid=False, output_dir='output/playground/no_filter') #{SUNRGBD,ARKitScenes,KITTI,nuScenes,Objectron,Hypersim} | |
| # show_data('SUNRGBD', filter_invalid=True, output_dir='output/playground/with_filter') #{SUNRGBD,ARKitScenes,KITTI,nuScenes,Objectron,Hypersim} | |
| # _ = category_distribution('SUNRGBD') | |
| AP_vs_no_of_classes('SUNRGBD') | |
| #spatial_statistics('SUNRGBD') | |
| # AP3D_vs_AP2D('SUNRGBD') | |
| # AP3D_vs_AP2D('SUNRGBD', mode='log') | |
| # init_dataloader() | |
| # vol_over_cat('SUNRGBD') | |
| # gt_stats('SUNRGBD') | |
| # gt_stats_in_terms_of_sigma('SUNRGBD') | |
| #gt_stats('SUNRGBD') | |
| # report_figures('SUNRGBD') | |
| parallel_coordinate_plot() | |