1 import cslam.utils.point_cloud2 
as pc2_utils
 
    2 from geometry_msgs.msg 
import Transform
 
    3 from scipy.spatial.transform 
import Rotation 
as R
 
    4 from std_msgs.msg 
import Header
 
    5 from sensor_msgs.msg 
import PointCloud2, PointField
 
   10 from scipy.spatial 
import cKDTree
 
   14     PointField(name=
'x', offset=0, datatype=PointField.FLOAT32, count=1),
 
   15     PointField(name=
'y', offset=4, datatype=PointField.FLOAT32, count=1),
 
   16     PointField(name=
'z', offset=8, datatype=PointField.FLOAT32, count=1),
 
   23     return np.asarray(pcd.points).T
 
   27     radius_normal = voxel_size * 2
 
   29         open3d.geometry.KDTreeSearchParamHybrid(radius=radius_normal,
 
   32     radius_feature = voxel_size * 5
 
   33     fpfh = open3d.pipelines.registration.compute_fpfh_feature(
 
   35         open3d.geometry.KDTreeSearchParamHybrid(radius=radius_feature,
 
   37     return np.array(fpfh.data).T
 
   41     feat1tree = cKDTree(feat1)
 
   42     dists, nn_inds = feat1tree.query(feat0, k=knn, workers=-1)
 
   50     nns01 = 
find_knn_cpu(feats0, feats1, knn=1, return_distance=
False)
 
   51     corres01_idx0 = np.arange(len(nns01))
 
   55         return corres01_idx0, corres01_idx1
 
   57     nns10 = 
find_knn_cpu(feats1, feats0, knn=1, return_distance=
False)
 
   58     corres10_idx1 = np.arange(len(nns10))
 
   61     mutual_filter = (corres10_idx0[corres01_idx1] == corres01_idx0)
 
   62     corres_idx0 = corres01_idx0[mutual_filter]
 
   63     corres_idx1 = corres01_idx1[mutual_filter]
 
   65     return corres_idx0, corres_idx1
 
   69     solver_params = teaserpp_python.RobustRegistrationSolver.Params()
 
   70     solver_params.cbar2 = 1.0
 
   71     solver_params.noise_bound = noise_bound
 
   72     solver_params.estimate_scaling = 
False 
   73     solver_params.inlier_selection_mode = \
 
   74         teaserpp_python.RobustRegistrationSolver.INLIER_SELECTION_MODE.PMC_EXACT
 
   75     solver_params.rotation_tim_graph = \
 
   76         teaserpp_python.RobustRegistrationSolver.INLIER_GRAPH_FORMULATION.CHAIN
 
   77     solver_params.rotation_estimation_algorithm = \
 
   78         teaserpp_python.RobustRegistrationSolver.ROTATION_ESTIMATION_ALGORITHM.GNC_TLS
 
   79     solver_params.rotation_gnc_factor = 1.4
 
   80     solver_params.rotation_max_iterations = 10000
 
   81     solver_params.rotation_cost_threshold = 1e-16
 
   82     solver = teaserpp_python.RobustRegistrationSolver(solver_params)
 
   94     open3d_cloud = open3d.geometry.PointCloud()
 
   95     open3d_cloud.points = open3d.utility.Vector3dVector(points)
 
   96     return open3d_cloud.voxel_down_sample(voxel_size=voxel_size)
 
  109     src_corr = src_xyz[:, corrs_src]  
 
  110     dst_corr = dst_xyz[:, corrs_dst]  
 
  113     solver.solve(src_corr, dst_corr)
 
  115     solution = solver.getSolution()
 
  117     valid = len(solver.getInlierMaxClique()) > min_inliers
 
  121         T_teaser = 
Rt2T(solution.rotation, solution.translation)
 
  122         icp_sol = open3d.pipelines.registration.registration_icp(
 
  123             src, dst, voxel_size, T_teaser,
 
  124             open3d.pipelines.registration.TransformationEstimationPointToPoint(
 
  126             open3d.pipelines.registration.ICPConvergenceCriteria(
 
  128         T_icp = icp_sol.transformation
 
  129         solution.translation = T_icp[:3, 3]
 
  130         solution.rotation = T_icp[:3, :3]
 
  132         rclpy.logging.get_logger(
'cslam').info(
 
  133             'Failed to compute loop closure. Number of inliers ( {} / {} )'.
 
  134             format(len(solver.getInlierMaxClique()), min_inliers))
 
  135     return valid, solution.translation, solution.rotation
 
  140     T.translation.x = translation[0]
 
  141     T.translation.y = translation[1]
 
  142     T.translation.z = translation[2]
 
  143     rotation_matrix = R.from_matrix(np.array(rotation))
 
  144     q = rotation_matrix.as_quat()
 
  155     points = np.asarray(open3d_cloud.points)
 
  156     return pc2_utils.create_cloud(header, fields, points)
 
  161     open3d_cloud = open3d.geometry.PointCloud()
 
  162     open3d_cloud.points = open3d.utility.Vector3dVector(points)
 
  167     return pc2_utils.read_points_numpy_filtered(pc_msg)[:, :3]
 
  175     """Computes a 3D transform between 2 point clouds using TEASER++. 
  177     Be careful: TEASER++ computes the transform from dst to src. 
  180         src (Open3D point cloud): pointcloud from 
  181         dst (Open3D point cloud): pointcloud to 
  182         voxel_size (int): Voxel size 
  183         min_inliers (int): Minimum number of inlier points correspondance to consider the registration a success 
  186         (Transform, bool): transform and success flag 
  188     valid, translation, rotation = 
solve_teaser(src, dst, voxel_size,
 
  192     return transform, success
 
def find_correspondences(feats0, feats1, mutual_filter=True)
 
def ros_pointcloud_to_points(pc_msg)
 
def downsample(points, voxel_size)
 
def downsample_ros_pointcloud(pc_msg, voxel_size)
 
def find_knn_cpu(feat0, feat1, knn=1, return_distance=False)
 
def open3d_to_ros(open3d_cloud)
 
def extract_fpfh(pcd, voxel_size)
 
def get_teaser_solver(noise_bound)
 
def compute_transform(src, dst, voxel_size, min_inliers)
 
def to_transform_msg(translation, rotation)
 
def solve_teaser(src, dst, voxel_size, min_inliers)