import os import numpy as np import torch from PIL import Image from pytorch3d.structures import Meshes from pytorch3d.renderer import TexturesVertex from ..scripts.fast_geo import fast_geo, create_sphere, create_box from ..scripts.project_mesh import get_cameras_list_azi_ele from ..mesh_reconstruction.recon import reconstruct_stage1 from ..mesh_reconstruction.refine import run_mesh_refine from ..mesh_reconstruction.func import make_star_cameras_orthographic, make_star_cameras_perspective from ..data.utils import ( simple_remove_bkg_normal, load_glb, load_obj_with_verts_faces) from ..scripts.utils import ( to_pyml_mesh, simple_clean_mesh, normal_rotation_img2img_c2w, rotate_normal_R, get_rotation_matrix_azi_ele, manage_elevation_azimuth) @torch.enable_grad() def reconstruction_pipe(normal_pils, rotation_angles_azi, rotation_angles_ele, front_index=0, back_index=2, side_index=1, weights=None, expansion_weight=0.1, expansion_weight_stage2=0.0, init_type="ball", sphere_r=None, # only used if init_type=="ball" box_width=1.0, # only used if init_type=="box" box_length=1.0, # only used if init_type=="box" box_height=1.0, # only used if init_type=="box" init_verts=None, init_faces=None, init_mesh_from_file="", stage1_steps=200, stage2_steps=200, projection_type="orthographic", fovy=None, radius=None, ortho_dist=1.1, camera_angles_azi=None, camera_angles_ele=None, rm_bkg=False, rm_bkg_with_rembg=False, # only used if rm_bkg normal_rotation_R=None, train_stage1=True, train_stage2=True, use_remesh_stage1=True, use_remesh_stage2=True, start_edge_len_stage1=0.1, end_edge_len_stage1=0.02, start_edge_len_stage2=0.02, end_edge_len_stage2=0.005, ): assert projection_type in ['perspective', 'orthographic'], f"projection_type ({projection_type}) should be one of ['perspective', 'orthographic']" if stage1_steps == 0: train_stage1 = False if stage2_steps == 0: train_stage2 = False if normal_rotation_R is not None: assert normal_rotation_R.shape[-2] == 3 and normal_rotation_R.shape[-1] == 3 assert len(normal_rotation_R.shape) == 2 normal_rotation_R = normal_rotation_R.float() camera_angles_azi = camera_angles_azi.float() camera_angles_ele = camera_angles_ele.float() camera_angles_ele, camera_angles_azi = manage_elevation_azimuth(camera_angles_ele, camera_angles_azi) if init_type in ["std", "thin"]: assert camera_angles_azi[front_index]%360==0, f"the camera_angles_azi associated with front image (index {front_index}) should be 0 not {camera_angles_azi[front_index]}" assert camera_angles_azi[back_index]%360==180, f"the camera_angles_azi associated with back image (index {back_index}) should be 180 not {camera_angles_azi[back_index]}" assert camera_angles_azi[side_index]%360==90, f"the camera_angles_azi associated with left side image (index {side_index}) should be 90, not {camera_angles_azi[back_index]}" if rm_bkg: if rm_bkg_with_rembg: os.environ["OMP_NUM_THREADS"] = '8' normal_pils = simple_remove_bkg_normal(normal_pils,rm_bkg_with_rembg) if rotation_angles_azi is not None: rotation_angles_azi = -rotation_angles_azi.float() rotation_angles_ele = rotation_angles_ele.float() rotation_angles_ele, rotation_angles_azi = manage_elevation_azimuth(rotation_angles_ele, rotation_angles_azi) assert len(normal_pils) == len(rotation_angles_azi), f'len(normal_pils) ({len(normal_pils)}) != len(rotation_angles_azi) ({len(rotation_angles_azi)})' if rotation_angles_ele is None: rotation_angles_ele = [0] * len(normal_pils) normal_pils_rotated = [] for i in range(len(normal_pils)): c2w_R = get_rotation_matrix_azi_ele(rotation_angles_azi[i], rotation_angles_ele[i]) rotated_ = normal_rotation_img2img_c2w(normal_pils[i], c2w=c2w_R) normal_pils_rotated.append(rotated_) normal_pils = normal_pils_rotated if normal_rotation_R is not None: normal_pils_rotated = [] for i in range(len(normal_pils)): rotated_ = rotate_normal_R(normal_pils[i], normal_rotation_R, save_addr="", device="cuda") normal_pils_rotated.append(rotated_) normal_pils = normal_pils_rotated normal_stg1 = [img for img in normal_pils] if init_type in ['thin', 'std']: front_ = normal_stg1[front_index] back_ = normal_stg1[back_index] side_ = normal_stg1[side_index] meshes, depth_front, depth_back, mesh_front, mesh_back = fast_geo(front_, back_, side_, init_type=init_type, return_depth_and_sep_mesh=True) elif init_type in ["ball", "box"]: if init_type == "ball": assert sphere_r is not None, f"sphere_r ({sphere_r}) should not be None when init_type is 'ball'" meshes = create_sphere(sphere_r) if init_type == "box": assert box_width is not None and box_length is not None and box_height is not None, f"box_width ({box_width}), box_length ({box_length}), and box_height ({box_height}) should not be None when init_type is 'box'" meshes = create_box(width=box_width, length=box_length, height=box_height) # add texture just in case num_meshes = len(meshes) num_verts_per_mesh = meshes.verts_packed().shape[0] // num_meshes black_texture = torch.zeros((num_meshes, num_verts_per_mesh, 3), device="cuda") textures = TexturesVertex(verts_features=black_texture) meshes.textures = textures elif init_type == "file": assert init_mesh_from_file or (init_verts is not None and init_faces is not None), f"init_mesh_from_file ({init_mesh_from_file}) should not be None when init_type is 'file', else init_verts and init_faces should not be None" if init_verts is not None and init_faces is not None: meshes = Meshes(verts=[init_verts], faces=[init_faces]).to('cuda') elif init_mesh_from_file.endswith('.glb'): meshes = load_glb(init_mesh_from_file).to('cuda') else: meshes = load_obj_with_verts_faces(init_mesh_from_file).to('cuda') # add texture just in case num_meshes = len(meshes) num_verts_per_mesh = meshes.verts_packed().shape[0] // num_meshes black_texture = torch.zeros((num_meshes, num_verts_per_mesh, 3), device="cuda") textures = TexturesVertex(verts_features=black_texture) meshes.textures = textures if projection_type == 'perspective': assert fovy is not None and radius is not None, f"fovy ({fovy}) and radius ({radius}) should not be None when projection_type is 'perspective'" cameras = get_cameras_list_azi_ele(camera_angles_azi, camera_angles_ele, fov_in_degrees=fovy,device="cuda", dist=radius, cam_type='fov') elif projection_type == 'orthographic': cameras = get_cameras_list_azi_ele(camera_angles_azi, camera_angles_ele, fov_in_degrees=fovy, device="cuda", focal=1., dist=ortho_dist, cam_type='orthographic') vertices, faces = meshes.verts_list()[0], meshes.faces_list()[0] render_camera_angles_azi = -camera_angles_azi render_camera_angles_ele = camera_angles_ele if projection_type == 'orthographic': mv, proj = make_star_cameras_orthographic(render_camera_angles_azi, render_camera_angles_ele) else: mv, proj = make_star_cameras_perspective(render_camera_angles_azi, render_camera_angles_ele, distance=radius, r=radius, fov=fovy, device='cuda') # stage 1 if train_stage1: vertices, faces = reconstruct_stage1(normal_stg1, mv=mv, proj=proj, steps=stage1_steps, vertices=vertices, faces=faces, start_edge_len=start_edge_len_stage1, end_edge_len=end_edge_len_stage1, gain=0.05, return_mesh=False, loss_expansion_weight=expansion_weight, use_remesh=use_remesh_stage1) # stage 2 if train_stage2: vertices, faces = run_mesh_refine(vertices, faces, normal_pils, mv=mv, proj=proj, weights=weights, steps=stage2_steps, start_edge_len=start_edge_len_stage2, end_edge_len=end_edge_len_stage2, decay=0.99, update_normal_interval=20, update_warmup=5, return_mesh=False, process_inputs=False, process_outputs=False, cameras=cameras, use_remesh=use_remesh_stage2, loss_expansion_weight=expansion_weight_stage2) meshes = simple_clean_mesh(to_pyml_mesh(vertices, faces), apply_smooth=True, stepsmoothnum=1, apply_sub_divide=True, sub_divide_threshold=0.25).to("cuda") return meshes