Team Ai
Apppublic

ryanhlewis/Pointcloud_Alignment

sourceHugging Facemitupdated 2y agoView on Hugging Face
1likes
app.py197 linesDownload Raw Back to root
1import gradio as gr2import open3d as o3d3import numpy as np4import time5import multiprocessing6import os7import tempfile8import trimesh9 10alignment_cache = {}11current_orientation = "+z"12 13def compute_alignment(files):14    """Align point clouds and cache results."""15    ck = tuple(files)16    if ck in alignment_cache: return alignment_cache[ck]17    if len(files) < 2: return (None, None)18    st = time.time()19    print(f"Aligning {len(files)} point clouds using {multiprocessing.cpu_count()} CPU cores")20    clouds, names = [], []21    for p in files:22        c = o3d.io.read_point_cloud(p)23        if not c.has_points(): return (None, f"Failed to load point cloud from {os.path.basename(p)}")24        clouds.append(c), names.append(os.path.basename(p))25    ref = clouds[0]26    trans = [np.eye(4)]27    for i, src in enumerate(clouds[1:], 1):28        vs = [0.5, 0.3, 0.1]29        t = np.eye(4)30        for v in vs:31            s_down, r_down = src.voxel_down_sample(v), ref.voxel_down_sample(v)32            rn = v * 233            s_down.estimate_normals(o3d.geometry.KDTreeSearchParamHybrid(radius=rn, max_nn=30))34            r_down.estimate_normals(o3d.geometry.KDTreeSearchParamHybrid(radius=rn, max_nn=30))35            d = v * 236            mi = 10 if i == 0 else 537            r = o3d.pipelines.registration.registration_icp(38                s_down, r_down, d, t,39                o3d.pipelines.registration.TransformationEstimationPointToPlane(),40                o3d.pipelines.registration.ICPConvergenceCriteria(max_iteration=mi)41            )42            t = r.transformation43        trans.append(t)44    aligned = []45    for c, t in zip(clouds, trans):46        cc = o3d.geometry.PointCloud(c)47        cc.transform(t)48        aligned.append(cc)49    ap = [np.asarray(x.points) for x in aligned]50    if not ap: return (None, "Failed to process point clouds")51    cat = np.vstack(ap)52    mn, mx = np.min(cat, 0), np.max(cat, 0)53    ctr = (mn + mx) / 254    scl = np.max(mx - mn)55    combined = o3d.geometry.PointCloud()56    for c in aligned: combined += c57    tm = time.time() - st58    msg = f"Successfully aligned {len(files)} point clouds in {tm:.2f} seconds."59    data = {60        'aligned_clouds': aligned, 'transformations': trans, 'clouds': clouds,61        'filenames': names, 'global_center': ctr, 'global_scale': scl,62        'combined_cloud': combined63    }64    alignment_cache[ck] = (data, msg)65    return data, msg66 67def visualize_alignment(files, show_colors=False, show_outlines=True, point_density=10000,68                        orientation="+z", point_size=0.005, is_initial_view=False):69    if not files or len(files) < 2: return (None, "Please upload at least 2 point cloud files (.ply)")70    res, msg = compute_alignment(files)71    if not res: return (None, msg)72    clouds = res['aligned_clouds']73    ctr, scl = res['global_center'], res['global_scale']74    cols = [[1,0,0],[0,1,0],[0,0,1],[1,1,0],[1,0,1],[0,1,1],[1,0.5,0]]75    tdir = tempfile.mkdtemp()76    global current_orientation77    current_orientation = orientation78    normed, boxes = [], []79    for i, c in enumerate(clouds):80        cp = o3d.geometry.PointCloud(c)81        if show_colors: cp.paint_uniform_color(cols[i % len(cols)])82        pts = np.asarray(cp.points)83        pts = (pts - ctr)/(scl/2)84        n = o3d.geometry.PointCloud()85        n.points = o3d.utility.Vector3dVector(pts)86        if cp.has_colors(): n.colors = cp.colors87        normed.append(n)88        if show_outlines:89            b = n.get_axis_aligned_bounding_box()90            b.color = cols[i % len(cols)]91            boxes.append(b)92    comb = o3d.geometry.PointCloud()93    for c in normed: comb += c94    if point_density < len(comb.points):95        bb = comb.get_axis_aligned_bounding_box()96        vol = np.prod(bb.get_extent())97        ppu = point_density / vol if vol > 0 else 198        vs = max(0.001, (1/ppu)**(1/3))99        comb = comb.voxel_down_sample(vs)100    vs, fs, vc = [], [], []101    pts = np.asarray(comb.points)102    if comb.has_colors(): pcols = np.asarray(comb.colors)103    else: pcols = np.tile([0.8,0.8,0.8], (len(pts),1))104    sph = trimesh.creation.icosphere(subdivisions=1, radius=point_size)105    mxp = min(point_density, len(pts))106    idxs = np.linspace(0, len(pts)-1, mxp, dtype=int)107    for i, idx in enumerate(idxs):108        p = pts[idx]109        c = pcols[idx]110        s = sph.copy()111        s.apply_translation(p)112        s.visual.vertex_colors = np.tile((c*255).astype(np.uint8), (len(s.vertices),1))113        si = len(vs)114        vs.extend(s.vertices)115        fs.extend(s.faces + si)116        vc.extend(s.visual.vertex_colors)117    cm = trimesh.Trimesh(vertices=vs, faces=fs, vertex_colors=vc)118    if show_outlines:119        box_edges = [(0,1),(1,2),(2,3),(3,0),(4,5),(5,6),(6,7),(7,4),120                     (0,4),(1,5),(2,6),(3,7)]121        for i,b in enumerate(boxes):122            mb, xb = b.min_bound, b.max_bound123            bv = [[mb[0],mb[1],mb[2]],[xb[0],mb[1],mb[2]],[xb[0],xb[1],mb[2]],[mb[0],xb[1],mb[2]],124                  [mb[0],mb[1],xb[2]],[xb[0],mb[1],xb[2]],[xb[0],xb[1],xb[2]],[mb[0],xb[1],xb[2]]]125            bc = cols[i % len(cols)]126            r = point_size*0.8127            for s0,s1 in box_edges:128                p0, p1 = bv[s0], bv[s1]129                d = np.array(p1) - np.array(p0)130                ln = np.linalg.norm(d)131                if ln<1e-6: continue132                d /= ln133                za = np.array([0,0,1])134                if abs(np.dot(d,za))>0.999: ra = np.array([1,0,0])135                else:136                    ra = np.cross(za,d)137                    ra /= np.linalg.norm(ra)138                ang = np.arccos(np.dot(za,d))139                rot = trimesh.transformations.rotation_matrix(ang,ra)140                trn = trimesh.transformations.translation_matrix(p0)141                cyl = trimesh.creation.cylinder(radius=r,height=ln,sections=8)142                cyl.apply_translation([0,0,ln/2])143                cyl.apply_transform(rot)144                cyl.apply_transform(trn)145                cyl.visual.face_colors = (np.array(bc)*255).astype(np.uint8)146                cm = trimesh.util.concatenate([cm,cyl])147    sc = trimesh.Scene(cm)148    sc.add_geometry(trimesh.creation.axis(origin_size=0.01, axis_radius=0.0025))149    if orientation == "+y":150        sc.apply_transform(trimesh.transformations.rotation_matrix(-np.pi/2,[1,0,0]))151    elif orientation == "-y":152        sc.apply_transform(trimesh.transformations.rotation_matrix(np.pi/2,[1,0,0]))153    elif orientation == "-z":154        sc.apply_transform(trimesh.transformations.rotation_matrix(np.pi,[1,0,0]))155    fo = os.path.join(tdir,"aligned_scene.glb")156    sc.export(fo)157    return (fo, msg)158 159def process_upload(files, sc, so, pd, o, ps):160    d, m = compute_alignment(files)161    if d is None: return (None, m)162    return visualize_alignment(files, sc, so, pd, o, ps, True)163 164def update_visualization_only(files, sc, so, pd, o, ps):165    if not files or len(files)<2: return None166    if tuple(files) not in alignment_cache: return None167    mo, _ = visualize_alignment(files, sc, so, pd, o, ps, False)168    return mo169 170with gr.Blocks(theme=gr.themes.Base()) as app:171    gr.Markdown("# Point Cloud Alignment Tool  -  3D ICP")172    with gr.Row():173        with gr.Column(scale=1):174            file_input = gr.File(file_count="multiple", file_types=[".ply"], label="Upload Point Cloud Files (.ply)", type="filepath")175            with gr.Row():176                show_colors = gr.Checkbox(label="Show Colored Models", value=False)177                show_outlines = gr.Checkbox(label="Show Bounding Box Outlines", value=True)178            point_density = gr.Slider(1000, 500000, 10000, step=1000, label="Point Density (fewer points = faster rendering)")179            orientation = gr.Dropdown(["+z","-z","+y","-y"], value="+z", label="Model Orientation (up direction)")180            point_size = gr.Slider(0.001, 0.05, 0.005, step=0.001, label="Point Size")181            submit_btn = gr.Button("Align Point Clouds", variant="primary")182        with gr.Column(scale=2):183            output_model = gr.Model3D(label="Aligned Point Clouds", clear_color=[0.1,0.1,0.1,1.0])184            output_text = gr.Textbox(label="Status")185    submit_btn.click(process_upload,186                     [file_input, show_colors, show_outlines, point_density, orientation, point_size],187                     [output_model, output_text])188    show_colors.change(update_visualization_only, [file_input, show_colors, show_outlines, point_density, orientation, point_size], [output_model])189    show_outlines.change(update_visualization_only, [file_input, show_colors, show_outlines, point_density, orientation, point_size], [output_model])190    point_density.change(update_visualization_only, [file_input, show_colors, show_outlines, point_density, orientation, point_size], [output_model])191    orientation.change(update_visualization_only, [file_input, show_colors, show_outlines, point_density, orientation, point_size], [output_model])192    point_size.change(update_visualization_only, [file_input, show_colors, show_outlines, point_density, orientation, point_size], [output_model])193    194if __name__ == "__main__":195    print(f"Using {multiprocessing.cpu_count()} CPU cores for parallel processing")196    app.launch(share=True)197