From 304bf0d660ce9de79ba6a247e1df9bf4c2277fbe Mon Sep 17 00:00:00 2001 From: whyekit-aptiv Date: Fri, 5 Jun 2020 18:07:41 +0800 Subject: [PATCH 1/4] Auto find liarseg labels present in projected pcl --- .../nuscenes/lidarseg/lidarseg_utils.py | 32 +++++++++++++++++++ python-sdk/nuscenes/nuscenes.py | 14 +++++--- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/python-sdk/nuscenes/lidarseg/lidarseg_utils.py b/python-sdk/nuscenes/lidarseg/lidarseg_utils.py index f0de80117..a909e3bab 100644 --- a/python-sdk/nuscenes/lidarseg/lidarseg_utils.py +++ b/python-sdk/nuscenes/lidarseg/lidarseg_utils.py @@ -165,3 +165,35 @@ def filter_colormap(colormap: np.array, classes_to_display: np.array) -> np.ndar colormap = np.concatenate((colormap, alpha.T), axis=1) return colormap + + +def get_labels_in_coloring(color_legend: np.ndarray, coloring: np.ndarray) -> List: + """ + Find the class labels which are present in a pointcloud which has been projected onto an image. + :param color_legend: A list of arrays in which each array corresponds to the RGB values of a class. + :param coloring: A list of arrays in which each array corresponds to the RGB values of a point in the portion of + the pointcloud projected onto the image. + :return List of class indices which are present in the image. + """ + + def __arreq_in_list(myarr, list_arrays) -> bool: + """ + Check if an array is in a list of arrays. + :param: myarr: An array. + :param: list_arrays: A list of arrays. + :return Whether the given array is in the list of arrays. + """ + # Credits: https://stackoverflow.com/questions/23979146/check-if-numpy-array-is-in-list-of-numpy-arrays + return next((True for elem in list_arrays if np.array_equal(elem, myarr)), False) + + filter_lidarseg_labels = [] + + # Get only the distinct colors present in the pointcloud so that we will not need to compare each color in + # the color legend with every single point in the pointcloud later. + distinct_colors = set(tuple(c) for c in coloring) + + for i, color in enumerate(color_legend): + if __arreq_in_list(color, distinct_colors): + filter_lidarseg_labels.append(i) + + return filter_lidarseg_labels diff --git a/python-sdk/nuscenes/nuscenes.py b/python-sdk/nuscenes/nuscenes.py index 035237653..67908840c 100644 --- a/python-sdk/nuscenes/nuscenes.py +++ b/python-sdk/nuscenes/nuscenes.py @@ -21,7 +21,7 @@ from tqdm import tqdm from nuscenes.lidarseg.lidarseg_utils import filter_colormap, get_arbitrary_colormap, plt_to_cv2, get_stats, \ - get_key_from_value + get_key_from_value, get_labels_in_coloring from nuscenes.utils.data_classes import LidarPointCloud, RadarPointCloud, Box from nuscenes.utils.geometry_utils import view_points, box_in_image, BoxVisibility, transform_matrix from nuscenes.utils.map_mask import MapMask @@ -888,15 +888,21 @@ def render_pointcloud_in_image(self, ax.axis('off') # Produce a legend with the unique colors from the scatter. - if pointsensor_channel == 'LIDAR_TOP' and show_lidarseg_legend: + if pointsensor_channel == 'LIDAR_TOP' and show_lidarseg_labels and show_lidarseg_legend: import matplotlib.patches as mpatches recs = [] classes_final = [] classes = [name for idx, name in sorted(self.nusc.lidarseg_idx2name_mapping.items())] color_legend = get_arbitrary_colormap(len(classes)) + + # If user does not specify a filter, then set the filter to contain the classes present in the pointcloud + # after it has been projected onto the image; this will allow displaying the legend only for classes which + # are present in the image (instead of all the classes). + if filter_lidarseg_labels is None: + filter_lidarseg_labels = get_labels_in_coloring(color_legend, coloring) + for i in range(len(classes)): - # Create legend only for labels which the user wants to see, - # if the user has specified a lidarseg filter. + # Create legend only for labels specified in the lidarseg filter. if filter_lidarseg_labels is None or i in filter_lidarseg_labels: recs.append(mpatches.Rectangle((0, 0), 1, 1, fc=color_legend[i])) classes_final.append(classes[i]) From e4e8da0eb346f68e12789f48dff8f8f5602bb683 Mon Sep 17 00:00:00 2001 From: whyekit-aptiv Date: Mon, 8 Jun 2020 08:54:54 +0800 Subject: [PATCH 2/4] Add assertion to check num of bin files equals num of lidarseg records --- python-sdk/nuscenes/nuscenes.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/python-sdk/nuscenes/nuscenes.py b/python-sdk/nuscenes/nuscenes.py index 67908840c..4e54c36f5 100644 --- a/python-sdk/nuscenes/nuscenes.py +++ b/python-sdk/nuscenes/nuscenes.py @@ -82,6 +82,11 @@ def __init__(self, print('Loading nuScenes-lidarseg...') self.lidarseg = self.__load_table__('lidarseg') + num_lidarseg_recs = len(self.lidarseg) + num_bin_files = len([name for name in os.listdir(os.path.join(self.dataroot, 'lidarseg', self.version)) + if name.endswith('.bin')]) + assert num_lidarseg_recs == num_bin_files, \ + 'Error: There are {} .bin files but {} lidarseg records.'.format(num_bin_files, num_lidarseg_recs) self.table_names.append('lidarseg') lidarseg_categories = self.__load_table__('category_lidarseg') From 86a7462bee6f35ce7f6867d202bdbf6a373d56d9 Mon Sep 17 00:00:00 2001 From: whyekit-aptiv Date: Mon, 8 Jun 2020 10:04:12 +0800 Subject: [PATCH 3/4] Address comments --- .../nuscenes/lidarseg/lidarseg_utils.py | 20 +++++++++---------- python-sdk/nuscenes/nuscenes.py | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/python-sdk/nuscenes/lidarseg/lidarseg_utils.py b/python-sdk/nuscenes/lidarseg/lidarseg_utils.py index a909e3bab..eb8b73b09 100644 --- a/python-sdk/nuscenes/lidarseg/lidarseg_utils.py +++ b/python-sdk/nuscenes/lidarseg/lidarseg_utils.py @@ -15,7 +15,7 @@ def get_stats(points_label: np.array, num_classes: int) -> List[int]: Get frequency of each label in a point cloud. :param num_classes: The number of classes. :param points_label: A numPy array which contains the labels of the point cloud; e.g. np.array([2, 1, 34, ..., 38]) - :returns: An array which contains the counts of each label in the point cloud. The index of the point cloud + :return: An array which contains the counts of each label in the point cloud. The index of the point cloud corresponds to the index of the class label. E.g. [0, 2345, 12, 451] means that there are no points in class 0, there are 2345 points in class 1, there are 12 points in class 2 etc. """ @@ -148,7 +148,7 @@ def filter_colormap(colormap: np.array, classes_to_display: np.array) -> np.ndar of the labels to be display set to 1.0 and those to be hidden set to 0.0 :param colormap: [n x 3] array where each row consist of the RGB values for the corresponding class index :param classes_to_display: An array of classes to display (e.g. [1, 8, 32]). The array need not be ordered. - :return (colormap ). + :return: (colormap ). colormap = np.array([[R1, G1, B1], colormap = np.array([[1.0, 1.0, 1.0, 0.0], [R2, G2, B2], ------> [R2, G2, B2, 1.0], @@ -167,33 +167,33 @@ def filter_colormap(colormap: np.array, classes_to_display: np.array) -> np.ndar return colormap -def get_labels_in_coloring(color_legend: np.ndarray, coloring: np.ndarray) -> List: +def get_labels_in_coloring(color_legend: np.ndarray, coloring: np.ndarray) -> List[int]: """ Find the class labels which are present in a pointcloud which has been projected onto an image. :param color_legend: A list of arrays in which each array corresponds to the RGB values of a class. :param coloring: A list of arrays in which each array corresponds to the RGB values of a point in the portion of the pointcloud projected onto the image. - :return List of class indices which are present in the image. + :return: List of class indices which are present in the image. """ - def __arreq_in_list(myarr, list_arrays) -> bool: + def _array_in_list(arr: List, list_arrays: List) -> bool: """ Check if an array is in a list of arrays. - :param: myarr: An array. + :param: arr: An array. :param: list_arrays: A list of arrays. - :return Whether the given array is in the list of arrays. + :return: Whether the given array is in the list of arrays. """ # Credits: https://stackoverflow.com/questions/23979146/check-if-numpy-array-is-in-list-of-numpy-arrays - return next((True for elem in list_arrays if np.array_equal(elem, myarr)), False) + return next((True for elem in list_arrays if np.array_equal(elem, arr)), False) filter_lidarseg_labels = [] # Get only the distinct colors present in the pointcloud so that we will not need to compare each color in # the color legend with every single point in the pointcloud later. - distinct_colors = set(tuple(c) for c in coloring) + distinct_colors = list(set(tuple(c) for c in coloring)) for i, color in enumerate(color_legend): - if __arreq_in_list(color, distinct_colors): + if _array_in_list(color, distinct_colors): filter_lidarseg_labels.append(i) return filter_lidarseg_labels diff --git a/python-sdk/nuscenes/nuscenes.py b/python-sdk/nuscenes/nuscenes.py index 4e54c36f5..a23e2a885 100644 --- a/python-sdk/nuscenes/nuscenes.py +++ b/python-sdk/nuscenes/nuscenes.py @@ -12,6 +12,7 @@ import cv2 import matplotlib.pyplot as plt +import matplotlib.patches as mpatches import numpy as np import sklearn.metrics from PIL import Image @@ -894,7 +895,6 @@ def render_pointcloud_in_image(self, # Produce a legend with the unique colors from the scatter. if pointsensor_channel == 'LIDAR_TOP' and show_lidarseg_labels and show_lidarseg_legend: - import matplotlib.patches as mpatches recs = [] classes_final = [] classes = [name for idx, name in sorted(self.nusc.lidarseg_idx2name_mapping.items())] From bebd6c588b424cdf79beed11485e065722e53862 Mon Sep 17 00:00:00 2001 From: whyekit-aptiv Date: Mon, 8 Jun 2020 12:19:31 +0800 Subject: [PATCH 4/4] Adjust legend for aesthetics --- python-sdk/nuscenes/nuscenes.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/python-sdk/nuscenes/nuscenes.py b/python-sdk/nuscenes/nuscenes.py index a23e2a885..19c9e7805 100644 --- a/python-sdk/nuscenes/nuscenes.py +++ b/python-sdk/nuscenes/nuscenes.py @@ -910,8 +910,10 @@ def render_pointcloud_in_image(self, # Create legend only for labels specified in the lidarseg filter. if filter_lidarseg_labels is None or i in filter_lidarseg_labels: recs.append(mpatches.Rectangle((0, 0), 1, 1, fc=color_legend[i])) - classes_final.append(classes[i]) - plt.legend(recs, classes_final, loc='lower left', ncol=3) + + # Truncate class names to only first 25 chars so that legend is not excessively long. + classes_final.append(classes[i][:25]) + plt.legend(recs, classes_final, loc='upper center', ncol=3) if out_path is not None: plt.savefig(out_path, bbox_inches='tight', pad_inches=0, dpi=200)