def test_kitti_export(bounding_box, tmpdir): label_manager = LabelManager(strategy="kitti", path_to_label_folder=tmpdir) label_manager.export_labels("testfolder/testpcd.ply", [bounding_box]) with open(os.path.join(tmpdir, "testpcd.txt"), "r") as read_file: data = read_file.readlines() assert data == ['test_bbox 0 0 0 0 0 0 0 1 1 1 0 0 0 -1.570796\n']
def __init__(self): # Point cloud management self.pcd_folder = PointCloudManger.PCD_FOLDER self.pcds = find_pcd_files(self.pcd_folder) self.no_of_pcds = len(self.pcds) self.current_id = -1 self.current_o3d_pcd = None self.view = None self.label_manager = LabelManager() # Point cloud control self.pointcloud = None
def test_centroid_abs_export(bounding_box, tmpdir): label_manager = LabelManager(strategy="centroid_abs", path_to_label_folder=tmpdir) label_manager.export_labels("testfolder/testpcd.ply", [bounding_box]) with open(os.path.join(tmpdir, "testpcd.json"), "r") as read_file: data = json.load(read_file) assert data == {'folder': 'testfolder', 'filename': 'testpcd.ply', 'path': 'testfolder/testpcd.ply', 'objects': [{'name': 'test_bbox', 'centroid': {'x': 0, 'y': 0, 'z': 0}, 'dimensions': {'length': 1, 'width': 1, 'height': 1}, 'rotations': {'x': 90, 'y': 180, 'z': 270}}]}
def test_vertices_export(bounding_box, tmpdir): label_manager = LabelManager(strategy="vertices", path_to_label_folder=tmpdir) label_manager.export_labels("testfolder/testpcd.ply", [bounding_box]) with open(os.path.join(tmpdir, "testpcd.json"), "r") as read_file: data = json.load(read_file) assert data == {'folder': 'testfolder', 'filename': 'testpcd.ply', 'path': 'testfolder/testpcd.ply', 'objects': [{'name': 'test_bbox', 'vertices': [[0.5, -0.5, 0.5], [0.5, -0.5, -0.5], [0.5, 0.5, -0.5], [0.5, 0.5, 0.5], [-0.5, -0.5, 0.5], [-0.5, -0.5, -0.5], [-0.5, 0.5, -0.5], [-0.5, 0.5, 0.5]]}]}
def test_kitti(label_kitti, tmpdir): # Write label to file with open(os.path.join(tmpdir, "test.txt"), "w") as write_file: write_file.write(label_kitti) # Import label file label_manager = LabelManager(strategy="kitti", path_to_label_folder=tmpdir) bounding_boxes = label_manager.import_labels("test.txt") bbox = bounding_boxes[0] # Check label content assert bbox.get_classname() == "cart" assert bbox.get_center() == (-0.409794, -0.012696, 0.076757) assert bbox.get_dimensions() == (0.75, 0.55, 0.15) assert bbox.get_rotations() == pytest.approx( (0, 0, 25)) # apply for rounding errors
def test_centroid_import(label_centroid, tmpdir, label_format, rotation): # Write label to file with open(os.path.join(tmpdir, "test.json"), "w") as write_file: write_file.write(label_centroid) # Import label file label_manager = LabelManager(strategy=label_format, path_to_label_folder=tmpdir) bounding_boxes = label_manager.import_labels("test.ply") bbox = bounding_boxes[0] # Check label content assert bbox.get_classname() == "cart" assert bbox.get_center() == (-0.186338, -0.241696, 0.054818) assert bbox.get_dimensions() == (0.80014, 0.512493, 0.186055) assert bbox.get_rotations() == rotation
def test_vertices(label_vertices, tmpdir): # Write label to file with open(os.path.join(tmpdir, "test.json"), "w") as write_file: write_file.write(label_vertices) # Import label file label_manager = LabelManager(strategy="vertices", path_to_label_folder=tmpdir) bounding_boxes = label_manager.import_labels("test.ply") bbox = bounding_boxes[0] # Check label content assert bbox.get_classname() == "cart" assert bbox.get_center() == pytest.approx( (-0.212846, -0.3679275, 0.0893245)) assert bbox.get_dimensions() == pytest.approx((0.75, 0.55, 0.15)) assert bbox.get_rotations() == pytest.approx( (270, 45, 25)) # apply for rounding errors
class PointCloudManger: PCD_EXTENSIONS = (".pcd", ".ply", ".pts", ".xyz", ".xyzn", ".xyzrgb", ".bin") PCD_FOLDER = config_parser.get_file_settings("POINTCLOUD_FOLDER") ORIGINALS_FOLDER = "original_pointclouds" TRANSLATION_FACTOR = config_parser.get_pointcloud_settings( "STD_TRANSLATION") ZOOM_FACTOR = config_parser.get_pointcloud_settings("STD_ZOOM") COLORIZE = config_parser.get_pointcloud_settings("COLORLESS_COLORIZE") def __init__(self): # Point cloud management self.pcd_folder = PointCloudManger.PCD_FOLDER self.pcds = find_pcd_files(self.pcd_folder) self.no_of_pcds = len(self.pcds) self.current_id = -1 self.current_o3d_pcd = None self.view = None self.label_manager = LabelManager() # Point cloud control self.pointcloud = None # GETTER def pcds_left(self) -> bool: return self.current_id + 1 < self.no_of_pcds def get_next_pcd(self): print("Loading next point cloud...") if self.pcds_left(): self.current_id += 1 self.pointcloud = self.load_pointcloud(self.get_current_path()) self.update_pcd_infos() else: if self.current_id == -1: show_no_pcd_dialog() sys.exit() raise Exception("No point cloud left for loading!") def get_prev_pcd(self): print("Loading previous point cloud...") if self.current_id > 0: self.current_id -= 1 self.pointcloud = self.load_pointcloud(self.get_current_path()) self.update_pcd_infos() else: raise Exception("No point cloud left for loading!") def get_pointcloud(self): return self.pointcloud def get_current_name(self) -> str: if self.current_id >= 0: return self.pcds[self.current_id] def get_current_details(self) -> Tuple[str, int, int]: if self.current_id >= 0: return self.get_current_name( ), self.current_id + 1, self.no_of_pcds def get_current_path(self) -> str: return os.path.join(self.pcd_folder, self.pcds[self.current_id]) def get_labels_from_file(self): bboxes = self.label_manager.import_labels(self.get_current_name()) print("Loaded %s bboxes!" % len(bboxes)) return bboxes # SETTER def set_view(self, view: 'GUI') -> None: self.view = view self.view.init_progress(min_value=0, max_value=self.no_of_pcds) self.view.glWidget.set_pointcloud_controler(self) self.get_next_pcd() def save_labels_into_file(self, bboxes: List[BBox]): self.label_manager.export_labels(self.get_current_path(), bboxes) self.update_label_completer(get_unique_classnames(bboxes)) # MANIPULATOR def load_pointcloud(self, path_to_pointcloud: str) -> PointCloud: print("=" * 20, "Loading", ntpath.basename(path_to_pointcloud), "=" * 20) if os.path.splitext(path_to_pointcloud )[1] == ".bin": # Loading binary pcds with numpy bin_pcd = np.fromfile(path_to_pointcloud, dtype=np.float32) points = bin_pcd.reshape( (-1, 4))[:, 0:3] # Reshape and drop reflection values self.current_o3d_pcd = o3d.geometry.PointCloud( o3d.utility.Vector3dVector(points)) else: self.current_o3d_pcd = o3d.io.read_point_cloud( path_to_pointcloud) # Load point cloud with open3d tmp_pcd = PointCloud(path_to_pointcloud) tmp_pcd.points = np.asarray(self.current_o3d_pcd.points).astype( "float32") # Unpack point cloud tmp_pcd.colors = np.asarray( self.current_o3d_pcd.colors).astype("float32") tmp_pcd.colorless = len(tmp_pcd.colors) == 0 print("Number of Points: %s" % len(tmp_pcd.points)) # Calculate and set initial translation to view full pointcloud tmp_pcd.center = self.current_o3d_pcd.get_center() tmp_pcd.set_mins_maxs() if PointCloudManger.COLORIZE and tmp_pcd.colorless: print("Generating colors for colorless point cloud!") min_height, max_height = tmp_pcd.get_min_max_height() tmp_pcd.colors = color_pointcloud(tmp_pcd.points, min_height, max_height) tmp_pcd.colorless = False max_dims = np.subtract(tmp_pcd.pcd_maxs, tmp_pcd.pcd_mins) init_trans_z = max( tmp_pcd.center[2] - max( ((max(max_dims[:2]) / 2) / np.tan(0.39) + 2), -15), -25) init_trans_x = -tmp_pcd.center[0] + max_dims[0] * 0.1 init_trans_y = -tmp_pcd.center[1] + max_dims[1] * -0.1 tmp_pcd.init_translation = init_trans_x, init_trans_y, init_trans_z tmp_pcd.reset_translation() tmp_pcd.print_details() if self.pointcloud is not None: # Skip first pcd to intialize OpenGL first tmp_pcd.write_vbo() print("Successfully loaded point cloud from %s!" % path_to_pointcloud) if tmp_pcd.colorless: print( "Did not find colors for the loaded point cloud, drawing in colorless mode!" ) print("=" * 65) return tmp_pcd def rotate_around_x(self, dangle): self.pointcloud.set_rot_x(self.pointcloud.rot_x - dangle) def rotate_around_y(self, dangle): self.pointcloud.set_rot_y(self.pointcloud.rot_y - dangle) def rotate_around_z(self, dangle): self.pointcloud.set_rot_z(self.pointcloud.rot_z - dangle) def translate_along_x(self, distance): self.pointcloud.set_trans_x(self.pointcloud.trans_x - distance * PointCloudManger.TRANSLATION_FACTOR) def translate_along_y(self, distance): self.pointcloud.set_trans_y(self.pointcloud.trans_y + distance * PointCloudManger.TRANSLATION_FACTOR) def translate_along_z(self, distance): self.pointcloud.set_trans_z(self.pointcloud.trans_z - distance * PointCloudManger.TRANSLATION_FACTOR) def zoom_into(self, distance): zoom_distance = distance * PointCloudManger.ZOOM_FACTOR self.pointcloud.set_trans_z(self.pointcloud.trans_z + zoom_distance) def reset_translation(self): self.pointcloud.reset_translation() def reset_rotation(self): self.pointcloud.rot_x, self.pointcloud.rot_y, self.pointcloud.rot_z = ( 0, 0, 0) def reset_transformations(self): self.reset_translation() self.reset_rotation() def rotate_pointcloud(self, axis: List[float], angle: float, rotation_point: List[float]) -> None: # Save current, original point cloud in ORIGINALS_FOLDER originals_path = os.path.join(self.pcd_folder, PointCloudManger.ORIGINALS_FOLDER) if not os.path.exists(originals_path): os.mkdir(originals_path) copyfile(self.get_current_path(), os.path.join(originals_path, self.get_current_name())) # o3d.io.write_point_cloud(os.path.join(originals_path, self.get_current_name()), self.current_o3d_pcd) # Rotate and translate point cloud rotation_matrix = o3d.geometry.get_rotation_matrix_from_axis_angle( np.multiply(axis, angle)) self.current_o3d_pcd.rotate(rotation_matrix, center=tuple(rotation_point)) self.current_o3d_pcd.translate([0, 0, -rotation_point[2]]) # Check if pointcloud is upside-down pcd_mins = np.amin(self.current_o3d_pcd.points, axis=0) pcd_maxs = np.amax(self.current_o3d_pcd.points, axis=0) if abs(pcd_mins[2]) > pcd_maxs[2]: print("Point cloud is upside down, rotating ...") self.current_o3d_pcd.rotate( o3d.geometry.get_rotation_matrix_from_xyz([np.pi, 0, 0]), center=(0, 0, 0)) # Overwrite current point cloud and reload # if os.path.splitext(self.get_current_path())[1] == ".bin": # points = np.asarray(self.current_o3d_pcd.points) # flattened_points = np.append(points, np.zeros((len(points), 1)), axis=1).flatten() # add dummy reflection # flattened_points.tofile(self.get_current_path()) # else: save_path = self.get_current_path() if os.path.splitext(save_path)[1] == ".bin": save_path = save_path[:-4] + ".pcd" o3d.io.write_point_cloud(save_path, self.current_o3d_pcd) self.pointcloud = self.load_pointcloud(save_path) # HELPER def get_perspective(self): x_rotation = self.pointcloud.rot_x # y_rotation = self.pcdc.get_pointcloud().rot_y z_rotation = self.pointcloud.rot_z # print("PCD-ROTX/y/z: %s, %s, %s" % (x_rotation, y_rotation, z_rotation)) cosz = round(np.cos(np.deg2rad(z_rotation)), 1) sinz = -round(np.sin(np.deg2rad(z_rotation)), 1) # detect bottom-up state bottom_up = 1 if 30 < x_rotation < 210: bottom_up = -1 return cosz, sinz, bottom_up # UPDATE GUI def update_label_completer(self, classnames: Set[str]): self.view.curr_class_edit.setCompleter(QCompleter(classnames)) def update_pcd_infos(self): self.view.set_pcd_label(self.get_current_name()) self.view.update_progress(self.current_id) if self.current_id <= 0: self.view.button_prev_pcd.setEnabled(False) else: self.view.button_next_pcd.setEnabled(True) self.view.button_prev_pcd.setEnabled(True)